new icons

royal guardian first pass
This commit is contained in:
craisinlord committed 2026-09-16 23:12:47 -05:00
1 parent 82cea4c293
commit c77d1da53b
158 files changed
+5313 -796

No files matched your search

Binary file not shown.
Whitespace-only changes.
Whitespace-only changes.
@@ -182,6 +182,10 @@ public final class AntarchyConfigCatalog {
|| name.startsWith("american") || name.startsWith("american")
|| name.startsWith("mogglesVision") || name.startsWith("mogglesVision")
|| name.equals("ductTapeRepairPercentPerUse") || name.equals("ductTapeRepairPercentPerUse")
|| name.startsWith("fallenKingCrown"); || name.startsWith("fallenKingCrown")
|| name.startsWith("royalArmor")
|| name.startsWith("royalGuardian")
|| name.startsWith("royalAssailant")
|| name.startsWith("royalWeapon");
} }
} }
@@ -212,6 +212,11 @@ public final class AntarchySettings {
private static int royalGuardianBootsArmorValue = 5; private static int royalGuardianBootsArmorValue = 5;
private static double royalGuardianArmorToughness = 4.0D; private static double royalGuardianArmorToughness = 4.0D;
private static double royalGuardianArmorKnockbackResistance = 0.15D; private static double royalGuardianArmorKnockbackResistance = 0.15D;
private static double royalGuardianHelmetJudgmentBonus = 0.06D;
private static double royalGuardianChestplateJudgmentBonus = 0.08D;
private static double royalGuardianLeggingsJudgmentBonus = 0.07D;
private static double royalGuardianBootsJudgmentBonus = 0.05D;
private static int royalGuardianJudgmentDurationTicks = 160;
private static int royalAssailantHelmetArmorValue = 5; private static int royalAssailantHelmetArmorValue = 5;
private static int royalAssailantChestplateArmorValue = 10; private static int royalAssailantChestplateArmorValue = 10;
private static int royalAssailantLeggingsArmorValue = 8; private static int royalAssailantLeggingsArmorValue = 8;
@@ -224,6 +229,14 @@ public final class AntarchySettings {
private static double royalAssailantBootsDoubleDamageChance = 0.05D; private static double royalAssailantBootsDoubleDamageChance = 0.05D;
private static double royalGuardianSwordAttackDamage = 111.0D; private static double royalGuardianSwordAttackDamage = 111.0D;
private static double royalGuardianSwordAttackSpeed = -2.5D; private static double royalGuardianSwordAttackSpeed = -2.5D;
private static int royalGuardianSwordElementalCooldownTicks = 120;
private static double royalGuardianSwordFirePrimaryDamage = 22.0D;
private static double royalGuardianSwordFireSplashDamage = 14.0D;
private static double royalGuardianSwordFrostSpikeDamage = 9.0D;
private static double royalGuardianSwordStormPrimaryDamage = 16.0D;
private static double royalGuardianSwordStormJumpDamage = 14.0D;
private static int royalGuardianSwordStormMaxJumps = 4;
private static double royalGuardianSwordStormJumpRange = 9.0D;
private static double royalAssailantBattleAxeAttackDamage = 151.0D; private static double royalAssailantBattleAxeAttackDamage = 151.0D;
private static double royalAssailantBattleAxeAttackSpeed = -2.9D; private static double royalAssailantBattleAxeAttackSpeed = -2.9D;
private static double royalWeaponAttackReachBonus = 1.0D; private static double royalWeaponAttackReachBonus = 1.0D;
@@ -231,6 +244,21 @@ public final class AntarchySettings {
private static int royalWeaponDurability = 4608; private static int royalWeaponDurability = 4608;
private static int royalWeaponEnchantability = 30; private static int royalWeaponEnchantability = 30;
private static int royalGuardianShieldDurability = 1008; private static int royalGuardianShieldDurability = 1008;
private static double royalGuardianMusterRange = 16.0D;
private static int royalGuardianMusterDurationTicks = 1200;
private static int royalGuardianMusterCooldownTicks = 500;
private static int royalGuardianMusterMobCap = 3;
private static double royalGuardianMusterDamageReduction = 0.40D;
private static double royalGuardianMusterAttackBonus = 0.50D;
private static double royalGuardianMusterSpeedBonus = 0.25D;
private static double royalGuardianMusterPassiveDamageFloor = 3.0D;
private static double royalGuardianBoundaryRadius = 9.0D;
private static int royalGuardianBoundaryDurationTicks = 120;
private static int royalGuardianBoundaryCooldownTicks = 700;
private static int royalGuardianBoundaryStrengthAmplifier = 1;
private static int royalGuardianBoundaryArrestTicks = 20;
private static int royalGuardianBoundaryDropTicks = 8;
private static int royalGuardianBoundaryProjectileCap = 64;
private static double royalBoltDamage = 6.0D; private static double royalBoltDamage = 6.0D;
private static int royalEggHatchChance = 3; private static int royalEggHatchChance = 3;
private static double princeHealth = 120.0D; private static double princeHealth = 120.0D;
@@ -1957,6 +1985,17 @@ public final class AntarchySettings {
public static double royalGuardianArmorKnockbackResistance() { return royalGuardianArmorKnockbackResistance; } public static double royalGuardianArmorKnockbackResistance() { return royalGuardianArmorKnockbackResistance; }
public static void setRoyalGuardianArmorKnockbackResistance(double v) { royalGuardianArmorKnockbackResistance = v; } public static void setRoyalGuardianArmorKnockbackResistance(double v) { royalGuardianArmorKnockbackResistance = v; }
public static double royalGuardianHelmetJudgmentBonus() { return royalGuardianHelmetJudgmentBonus; }
public static void setRoyalGuardianHelmetJudgmentBonus(double v) { royalGuardianHelmetJudgmentBonus = v; }
public static double royalGuardianChestplateJudgmentBonus() { return royalGuardianChestplateJudgmentBonus; }
public static void setRoyalGuardianChestplateJudgmentBonus(double v) { royalGuardianChestplateJudgmentBonus = v; }
public static double royalGuardianLeggingsJudgmentBonus() { return royalGuardianLeggingsJudgmentBonus; }
public static void setRoyalGuardianLeggingsJudgmentBonus(double v) { royalGuardianLeggingsJudgmentBonus = v; }
public static double royalGuardianBootsJudgmentBonus() { return royalGuardianBootsJudgmentBonus; }
public static void setRoyalGuardianBootsJudgmentBonus(double v) { royalGuardianBootsJudgmentBonus = v; }
public static int royalGuardianJudgmentDurationTicks() { return royalGuardianJudgmentDurationTicks; }
public static void setRoyalGuardianJudgmentDurationTicks(int v) { royalGuardianJudgmentDurationTicks = v; }
public static int royalAssailantHelmetArmorValue() { return royalAssailantHelmetArmorValue; } public static int royalAssailantHelmetArmorValue() { return royalAssailantHelmetArmorValue; }
public static void setRoyalAssailantHelmetArmorValue(int v) { royalAssailantHelmetArmorValue = v; } public static void setRoyalAssailantHelmetArmorValue(int v) { royalAssailantHelmetArmorValue = v; }
@@ -1993,6 +2032,23 @@ public final class AntarchySettings {
public static double royalGuardianSwordAttackSpeed() { return royalGuardianSwordAttackSpeed; } public static double royalGuardianSwordAttackSpeed() { return royalGuardianSwordAttackSpeed; }
public static void setRoyalGuardianSwordAttackSpeed(double v) { royalGuardianSwordAttackSpeed = v; } public static void setRoyalGuardianSwordAttackSpeed(double v) { royalGuardianSwordAttackSpeed = v; }
public static int royalGuardianSwordElementalCooldownTicks() { return royalGuardianSwordElementalCooldownTicks; }
public static void setRoyalGuardianSwordElementalCooldownTicks(int v) { royalGuardianSwordElementalCooldownTicks = v; }
public static double royalGuardianSwordFirePrimaryDamage() { return royalGuardianSwordFirePrimaryDamage; }
public static void setRoyalGuardianSwordFirePrimaryDamage(double v) { royalGuardianSwordFirePrimaryDamage = v; }
public static double royalGuardianSwordFireSplashDamage() { return royalGuardianSwordFireSplashDamage; }
public static void setRoyalGuardianSwordFireSplashDamage(double v) { royalGuardianSwordFireSplashDamage = v; }
public static double royalGuardianSwordFrostSpikeDamage() { return royalGuardianSwordFrostSpikeDamage; }
public static void setRoyalGuardianSwordFrostSpikeDamage(double v) { royalGuardianSwordFrostSpikeDamage = v; }
public static double royalGuardianSwordStormPrimaryDamage() { return royalGuardianSwordStormPrimaryDamage; }
public static void setRoyalGuardianSwordStormPrimaryDamage(double v) { royalGuardianSwordStormPrimaryDamage = v; }
public static double royalGuardianSwordStormJumpDamage() { return royalGuardianSwordStormJumpDamage; }
public static void setRoyalGuardianSwordStormJumpDamage(double v) { royalGuardianSwordStormJumpDamage = v; }
public static int royalGuardianSwordStormMaxJumps() { return royalGuardianSwordStormMaxJumps; }
public static void setRoyalGuardianSwordStormMaxJumps(int v) { royalGuardianSwordStormMaxJumps = v; }
public static double royalGuardianSwordStormJumpRange() { return royalGuardianSwordStormJumpRange; }
public static void setRoyalGuardianSwordStormJumpRange(double v) { royalGuardianSwordStormJumpRange = v; }
public static double royalAssailantBattleAxeAttackDamage() { return royalAssailantBattleAxeAttackDamage; } public static double royalAssailantBattleAxeAttackDamage() { return royalAssailantBattleAxeAttackDamage; }
public static void setRoyalAssailantBattleAxeAttackDamage(double v) { royalAssailantBattleAxeAttackDamage = v; } public static void setRoyalAssailantBattleAxeAttackDamage(double v) { royalAssailantBattleAxeAttackDamage = v; }
@@ -2014,6 +2070,37 @@ public final class AntarchySettings {
public static int royalGuardianShieldDurability() { return royalGuardianShieldDurability; } public static int royalGuardianShieldDurability() { return royalGuardianShieldDurability; }
public static void setRoyalGuardianShieldDurability(int v) { royalGuardianShieldDurability = v; } public static void setRoyalGuardianShieldDurability(int v) { royalGuardianShieldDurability = v; }
public static double royalGuardianMusterRange() { return royalGuardianMusterRange; }
public static void setRoyalGuardianMusterRange(double v) { royalGuardianMusterRange = v; }
public static int royalGuardianMusterDurationTicks() { return royalGuardianMusterDurationTicks; }
public static void setRoyalGuardianMusterDurationTicks(int v) { royalGuardianMusterDurationTicks = v; }
public static int royalGuardianMusterCooldownTicks() { return royalGuardianMusterCooldownTicks; }
public static void setRoyalGuardianMusterCooldownTicks(int v) { royalGuardianMusterCooldownTicks = v; }
public static int royalGuardianMusterMobCap() { return royalGuardianMusterMobCap; }
public static void setRoyalGuardianMusterMobCap(int v) { royalGuardianMusterMobCap = v; }
public static double royalGuardianMusterDamageReduction() { return royalGuardianMusterDamageReduction; }
public static void setRoyalGuardianMusterDamageReduction(double v) { royalGuardianMusterDamageReduction = v; }
public static double royalGuardianMusterAttackBonus() { return royalGuardianMusterAttackBonus; }
public static void setRoyalGuardianMusterAttackBonus(double v) { royalGuardianMusterAttackBonus = v; }
public static double royalGuardianMusterSpeedBonus() { return royalGuardianMusterSpeedBonus; }
public static void setRoyalGuardianMusterSpeedBonus(double v) { royalGuardianMusterSpeedBonus = v; }
public static double royalGuardianMusterPassiveDamageFloor() { return royalGuardianMusterPassiveDamageFloor; }
public static void setRoyalGuardianMusterPassiveDamageFloor(double v) { royalGuardianMusterPassiveDamageFloor = v; }
public static double royalGuardianBoundaryRadius() { return royalGuardianBoundaryRadius; }
public static void setRoyalGuardianBoundaryRadius(double v) { royalGuardianBoundaryRadius = v; }
public static int royalGuardianBoundaryDurationTicks() { return royalGuardianBoundaryDurationTicks; }
public static void setRoyalGuardianBoundaryDurationTicks(int v) { royalGuardianBoundaryDurationTicks = v; }
public static int royalGuardianBoundaryCooldownTicks() { return royalGuardianBoundaryCooldownTicks; }
public static void setRoyalGuardianBoundaryCooldownTicks(int v) { royalGuardianBoundaryCooldownTicks = v; }
public static int royalGuardianBoundaryStrengthAmplifier() { return royalGuardianBoundaryStrengthAmplifier; }
public static void setRoyalGuardianBoundaryStrengthAmplifier(int v) { royalGuardianBoundaryStrengthAmplifier = v; }
public static int royalGuardianBoundaryArrestTicks() { return royalGuardianBoundaryArrestTicks; }
public static void setRoyalGuardianBoundaryArrestTicks(int v) { royalGuardianBoundaryArrestTicks = v; }
public static int royalGuardianBoundaryDropTicks() { return royalGuardianBoundaryDropTicks; }
public static void setRoyalGuardianBoundaryDropTicks(int v) { royalGuardianBoundaryDropTicks = v; }
public static int royalGuardianBoundaryProjectileCap() { return royalGuardianBoundaryProjectileCap; }
public static void setRoyalGuardianBoundaryProjectileCap(int v) { royalGuardianBoundaryProjectileCap = v; }
public static double royalBoltDamage() { return royalBoltDamage; } public static double royalBoltDamage() { return royalBoltDamage; }
public static void setRoyalBoltDamage(double v) { royalBoltDamage = v; } public static void setRoyalBoltDamage(double v) { royalBoltDamage = v; }
@@ -329,6 +329,7 @@ public final class AntarchyObjects {
public static Supplier<Holder<MobEffect>> STINKY_EFFECT = (Supplier<Holder<MobEffect>>) UNBOUND; public static Supplier<Holder<MobEffect>> STINKY_EFFECT = (Supplier<Holder<MobEffect>>) UNBOUND;
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
public static Supplier<Holder<MobEffect>> GLIMMERING_EFFECT = (Supplier<Holder<MobEffect>>) UNBOUND; public static Supplier<Holder<MobEffect>> GLIMMERING_EFFECT = (Supplier<Holder<MobEffect>>) UNBOUND;
public static Supplier<Holder<MobEffect>> ROYAL_VITALITY_PENALTY_EFFECT = (Supplier<Holder<MobEffect>>) UNBOUND;
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
public static Supplier<Fluid> LUMEN = (Supplier<Fluid>) UNBOUND; public static Supplier<Fluid> LUMEN = (Supplier<Fluid>) UNBOUND;
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
@@ -823,6 +824,10 @@ public final class AntarchyObjects {
GLIMMERING_EFFECT = supplier; GLIMMERING_EFFECT = supplier;
} }
public static void setRoyalVitalityPenaltyEffect(Supplier<Holder<MobEffect>> supplier) {
ROYAL_VITALITY_PENALTY_EFFECT = supplier;
}
public static void setElka(Supplier<EntityType<com.craisinlord.antarchy.content.entity.ElkaEntity>> supplier) { public static void setElka(Supplier<EntityType<com.craisinlord.antarchy.content.entity.ElkaEntity>> supplier) {
ELKA = supplier; ELKA = supplier;
} }
@@ -0,0 +1,19 @@
package com.craisinlord.antarchy.content.antmail;
import com.craisinlord.antarchy.Antarchy;
/** Opt-in diagnostics for AntMail networking and mailbox state transitions. */
public final class AntmailDebug {
private static final String PROPERTY = "antarchy.antmail.debug";
private AntmailDebug() {
}
public static void log(String message) {
if (Boolean.getBoolean(PROPERTY)) Antarchy.LOGGER.info("[AntMail debug] {}", message);
}
public static void error(String message, Throwable throwable) {
Antarchy.LOGGER.error("[AntMail] {}", message, throwable);
}
}
@@ -13,8 +13,10 @@ import net.minecraft.world.level.saveddata.SavedData;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.LinkedHashMap; import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Set;
import java.util.UUID; import java.util.UUID;
public final class AntmailServerData extends SavedData { public final class AntmailServerData extends SavedData {
@@ -301,16 +303,21 @@ public final class AntmailServerData extends SavedData {
public synchronized void drain(MinecraftServer server, AntmailAddress address) { public synchronized void drain(MinecraftServer server, AntmailAddress address) {
AntmailMailbox mailbox = mailboxes.getOrCreate(address); AntmailMailbox mailbox = mailboxes.getOrCreate(address);
final boolean[] delivered = {false}; final boolean[] delivered = {false};
Set<AntmailAddress> deliveredSenders = new LinkedHashSet<>();
pending.removeIf(pendingMessage -> { pending.removeIf(pendingMessage -> {
if (!pendingMessage.recipient().equals(address) || mailbox.inbox().size() >= AntmailValidation.MAX_MAILBOX_MESSAGES) return false; if (!pendingMessage.recipient().equals(address) || mailbox.inbox().size() >= AntmailValidation.MAX_MAILBOX_MESSAGES) return false;
if (!mailbox.addIncoming(pendingMessage.message())) return false; if (!mailbox.addIncoming(pendingMessage.message())) return false;
pendingMessage.message().setDeliveryStatus("DELIVERED"); pendingMessage.message().setDeliveryStatus("DELIVERED");
delivered[0] = true; delivered[0] = true;
deliveredSenders.add(pendingMessage.message().sender());
return true; return true;
}); });
if (delivered[0]) touchMailbox(address); if (delivered[0]) {
touchMailbox(address);
for (AntmailAddress sender : deliveredSenders) touchMailbox(sender);
setDirty(); setDirty();
} }
}
public synchronized AntmailDeliveryResult retry(MinecraftServer server, AntmailAddress sender, UUID messageId) { public synchronized AntmailDeliveryResult retry(MinecraftServer server, AntmailAddress sender, UUID messageId) {
AntmailMessage message = mailboxes.getOrCreate(sender).findSent(messageId); AntmailMessage message = mailboxes.getOrCreate(sender).findSent(messageId);
@@ -2,13 +2,19 @@ package com.craisinlord.antarchy.content.antmail;
import net.minecraft.nbt.CompoundTag; import net.minecraft.nbt.CompoundTag;
import net.minecraft.nbt.ListTag; import net.minecraft.nbt.ListTag;
import net.minecraft.nbt.TagParser; import net.minecraft.nbt.NbtAccounter;
import net.minecraft.nbt.NbtIo;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Base64; import java.util.Base64;
import java.util.List; import java.util.List;
public final class AntmailWire { public final class AntmailWire {
private static final int MAX_COMPRESSED_BYTES = 1024 * 1024;
private static final long MAX_DECOMPRESSED_BYTES = 8L * 1024L * 1024L;
private AntmailWire() { private AntmailWire() {
} }
@@ -19,18 +25,30 @@ public final class AntmailWire {
ListTag list = new ListTag(); ListTag list = new ListTag();
for (AntmailAttachment attachment : attachments) list.add(attachment.toTag()); for (AntmailAttachment attachment : attachments) list.add(attachment.toTag());
root.put("Attachments", list); root.put("Attachments", list);
String encoded = Base64.getEncoder().encodeToString(root.toString().getBytes(java.nio.charset.StandardCharsets.UTF_8)); String encoded = encodeTag(root);
if (encoded.length() > AntmailValidation.MAX_ATTACHMENT_WIRE_CHARS) throw new IllegalArgumentException("Attachment payload is too large"); if (encoded.length() > AntmailValidation.MAX_ATTACHMENT_WIRE_CHARS) throw new IllegalArgumentException("Attachment payload is too large");
return encoded; return encoded;
} }
public static String encodeTag(CompoundTag tag) { public static String encodeTag(CompoundTag tag) {
return Base64.getEncoder().encodeToString(tag.toString().getBytes(java.nio.charset.StandardCharsets.UTF_8)); if (tag == null) throw new IllegalArgumentException("Antmail tag cannot be null");
try {
ByteArrayOutputStream output = new ByteArrayOutputStream();
NbtIo.writeCompressed(tag, output);
byte[] compressed = output.toByteArray();
if (compressed.length > MAX_COMPRESSED_BYTES) throw new IllegalArgumentException("Antmail data is too large");
return Base64.getEncoder().encodeToString(compressed);
} catch (IOException exception) {
throw new IllegalArgumentException("Could not encode Antmail data", exception);
}
} }
public static CompoundTag decodeTag(String encoded) { public static CompoundTag decodeTag(String encoded) {
try { try {
return TagParser.parseTag(new String(Base64.getDecoder().decode(encoded), java.nio.charset.StandardCharsets.UTF_8)); if (encoded == null || encoded.isBlank()) throw new IllegalArgumentException("Antmail data is empty");
byte[] compressed = Base64.getDecoder().decode(encoded);
if (compressed.length > MAX_COMPRESSED_BYTES) throw new IllegalArgumentException("Antmail data is too large");
return NbtIo.readCompressed(new ByteArrayInputStream(compressed), NbtAccounter.create(MAX_DECOMPRESSED_BYTES));
} catch (Exception exception) { } catch (Exception exception) {
throw new IllegalArgumentException("Invalid Antmail data", exception); throw new IllegalArgumentException("Invalid Antmail data", exception);
} }
@@ -40,8 +58,7 @@ public final class AntmailWire {
if (encoded == null || encoded.isBlank()) return List.of(); if (encoded == null || encoded.isBlank()) return List.of();
if (encoded.length() > AntmailValidation.MAX_ATTACHMENT_WIRE_CHARS) throw new IllegalArgumentException("Attachment payload is too large"); if (encoded.length() > AntmailValidation.MAX_ATTACHMENT_WIRE_CHARS) throw new IllegalArgumentException("Attachment payload is too large");
try { try {
String snbt = new String(Base64.getDecoder().decode(encoded), java.nio.charset.StandardCharsets.UTF_8); CompoundTag root = decodeTag(encoded);
CompoundTag root = TagParser.parseTag(snbt);
ListTag list = root.getList("Attachments", 10); ListTag list = root.getList("Attachments", 10);
List<AntmailAttachment> result = new ArrayList<>(); List<AntmailAttachment> result = new ArrayList<>();
for (int i = 0; i < list.size(); i++) result.add(AntmailAttachment.fromTag(list.getCompound(i))); for (int i = 0; i < list.size(); i++) result.add(AntmailAttachment.fromTag(list.getCompound(i)));
@@ -49,6 +49,10 @@ public final class QueenTrailSpawnMarkerBlockEntity extends BlockEntity {
return this.configured; return this.configured;
} }
public boolean isConsumed() {
return this.consumed;
}
public static void serverTick(ServerLevel level, QueenTrailSpawnMarkerBlockEntity marker) { public static void serverTick(ServerLevel level, QueenTrailSpawnMarkerBlockEntity marker) {
if (!marker.configured || marker.consumed || level.getDifficulty() == Difficulty.PEACEFUL) { if (!marker.configured || marker.consumed || level.getDifficulty() == Difficulty.PEACEFUL) {
return; return;
@@ -88,6 +92,22 @@ public final class QueenTrailSpawnMarkerBlockEntity extends BlockEntity {
} }
} }
QueenEntity queen = QueenEntity.spawnFromUndersideTrail(level, this.spawnPos, this.homePos, this.siteId, this.yaw); QueenEntity queen = QueenEntity.spawnFromUndersideTrail(level, this.spawnPos, this.homePos, this.siteId, this.yaw);
if (queen == null) {
for (int radius = 2; radius <= 10 && queen == null; radius += 2) {
for (int dx = -radius; dx <= radius && queen == null; dx += radius) {
for (int dz = -radius; dz <= radius && queen == null; dz += radius) {
BlockPos candidate = this.spawnPos.offset(dx, 0, dz);
if (candidate.equals(this.spawnPos)) {
continue;
}
queen = QueenEntity.spawnFromUndersideTrail(level, candidate, this.homePos, this.siteId, this.yaw);
if (queen != null) {
this.spawnPos = candidate.immutable();
}
}
}
}
}
if (queen != null) { if (queen != null) {
this.consumed = true; this.consumed = true;
this.queenUuid = queen.getUUID(); this.queenUuid = queen.getUUID();
@@ -1,6 +1,7 @@
package com.craisinlord.antarchy.content.client; package com.craisinlord.antarchy.content.client;
import com.craisinlord.antarchy.content.network.AntmailResultPayload; import com.craisinlord.antarchy.content.network.AntmailResultPayload;
import com.craisinlord.antarchy.content.antmail.AntmailDebug;
import com.craisinlord.antarchy.content.antmail.AntmailMessage; import com.craisinlord.antarchy.content.antmail.AntmailMessage;
import net.minecraft.core.BlockPos; import net.minecraft.core.BlockPos;
@@ -18,9 +19,19 @@ public final class AntmailClientState {
} }
public static void update(AntmailResultPayload payload) { public static void update(AntmailResultPayload payload) {
AntmailDebug.log("S2C result pos=" + payload.pos() + " status=" + payload.status() + " detail=" + payload.detail()
+ " addressPresent=" + !payload.address().isBlank() + " dataChars=" + payload.data().length()
+ " version=" + payload.version());
RESULTS.put(payload.pos(), payload); RESULTS.put(payload.pos(), payload);
if ("unchanged".equals(payload.detail())) { if ("unchanged".equals(payload.detail())) {
if (MAILBOXES.containsKey(payload.pos())) {
VERSIONS.put(payload.pos(), payload.version()); VERSIONS.put(payload.pos(), payload.version());
} else {
// An unchanged response cannot initialize a client that has no cached snapshot.
// Keep the next poll unconditional instead of getting stuck on "unchanged".
AntmailDebug.log("received unchanged without cached mailbox at " + payload.pos() + "; forcing full snapshot on next poll");
VERSIONS.remove(payload.pos());
}
return; return;
} else if ("message_detail".equals(payload.detail()) && !payload.data().isBlank()) { } else if ("message_detail".equals(payload.detail()) && !payload.data().isBlank()) {
try { try {
@@ -13,6 +13,13 @@ public final class ComputerAccessClientState {
} }
public static void update(ComputerAccessResultPayload result) { public static void update(ComputerAccessResultPayload result) {
ComputerStructureLocatorClientState.update(result);
if (result.data().startsWith(com.craisinlord.antarchy.content.network.ComputerAccessPayload.ARCHIVE_STATE + "\0")) {
String[] envelope = result.data().split("\u0000", 3);
if (envelope.length == 3) {
com.craisinlord.antarchy.content.guide.ComputerGuideData.applyNetworkSnapshot(envelope[2]);
}
}
if (result.data().startsWith("18\u0000") || result.data().startsWith("19\u0000")) { if (result.data().startsWith("18\u0000") || result.data().startsWith("19\u0000")) {
BlockleClientState.update(result); BlockleClientState.update(result);
} }
@@ -25,6 +25,16 @@ public final class ComputerFileSystemClientState {
} catch (NumberFormatException ignored) { } catch (NumberFormatException ignored) {
return; return;
} }
// Terminal, desktop, and game responses use the same payload envelope,
// but their status must not leak into the file explorer's shared state.
if (action != ComputerAccessPayload.FILE_LIST
&& action != ComputerAccessPayload.FILE_OPEN
&& action != ComputerAccessPayload.FILE_CREATE
&& action != ComputerAccessPayload.FILE_SAVE
&& action != ComputerAccessPayload.FILE_DELETE
&& action != ComputerAccessPayload.FILE_MOVE) {
return;
}
State state = STATES.computeIfAbsent(payload.pos(), ignored -> new State()); State state = STATES.computeIfAbsent(payload.pos(), ignored -> new State());
state.success = payload.result() == ComputerAccessResultPayload.SUCCESS; state.success = payload.result() == ComputerAccessResultPayload.SUCCESS;
state.error = envelope[1]; state.error = envelope[1];
@@ -0,0 +1,43 @@
package com.craisinlord.antarchy.content.client;
import com.craisinlord.antarchy.content.network.ComputerAccessPayload;
import com.craisinlord.antarchy.content.network.ComputerAccessResultPayload;
import net.minecraft.core.BlockPos;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
public final class ComputerStructureLocatorClientState {
private static final Map<BlockPos, String> RESULTS = new ConcurrentHashMap<>();
private ComputerStructureLocatorClientState() {
}
public static void searching(BlockPos pos, String dimensionId) {
String dimension = dimensionId == null || dimensionId.isBlank() ? "TARGET DIMENSION" : dimensionId.toUpperCase(java.util.Locale.ROOT);
RESULTS.put(pos, "SEARCHING IN " + dimension + "...");
}
public static void update(ComputerAccessResultPayload payload) {
String[] envelope = payload.data().split("\u0000", 3);
if (envelope.length != 3) return;
try {
if (Integer.parseInt(envelope[0]) != ComputerAccessPayload.LOCATE_STRUCTURE) return;
} catch (NumberFormatException ignored) {
return;
}
String message = switch (envelope[1]) {
case "not_found" -> "NO STRUCTURE FOUND WITHIN RANGE";
case "dimension_unavailable" -> "TARGET DIMENSION UNAVAILABLE";
default -> "LOCATOR UNAVAILABLE";
};
RESULTS.put(payload.pos(), payload.result() == ComputerAccessResultPayload.SUCCESS
? "STRUCTURE START // " + envelope[2]
: message);
}
public static String get(BlockPos pos) {
return RESULTS.getOrDefault(pos, "COORDINATES // SEARCH WHEN SELECTED");
}
}
@@ -0,0 +1,36 @@
package com.craisinlord.antarchy.content.client;
public final class KingJudgmentFlashClientState {
private static int flashTicks;
private static int flashDuration;
private KingJudgmentFlashClientState() {
}
public static void trigger(int ticks) {
int duration = Math.max(0, ticks);
if (duration >= flashTicks) {
flashTicks = duration;
flashDuration = duration;
}
}
public static void tick() {
if (flashTicks > 0) {
flashTicks--;
}
}
public static float alpha() {
if (flashTicks <= 0 || flashDuration <= 0) {
return 0.0F;
}
float remaining = flashTicks / (float) flashDuration;
return Math.min(0.92F, 0.92F * remaining * remaining);
}
public static void clear() {
flashTicks = 0;
flashDuration = 0;
}
}
@@ -0,0 +1,17 @@
package com.craisinlord.antarchy.content.client;
import net.minecraft.client.gui.GuiGraphics;
public final class KingJudgmentFlashHudRenderer {
private KingJudgmentFlashHudRenderer() {
}
public static void render(GuiGraphics guiGraphics) {
int alpha = Math.round(KingJudgmentFlashClientState.alpha() * 255.0F);
if (alpha <= 0) {
return;
}
guiGraphics.fill(0, 0, guiGraphics.guiWidth(), guiGraphics.guiHeight(),
alpha << 24 | 0x00FFFFFF);
}
}
@@ -0,0 +1,32 @@
package com.craisinlord.antarchy.content.client.renderer;
import com.craisinlord.antarchy.content.effect.JudgmentMarkAccess;
import com.craisinlord.antarchy.content.effect.RoyalEffectHooks;
import net.minecraft.client.renderer.MultiBufferSource;
import net.minecraft.client.renderer.RenderType;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.entity.LivingEntity;
import org.jetbrains.annotations.Nullable;
import software.bernie.geckolib.animatable.GeoAnimatable;
import software.bernie.geckolib.cache.object.BakedGeoModel;
import software.bernie.geckolib.renderer.GeoRenderer;
import software.bernie.geckolib.renderer.layer.GeoRenderLayer;
public final class RoyalIndicatorGeoLayer<T extends GeoAnimatable> extends GeoRenderLayer<T> {
public RoyalIndicatorGeoLayer(GeoRenderer<T> renderer) {
super(renderer);
}
@Override
public void render(com.mojang.blaze3d.vertex.PoseStack poseStack, T animatable, BakedGeoModel bakedModel,
@Nullable RenderType renderType, MultiBufferSource bufferSource,
@Nullable com.mojang.blaze3d.vertex.VertexConsumer buffer, float partialTick, int packedLight, int packedOverlay) {
if (!(animatable instanceof Entity entity) || !(entity instanceof LivingEntity living)) {
return;
}
RoyalIndicatorRenderer.render(poseStack, bufferSource, living,
RoyalEffectHooks.commandedHolder() != null && living.hasEffect(RoyalEffectHooks.commandedHolder()),
living instanceof JudgmentMarkAccess access && access.antarchy$isJudgmentMarked(),
living.tickCount + partialTick);
}
}
@@ -0,0 +1,24 @@
package com.craisinlord.antarchy.content.client.renderer;
import com.craisinlord.antarchy.content.effect.JudgmentMarkAccess;
import com.craisinlord.antarchy.content.effect.RoyalEffectHooks;
import net.minecraft.client.model.EntityModel;
import net.minecraft.client.renderer.MultiBufferSource;
import net.minecraft.client.renderer.entity.RenderLayerParent;
import net.minecraft.client.renderer.entity.layers.RenderLayer;
import net.minecraft.world.entity.LivingEntity;
public final class RoyalIndicatorLivingLayer<T extends LivingEntity, M extends EntityModel<T>> extends RenderLayer<T, M> {
public RoyalIndicatorLivingLayer(RenderLayerParent<T, M> renderer) {
super(renderer);
}
@Override
public void render(com.mojang.blaze3d.vertex.PoseStack poseStack, MultiBufferSource buffers, int packedLight, T entity,
float limbSwing, float limbSwingAmount, float partialTick, float ageInTicks,
float netHeadYaw, float headPitch) {
RoyalIndicatorRenderer.render(poseStack, buffers, entity,
RoyalEffectHooks.commandedHolder() != null && entity.hasEffect(RoyalEffectHooks.commandedHolder()),
entity instanceof JudgmentMarkAccess access && access.antarchy$isJudgmentMarked(), ageInTicks);
}
}
@@ -0,0 +1,79 @@
package com.craisinlord.antarchy.content.client.renderer;
import com.mojang.blaze3d.vertex.PoseStack;
import com.mojang.blaze3d.vertex.VertexConsumer;
import net.minecraft.client.renderer.MultiBufferSource;
import net.minecraft.client.renderer.RenderType;
import net.minecraft.world.entity.LivingEntity;
public final class RoyalIndicatorRenderer {
private RoyalIndicatorRenderer() {
}
public static void render(PoseStack poseStack, MultiBufferSource buffers, LivingEntity entity,
boolean commanded, boolean judged, float ageInTicks) {
if ((!commanded && !judged) || entity.isInvisible()) {
return;
}
float radius = Math.max(0.28F, Math.min(1.15F, entity.getBbWidth() * 0.58F));
float baseY = entity.getBbHeight() + 0.18F;
poseStack.pushPose();
poseStack.translate(0.0D, baseY, 0.0D);
VertexConsumer lines = buffers.getBuffer(RenderType.lines());
PoseStack.Pose pose = poseStack.last();
float spin = ageInTicks * (judged ? 0.055F : 0.025F);
if (commanded) {
ring(lines, pose, radius, spin, 0.95F, 0.63F, 0.10F, 0.95F, 36);
crownPoints(lines, pose, radius, spin, 1.0F, 0.80F, 0.22F, 0.95F);
}
if (judged) {
float markY = commanded ? -0.13F : 0.0F;
poseStack.translate(0.0D, markY, 0.0D);
pose = poseStack.last();
brackets(lines, pose, radius * 1.18F, -spin * 1.7F);
ring(lines, pose, radius * 0.73F, -spin * 0.7F, 1.0F, 0.91F, 0.48F, 0.85F, 24);
}
poseStack.popPose();
}
private static void ring(VertexConsumer out, PoseStack.Pose pose, float radius, float rotation,
float r, float g, float b, float a, int segments) {
for (int i = 0; i < segments; i++) {
double angleA = (Math.PI * 2.0D * i) / segments + rotation;
double angleB = (Math.PI * 2.0D * (i + 1)) / segments + rotation;
line(out, pose, (float) Math.cos(angleA) * radius, 0.0F, (float) Math.sin(angleA) * radius,
(float) Math.cos(angleB) * radius, 0.0F, (float) Math.sin(angleB) * radius, r, g, b, a);
}
}
private static void crownPoints(VertexConsumer out, PoseStack.Pose pose, float radius, float rotation,
float r, float g, float b, float a) {
for (int i = 0; i < 3; i++) {
double angle = rotation + Math.PI * 2.0D * i / 3.0D;
float x = (float) Math.cos(angle) * radius;
float z = (float) Math.sin(angle) * radius;
line(out, pose, x, 0.0F, z, x * 0.68F, 0.19F, z * 0.68F, r, g, b, a);
}
}
private static void brackets(VertexConsumer out, PoseStack.Pose pose, float radius, float rotation) {
for (int i = 0; i < 3; i++) {
double angle = rotation + Math.PI * 2.0D * i / 3.0D;
float cx = (float) Math.cos(angle) * radius;
float cz = (float) Math.sin(angle) * radius;
float tx = (float) -Math.sin(angle);
float tz = (float) Math.cos(angle);
line(out, pose, cx - tx * 0.13F, 0.0F, cz - tz * 0.13F,
cx + tx * 0.13F, 0.0F, cz + tz * 0.13F, 1.0F, 0.81F, 0.20F, 1.0F);
line(out, pose, cx, 0.0F, cz, cx * 0.82F, 0.0F, cz * 0.82F, 1.0F, 0.91F, 0.52F, 0.9F);
}
}
private static void line(VertexConsumer out, PoseStack.Pose pose,
float x1, float y1, float z1, float x2, float y2, float z2,
float r, float g, float b, float a) {
out.addVertex(pose, x1, y1, z1).setColor(r, g, b, a).setNormal(pose, 0.0F, 1.0F, 0.0F);
out.addVertex(pose, x2, y2, z2).setColor(r, g, b, a).setNormal(pose, 0.0F, 1.0F, 0.0F);
}
}
@@ -69,6 +69,10 @@ public final class QueenLocateCommand {
continue; continue;
} }
QueenEntity queen = marker.spawnNow(thoraxis); QueenEntity queen = marker.spawnNow(thoraxis);
if (queen == null && !marker.isConsumed()) {
source.sendFailure(Component.literal("A Queen spawn site was found, but the Queen could not spawn at a clear location. The site can be retried later."));
return 0;
}
BlockPos spawn = marker.getSpawnPos(); BlockPos spawn = marker.getSpawnPos();
if (queen != null) { if (queen != null) {
spawn = BlockPos.containing(queen.position()); spawn = BlockPos.containing(queen.position());
@@ -77,7 +81,8 @@ public final class QueenLocateCommand {
String coordinates = "[" + spawn.getX() + ", " + spawn.getY() + ", " + spawn.getZ() + "]"; String coordinates = "[" + spawn.getX() + ", " + spawn.getY() + ", " + spawn.getZ() + "]";
String teleportCommand = "/execute in " + THORAXIS.location() + " run tp @s " String teleportCommand = "/execute in " + THORAXIS.location() + " run tp @s "
+ spawn.getX() + " " + spawn.getY() + " " + spawn.getZ(); + spawn.getX() + " " + spawn.getY() + " " + spawn.getZ();
source.sendSuccess(() -> Component.literal("Nearest Queen spawn in " + THORAXIS.location() + ": ") String state = queen == null ? " (already spawned; Queen is not currently loaded)" : "";
source.sendSuccess(() -> Component.literal("Nearest Queen spawn in " + THORAXIS.location() + state + ": ")
.append(Component.literal(coordinates) .append(Component.literal(coordinates)
.withStyle(Style.EMPTY .withStyle(Style.EMPTY
.withColor(net.minecraft.ChatFormatting.AQUA) .withColor(net.minecraft.ChatFormatting.AQUA)
@@ -1,76 +1,98 @@
package com.craisinlord.antarchy.content.computer.blockle; package com.craisinlord.antarchy.content.computer.blockle;
import com.craisinlord.antarchy.Antarchy; import com.craisinlord.antarchy.Antarchy;
import com.google.gson.JsonElement;
import com.google.gson.JsonParser;
import net.minecraft.resources.ResourceLocation; import net.minecraft.resources.ResourceLocation;
import net.minecraft.server.packs.resources.ResourceManager;
import net.minecraft.server.packs.resources.SimplePreparableReloadListener;
import net.minecraft.util.profiling.ProfilerFiller;
import java.util.LinkedHashSet;
import java.util.List; import java.util.List;
import java.util.Map;
public final class BlockleAnswers { public final class BlockleAnswers extends SimplePreparableReloadListener<List<String>> {
public static final int VERSION = 1; public static final int VERSION = 1;
private static final int SALT = 0x4A17; private static final int SALT = 0x4A17;
private static final List<Answer> ANSWERS = List.of( private static final ResourceLocation ANSWERS_FILE = ResourceLocation.fromNamespaceAndPath(Antarchy.MODID, "blockle/answers.json");
new Answer("anvil", id("anvil")), private static final BlockleAnswers INSTANCE = new BlockleAnswers();
new Answer("apple", id("apple")), private static final Map<String, ResourceLocation> ANSWER_ITEMS = Map.ofEntries(
new Answer("arrow", id("arrow")), Map.entry("anvil", vanilla("anvil")), Map.entry("apple", vanilla("apple")),
new Answer("bread", id("bread")), Map.entry("arrow", vanilla("arrow")), Map.entry("bread", vanilla("bread")),
new Answer("brick", id("brick")), Map.entry("brick", vanilla("brick")), Map.entry("chain", vanilla("chain")),
new Answer("chain", id("chain")), Map.entry("chest", vanilla("chest")), Map.entry("clock", vanilla("clock")),
new Answer("chest", id("chest")), Map.entry("cocoa", vanilla("cocoa")), Map.entry("flint", vanilla("flint")),
new Answer("clock", id("clock")), Map.entry("glass", vanilla("glass")), Map.entry("melon", vanilla("melon")),
new Answer("cocoa", id("cocoa")), Map.entry("paper", vanilla("paper")), Map.entry("poppy", vanilla("poppy")),
new Answer("flint", id("flint")), Map.entry("sculk", vanilla("sculk")), Map.entry("stone", vanilla("stone")),
new Answer("glass", id("glass")), Map.entry("stick", vanilla("stick")), Map.entry("sugar", vanilla("sugar")),
new Answer("melon", id("melon")), Map.entry("torch", vanilla("torch")), Map.entry("vault", vanilla("vault")),
new Answer("paper", id("paper")), Map.entry("wheat", vanilla("wheat")), Map.entry("allay", vanilla("allay_spawn_egg")),
new Answer("poppy", id("poppy")), Map.entry("blaze", vanilla("blaze_spawn_egg")), Map.entry("camel", vanilla("camel_spawn_egg")),
new Answer("sculk", id("sculk")), Map.entry("drown", vanilla("drowned_spawn_egg")), Map.entry("ghast", vanilla("ghast_spawn_egg")),
new Answer("stone", id("stone")), Map.entry("horse", vanilla("horse_spawn_egg")), Map.entry("llama", vanilla("llama_spawn_egg")),
new Answer("stick", id("stick")), Map.entry("panda", vanilla("panda_spawn_egg")), Map.entry("sheep", vanilla("sheep_spawn_egg")),
new Answer("sugar", id("sugar")), Map.entry("slime", vanilla("slime_spawn_egg")), Map.entry("squid", vanilla("squid_spawn_egg")),
new Answer("torch", id("torch")), Map.entry("stray", vanilla("stray_spawn_egg")), Map.entry("witch", vanilla("witch_spawn_egg")),
new Answer("vault", id("vault")), Map.entry("lever", vanilla("lever")), Map.entry("sword", vanilla("iron_sword")),
new Answer("wheat", id("wheat")), Map.entry("cheep", antarchy("cheep_spawn_egg")), Map.entry("jerry", antarchy("jerry_spawn_egg")),
new Answer("allay", id("allay_spawn_egg")), Map.entry("lucid", antarchy("lucid_spawn_egg")), Map.entry("queen", antarchy("queen_spawn_egg")),
new Answer("blaze", id("blaze_spawn_egg")), Map.entry("ichor", antarchy("ichor")), Map.entry("lotus", antarchy("lotus")),
new Answer("camel", id("camel_spawn_egg")), Map.entry("mucus", antarchy("mucus")), Map.entry("peach", antarchy("peach"))
new Answer("drown", id("drowned_spawn_egg")),
new Answer("ghast", id("ghast_spawn_egg")),
new Answer("horse", id("horse_spawn_egg")),
new Answer("llama", id("llama_spawn_egg")),
new Answer("panda", id("panda_spawn_egg")),
new Answer("sheep", id("sheep_spawn_egg")),
new Answer("slime", id("slime_spawn_egg")),
new Answer("squid", id("squid_spawn_egg")),
new Answer("stray", id("stray_spawn_egg")),
new Answer("witch", id("witch_spawn_egg")),
new Answer("lever", id("lever")),
new Answer("sword", id("iron_sword")),
new Answer("cheep", antarchy("cheep_spawn_egg")),
new Answer("jerry", antarchy("jerry_spawn_egg")),
new Answer("lucid", antarchy("lucid_spawn_egg")),
new Answer("queen", antarchy("queen_spawn_egg")),
new Answer("ichor", antarchy("ichor")),
new Answer("lotus", antarchy("lotus")),
new Answer("mucus", antarchy("mucus")),
new Answer("peach", antarchy("peach"))
); );
private static volatile List<String> answers = List.of();
private BlockleAnswers() { private BlockleAnswers() {
} }
public static BlockleAnswers instance() {
return INSTANCE;
}
public static Answer answerForDay(long day) { public static Answer answerForDay(long day) {
int index = (int) Math.floorMod(day + SALT, ANSWERS.size()); List<String> currentAnswers = answers;
return ANSWERS.get(index); if (currentAnswers.isEmpty()) return null;
int index = (int) Math.floorMod(day + SALT, currentAnswers.size());
String word = currentAnswers.get(index);
return new Answer(word, ANSWER_ITEMS.getOrDefault(word, vanilla("book")));
} }
public static int size() { public static int size() {
return ANSWERS.size(); return answers.size();
}
@Override
protected List<String> prepare(ResourceManager resourceManager, ProfilerFiller profiler) {
var resource = resourceManager.getResource(ANSWERS_FILE);
if (resource.isEmpty()) return List.of();
try (var reader = resource.get().openAsReader()) {
JsonElement json = JsonParser.parseReader(reader);
if (!json.isJsonArray()) return List.of();
LinkedHashSet<String> validAnswers = new LinkedHashSet<>();
json.getAsJsonArray().forEach(element -> {
if (element.isJsonPrimitive() && element.getAsJsonPrimitive().isString()) {
String word = element.getAsString();
if (word.matches("(?i)[a-z]{5}")) validAnswers.add(word.toLowerCase(java.util.Locale.ROOT));
}
});
return List.copyOf(validAnswers);
} catch (Exception ignored) {
return List.of();
}
}
@Override
protected void apply(List<String> loadedAnswers, ResourceManager resourceManager, ProfilerFiller profiler) {
answers = loadedAnswers;
Antarchy.LOGGER.info("Loaded {} valid Blockle answers", answers.size());
} }
public record Answer(String word, ResourceLocation itemId) { public record Answer(String word, ResourceLocation itemId) {
} }
private static ResourceLocation id(String path) { private static ResourceLocation vanilla(String path) {
return ResourceLocation.withDefaultNamespace(path); return ResourceLocation.withDefaultNamespace(path);
} }
@@ -1,13 +1,18 @@
package com.craisinlord.antarchy.content.effect; package com.craisinlord.antarchy.content.effect;
import com.craisinlord.antarchy.config.AntarchySettings;
import java.util.UUID; import java.util.UUID;
import net.minecraft.world.entity.Entity; import net.minecraft.world.entity.Entity;
import net.minecraft.world.entity.LivingEntity; import net.minecraft.world.entity.LivingEntity;
import net.minecraft.world.entity.Mob; import net.minecraft.world.entity.Mob;
import net.minecraft.server.level.ServerLevel; import net.minecraft.server.level.ServerLevel;
import net.minecraft.world.entity.ai.attributes.Attributes; import net.minecraft.world.entity.ai.attributes.Attributes;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.world.entity.ai.attributes.AttributeModifier;
public final class CommandedBehavior { public final class CommandedBehavior {
private static final ResourceLocation INVESTITURE_ATTACK = ResourceLocation.fromNamespaceAndPath("antarchy", "royal_investiture_attack");
private static final ResourceLocation INVESTITURE_SPEED = ResourceLocation.fromNamespaceAndPath("antarchy", "royal_investiture_speed");
private CommandedBehavior() { private CommandedBehavior() {
} }
@@ -26,6 +31,7 @@ public final class CommandedBehavior {
clear(mob); clear(mob);
return; return;
} }
syncInvestiture(mob, access.antarchy$isRoyalInvested());
if (mob.tickCount % 10 == 0) { if (mob.tickCount % 10 == 0) {
LivingEntity commandedCandidate = findCommanderTarget(mob, commander); LivingEntity commandedCandidate = findCommanderTarget(mob, commander);
if (commandedCandidate != null) { if (commandedCandidate != null) {
@@ -100,6 +106,11 @@ public final class CommandedBehavior {
} }
private static LivingEntity findCommanderTarget(Mob mob, Entity commander) { private static LivingEntity findCommanderTarget(Mob mob, Entity commander) {
if (mob instanceof CommandedEntityAccess access && access.antarchy$isRoyalInvested()
&& commander instanceof net.minecraft.world.entity.player.Player player) {
LivingEntity marked = JudgmentMarkManager.markedTarget(player);
if (isValidTarget(mob, commander, marked)) return marked;
}
LivingEntity target = commander instanceof Mob commanderMob ? commanderMob.getTarget() : null; LivingEntity target = commander instanceof Mob commanderMob ? commanderMob.getTarget() : null;
if (isValidTarget(mob, commander, target)) { if (isValidTarget(mob, commander, target)) {
return target; return target;
@@ -130,7 +141,9 @@ public final class CommandedBehavior {
if (mob.tickCount % 20 != 0 || mob.distanceToSqr(target) > 4.0D) { if (mob.tickCount % 20 != 0 || mob.distanceToSqr(target) > 4.0D) {
return; return;
} }
if (target.hurt(mob.damageSources().mobAttack(mob), 1.0F)) { float damage = mob instanceof CommandedEntityAccess access && access.antarchy$isRoyalInvested()
? (float) AntarchySettings.royalGuardianMusterPassiveDamageFloor() : 1.0F;
if (target.hurt(mob.damageSources().mobAttack(mob), damage)) {
mob.swing(net.minecraft.world.InteractionHand.MAIN_HAND); mob.swing(net.minecraft.world.InteractionHand.MAIN_HAND);
} }
} }
@@ -151,6 +164,28 @@ public final class CommandedBehavior {
mob.setTarget(null); mob.setTarget(null);
} }
access.antarchy$setCommandedTargetOwned(false); access.antarchy$setCommandedTargetOwned(false);
access.antarchy$setRoyalInvested(false);
syncInvestiture(mob, false);
access.antarchy$setCommanderUuid(null); access.antarchy$setCommanderUuid(null);
} }
public static void syncInvestiture(Mob mob, boolean active) {
setModifier(mob, Attributes.ATTACK_DAMAGE, INVESTITURE_ATTACK,
active ? AntarchySettings.royalGuardianMusterAttackBonus() : 0.0D);
setModifier(mob, Attributes.MOVEMENT_SPEED, INVESTITURE_SPEED,
active ? AntarchySettings.royalGuardianMusterSpeedBonus() : 0.0D);
}
private static void setModifier(Mob mob, net.minecraft.core.Holder<net.minecraft.world.entity.ai.attributes.Attribute> attribute,
ResourceLocation id, double amount) {
var instance = mob.getAttribute(attribute);
if (instance == null) return;
if (amount > 0.0D) {
if (!instance.hasModifier(id)) {
instance.addTransientModifier(new AttributeModifier(id, amount, AttributeModifier.Operation.ADD_MULTIPLIED_BASE));
}
} else {
instance.removeModifier(id);
}
}
} }
@@ -7,4 +7,6 @@ public interface CommandedEntityAccess {
void antarchy$setCommanderUuid(UUID uuid); void antarchy$setCommanderUuid(UUID uuid);
boolean antarchy$isCommandedTargetOwned(); boolean antarchy$isCommandedTargetOwned();
void antarchy$setCommandedTargetOwned(boolean owned); void antarchy$setCommandedTargetOwned(boolean owned);
boolean antarchy$isRoyalInvested();
void antarchy$setRoyalInvested(boolean invested);
} }
@@ -0,0 +1,8 @@
package com.craisinlord.antarchy.content.effect;
/** Synchronized, presentation-only state for the short-lived Royal Judgment mark. */
public interface JudgmentMarkAccess {
boolean antarchy$isJudgmentMarked();
void antarchy$setJudgmentMarked(boolean marked);
}
@@ -0,0 +1,140 @@
package com.craisinlord.antarchy.content.effect;
import com.craisinlord.antarchy.config.AntarchySettings;
import com.craisinlord.antarchy.content.item.RoyalGuardianArmorItem;
import java.util.HashMap;
import java.util.Map;
import java.util.HashSet;
import java.util.Set;
import java.util.UUID;
import java.util.WeakHashMap;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.world.entity.EquipmentSlot;
import net.minecraft.world.entity.LivingEntity;
import net.minecraft.world.entity.player.Player;
/** Server-owned, player-specific Judgment marks. The visible bit is synchronized by vanilla entity data. */
public final class JudgmentMarkManager {
private static final Map<ServerLevel, MarkBook> MARKS = new WeakHashMap<>();
private JudgmentMarkManager() {
}
/** Returns true when this owner had already marked this target before the current hit. */
public static boolean recordSuccessfulHit(Player owner, LivingEntity target) {
if (!(owner instanceof ServerPlayer serverPlayer) || !(target.level() instanceof ServerLevel level)
|| owner.level() != target.level() || !hasGuardianArmor(owner) || !target.isAlive()) {
return false;
}
MarkBook book = MARKS.computeIfAbsent(level, ignored -> new MarkBook());
UUID ownerId = owner.getUUID();
Mark current = book.byOwner.get(ownerId);
boolean wasMarked = current != null && current.targetId.equals(target.getUUID())
&& current.expiresAt > level.getGameTime();
if (current != null && !current.targetId.equals(target.getUUID())) {
removeOwnerFromTarget(book, ownerId, current.targetId);
refreshVisibleState(level, current.targetId);
}
book.byOwner.put(ownerId, new Mark(target.getUUID(), level.getGameTime()
+ Math.max(1, AntarchySettings.royalGuardianJudgmentDurationTicks())));
book.ownersByTarget.computeIfAbsent(target.getUUID(), ignored -> new HashSet<>()).add(ownerId);
refreshVisibleState(target);
return wasMarked;
}
public static boolean isMarkedBy(Player owner, LivingEntity target) {
if (!(target.level() instanceof ServerLevel level)) {
return false;
}
MarkBook book = MARKS.get(level);
if (book == null) {
return false;
}
Mark mark = book.byOwner.get(owner.getUUID());
return mark != null && mark.targetId.equals(target.getUUID()) && mark.expiresAt > level.getGameTime()
&& hasGuardianArmor(owner);
}
public static LivingEntity markedTarget(Player owner) {
if (!(owner instanceof ServerPlayer serverPlayer) || !(owner.level() instanceof ServerLevel level)) return null;
MarkBook book = MARKS.get(level);
Mark mark = book == null ? null : book.byOwner.get(owner.getUUID());
if (mark == null || mark.expiresAt <= level.getGameTime() || !hasGuardianArmor(owner)) return null;
net.minecraft.world.entity.Entity entity = level.getEntity(mark.targetId);
return entity instanceof LivingEntity living && living.isAlive() ? living : null;
}
/** Prunes marks from this target and updates the vanilla-synchronized render bit. */
public static void tickTarget(LivingEntity target) {
if (!(target.level() instanceof ServerLevel level)) {
return;
}
MarkBook book = MARKS.get(level);
if (book == null || book.byOwner.isEmpty()) {
return;
}
long now = level.getGameTime();
UUID targetId = target.getUUID();
Set<UUID> ownerIds = book.ownersByTarget.get(targetId);
if (ownerIds == null || ownerIds.isEmpty()) {
if (target instanceof JudgmentMarkAccess access) access.antarchy$setJudgmentMarked(false);
return;
}
for (UUID ownerId : Set.copyOf(ownerIds)) {
Mark mark = book.byOwner.get(ownerId);
ServerPlayer owner = level.getServer().getPlayerList().getPlayer(ownerId);
if (mark == null || mark.expiresAt <= now || owner == null || owner.level() != level || !hasGuardianArmor(owner)) {
book.byOwner.remove(ownerId);
ownerIds.remove(ownerId);
}
}
if (ownerIds.isEmpty()) book.ownersByTarget.remove(targetId);
boolean active = !ownerIds.isEmpty();
if (target instanceof JudgmentMarkAccess access) {
access.antarchy$setJudgmentMarked(active);
}
if (book.byOwner.isEmpty()) {
MARKS.remove(level);
}
}
private static void refreshVisibleState(LivingEntity target) {
if (target instanceof JudgmentMarkAccess access) {
access.antarchy$setJudgmentMarked(true);
}
}
private static void refreshVisibleState(ServerLevel level, UUID targetId) {
net.minecraft.world.entity.Entity entity = level.getEntity(targetId);
if (entity instanceof LivingEntity target && target instanceof JudgmentMarkAccess access) {
MarkBook book = MARKS.get(level);
boolean stillMarked = book != null && !book.ownersByTarget.getOrDefault(targetId, Set.of()).isEmpty();
access.antarchy$setJudgmentMarked(stillMarked);
}
}
private static void removeOwnerFromTarget(MarkBook book, UUID ownerId, UUID targetId) {
Set<UUID> owners = book.ownersByTarget.get(targetId);
if (owners != null) {
owners.remove(ownerId);
if (owners.isEmpty()) book.ownersByTarget.remove(targetId);
}
}
private static boolean hasGuardianArmor(Player player) {
return player.getItemBySlot(EquipmentSlot.HEAD).getItem() instanceof RoyalGuardianArmorItem
|| player.getItemBySlot(EquipmentSlot.CHEST).getItem() instanceof RoyalGuardianArmorItem
|| player.getItemBySlot(EquipmentSlot.LEGS).getItem() instanceof RoyalGuardianArmorItem
|| player.getItemBySlot(EquipmentSlot.FEET).getItem() instanceof RoyalGuardianArmorItem;
}
private record Mark(UUID targetId, long expiresAt) {
}
private static final class MarkBook {
private final Map<UUID, Mark> byOwner = new HashMap<>();
private final Map<UUID, Set<UUID>> ownersByTarget = new HashMap<>();
}
}
@@ -0,0 +1,269 @@
package com.craisinlord.antarchy.content.effect;
import com.craisinlord.antarchy.config.AntarchySettings;
import com.craisinlord.antarchy.content.item.RoyalGuardianArmorItem;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
import java.util.WeakHashMap;
import net.minecraft.core.particles.ParticleTypes;
import net.minecraft.core.particles.DustParticleOptions;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.sounds.SoundEvents;
import net.minecraft.sounds.SoundSource;
import net.minecraft.world.effect.MobEffectInstance;
import net.minecraft.world.effect.MobEffects;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.entity.EquipmentSlot;
import net.minecraft.world.entity.LivingEntity;
import net.minecraft.world.entity.OwnableEntity;
import net.minecraft.world.entity.projectile.Projectile;
import net.minecraft.world.phys.AABB;
import net.minecraft.world.phys.Vec3;
import org.joml.Vector3f;
/** Server-authoritative Royal Guardian chestplate field and temporary projectile arrest state. */
public final class RoyalBoundaryManager {
private static final int ALLY_REFRESH_INTERVAL = 10;
private static final DustParticleOptions ROYAL_GOLD = new DustParticleOptions(new Vector3f(1.0F, 0.72F, 0.12F), 1.15F);
private static final Map<ServerLevel, Map<UUID, Boundary>> BOUNDARIES = new WeakHashMap<>();
private static final Map<ServerLevel, Map<UUID, ArrestedProjectile>> ARRESTED = new WeakHashMap<>();
private RoyalBoundaryManager() {
}
public static boolean activate(ServerPlayer owner) {
if (!(owner.level() instanceof ServerLevel level) || !wearsGuardianChestplate(owner)) return false;
long now = level.getGameTime();
Map<UUID, Boundary> boundaries = BOUNDARIES.computeIfAbsent(level, ignored -> new HashMap<>());
net.minecraft.world.item.Item chestplate = owner.getItemBySlot(EquipmentSlot.CHEST).getItem();
if (owner.getCooldowns().isOnCooldown(chestplate) || boundaries.containsKey(owner.getUUID())) {
owner.displayClientMessage(net.minecraft.network.chat.Component.translatable("message.antarchy.royal_boundary.recharging"), true);
return false;
}
double radius = Math.max(0.5D, AntarchySettings.royalGuardianBoundaryRadius());
boundaries.put(owner.getUUID(), new Boundary(now + Math.max(1, AntarchySettings.royalGuardianBoundaryDurationTicks()), radius));
owner.getCooldowns().addCooldown(chestplate, Math.max(0, AntarchySettings.royalGuardianBoundaryCooldownTicks()));
owner.displayClientMessage(net.minecraft.network.chat.Component.translatable("message.antarchy.royal_boundary.activated"), true);
level.playSound(null, owner.blockPosition(), SoundEvents.BEACON_ACTIVATE, SoundSource.PLAYERS, 1.0F, 1.25F);
burst(level, center(owner), radius, 36);
return true;
}
/** Called from ServerPlayer.tick; ends fields immediately when their owner can no longer sustain them. */
public static void tickOwner(ServerPlayer owner) {
if (!(owner.level() instanceof ServerLevel level)) return;
UUID ownerId = owner.getUUID();
for (ServerLevel otherLevel : BOUNDARIES.keySet().toArray(ServerLevel[]::new)) {
if (otherLevel != level) removeBoundary(otherLevel, ownerId);
}
Map<UUID, Boundary> boundaries = BOUNDARIES.get(level);
if (boundaries == null) return;
Boundary boundary = boundaries.get(ownerId);
if (boundary == null) return;
long now = level.getGameTime();
if (!owner.isAlive() || owner.isRemoved() || !wearsGuardianChestplate(owner) || now >= boundary.expiresAt) {
boundaries.remove(ownerId);
if (boundaries.isEmpty()) BOUNDARIES.remove(level);
return;
}
if (owner.tickCount % ALLY_REFRESH_INTERVAL == 0) {
AABB area = owner.getBoundingBox().inflate(boundary.radius);
for (LivingEntity entity : level.getEntitiesOfClass(LivingEntity.class, area,
living -> living.isAlive() && living.distanceToSqr(owner) <= boundary.radius * boundary.radius && isAlly(owner, living))) {
entity.addEffect(new MobEffectInstance(MobEffects.DAMAGE_BOOST, ALLY_REFRESH_INTERVAL + 5,
Math.max(0, AntarchySettings.royalGuardianBoundaryStrengthAmplifier()), true, false, true), owner);
}
}
if (owner.tickCount % 5 == 0) renderBoundary(level, center(owner), boundary.radius);
}
public static void clearOwner(ServerPlayer owner) {
UUID ownerId = owner.getUUID();
for (ServerLevel level : BOUNDARIES.keySet().toArray(ServerLevel[]::new)) removeBoundary(level, ownerId);
}
private static void removeBoundary(ServerLevel level, UUID ownerId) {
Map<UUID, Boundary> boundaries = BOUNDARIES.get(level);
if (boundaries != null) {
boundaries.remove(ownerId);
if (boundaries.isEmpty()) BOUNDARIES.remove(level);
}
}
/** Predicts this tick's movement so a fast projectile is arrested before its own collision pass. */
public static void prepareProjectile(Projectile projectile) {
if (!(projectile.level() instanceof ServerLevel level) || projectile.isRemoved()) return;
Map<UUID, ArrestedProjectile> arrested = ARRESTED.get(level);
if (arrested != null && arrested.containsKey(projectile.getUUID())) {
ArrestedProjectile state = arrested.get(projectile.getUUID());
if (!state.dropping) {
projectile.setDeltaMovement(Vec3.ZERO);
projectile.setNoGravity(true);
}
return;
}
Vec3 start = projectile.position();
Vec3 movement = projectile.getDeltaMovement();
if (movement.lengthSqr() > 1.0E-8D) {
tryCapture(level, projectile, start, start.add(movement));
}
}
/** Swept fallback for unusual projectiles and arrest-state progression. */
public static void tickProjectile(Projectile projectile) {
if (!(projectile.level() instanceof ServerLevel level)) return;
Map<UUID, ArrestedProjectile> arrested = ARRESTED.get(level);
ArrestedProjectile state = arrested == null ? null : arrested.get(projectile.getUUID());
if (state != null) {
tickArrested(level, projectile, state);
return;
}
tryCapture(level, projectile, new Vec3(projectile.xo, projectile.yo, projectile.zo), projectile.position());
}
private static void tryCapture(ServerLevel level, Projectile projectile, Vec3 start, Vec3 end) {
Map<UUID, Boundary> boundaries = BOUNDARIES.get(level);
if (boundaries == null || boundaries.isEmpty() || projectile.isRemoved()) return;
var boundaryIterator = boundaries.entrySet().iterator();
while (boundaryIterator.hasNext()) {
Map.Entry<UUID, Boundary> entry = boundaryIterator.next();
ServerPlayer owner = level.getServer().getPlayerList().getPlayer(entry.getKey());
if (owner == null || owner.level() != level || !owner.isAlive() || !wearsGuardianChestplate(owner)
|| level.getGameTime() >= entry.getValue().expiresAt) {
boundaryIterator.remove();
continue;
}
Map<UUID, ArrestedProjectile> states = ARRESTED.computeIfAbsent(level, ignored -> new HashMap<>());
long ownedArrests = states.values().stream().filter(arrest -> entry.getKey().equals(arrest.ownerId)).count();
if (ownedArrests >= Math.max(0, AntarchySettings.royalGuardianBoundaryProjectileCap())) continue;
if (isProjectileAlly(owner, projectile)) continue;
Vec3 crossing = inwardIntersection(start, end, center(owner), entry.getValue().radius);
if (crossing == null) continue;
projectile.setPos(crossing.x, crossing.y, crossing.z);
projectile.setDeltaMovement(Vec3.ZERO);
boolean noGravity = projectile.isNoGravity();
projectile.setNoGravity(true);
states.put(projectile.getUUID(), new ArrestedProjectile(entry.getKey(),
level.getGameTime() + Math.max(0, AntarchySettings.royalGuardianBoundaryArrestTicks()), false, 0L, noGravity));
level.playSound(null, crossing.x, crossing.y, crossing.z, SoundEvents.AMETHYST_BLOCK_CHIME, SoundSource.PLAYERS, 0.65F, 1.7F);
level.sendParticles(ROYAL_GOLD, crossing.x, crossing.y, crossing.z, 18, 0.3D, 0.3D, 0.3D, 0.3D);
return;
}
if (boundaries.isEmpty()) BOUNDARIES.remove(level);
}
private static void tickArrested(ServerLevel level, Projectile projectile, ArrestedProjectile state) {
if (projectile.isRemoved()) {
removeArrest(level, projectile.getUUID());
return;
}
long now = level.getGameTime();
if (!state.dropping && now < state.releaseAt) {
projectile.setDeltaMovement(Vec3.ZERO);
projectile.setNoGravity(true);
if (now % 2 == 0) {
level.sendParticles(ROYAL_GOLD, projectile.getX(), projectile.getY(), projectile.getZ(), 3, 0.12D, 0.12D, 0.12D, 0.02D);
}
if (state.releaseAt - now <= 6) projectile.setXRot((float) (90.0D * (1.0D - (state.releaseAt - now) / 6.0D)));
return;
}
if (!state.dropping) {
state.dropping = true;
state.dropUntil = now + Math.max(0, AntarchySettings.royalGuardianBoundaryDropTicks());
projectile.setNoGravity(false);
projectile.setXRot(90.0F);
}
if (now < state.dropUntil) {
projectile.setDeltaMovement(0.0D, -0.45D, 0.0D);
projectile.setNoGravity(false);
} else {
projectile.setNoGravity(state.wasNoGravity);
removeArrest(level, projectile.getUUID());
}
}
private static void removeArrest(ServerLevel level, UUID projectileId) {
Map<UUID, ArrestedProjectile> states = ARRESTED.get(level);
if (states != null) {
states.remove(projectileId);
if (states.isEmpty()) ARRESTED.remove(level);
}
}
private static Vec3 inwardIntersection(Vec3 start, Vec3 end, Vec3 center, double radius) {
Vec3 offset = start.subtract(center);
if (offset.lengthSqr() <= radius * radius) return null;
Vec3 segment = end.subtract(start);
double a = segment.lengthSqr();
if (a < 1.0E-8D) return null;
double b = 2.0D * offset.dot(segment);
double c = offset.lengthSqr() - radius * radius;
double discriminant = b * b - 4.0D * a * c;
if (discriminant < 0.0D) return null;
double t = (-b - Math.sqrt(discriminant)) / (2.0D * a);
return t >= 0.0D && t <= 1.0D ? start.add(segment.scale(t)) : null;
}
private static boolean isProjectileAlly(ServerPlayer owner, Projectile projectile) {
Entity shooter = projectile.getOwner();
return shooter == owner || (shooter != null && (shooter.isAlliedTo(owner) || owner.isAlliedTo(shooter)
|| (shooter instanceof CommandedEntityAccess access && owner.getUUID().equals(access.antarchy$getCommanderUuid()))));
}
private static boolean isAlly(ServerPlayer owner, LivingEntity entity) {
if (entity == owner || entity.isAlliedTo(owner) || owner.isAlliedTo(entity)) return true;
if (entity instanceof OwnableEntity ownable && owner.getUUID().equals(ownable.getOwnerUUID())) return true;
return entity instanceof CommandedEntityAccess access && owner.getUUID().equals(access.antarchy$getCommanderUuid());
}
private static boolean wearsGuardianChestplate(ServerPlayer player) {
return player.getItemBySlot(EquipmentSlot.CHEST).getItem() instanceof RoyalGuardianArmorItem armor
&& armor.getArmorType() == net.minecraft.world.item.ArmorItem.Type.CHESTPLATE;
}
private static Vec3 center(ServerPlayer owner) {
return owner.position().add(0.0D, owner.getBbHeight() * 0.5D, 0.0D);
}
private static void renderBoundary(ServerLevel level, Vec3 center, double radius) {
for (int i = 0; i < 24; i++) {
double angle = (Math.PI * 2.0D * i) / 24.0D;
double x = center.x + Math.cos(angle) * radius;
double z = center.z + Math.sin(angle) * radius;
level.sendParticles(ROYAL_GOLD, x, center.y - 1.0D, z, 1, 0.04D, 0.1D, 0.04D, 0.02D);
if (i % 4 == 0) level.sendParticles(ParticleTypes.END_ROD, x, center.y + 1.2D, z, 1, 0.02D, 0.3D, 0.02D, 0.0D);
}
}
private static void burst(ServerLevel level, Vec3 center, double radius, int count) {
level.sendParticles(ROYAL_GOLD, center.x, center.y, center.z, count, radius * 0.35D, 1.2D, radius * 0.35D, 0.18D);
level.sendParticles(ParticleTypes.END_ROD, center.x, center.y, center.z, count / 2, radius * 0.25D, 0.8D, radius * 0.25D, 0.08D);
}
private static final class Boundary {
private final long expiresAt;
private final double radius;
private Boundary(long expiresAt, double radius) {
this.expiresAt = expiresAt;
this.radius = radius;
}
}
private static final class ArrestedProjectile {
private final UUID ownerId;
private final long releaseAt;
private boolean dropping;
private long dropUntil;
private final boolean wasNoGravity;
private ArrestedProjectile(UUID ownerId, long releaseAt, boolean dropping, long dropUntil, boolean wasNoGravity) {
this.ownerId = ownerId;
this.releaseAt = releaseAt;
this.dropping = dropping;
this.dropUntil = dropUntil;
this.wasNoGravity = wasNoGravity;
}
}
}
@@ -0,0 +1,34 @@
package com.craisinlord.antarchy.content.effect;
import com.craisinlord.antarchy.Antarchy;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.world.effect.MobEffect;
import net.minecraft.world.effect.MobEffectCategory;
import net.minecraft.world.entity.ai.attributes.AttributeInstance;
import net.minecraft.world.entity.ai.attributes.AttributeMap;
import net.minecraft.world.entity.ai.attributes.AttributeModifier;
import net.minecraft.world.entity.ai.attributes.Attributes;
public final class RoyalVitalityPenaltyEffect extends MobEffect {
public static final ResourceLocation MODIFIER_ID =
ResourceLocation.fromNamespaceAndPath(Antarchy.MODID, "royal_vitality_penalty");
public RoyalVitalityPenaltyEffect() {
super(MobEffectCategory.HARMFUL, 0xD8A52B);
}
@Override
public void addAttributeModifiers(AttributeMap attributeMap, int amplifier) {
AttributeInstance maxHealth = attributeMap.getInstance(Attributes.MAX_HEALTH);
if (maxHealth == null) return;
maxHealth.addTransientModifier(new AttributeModifier(
MODIFIER_ID, -0.5D, AttributeModifier.Operation.ADD_MULTIPLIED_TOTAL));
}
@Override
public void removeAttributeModifiers(AttributeMap attributeMap) {
AttributeInstance maxHealth = attributeMap.getInstance(Attributes.MAX_HEALTH);
if (maxHealth == null) return;
maxHealth.removeModifier(MODIFIER_ID);
}
}
@@ -113,6 +113,9 @@ public final class AntTeleportHelper {
} }
Vec3 destinationPos = getDestinationPosition(player, destination); Vec3 destinationPos = getDestinationPosition(player, destination);
if (destinationPos == null) {
return failArrival(player);
}
teleportPlayerWithCompanions(player, destination, destinationPos); teleportPlayerWithCompanions(player, destination, destinationPos);
player.playSound(SoundEvents.ENDERMAN_TELEPORT, 1.0F, 1.0F); player.playSound(SoundEvents.ENDERMAN_TELEPORT, 1.0F, 1.0F);
return InteractionResult.CONSUME; return InteractionResult.CONSUME;
@@ -121,6 +124,9 @@ public final class AntTeleportHelper {
public static InteractionResult teleportPlayerToReturnDestination(ServerPlayer player) { public static InteractionResult teleportPlayerToReturnDestination(ServerPlayer player) {
ServerLevel destination = resolveReturnDestinationLevel(player); ServerLevel destination = resolveReturnDestinationLevel(player);
Vec3 destinationPos = getDestinationPosition(player, destination); Vec3 destinationPos = getDestinationPosition(player, destination);
if (destinationPos == null) {
return failArrival(player);
}
teleportPlayerWithCompanions(player, destination, destinationPos); teleportPlayerWithCompanions(player, destination, destinationPos);
player.playSound(SoundEvents.ENDERMAN_TELEPORT, 1.0F, 1.0F); player.playSound(SoundEvents.ENDERMAN_TELEPORT, 1.0F, 1.0F);
return InteractionResult.CONSUME; return InteractionResult.CONSUME;
@@ -160,6 +166,9 @@ public final class AntTeleportHelper {
} }
Vec3 destinationPos = getDestinationPosition(player, destination); Vec3 destinationPos = getDestinationPosition(player, destination);
if (destinationPos == null) {
return failArrival(player);
}
teleportPlayerWithCompanions(player, destination, destinationPos); teleportPlayerWithCompanions(player, destination, destinationPos);
player.playSound(SoundEvents.ENDERMAN_TELEPORT, 1.0F, 1.0F); player.playSound(SoundEvents.ENDERMAN_TELEPORT, 1.0F, 1.0F);
return InteractionResult.CONSUME; return InteractionResult.CONSUME;
@@ -245,6 +254,7 @@ public final class AntTeleportHelper {
return player.server.overworld(); return player.server.overworld();
} }
@Nullable
public static Vec3 getDestinationPosition(ServerPlayer player, ServerLevel destination) { public static Vec3 getDestinationPosition(ServerPlayer player, ServerLevel destination) {
BlockPos respawnPos = player.getRespawnPosition(); BlockPos respawnPos = player.getRespawnPosition();
BlockPos preferredPos; BlockPos preferredPos;
@@ -255,13 +265,9 @@ public final class AntTeleportHelper {
} }
Vec3 safeArrivalPos = findSafeArrivalPosition(player, destination, preferredPos); Vec3 safeArrivalPos = findSafeArrivalPosition(player, destination, preferredPos);
if (safeArrivalPos != null) {
return safeArrivalPos; return safeArrivalPos;
} }
return createEmergencyArrivalPlatform(destination, findEmergencyPlatformPosition(destination, preferredPos));
}
@Nullable @Nullable
private static Vec3 findSafeArrivalPosition(ServerPlayer player, ServerLevel destination, BlockPos preferredPos) { private static Vec3 findSafeArrivalPosition(ServerPlayer player, ServerLevel destination, BlockPos preferredPos) {
loadArrivalSearchArea(destination, preferredPos); loadArrivalSearchArea(destination, preferredPos);
@@ -310,7 +316,7 @@ public final class AntTeleportHelper {
/** /**
* Arrival validation needs neighboring chunks for collision checks. Load a * Arrival validation needs neighboring chunks for collision checks. Load a
* bounded area up front so unloaded terrain is not mistaken for an unsafe * bounded area up front so unloaded terrain is not mistaken for an unsafe
* destination and sent straight to the emergency-platform path. * destination and rejected prematurely.
*/ */
private static void loadArrivalSearchArea(ServerLevel destination, BlockPos preferredPos) { private static void loadArrivalSearchArea(ServerLevel destination, BlockPos preferredPos) {
ChunkPos center = new ChunkPos(preferredPos); ChunkPos center = new ChunkPos(preferredPos);
@@ -343,6 +349,7 @@ public final class AntTeleportHelper {
@Nullable @Nullable
private static Vec3 findSafeArrivalPositionInYRange(ServerPlayer player, ServerLevel destination, BlockPos preferredPos, int minY, int maxY) { private static Vec3 findSafeArrivalPositionInYRange(ServerPlayer player, ServerLevel destination, BlockPos preferredPos, int minY, int maxY) {
Set<BlockPos> surfaceCandidates = new LinkedHashSet<>(); Set<BlockPos> surfaceCandidates = new LinkedHashSet<>();
boolean elythia = destination.dimension() == PermanentPortalType.ELYTHIA.primaryDimension();
for (int radius = 0; radius <= ARRIVAL_SEARCH_RADIUS; radius++) { for (int radius = 0; radius <= ARRIVAL_SEARCH_RADIUS; radius++) {
for (int xOff = -radius; xOff <= radius; xOff++) { for (int xOff = -radius; xOff <= radius; xOff++) {
for (int zOff = -radius; zOff <= radius; zOff++) { for (int zOff = -radius; zOff <= radius; zOff++) {
@@ -357,16 +364,23 @@ public final class AntTeleportHelper {
} }
BlockPos surface = destination.getHeightmapPos(Heightmap.Types.MOTION_BLOCKING_NO_LEAVES, column); BlockPos surface = destination.getHeightmapPos(Heightmap.Types.MOTION_BLOCKING_NO_LEAVES, column);
if (elythia) {
surfaceCandidates.add(surface);
surfaceCandidates.add(surface.above());
} else {
addArrivalCandidate(surfaceCandidates, surface); addArrivalCandidate(surfaceCandidates, surface);
addArrivalCandidate(surfaceCandidates, destination.getHeightmapPos(Heightmap.Types.WORLD_SURFACE, column)); addArrivalCandidate(surfaceCandidates, destination.getHeightmapPos(Heightmap.Types.WORLD_SURFACE, column));
}
for (int yOffset = 1; yOffset <= ARRIVAL_VERTICAL_SEARCH; yOffset++) { for (int yOffset = 1; yOffset <= ARRIVAL_VERTICAL_SEARCH; yOffset++) {
surfaceCandidates.add(surface.above(yOffset)); surfaceCandidates.add(surface.above(yOffset));
if (!elythia) {
surfaceCandidates.add(surface.below(yOffset)); surfaceCandidates.add(surface.below(yOffset));
} }
} }
} }
} }
}
// Prefer actual generated surfaces. This is especially important in // Prefer actual generated surfaces. This is especially important in
// Elythia, where the configured spawn height is not necessarily the // Elythia, where the configured spawn height is not necessarily the
@@ -381,8 +395,15 @@ public final class AntTeleportHelper {
} }
} }
// Elythia arrivals must stay on the generated surface; a vertical scan
// can otherwise select an underground cave floor after a surface miss.
if (elythia) {
return null;
}
// Heightmaps can be stale or unsuitable around structures, so retain a // Heightmaps can be stale or unsuitable around structures, so retain a
// bounded exhaustive search as a final generated-terrain attempt. // bounded exhaustive search as a final generated-terrain attempt in
// dimensions whose configured arrival ranges intentionally allow it.
for (int radius = 0; radius <= ARRIVAL_SEARCH_RADIUS; radius++) { for (int radius = 0; radius <= ARRIVAL_SEARCH_RADIUS; radius++) {
for (int xOff = -radius; xOff <= radius; xOff++) { for (int xOff = -radius; xOff <= radius; xOff++) {
for (int zOff = -radius; zOff <= radius; zOff++) { for (int zOff = -radius; zOff <= radius; zOff++) {
@@ -442,6 +463,37 @@ public final class AntTeleportHelper {
} }
private static boolean isValidArrivalPosition(ServerLevel destination, Vec3 safePos) { private static boolean isValidArrivalPosition(ServerLevel destination, Vec3 safePos) {
if (destination.dimension() == PermanentPortalType.ELYTHIA.primaryDimension()) {
BlockPos feet = BlockPos.containing(safePos);
BlockPos supportPos = feet.below();
if (!destination.hasChunkAt(supportPos)) {
return false;
}
BlockState support = destination.getBlockState(supportPos);
if (!support.isFaceSturdy(destination, supportPos, Direction.UP)
|| !support.getFluidState().isEmpty()
|| support.is(net.minecraft.tags.BlockTags.LEAVES)) {
return false;
}
int nearbySupports = 0;
for (Direction direction : Direction.Plane.HORIZONTAL) {
for (int yOffset = -2; yOffset <= 2; yOffset++) {
BlockPos nearby = supportPos.relative(direction).offset(0, yOffset, 0);
BlockState nearbyState = destination.getBlockState(nearby);
if (nearbyState.isFaceSturdy(destination, nearby, Direction.UP)
&& nearbyState.getFluidState().isEmpty()
&& !nearbyState.is(net.minecraft.tags.BlockTags.LEAVES)) {
nearbySupports++;
break;
}
}
}
if (nearbySupports < 2) {
return false;
}
}
if (destination.dimension() != AntarchySettings.termiteDestinationDimension()) { if (destination.dimension() != AntarchySettings.termiteDestinationDimension()) {
return true; return true;
} }
@@ -469,98 +521,8 @@ public final class AntTeleportHelper {
return true; return true;
} }
private static BlockPos getClampedFallbackPosition(ServerLevel destination, BlockPos preferredPos) { private static InteractionResult failArrival(ServerPlayer player) {
int minY = destination.getMinBuildHeight() + 1; player.displayClientMessage(Component.translatable("message.antarchy.teleport_arrival_failed"), true);
int maxY = destination.getMaxBuildHeight() - 1; return InteractionResult.CONSUME;
return new BlockPos(preferredPos.getX(), Mth.clamp(preferredPos.getY(), minY, maxY), preferredPos.getZ());
}
/**
* Keep the emergency platform grounded in generated terrain when possible.
* A configured/shared spawn can be an arbitrary coordinate in a custom
* dimension, so using its Y directly can strand the player in the sky.
*/
private static BlockPos findEmergencyPlatformPosition(ServerLevel destination, BlockPos preferredPos) {
BlockPos best = null;
int bestDistance = Integer.MAX_VALUE;
int minY = destination.getMinBuildHeight() + 1;
int maxY = destination.getMaxBuildHeight() - 2;
for (int radius = 0; radius <= 8; radius++) {
for (int xOffset = -radius; xOffset <= radius; xOffset++) {
for (int zOffset = -radius; zOffset <= radius; zOffset++) {
if (radius > 0 && Math.abs(xOffset) != radius && Math.abs(zOffset) != radius) {
continue;
}
BlockPos searchPos = preferredPos.offset(xOffset, 0, zOffset);
if (!destination.hasChunkAt(searchPos)) {
continue;
}
BlockPos surface = destination.getHeightmapPos(Heightmap.Types.MOTION_BLOCKING_NO_LEAVES, searchPos);
if (surface.getY() < minY || surface.getY() > maxY) {
continue;
}
int distance = Math.abs(xOffset) + Math.abs(zOffset);
if (distance < bestDistance) {
best = surface;
bestDistance = distance;
}
}
}
if (best != null) {
break;
}
}
return best != null ? best : getClampedFallbackPosition(destination, preferredPos);
}
/**
* Last-resort landing location for custom dimensions whose generated spawn
* area has no valid player-sized space. The normal search always runs first;
* this only modifies terrain when there is no safe generated location.
*/
private static Vec3 createEmergencyArrivalPlatform(ServerLevel destination, BlockPos preferredPos) {
BlockPos center = getClampedFallbackPosition(destination, preferredPos);
int minY = destination.getMinBuildHeight() + 1;
int maxY = destination.getMaxBuildHeight() - 2;
center = new BlockPos(center.getX(), Mth.clamp(center.getY(), minY, maxY), center.getZ());
destination.getChunkAt(center);
BlockState platformState = emergencyPlatformState(destination);
for (int xOffset = -2; xOffset <= 2; xOffset++) {
for (int zOffset = -2; zOffset <= 2; zOffset++) {
BlockPos column = center.offset(xOffset, 0, zOffset);
destination.setBlock(column.below(), platformState, Block.UPDATE_ALL);
for (int yOffset = 0; yOffset <= 3; yOffset++) {
destination.removeBlock(column.above(yOffset), false);
}
}
}
return Vec3.atBottomCenterOf(center);
}
private static BlockState emergencyPlatformState(ServerLevel destination) {
if (destination.dimension() == PermanentPortalType.ELYTHIA.primaryDimension()) {
return PermanentPortalType.ELYTHIA.platformBlock().defaultBlockState();
}
if (destination.dimension() == PermanentPortalType.THORAXIS.primaryDimension()) {
return PermanentPortalType.THORAXIS.platformBlock().defaultBlockState();
}
if (destination.dimension() == PermanentPortalType.CAVARYN.primaryDimension()) {
return PermanentPortalType.CAVARYN.platformBlock().defaultBlockState();
}
if (destination.dimension() == Level.NETHER) {
return Blocks.NETHERRACK.defaultBlockState();
}
if (destination.dimension() == Level.END) {
return Blocks.END_STONE.defaultBlockState();
}
return Blocks.DIRT.defaultBlockState();
} }
} }
@@ -1323,6 +1323,7 @@ public abstract class BaseAntEntity extends Animal implements GeoEntity {
return AntTeleportHelper.resolveReturnDestinationLevel(player); return AntTeleportHelper.resolveReturnDestinationLevel(player);
} }
@Nullable
protected Vec3 getDestinationPosition(ServerLevel destination, ServerPlayer player) { protected Vec3 getDestinationPosition(ServerLevel destination, ServerPlayer player) {
return AntTeleportHelper.getDestinationPosition(player, destination); return AntTeleportHelper.getDestinationPosition(player, destination);
} }
@@ -123,6 +123,10 @@ public class RainbowAntEntity extends BaseAntEntity implements GeoEntity {
if (this.isInInfinityDimension(serverPlayer.serverLevel())) { if (this.isInInfinityDimension(serverPlayer.serverLevel())) {
ServerLevel returnLevel = this.resolveReturnDestinationLevel(serverPlayer); ServerLevel returnLevel = this.resolveReturnDestinationLevel(serverPlayer);
Vec3 returnPos = this.getDestinationPosition(returnLevel, serverPlayer); Vec3 returnPos = this.getDestinationPosition(returnLevel, serverPlayer);
if (returnPos == null) {
serverPlayer.displayClientMessage(Component.translatable("message.antarchy.teleport_arrival_failed"), true);
return InteractionResult.CONSUME;
}
serverPlayer.teleportTo(returnLevel, returnPos.x, returnPos.y, returnPos.z, serverPlayer.getYRot(), serverPlayer.getXRot()); serverPlayer.teleportTo(returnLevel, returnPos.x, returnPos.y, returnPos.z, serverPlayer.getYRot(), serverPlayer.getXRot());
serverPlayer.playSound(SoundEvents.ENDERMAN_TELEPORT, 1.0F, 1.0F); serverPlayer.playSound(SoundEvents.ENDERMAN_TELEPORT, 1.0F, 1.0F);
return InteractionResult.CONSUME; return InteractionResult.CONSUME;
@@ -3,6 +3,9 @@ package com.craisinlord.antarchy.content.entity.nightmare;
import com.craisinlord.antarchy.config.AntarchySettings; import com.craisinlord.antarchy.config.AntarchySettings;
import com.craisinlord.antarchy.content.AntarchySoundEvents; import com.craisinlord.antarchy.content.AntarchySoundEvents;
import com.craisinlord.antarchy.content.AntarchyObjects; import com.craisinlord.antarchy.content.AntarchyObjects;
import com.craisinlord.antarchy.content.gravity.AntarchyGravityApi;
import com.craisinlord.antarchy.content.gravity.AntarchyGravityDirection;
import com.craisinlord.antarchy.content.gravity.AntarchyGravityRotationUtil;
import net.minecraft.nbt.CompoundTag; import net.minecraft.nbt.CompoundTag;
import net.minecraft.network.syncher.EntityDataAccessor; import net.minecraft.network.syncher.EntityDataAccessor;
import net.minecraft.network.syncher.EntityDataSerializers; import net.minecraft.network.syncher.EntityDataSerializers;
@@ -30,6 +33,7 @@ import java.util.UUID;
public class NightmareBiteEntity extends Entity implements GeoEntity { public class NightmareBiteEntity extends Entity implements GeoEntity {
private static final EntityDataAccessor<Boolean> PHASE_TWO = SynchedEntityData.defineId(NightmareBiteEntity.class, EntityDataSerializers.BOOLEAN); private static final EntityDataAccessor<Boolean> PHASE_TWO = SynchedEntityData.defineId(NightmareBiteEntity.class, EntityDataSerializers.BOOLEAN);
private static final EntityDataAccessor<Boolean> TELEGRAPH_ONLY = SynchedEntityData.defineId(NightmareBiteEntity.class, EntityDataSerializers.BOOLEAN);
private static final RawAnimation BITE_ANIM = RawAnimation.begin().thenPlay("bite"); private static final RawAnimation BITE_ANIM = RawAnimation.begin().thenPlay("bite");
private static final int LIFETIME_TICKS = 12; private static final int LIFETIME_TICKS = 12;
private static final int SOUND_TICK = 7; private static final int SOUND_TICK = 7;
@@ -64,9 +68,21 @@ public class NightmareBiteEntity extends Entity implements GeoEntity {
return bite; return bite;
} }
public static NightmareBiteEntity spawnTelegraphAt(ServerLevel level, LivingEntity target, boolean phaseTwo) {
NightmareBiteEntity bite = new NightmareBiteEntity(AntarchyObjects.NIGHTMARE_BITE.get(), level);
bite.targetId = target.getUUID();
bite.setPhaseTwo(phaseTwo);
bite.setTelegraphOnly(true);
bite.approachSide = target.getRandom().nextBoolean() ? 1 : -1;
bite.updateTrackingPosition(target, 0.0D);
level.addFreshEntity(bite);
return bite;
}
@Override @Override
protected void defineSynchedData(SynchedEntityData.Builder builder) { protected void defineSynchedData(SynchedEntityData.Builder builder) {
builder.define(PHASE_TWO, false); builder.define(PHASE_TWO, false);
builder.define(TELEGRAPH_ONLY, false);
} }
@Override @Override
@@ -106,6 +122,14 @@ public class NightmareBiteEntity extends Entity implements GeoEntity {
this.getEntityData().set(PHASE_TWO, phaseTwo); this.getEntityData().set(PHASE_TWO, phaseTwo);
} }
private boolean isTelegraphOnly() {
return this.entityData.get(TELEGRAPH_ONLY);
}
private void setTelegraphOnly(boolean telegraphOnly) {
this.getEntityData().set(TELEGRAPH_ONLY, telegraphOnly);
}
@Override @Override
public void tick() { public void tick() {
super.tick(); super.tick();
@@ -113,7 +137,8 @@ public class NightmareBiteEntity extends Entity implements GeoEntity {
return; return;
} }
LivingEntity target = this.resolveTarget(); LivingEntity target = this.resolveTarget();
if (target == null || !target.isAlive() || !target.hasEffect(AntarchyObjects.DREAD.get())) { if (target == null || !target.isAlive()
|| !this.isTelegraphOnly() && !target.hasEffect(AntarchyObjects.DREAD.get())) {
this.discard(); this.discard();
return; return;
} }
@@ -126,8 +151,10 @@ public class NightmareBiteEntity extends Entity implements GeoEntity {
} }
if (!this.biteDamageApplied && nextAge >= DAMAGE_TICK) { if (!this.biteDamageApplied && nextAge >= DAMAGE_TICK) {
this.biteDamageApplied = true; this.biteDamageApplied = true;
if (!this.isTelegraphOnly()) {
this.performBite(target); this.performBite(target);
} }
}
if (nextAge >= LIFETIME_TICKS) { if (nextAge >= LIFETIME_TICKS) {
this.discard(); this.discard();
} }
@@ -143,6 +170,10 @@ public class NightmareBiteEntity extends Entity implements GeoEntity {
} }
private void updateTrackingPosition(LivingEntity target, double progress) { private void updateTrackingPosition(LivingEntity target, double progress) {
AntarchyGravityDirection targetGravity = AntarchyGravityApi.getGravityDirection(target);
if (AntarchyGravityApi.getGravityDirection(this) != targetGravity) {
AntarchyGravityApi.setGravityDirection(this, targetGravity);
}
Vec3 forward = target.getLookAngle().multiply(1.0D, 0.0D, 1.0D); Vec3 forward = target.getLookAngle().multiply(1.0D, 0.0D, 1.0D);
if (forward.lengthSqr() < 1.0E-4D) { if (forward.lengthSqr() < 1.0E-4D) {
forward = this.getViewVector(1.0F).multiply(1.0D, 0.0D, 1.0D); forward = this.getViewVector(1.0F).multiply(1.0D, 0.0D, 1.0D);
@@ -151,17 +182,21 @@ public class NightmareBiteEntity extends Entity implements GeoEntity {
forward = new Vec3(0.0D, 0.0D, 1.0D); forward = new Vec3(0.0D, 0.0D, 1.0D);
} }
forward = forward.normalize(); forward = forward.normalize();
Vec3 right = new Vec3(-forward.z, 0.0D, forward.x); Vec3 localUp = AntarchyGravityRotationUtil.vecPlayerToWorld(
0.0D, 1.0D, 0.0D, targetGravity);
Vec3 right = forward.cross(localUp).normalize();
double sideOffset = START_SIDE_OFFSET + (END_SIDE_OFFSET - START_SIDE_OFFSET) * progress; double sideOffset = START_SIDE_OFFSET + (END_SIDE_OFFSET - START_SIDE_OFFSET) * progress;
double backOffset = START_BACK_OFFSET + (END_BACK_OFFSET - START_BACK_OFFSET) * progress; double backOffset = START_BACK_OFFSET + (END_BACK_OFFSET - START_BACK_OFFSET) * progress;
Vec3 desiredPos = target.position() Vec3 bodyCenter = target.position().add(AntarchyGravityRotationUtil.vecPlayerToWorld(
.add(0.0D, target.getBbHeight() * 0.55D, 0.0D) 0.0D, target.getBbHeight() * 0.55D, 0.0D, targetGravity));
Vec3 desiredPos = bodyCenter
.add(right.scale(this.approachSide * sideOffset)) .add(right.scale(this.approachSide * sideOffset))
.subtract(forward.scale(backOffset)); .subtract(forward.scale(backOffset));
Vec3 toTarget = target.getEyePosition().subtract(desiredPos); Vec3 toTarget = bodyCenter.subtract(desiredPos);
this.setPos(desiredPos.x, desiredPos.y, desiredPos.z); this.setPos(desiredPos.x, desiredPos.y, desiredPos.z);
if (toTarget.lengthSqr() > 1.0E-4D) { if (toTarget.lengthSqr() > 1.0E-4D) {
float yaw = (float) Math.toDegrees(Math.atan2(toTarget.z, toTarget.x)) - 90.0F; Vec3 localToTarget = AntarchyGravityRotationUtil.vecWorldToPlayer(toTarget, targetGravity);
float yaw = (float) Math.toDegrees(Math.atan2(localToTarget.z, localToTarget.x)) - 90.0F;
this.setYRot(yaw); this.setYRot(yaw);
} }
} }
@@ -177,6 +212,7 @@ public class NightmareBiteEntity extends Entity implements GeoEntity {
@Override @Override
public void addAdditionalSaveData(CompoundTag tag) { public void addAdditionalSaveData(CompoundTag tag) {
tag.putBoolean("PhaseTwo", this.isPhaseTwo()); tag.putBoolean("PhaseTwo", this.isPhaseTwo());
tag.putBoolean("TelegraphOnly", this.isTelegraphOnly());
tag.putInt("AgeTicks", this.ageTicks); tag.putInt("AgeTicks", this.ageTicks);
tag.putInt("ApproachSide", this.approachSide); tag.putInt("ApproachSide", this.approachSide);
tag.putBoolean("BiteSoundPlayed", this.biteSoundPlayed); tag.putBoolean("BiteSoundPlayed", this.biteSoundPlayed);
@@ -189,6 +225,7 @@ public class NightmareBiteEntity extends Entity implements GeoEntity {
@Override @Override
public void readAdditionalSaveData(CompoundTag tag) { public void readAdditionalSaveData(CompoundTag tag) {
this.setPhaseTwo(tag.getBoolean("PhaseTwo")); this.setPhaseTwo(tag.getBoolean("PhaseTwo"));
this.setTelegraphOnly(tag.getBoolean("TelegraphOnly"));
this.ageTicks = tag.getInt("AgeTicks"); this.ageTicks = tag.getInt("AgeTicks");
this.approachSide = tag.getInt("ApproachSide"); this.approachSide = tag.getInt("ApproachSide");
this.biteSoundPlayed = tag.getBoolean("BiteSoundPlayed"); this.biteSoundPlayed = tag.getBoolean("BiteSoundPlayed");
@@ -642,6 +642,11 @@ public class NightmareEntity extends Monster implements GeoEntity {
return this.entityData.get(ANIMATION_STATE); return this.entityData.get(ANIMATION_STATE);
} }
/** Selects the looping flight pose for the isolated computer archive preview instance. */
public void setArchivePreviewFlying() {
this.setAnimationState(ANIM_FLY);
}
public int getActiveSpecialAnimation() { public int getActiveSpecialAnimation() {
return this.entityData.get(ACTIVE_SPECIAL_ANIMATION); return this.entityData.get(ACTIVE_SPECIAL_ANIMATION);
} }
@@ -5,12 +5,16 @@ import com.craisinlord.antarchy.content.AntarchyObjects;
import com.craisinlord.antarchy.content.AntarchySoundEvents; import com.craisinlord.antarchy.content.AntarchySoundEvents;
import com.craisinlord.antarchy.content.effect.RoyalEffectEligibility; import com.craisinlord.antarchy.content.effect.RoyalEffectEligibility;
import com.craisinlord.antarchy.content.effect.RoyalEffectHooks; import com.craisinlord.antarchy.content.effect.RoyalEffectHooks;
import com.craisinlord.antarchy.content.gravity.AntarchyGravityApi;
import com.craisinlord.antarchy.content.gravity.AntarchyGravityRotationUtil;
import com.craisinlord.antarchy.content.network.KingJudgmentFlashSync;
import net.minecraft.sounds.SoundEvent; import net.minecraft.sounds.SoundEvent;
import net.minecraft.sounds.SoundEvents; import net.minecraft.sounds.SoundEvents;
import net.minecraft.sounds.SoundSource; import net.minecraft.sounds.SoundSource;
import net.minecraft.network.chat.Component; import net.minecraft.network.chat.Component;
import net.minecraft.ChatFormatting; import net.minecraft.ChatFormatting;
import net.minecraft.core.particles.ParticleTypes; import net.minecraft.core.particles.ParticleTypes;
import net.minecraft.core.particles.DustParticleOptions;
import net.minecraft.network.protocol.game.ClientboundClearTitlesPacket; import net.minecraft.network.protocol.game.ClientboundClearTitlesPacket;
import net.minecraft.network.protocol.game.ClientboundSetSubtitleTextPacket; import net.minecraft.network.protocol.game.ClientboundSetSubtitleTextPacket;
import net.minecraft.network.protocol.game.ClientboundSetTitleTextPacket; import net.minecraft.network.protocol.game.ClientboundSetTitleTextPacket;
@@ -46,6 +50,7 @@ import net.minecraft.server.level.ServerPlayer;
import net.minecraft.world.phys.AABB; import net.minecraft.world.phys.AABB;
import net.minecraft.world.phys.Vec3; import net.minecraft.world.phys.Vec3;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
import org.joml.Vector3f;
import com.craisinlord.antarchy.content.entity.royal.beam.RoyalBeamSettings; import com.craisinlord.antarchy.content.entity.royal.beam.RoyalBeamSettings;
import com.craisinlord.antarchy.content.entity.royal.beam.RoyalBeamTerrainMode; import com.craisinlord.antarchy.content.entity.royal.beam.RoyalBeamTerrainMode;
import com.craisinlord.antarchy.content.entity.royal.beam.RoyalBeamElement; import com.craisinlord.antarchy.content.entity.royal.beam.RoyalBeamElement;
@@ -73,6 +78,10 @@ import java.util.Set;
import java.util.UUID; import java.util.UUID;
public class KingEntity extends RoyalBossEntity { public class KingEntity extends RoyalBossEntity {
private static final DustParticleOptions KING_GOLD_DUST =
new DustParticleOptions(new Vector3f(1.0F, 0.72F, 0.12F), 1.25F);
private static final DustParticleOptions KING_WHITE_DUST =
new DustParticleOptions(new Vector3f(1.0F, 1.0F, 1.0F), 1.35F);
public enum Behavior { public enum Behavior {
AIRBORNE, SPRINTING, RETREATING, HUGGING, RANGED, KEEPING_FAR AIRBORNE, SPRINTING, RETREATING, HUGGING, RANGED, KEEPING_FAR
} }
@@ -83,6 +92,12 @@ public class KingEntity extends RoyalBossEntity {
private static final int WING_GUST_COOLDOWN_TICKS = 320; private static final int WING_GUST_COOLDOWN_TICKS = 320;
private static final int WING_GUST_WINDUP_TICKS = 14; private static final int WING_GUST_WINDUP_TICKS = 14;
private static final double WING_GUST_RADIUS = 22.0D; private static final double WING_GUST_RADIUS = 22.0D;
private static final int ROYAL_CHARGE_WINDUP_TICKS = 24;
private static final int ROYAL_CHARGE_ACTIVE_TICKS = 18;
private static final int ROYAL_CHARGE_RECOVERY_TICKS = 18;
private static final double ROYAL_CHARGE_SPEED = 2.35D;
private static final double ROYAL_CHARGE_MIN_RANGE = 10.0D;
private static final double ROYAL_CHARGE_MAX_RANGE = 50.0D;
private static final int CHAIN_LIGHTNING_INTERVAL = 12; private static final int CHAIN_LIGHTNING_INTERVAL = 12;
private static final int CHAIN_LIGHTNING_JUMPS = 4; private static final int CHAIN_LIGHTNING_JUMPS = 4;
private static final double CHAIN_LIGHTNING_JUMP_RANGE = 9.0D; private static final double CHAIN_LIGHTNING_JUMP_RANGE = 9.0D;
@@ -92,13 +107,16 @@ public class KingEntity extends RoyalBossEntity {
private static final double TREE_ORBIT_RADIUS = 128.0D; private static final double TREE_ORBIT_RADIUS = 128.0D;
private static final double TREE_ORBIT_ANGLE_STEP = 0.014D; private static final double TREE_ORBIT_ANGLE_STEP = 0.014D;
private static final int TREE_ORBIT_PATH_SAMPLES = 16; private static final int TREE_ORBIT_PATH_SAMPLES = 16;
private static final double KING_FOLLOW_RANGE = 192.0D; private static final double COMBAT_ORBIT_ANGLE_STEP = 0.62D;
private static final double KING_DAMAGE_MULTIPLIER = 1.5D;
private static final double KING_FOLLOW_RANGE = 216.0D;
private static final double COME_NO_CLOSER_RADIUS = 16.0D; private static final double COME_NO_CLOSER_RADIUS = 16.0D;
private static final int COME_NO_CLOSER_PARTICLE_INTERVAL = 4; private static final int COME_NO_CLOSER_PARTICLE_INTERVAL = 4;
private static final int COME_NO_CLOSER_PARTICLE_COUNT = 40; private static final int COME_NO_CLOSER_PARTICLE_COUNT = 40;
private static final double CLOSE_QUARTERS_RADIUS = 16.0D; private static final double CLOSE_QUARTERS_RADIUS = 16.0D;
private static final int CLOSE_QUARTERS_APPROACH_TICKS = 120; private static final int CLOSE_QUARTERS_APPROACH_TICKS = 120;
private static final int CLOSE_QUARTERS_EXIT_GRACE_TICKS = 40; private static final int CLOSE_QUARTERS_EXIT_GRACE_TICKS = 40;
private static final int DECREE_RESPONSE_GRACE_TICKS = 30;
private static final int PROJECTILE_PRESSURE_MAX = 60; private static final int PROJECTILE_PRESSURE_MAX = 60;
private static final int PROJECTILE_PRESSURE_PER_HIT = 10; private static final int PROJECTILE_PRESSURE_PER_HIT = 10;
private static final int GROUND_ASSAULT_TICKS = 200; private static final int GROUND_ASSAULT_TICKS = 200;
@@ -109,6 +127,8 @@ public class KingEntity extends RoyalBossEntity {
private static final int TAKEOFF_TICKS = 30; private static final int TAKEOFF_TICKS = 30;
private static final int COMBAT_WAYPOINT_MIN_TICKS = 45; private static final int COMBAT_WAYPOINT_MIN_TICKS = 45;
private static final int COMBAT_WAYPOINT_VARIANCE = 35; private static final int COMBAT_WAYPOINT_VARIANCE = 35;
private static final int GROUND_COMBAT_WAYPOINT_MIN_TICKS = 18;
private static final int GROUND_COMBAT_WAYPOINT_VARIANCE = 12;
@Nullable @Nullable
private Vec3 patrolCenter; private Vec3 patrolCenter;
@@ -127,11 +147,16 @@ public class KingEntity extends RoyalBossEntity {
private int treePatrolFailures; private int treePatrolFailures;
private int decreeCooldownTicks; private int decreeCooldownTicks;
private int activeDecreeTicks; private int activeDecreeTicks;
private int decreeResponseGraceTicks;
private int stompCooldownTicks; private int stompCooldownTicks;
private int wingGustCooldownTicks; private int wingGustCooldownTicks;
private int fireballCooldownTicks = 80; private int fireballCooldownTicks = 80;
private int iceballCooldownTicks = 120; private int iceballCooldownTicks = 120;
private int iceSpikeCooldownTicks = 160; private int iceSpikeCooldownTicks = 160;
private int royalChargeOpeningDelayTicks = 35;
private boolean royalChargeActive;
private Vec3 royalChargeDirection = Vec3.ZERO;
private final Set<UUID> royalChargeHitTargets = new HashSet<>();
private boolean decreeRetreatPressure; private boolean decreeRetreatPressure;
@Nullable @Nullable
private String lastDecreeKey; private String lastDecreeKey;
@@ -150,6 +175,9 @@ public class KingEntity extends RoyalBossEntity {
private Vec3 kingCombatProgressPosition; private Vec3 kingCombatProgressPosition;
private int kingCombatWaypointTicks; private int kingCombatWaypointTicks;
private int kingCombatProgressTicks; private int kingCombatProgressTicks;
private int kingGroundCombatWaypointTicks;
@Nullable
private Vec3 kingGroundCombatWaypoint;
private int kingStrafeDirection = 1; private int kingStrafeDirection = 1;
private int royalBoundaryGraceTicks; private int royalBoundaryGraceTicks;
@Nullable @Nullable
@@ -194,10 +222,16 @@ public class KingEntity extends RoyalBossEntity {
} }
public static AttributeSupplier.Builder createAttributes() { public static AttributeSupplier.Builder createAttributes() {
return createBaseAttributes(AntarchySettings.kingHealth(), AntarchySettings.kingAttackDamage(), AntarchySettings.kingArmor()) return createBaseAttributes(AntarchySettings.kingHealth(),
AntarchySettings.kingAttackDamage() * KING_DAMAGE_MULTIPLIER, AntarchySettings.kingArmor())
.add(net.minecraft.world.entity.ai.attributes.Attributes.FOLLOW_RANGE, KING_FOLLOW_RANGE); .add(net.minecraft.world.entity.ai.attributes.Attributes.FOLLOW_RANGE, KING_FOLLOW_RANGE);
} }
@Override
public float scaleRoyalDamage(double damage) {
return super.scaleRoyalDamage(damage) * (float) KING_DAMAGE_MULTIPLIER;
}
@Override @Override
protected int maxConcurrentHeadAttacks(Phase phase) { protected int maxConcurrentHeadAttacks(Phase phase) {
return phase.maxConcurrentHeadAttacks(); return phase.maxConcurrentHeadAttacks();
@@ -205,14 +239,34 @@ public class KingEntity extends RoyalBossEntity {
@Override @Override
protected boolean blocksRoyalAttacksForMovement() { protected boolean blocksRoyalAttacksForMovement() {
return this.kingLandingStage == KingLandingStage.TELEGRAPH return this.royalChargeActive
|| this.kingLandingStage == KingLandingStage.TELEGRAPH
|| this.kingLandingStage == KingLandingStage.DESCENT || this.kingLandingStage == KingLandingStage.DESCENT
|| this.kingLandingStage == KingLandingStage.IMPACT || this.kingLandingStage == KingLandingStage.IMPACT
|| this.kingLandingStage == KingLandingStage.TAKEOFF; || this.kingLandingStage == KingLandingStage.TAKEOFF;
} }
@Override
protected boolean blocksRoyalBeamScheduling() {
if (this.royalChargeActive) {
return true;
}
LivingEntity target = this.getTarget();
if (target == null || this.activeDecree != null || this.royalChargeOpeningDelayTicks > 0
|| this.kingLandingStage != KingLandingStage.AERIAL
|| !this.attackScheduler.ready("royal_charge", RoyalAttackLane.MOVEMENT)) {
return false;
}
double distance = this.distanceTo(target);
return distance >= ROYAL_CHARGE_MIN_RANGE && distance <= ROYAL_CHARGE_MAX_RANGE;
}
@Override @Override
protected void onRoyalPhaseTransitionStarted(Phase previousPhase, Phase nextPhase) { protected void onRoyalPhaseTransitionStarted(Phase previousPhase, Phase nextPhase) {
this.attackScheduler.cancel(RoyalAttackLane.MOVEMENT);
this.royalChargeActive = false;
this.royalChargeDirection = Vec3.ZERO;
this.royalChargeHitTargets.clear();
if (this.activeDecree != null) { if (this.activeDecree != null) {
this.endDecree(null); this.endDecree(null);
} }
@@ -226,7 +280,7 @@ public class KingEntity extends RoyalBossEntity {
Component title = Component.translatable(nextPhase == Phase.TWO Component title = Component.translatable(nextPhase == Phase.TWO
? "boss.antarchy.king.phase_two" : "boss.antarchy.king.phase_three") ? "boss.antarchy.king.phase_two" : "boss.antarchy.king.phase_three")
.withStyle(style -> style.withColor(nextPhase == Phase.TWO .withStyle(style -> style.withColor(nextPhase == Phase.TWO
? ChatFormatting.GOLD : ChatFormatting.DARK_PURPLE).withBold(true)); ? ChatFormatting.GOLD : ChatFormatting.YELLOW).withBold(true));
for (ServerPlayer player : this.royalEncounterPlayers(level)) { for (ServerPlayer player : this.royalEncounterPlayers(level)) {
player.connection.send(new ClientboundSetTitlesAnimationPacket(5, 45, 10)); player.connection.send(new ClientboundSetTitlesAnimationPacket(5, 45, 10));
player.connection.send(new ClientboundSetTitleTextPacket(title)); player.connection.send(new ClientboundSetTitleTextPacket(title));
@@ -239,7 +293,7 @@ public class KingEntity extends RoyalBossEntity {
this.getNavigation().stop(); this.getNavigation().stop();
this.setDeltaMovement(this.getDeltaMovement().scale(0.7D)); this.setDeltaMovement(this.getDeltaMovement().scale(0.7D));
if (this.level() instanceof ServerLevel level && elapsedTicks % 3 == 0) { if (this.level() instanceof ServerLevel level && elapsedTicks % 3 == 0) {
level.sendParticles(nextPhase == Phase.THREE ? ParticleTypes.WITCH : ParticleTypes.ENCHANT, level.sendParticles(nextPhase == Phase.THREE ? KING_GOLD_DUST : ParticleTypes.ELECTRIC_SPARK,
this.getX(), this.getY() + this.getBbHeight() * 0.55D, this.getZ(), this.getX(), this.getY() + this.getBbHeight() * 0.55D, this.getZ(),
18, this.getBbWidth() * 0.25D, this.getBbHeight() * 0.25D, 18, this.getBbWidth() * 0.25D, this.getBbHeight() * 0.25D,
this.getBbWidth() * 0.25D, 0.08D); this.getBbWidth() * 0.25D, 0.08D);
@@ -337,6 +391,12 @@ public class KingEntity extends RoyalBossEntity {
: AntarchySoundEvents.KING_BEAM_SHOOT.get(); : AntarchySoundEvents.KING_BEAM_SHOOT.get();
} }
@Override
protected float royalBeamStartSoundVolume(RoyalHead head) {
return head.slot() == RoyalHead.Slot.LEFT
? 0.18F : (float) AntarchySettings.royalBossSoundVolume();
}
@Override @Override
protected SoundEvent royalBeamLoopSound() { return AntarchySoundEvents.KING_BEAM_SHOOT.get(); } protected SoundEvent royalBeamLoopSound() { return AntarchySoundEvents.KING_BEAM_SHOOT.get(); }
@@ -421,7 +481,7 @@ public class KingEntity extends RoyalBossEntity {
private void chainLightning(ServerLevel level, Vec3 origin) { private void chainLightning(ServerLevel level, Vec3 origin) {
java.util.Set<UUID> struck = new java.util.HashSet<>(); java.util.Set<UUID> struck = new java.util.HashSet<>();
Vec3 from = origin; Vec3 from = origin;
float damage = (float) AntarchySettings.kingBeamDamage() * 0.6F; float damage = this.scaleRoyalDamage(AntarchySettings.kingBeamDamage() * 0.6D);
for (int jump = 0; jump < CHAIN_LIGHTNING_JUMPS; jump++) { for (int jump = 0; jump < CHAIN_LIGHTNING_JUMPS; jump++) {
LivingEntity next = null; LivingEntity next = null;
double bestScore = Double.MAX_VALUE; double bestScore = Double.MAX_VALUE;
@@ -501,6 +561,9 @@ public class KingEntity extends RoyalBossEntity {
if (this.tickCount % 20 == 0 && this.getArrowCount() > 6) { if (this.tickCount % 20 == 0 && this.getArrowCount() > 6) {
this.setArrowCount(6); this.setArrowCount(6);
} }
if (this.royalChargeOpeningDelayTicks > 0) {
this.royalChargeOpeningDelayTicks--;
}
if (this.isRoyalPhaseTransitionActive()) { if (this.isRoyalPhaseTransitionActive()) {
return; return;
} }
@@ -554,7 +617,7 @@ public class KingEntity extends RoyalBossEntity {
double angle = (Math.PI * 2.0D * index) / COME_NO_CLOSER_PARTICLE_COUNT; double angle = (Math.PI * 2.0D * index) / COME_NO_CLOSER_PARTICLE_COUNT;
double x = this.getX() + Math.cos(angle) * radius; double x = this.getX() + Math.cos(angle) * radius;
double z = this.getZ() + Math.sin(angle) * radius; double z = this.getZ() + Math.sin(angle) * radius;
serverLevel.sendParticles(keepOut ? ParticleTypes.END_ROD : ParticleTypes.ENCHANT, serverLevel.sendParticles(keepOut ? ParticleTypes.END_ROD : KING_GOLD_DUST,
x, this.getY() + 0.25D, z, x, this.getY() + 0.25D, z,
1, 0.0D, 0.0D, 0.0D, 0.0D); 1, 0.0D, 0.0D, 0.0D, 0.0D);
} }
@@ -700,6 +763,10 @@ public class KingEntity extends RoyalBossEntity {
} }
private void tickKingCombatMovement(LivingEntity target) { private void tickKingCombatMovement(LivingEntity target) {
if (this.royalChargeActive) {
this.getNavigation().stop();
return;
}
boolean closeQuarters = this.activeDecree instanceof CloseQuartersDecree; boolean closeQuarters = this.activeDecree instanceof CloseQuartersDecree;
if (closeQuarters && this.isRoyalFlying() && this.kingLandingStage == KingLandingStage.AERIAL) { if (closeQuarters && this.isRoyalFlying() && this.kingLandingStage == KingLandingStage.AERIAL) {
this.beginKingLanding(target); this.beginKingLanding(target);
@@ -728,9 +795,13 @@ public class KingEntity extends RoyalBossEntity {
this.setRoyalFlying(true); this.setRoyalFlying(true);
this.getMoveControl().setWantedPosition(landing.x, landing.y, landing.z, 1.35D); this.getMoveControl().setWantedPosition(landing.x, landing.y, landing.z, 1.35D);
this.tickKingLandingIndicator(); this.tickKingLandingIndicator();
Vec3 localLandingDelta = AntarchyGravityRotationUtil.vecWorldToPlayer(
this.position().subtract(landing), AntarchyGravityApi.getGravityDirection(this));
double horizontalDistanceSquared = this.position().subtract(landing)
.multiply(1.0D, 0.0D, 1.0D).lengthSqr();
boolean reachedLanding = this.onGround() boolean reachedLanding = this.onGround()
|| this.position().distanceToSqr(landing) <= 2.25D || this.position().distanceToSqr(landing) <= 2.25D
|| this.getY() <= landing.y + 1.25D; || Math.abs(localLandingDelta.y) <= 1.25D && horizontalDistanceSquared <= 4.0D;
if (reachedLanding) { if (reachedLanding) {
this.setRoyalFlying(false); this.setRoyalFlying(false);
this.setPos(this.getX(), landing.y, this.getZ()); this.setPos(this.getX(), landing.y, this.getZ());
@@ -750,8 +821,8 @@ public class KingEntity extends RoyalBossEntity {
if (--this.kingLandingStageTicks <= 0) { if (--this.kingLandingStageTicks <= 0) {
this.kingLandingStage = KingLandingStage.GROUND_COMBAT; this.kingLandingStage = KingLandingStage.GROUND_COMBAT;
this.kingLandingStageTicks = closeQuarters this.kingLandingStageTicks = closeQuarters
? Math.max(GROUND_ASSAULT_TICKS, this.activeDecreeTicks + 20) ? Math.max(this.groundAssaultTicksForPhase(), this.activeDecreeTicks + 20)
: GROUND_ASSAULT_TICKS; : this.groundAssaultTicksForPhase();
this.kingLandingWaypoint = null; this.kingLandingWaypoint = null;
} }
return; return;
@@ -760,7 +831,14 @@ public class KingEntity extends RoyalBossEntity {
if (closeQuarters) { if (closeQuarters) {
this.kingLandingStageTicks = Math.max(this.kingLandingStageTicks, this.activeDecreeTicks + 20); this.kingLandingStageTicks = Math.max(this.kingLandingStageTicks, this.activeDecreeTicks + 20);
} }
this.getMoveControl().setWantedPosition(target.getX(), target.getY(), target.getZ(), 1.25D); if (this.kingGroundCombatWaypoint == null || --this.kingGroundCombatWaypointTicks <= 0
|| this.position().distanceToSqr(this.kingGroundCombatWaypoint) < 4.0D) {
this.selectKingGroundCombatWaypoint(target);
}
if (this.kingGroundCombatWaypoint != null) {
this.getMoveControl().setWantedPosition(this.kingGroundCombatWaypoint.x,
this.kingGroundCombatWaypoint.y, this.kingGroundCombatWaypoint.z, 1.25D);
}
if (--this.kingLandingStageTicks <= 0 && !closeQuarters) { if (--this.kingLandingStageTicks <= 0 && !closeQuarters) {
this.beginKingTakeoff(target); this.beginKingTakeoff(target);
} }
@@ -808,6 +886,9 @@ public class KingEntity extends RoyalBossEntity {
this.kingCombatWaypoint = null; this.kingCombatWaypoint = null;
this.playRoyalSound(AntarchySoundEvents.KING_ROAR.get(), 1.12F); this.playRoyalSound(AntarchySoundEvents.KING_ROAR.get(), 1.12F);
} else { } else {
// Keep Close Quarters from silently dissolving when the first ring is obstructed.
// The next aerial tick will retry the expanded landing search.
this.kingLandingWaypoint = null;
this.groundAssaultCooldownTicks = 100; this.groundAssaultCooldownTicks = 100;
} }
} }
@@ -821,7 +902,8 @@ public class KingEntity extends RoyalBossEntity {
this.performWingGust(level, target); this.performWingGust(level, target);
} }
this.setRoyalFlying(true); this.setRoyalFlying(true);
this.setDeltaMovement(this.getDeltaMovement().add(0.0D, 0.42D, 0.0D)); this.addGravityAwareImpulse(this, AntarchyGravityRotationUtil.vecPlayerToWorld(
0.0D, 0.42D, 0.0D, AntarchyGravityApi.getGravityDirection(this)));
this.hasImpulse = true; this.hasImpulse = true;
} }
@@ -835,9 +917,11 @@ public class KingEntity extends RoyalBossEntity {
double radius = Mth.lerp(progress, 4.0D, STOMP_RADIUS); double radius = Mth.lerp(progress, 4.0D, STOMP_RADIUS);
for (int index = 0; index < 36; index++) { for (int index = 0; index < 36; index++) {
double angle = Mth.TWO_PI * index / 36.0D; double angle = Mth.TWO_PI * index / 36.0D;
level.sendParticles(ParticleTypes.ENCHANT, Vec3 indicator = this.kingLandingWaypoint.add(AntarchyGravityRotationUtil.vecPlayerToWorld(
0.0D, 0.2D, 0.0D, AntarchyGravityApi.getGravityDirection(this)));
level.sendParticles(ParticleTypes.ELECTRIC_SPARK,
this.kingLandingWaypoint.x + Math.cos(angle) * radius, this.kingLandingWaypoint.x + Math.cos(angle) * radius,
this.kingLandingWaypoint.y + 0.2D, indicator.y,
this.kingLandingWaypoint.z + Math.sin(angle) * radius, this.kingLandingWaypoint.z + Math.sin(angle) * radius,
1, 0.0D, 0.02D, 0.0D, 0.0D); 1, 0.0D, 0.02D, 0.0D, 0.0D);
} }
@@ -847,13 +931,15 @@ public class KingEntity extends RoyalBossEntity {
private Vec3 findKingLandingPoint(LivingEntity target) { private Vec3 findKingLandingPoint(LivingEntity target) {
Vec3 away = this.position().subtract(target.position()).multiply(1.0D, 0.0D, 1.0D); Vec3 away = this.position().subtract(target.position()).multiply(1.0D, 0.0D, 1.0D);
double baseAngle = away.lengthSqr() > 1.0E-4D ? Math.atan2(away.z, away.x) : this.random.nextDouble() * Mth.TWO_PI; double baseAngle = away.lengthSqr() > 1.0E-4D ? Math.atan2(away.z, away.x) : this.random.nextDouble() * Mth.TWO_PI;
double[] radii = {9.0D, 13.0D, 17.0D, 6.0D}; double[] radii = {9.0D, 13.0D, 17.0D, 6.0D, 4.0D, 2.0D, 0.0D};
for (int angleIndex = 0; angleIndex < 12; angleIndex++) { for (int angleIndex = 0; angleIndex < 16; angleIndex++) {
double angle = baseAngle + angleIndex * Mth.TWO_PI / 12.0D; double angle = baseAngle + angleIndex * Mth.TWO_PI / 16.0D;
for (double radius : radii) { for (double radius : radii) {
double x = target.getX() + Math.cos(angle) * radius; double x = target.getX() + Math.cos(angle) * radius;
double z = target.getZ() + Math.sin(angle) * radius; double z = target.getZ() + Math.sin(angle) * radius;
double y = this.groundYBelow(x, z) + 0.1D; double y = AntarchyGravityApi.isGravityInverted(this)
? target.position().y - 0.1D
: this.groundYBelow(x, z) + 0.1D;
Vec3 candidate = new Vec3(x, y, z); Vec3 candidate = new Vec3(x, y, z);
AABB box = this.getBoundingBox().move( AABB box = this.getBoundingBox().move(
candidate.x - this.getX(), candidate.y - this.getY(), candidate.z - this.getZ()); candidate.x - this.getX(), candidate.y - this.getY(), candidate.z - this.getZ());
@@ -862,7 +948,25 @@ public class KingEntity extends RoyalBossEntity {
} }
} }
} }
return null; double fallbackY = AntarchyGravityApi.isGravityInverted(this)
? target.position().y - 0.1D
: this.groundYBelow(target.getX(), target.getZ()) + 0.1D;
Vec3 fallback = new Vec3(target.getX(), fallbackY, target.getZ());
AABB fallbackBox = this.getBoundingBox().move(
fallback.x - this.getX(), fallback.y - this.getY(), fallback.z - this.getZ());
if (this.level().noCollision(this, fallbackBox)) {
return fallback;
}
// Last resort: land directly beneath the King if the target ring is obstructed.
// This still gives Close Quarters a committed ground sequence instead of silently
// remaining airborne over difficult tree geometry.
double directY = AntarchyGravityApi.isGravityInverted(this)
? this.position().y - 0.1D
: this.groundYBelow(this.getX(), this.getZ()) + 0.1D;
Vec3 direct = new Vec3(this.getX(), directY, this.getZ());
AABB directBox = this.getBoundingBox().move(
direct.x - this.getX(), direct.y - this.getY(), direct.z - this.getZ());
return this.level().noCollision(this, directBox) ? direct : null;
} }
private void selectKingCombatWaypoint(LivingEntity target) { private void selectKingCombatWaypoint(LivingEntity target) {
@@ -871,22 +975,71 @@ public class KingEntity extends RoyalBossEntity {
radial = new Vec3(1.0D, 0.0D, 0.0D); radial = new Vec3(1.0D, 0.0D, 0.0D);
} }
radial = radial.normalize(); radial = radial.normalize();
Vec3 tangent = new Vec3(-radial.z * this.kingStrafeDirection, 0.0D,
radial.x * this.kingStrafeDirection);
double range = this.activeDecree instanceof ComeNoCloserDecree || this.projectilePressure >= 30 double range = this.activeDecree instanceof ComeNoCloserDecree || this.projectilePressure >= 30
? 40.0D : 34.0D; ? 40.0D : this.aerialCombatRangeForPhase();
Vec3 horizontal = target.position().add(radial.scale(range)).add(tangent.scale(12.0D)); double currentAngle = Math.atan2(radial.z, radial.x);
double groundY = this.groundYBelow(horizontal.x, horizontal.z); Vec3 orbitWaypoint = null;
double y = Mth.clamp(target.getY() + 8.0D, groundY + FLYING_MIN_HOVER, boolean treeBoundCombat = this.treePatrolBound && this.treePatrolCenter != null;
groundY + FLYING_MAX_HOVER_ABOVE_GROUND); double boundaryRadius = treeBoundCombat
this.kingCombatWaypoint = new Vec3(horizontal.x, y, horizontal.z); ? Math.max(32.0D, AntarchySettings.royalBoundaryRadius()) - this.getBbWidth() * 0.5D
: Double.POSITIVE_INFINITY;
double hoverOffset = this.phase() == Phase.ONE ? 8.0D : this.phase() == Phase.TWO ? 6.0D : 4.0D;
for (int angleAttempt = 0; angleAttempt < 4 && orbitWaypoint == null; angleAttempt++) {
double angle = currentAngle + this.kingStrafeDirection * COMBAT_ORBIT_ANGLE_STEP * (angleAttempt + 1);
double candidateRange = range + (angleAttempt % 2 == 0 ? 0.0D : -6.0D);
double x = target.getX() + Math.cos(angle) * candidateRange;
double z = target.getZ() + Math.sin(angle) * candidateRange;
double groundY = this.groundYBelow(x, z);
double y = Mth.clamp(target.getY() + hoverOffset,
groundY + FLYING_MIN_HOVER, groundY + FLYING_MAX_HOVER_ABOVE_GROUND);
Vec3 candidate = new Vec3(x, y, z);
if (treeBoundCombat) {
double dx = x - this.treePatrolCenter.x;
double dz = z - this.treePatrolCenter.z;
if (dx * dx + dz * dz > boundaryRadius * boundaryRadius) {
continue;
}
}
if (this.clearTreePatrolPath(candidate)) {
orbitWaypoint = candidate;
}
}
if (orbitWaypoint == null && treeBoundCombat) {
// If the tree blocks the next orbital lane, turn away and try the opposite side
// before falling back to a clear target-relative position.
this.kingStrafeDirection *= -1;
double angle = currentAngle + this.kingStrafeDirection * COMBAT_ORBIT_ANGLE_STEP;
double x = target.getX() + Math.cos(angle) * range;
double z = target.getZ() + Math.sin(angle) * range;
double groundY = this.groundYBelow(x, z);
double y = Mth.clamp(target.getY() + hoverOffset,
groundY + FLYING_MIN_HOVER, groundY + FLYING_MAX_HOVER_ABOVE_GROUND);
Vec3 candidate = new Vec3(x, y, z);
double dx = candidate.x - this.treePatrolCenter.x;
double dz = candidate.z - this.treePatrolCenter.z;
if (dx * dx + dz * dz <= boundaryRadius * boundaryRadius
&& this.clearTreePatrolPath(candidate)) {
orbitWaypoint = candidate;
}
}
if (orbitWaypoint == null) {
double angle = currentAngle + this.kingStrafeDirection * COMBAT_ORBIT_ANGLE_STEP;
double x = target.getX() + Math.cos(angle) * range;
double z = target.getZ() + Math.sin(angle) * range;
double groundY = this.groundYBelow(x, z);
double y = Mth.clamp(target.getY() + hoverOffset,
groundY + FLYING_MIN_HOVER, groundY + FLYING_MAX_HOVER_ABOVE_GROUND);
orbitWaypoint = new Vec3(x, y, z);
if (treeBoundCombat) {
orbitWaypoint = this.findTreePatrolWaypoint(this.treePatrolCenter, this.treePatrolAngle);
}
}
this.kingCombatWaypoint = orbitWaypoint;
this.kingCombatWaypointTicks = COMBAT_WAYPOINT_MIN_TICKS this.kingCombatWaypointTicks = COMBAT_WAYPOINT_MIN_TICKS
+ this.random.nextInt(COMBAT_WAYPOINT_VARIANCE + 1); + this.random.nextInt(COMBAT_WAYPOINT_VARIANCE + 1);
this.kingCombatProgressPosition = this.position(); this.kingCombatProgressPosition = this.position();
this.kingCombatProgressTicks = 0; this.kingCombatProgressTicks = 0;
if (this.random.nextInt(4) == 0) { // Keep a consistent orbit direction for several passes so movement reads as circling.
this.kingStrafeDirection *= -1;
}
} }
private void stabilizeKingRotation() { private void stabilizeKingRotation() {
@@ -908,11 +1061,38 @@ public class KingEntity extends RoyalBossEntity {
this.kingCombatProgressPosition = null; this.kingCombatProgressPosition = null;
this.kingCombatWaypointTicks = 0; this.kingCombatWaypointTicks = 0;
this.kingCombatProgressTicks = 0; this.kingCombatProgressTicks = 0;
this.kingGroundCombatWaypoint = null;
this.kingGroundCombatWaypointTicks = 0;
if (this.groundAssaultCooldownTicks <= 0) { if (this.groundAssaultCooldownTicks <= 0) {
this.groundAssaultCooldownTicks = 400; this.groundAssaultCooldownTicks = 400;
} }
} }
private void selectKingGroundCombatWaypoint(LivingEntity target) {
Vec3 radial = this.position().subtract(target.position()).multiply(1.0D, 0.0D, 1.0D);
if (radial.lengthSqr() < 1.0E-4D) {
radial = new Vec3(1.0D, 0.0D, 0.0D);
}
radial = radial.normalize();
Vec3 tangent = new Vec3(-radial.z * this.kingStrafeDirection, 0.0D,
radial.x * this.kingStrafeDirection);
double distance = 4.0D + this.random.nextDouble() * 3.0D;
Vec3 candidate = target.position().add(radial.scale(distance)).add(tangent.scale(2.5D));
Vec3 waypoint = new Vec3(candidate.x, target.getY(), candidate.z);
AABB box = this.getBoundingBox().move(waypoint.x - this.getX(), waypoint.y - this.getY(), waypoint.z - this.getZ());
if (!this.level().noCollision(this, box)) {
waypoint = target.position().add(radial.scale(distance));
waypoint = new Vec3(waypoint.x, target.getY(), waypoint.z);
box = this.getBoundingBox().move(waypoint.x - this.getX(), waypoint.y - this.getY(), waypoint.z - this.getZ());
}
this.kingGroundCombatWaypoint = this.level().noCollision(this, box) ? waypoint : target.position();
this.kingGroundCombatWaypointTicks = GROUND_COMBAT_WAYPOINT_MIN_TICKS
+ this.random.nextInt(GROUND_COMBAT_WAYPOINT_VARIANCE + 1);
if (this.random.nextInt(4) == 0) {
this.kingStrafeDirection *= -1;
}
}
public void requestCloseQuartersAssault(LivingEntity target) { public void requestCloseQuartersAssault(LivingEntity target) {
if (target != null && target.isAlive() && this.isRoyalFlying() if (target != null && target.isAlive() && this.isRoyalFlying()
&& this.kingLandingStage == KingLandingStage.AERIAL) { && this.kingLandingStage == KingLandingStage.AERIAL) {
@@ -937,6 +1117,13 @@ public class KingEntity extends RoyalBossEntity {
? RoyalDecree.Evaluation.VIOLATED : RoyalDecree.Evaluation.COMPLIANT; ? RoyalDecree.Evaluation.VIOLATED : RoyalDecree.Evaluation.COMPLIANT;
} }
public int closeQuartersCountdownTicks(LivingEntity target) {
if (this.closeQuartersEntered.contains(target.getUUID())) {
return -1;
}
return Math.max(0, CLOSE_QUARTERS_APPROACH_TICKS - (300 - this.activeDecreeTicks));
}
private void tickRoyalPunishment() { private void tickRoyalPunishment() {
if (this.exileTarget == null && this.titheTarget == null && this.targetShotPlayer == null && this.sealedTarget == null) { if (this.exileTarget == null && this.titheTarget == null && this.targetShotPlayer == null && this.sealedTarget == null) {
return; return;
@@ -973,6 +1160,9 @@ public class KingEntity extends RoyalBossEntity {
this.exileTarget.setDeltaMovement(away.x * 2.6D, 1.35D, away.z * 2.6D); this.exileTarget.setDeltaMovement(away.x * 2.6D, 1.35D, away.z * 2.6D);
this.exileTarget.hasImpulse = true; this.exileTarget.hasImpulse = true;
this.playRoyalSound(AntarchySoundEvents.KING_WING_FLAP.get(), 0.82F); this.playRoyalSound(AntarchySoundEvents.KING_WING_FLAP.get(), 0.82F);
if (this.level() instanceof ServerLevel level) {
this.spawnKingWingPressure(level, away);
}
this.exileWingAnimationPending = true; this.exileWingAnimationPending = true;
} }
} }
@@ -1147,11 +1337,12 @@ public class KingEntity extends RoyalBossEntity {
this.endDecree(this.getTarget()); this.endDecree(this.getTarget());
this.activeDecree = decree; this.activeDecree = decree;
this.activeDecreeTicks = 300; this.activeDecreeTicks = 300;
this.decreeResponseGraceTicks = DECREE_RESPONSE_GRACE_TICKS;
this.decreeCooldownTicks = 0; this.decreeCooldownTicks = 0;
this.closeQuartersEntered.clear(); this.closeQuartersEntered.clear();
this.closeQuartersOutsideTicks.clear(); this.closeQuartersOutsideTicks.clear();
this.decreeRecipients.clear(); this.decreeRecipients.clear();
this.projectRoyalSound(AntarchySoundEvents.KING_DECREE.get(), 4.0F, 1.0F, target); this.projectRoyalSound(AntarchySoundEvents.KING_DECREE.get(), 1.15F, 1.0F, target);
this.broadcastActiveDecree((ServerLevel) this.level()); this.broadcastActiveDecree((ServerLevel) this.level());
return true; return true;
} }
@@ -1189,7 +1380,7 @@ public class KingEntity extends RoyalBossEntity {
case KINGS_SEAL -> { case KINGS_SEAL -> {
this.sealUntil = level.getGameTime() + 100L; this.sealUntil = level.getGameTime() + 100L;
target.displayClientMessage(Component.literal("The King seals your inventory."), true); target.displayClientMessage(Component.literal("The King seals your inventory."), true);
level.sendParticles(ParticleTypes.ENCHANT, target.getX(), target.getY() + 1.0D, target.getZ(), level.sendParticles(KING_GOLD_DUST, target.getX(), target.getY() + 1.0D, target.getZ(),
30, 0.6D, 1.0D, 0.6D, 0.1D); 30, 0.6D, 1.0D, 0.6D, 0.1D);
this.startSeal(target); this.startSeal(target);
return true; return true;
@@ -1249,12 +1440,16 @@ public class KingEntity extends RoyalBossEntity {
|| this.activeDecree instanceof ComeNoCloserDecree || this.activeDecree instanceof ComeNoCloserDecree
; ;
if (!forceFar && this.activeDecree == null && this.tryStartRoyalCharge(level, target)) {
return;
}
{ {
double distance = target.distanceTo(this); double distance = target.distanceTo(this);
if (this.attackScheduler.ready("stomp", RoyalAttackLane.BODY) if (this.attackScheduler.ready("stomp", RoyalAttackLane.BODY)
&& (distance <= STOMP_RADIUS || skyIsMine) && (distance <= STOMP_RADIUS || skyIsMine)
&& this.beginRoyalAttack("stomp", RoyalAttackLane.BODY, && this.beginRoyalAttack("stomp", RoyalAttackLane.BODY,
skyIsMine ? STOMP_COOLDOWN_TICKS / 2 : STOMP_COOLDOWN_TICKS, this.stompCooldownForPhase(skyIsMine),
STOMP_WINDUP_TICKS, 1, 18, new com.craisinlord.antarchy.content.entity.royal.attack.RoyalAttackScheduler.Action() { STOMP_WINDUP_TICKS, 1, 18, new com.craisinlord.antarchy.content.entity.royal.attack.RoyalAttackScheduler.Action() {
@Override public void onStart() { @Override public void onStart() {
KingEntity.this.triggerAnim("body_action", "stomp"); KingEntity.this.triggerAnim("body_action", "stomp");
@@ -1268,8 +1463,8 @@ public class KingEntity extends RoyalBossEntity {
this.getMoveControl().setWantedPosition(this.getX(), target.getY(), this.getZ(), 1.2D); this.getMoveControl().setWantedPosition(this.getX(), target.getY(), this.getZ(), 1.2D);
} }
} else if (this.attackScheduler.ready("wing_gust", RoyalAttackLane.BODY) } else if (this.attackScheduler.ready("wing_gust", RoyalAttackLane.BODY)
&& distance <= WING_GUST_RADIUS && (forceFar || this.random.nextInt(3) == 0) && distance <= WING_GUST_RADIUS && (forceFar || this.random.nextInt(this.phase() == Phase.ONE ? 3 : 2) == 0)
&& this.beginRoyalAttack("wing_gust", RoyalAttackLane.BODY, WING_GUST_COOLDOWN_TICKS, && this.beginRoyalAttack("wing_gust", RoyalAttackLane.BODY, this.wingGustCooldownForPhase(),
WING_GUST_WINDUP_TICKS, 1, this.animationRecovery(55, WING_GUST_WINDUP_TICKS, 1, 16), new com.craisinlord.antarchy.content.entity.royal.attack.RoyalAttackScheduler.Action() { WING_GUST_WINDUP_TICKS, 1, this.animationRecovery(55, WING_GUST_WINDUP_TICKS, 1, 16), new com.craisinlord.antarchy.content.entity.royal.attack.RoyalAttackScheduler.Action() {
@Override public void onStart() { @Override public void onStart() {
KingEntity.this.triggerAnim("wing_action", "wing_gust"); KingEntity.this.triggerAnim("wing_action", "wing_gust");
@@ -1281,7 +1476,7 @@ public class KingEntity extends RoyalBossEntity {
})) { })) {
} else if (this.attackScheduler.ready("royal_muster", RoyalAttackLane.BODY) } else if (this.attackScheduler.ready("royal_muster", RoyalAttackLane.BODY)
&& !this.findRoyalMusterTargets(level).isEmpty() && !this.findRoyalMusterTargets(level).isEmpty()
&& (forceFar || this.random.nextInt(4) == 0) && (forceFar || this.random.nextInt(this.phase() == Phase.THREE ? 3 : 4) == 0)
&& this.beginRoyalAttack("royal_muster", RoyalAttackLane.BODY, && this.beginRoyalAttack("royal_muster", RoyalAttackLane.BODY,
AntarchySettings.kingRoyalMusterCooldownTicks(), AntarchySettings.kingRoyalMusterCooldownTicks(),
AntarchySettings.kingRoyalMusterWindupTicks(), 1, AntarchySettings.kingRoyalMusterWindupTicks(), 1,
@@ -1289,8 +1484,8 @@ public class KingEntity extends RoyalBossEntity {
new com.craisinlord.antarchy.content.entity.royal.attack.RoyalAttackScheduler.Action() { new com.craisinlord.antarchy.content.entity.royal.attack.RoyalAttackScheduler.Action() {
@Override public void onStart() { @Override public void onStart() {
KingEntity.this.triggerAnim("body_action", "minion_spawn"); KingEntity.this.triggerAnim("body_action", "minion_spawn");
KingEntity.this.playRoyalSound(AntarchySoundEvents.KING_DECREE_CAST.get(), 0.72F); KingEntity.this.playRoyalSound(AntarchySoundEvents.KING_DECREE_CAST.get(), 0.34F, 0.72F);
level.sendParticles(ParticleTypes.ENCHANT, level.sendParticles(KING_GOLD_DUST,
KingEntity.this.getX(), KingEntity.this.getY() + 2.0D, KingEntity.this.getZ(), KingEntity.this.getX(), KingEntity.this.getY() + 2.0D, KingEntity.this.getZ(),
36, 5.0D, 2.0D, 5.0D, 0.12D); 36, 5.0D, 2.0D, 5.0D, 0.12D);
} }
@@ -1315,6 +1510,151 @@ public class KingEntity extends RoyalBossEntity {
this.tickKingElementalProjectiles(level, target); this.tickKingElementalProjectiles(level, target);
} }
private boolean tryStartRoyalCharge(ServerLevel level, LivingEntity target) {
double distance = this.distanceTo(target);
if (this.royalChargeOpeningDelayTicks > 0
|| this.kingLandingStage != KingLandingStage.AERIAL
|| distance < ROYAL_CHARGE_MIN_RANGE || distance > ROYAL_CHARGE_MAX_RANGE
|| this.attackScheduler.laneBusy(RoyalAttackLane.LEFT_HEAD)
|| this.attackScheduler.laneBusy(RoyalAttackLane.CENTER_HEAD)
|| this.attackScheduler.laneBusy(RoyalAttackLane.RIGHT_HEAD)
|| !this.attackScheduler.ready("royal_charge", RoyalAttackLane.MOVEMENT)) {
return false;
}
return this.beginRoyalAttack("royal_charge", RoyalAttackLane.MOVEMENT,
this.royalChargeCooldownForPhase(), ROYAL_CHARGE_WINDUP_TICKS,
ROYAL_CHARGE_ACTIVE_TICKS, ROYAL_CHARGE_RECOVERY_TICKS,
new com.craisinlord.antarchy.content.entity.royal.attack.RoyalAttackScheduler.Action() {
@Override
public void onStart() {
KingEntity.this.royalChargeActive = true;
KingEntity.this.royalChargeHitTargets.clear();
KingEntity.this.lockRoyalChargeDirection(target);
KingEntity.this.getNavigation().stop();
KingEntity.this.setDeltaMovement(KingEntity.this.getDeltaMovement().scale(0.2D));
KingEntity.this.triggerAnim("wing_action", "wing_gust");
KingEntity.this.playRoyalSound(AntarchySoundEvents.KING_WING_FLAP.get(), 0.55F, 0.78F);
}
@Override
public void onWindup(int elapsedTicks, int remainingTicks) {
KingEntity.this.getNavigation().stop();
KingEntity.this.setDeltaMovement(KingEntity.this.getDeltaMovement().scale(0.72D));
if (target.isAlive() && target.level() == level && remainingTicks > 8) {
KingEntity.this.lockRoyalChargeDirection(target);
}
KingEntity.this.sendRoyalChargeTelegraph(level, elapsedTicks);
}
@Override
public void onActive(int elapsedTicks) {
KingEntity.this.setRoyalFlying(true);
KingEntity.this.setDeltaMovement(KingEntity.this.royalChargeDirection.scale(ROYAL_CHARGE_SPEED));
KingEntity.this.hasImpulse = true;
KingEntity.this.applyRoyalChargeHits(level);
level.sendParticles(elapsedTicks % 3 == 0 ? ParticleTypes.END_ROD : KING_GOLD_DUST,
KingEntity.this.getX(), KingEntity.this.getY() + KingEntity.this.getBbHeight() * 0.5D,
KingEntity.this.getZ(), 6, KingEntity.this.getBbWidth() * 0.3D,
KingEntity.this.getBbHeight() * 0.22D, KingEntity.this.getBbWidth() * 0.3D, 0.03D);
if (elapsedTicks == ROYAL_CHARGE_ACTIVE_TICKS - 1) {
KingEntity.this.setDeltaMovement(KingEntity.this.getDeltaMovement().scale(0.12D));
}
}
@Override
public void onComplete() {
KingEntity.this.royalChargeActive = false;
KingEntity.this.royalChargeDirection = Vec3.ZERO;
KingEntity.this.royalChargeHitTargets.clear();
KingEntity.this.setDeltaMovement(KingEntity.this.getDeltaMovement().scale(0.2D));
KingEntity.this.startRoyalRecovery(16);
}
});
}
private void lockRoyalChargeDirection(LivingEntity target) {
Vec3 aimPoint = target.getEyePosition().add(target.getDeltaMovement().scale(4.0D));
Vec3 direction = aimPoint.subtract(this.position().add(0.0D, this.getBbHeight() * 0.5D, 0.0D));
if (direction.lengthSqr() > 1.0E-4D) {
this.royalChargeDirection = direction.normalize();
this.getLookControl().setLookAt(aimPoint.x, aimPoint.y, aimPoint.z, 30.0F, 30.0F);
}
}
private void sendRoyalChargeTelegraph(ServerLevel level, int elapsedTicks) {
if (this.royalChargeDirection.lengthSqr() < 1.0E-4D || elapsedTicks % 2 != 0) {
return;
}
Vec3 origin = this.position().add(0.0D, this.getBbHeight() * 0.5D, 0.0D);
double length = 12.0D + elapsedTicks * 1.25D;
int samples = Math.max(6, (int) (length / 2.5D));
for (int sample = 1; sample <= samples; sample++) {
Vec3 point = origin.add(this.royalChargeDirection.scale(length * sample / samples));
level.sendParticles(sample % 4 == 0 ? ParticleTypes.END_ROD : KING_GOLD_DUST,
point.x, point.y, point.z, 1, 0.025D, 0.025D, 0.025D, 0.0D);
}
}
private void applyRoyalChargeHits(ServerLevel level) {
AABB hitBox = this.getBoundingBox().inflate(2.25D);
for (LivingEntity living : level.getEntitiesOfClass(LivingEntity.class, hitBox,
entity -> entity.isAlive() && entity instanceof Player && this.canDamageWithRoyalAttack(entity))) {
if (!this.royalChargeHitTargets.add(living.getUUID())) {
continue;
}
living.hurt(this.damageSources().mobAttack(this), this.scaleRoyalDamage(12.0D));
Vec3 lift = AntarchyGravityRotationUtil.vecPlayerToWorld(
0.0D, 0.45D, 0.0D, AntarchyGravityApi.getGravityDirection(living));
this.addGravityAwareImpulse(living, this.royalChargeDirection.scale(2.1D).add(lift));
living.hasImpulse = true;
level.sendParticles(ParticleTypes.END_ROD, living.getX(), living.getY() + living.getBbHeight() * 0.5D,
living.getZ(), 18, 0.55D, 0.7D, 0.55D, 0.08D);
}
}
private int royalChargeCooldownForPhase() {
return switch (this.phase()) {
case ONE -> 300;
case TWO -> 250;
case THREE -> 210;
};
}
private int groundAssaultTicksForPhase() {
return switch (this.phase()) {
case ONE -> GROUND_ASSAULT_TICKS;
case TWO -> GROUND_ASSAULT_TICKS + 40;
case THREE -> GROUND_ASSAULT_TICKS + 80;
};
}
private double aerialCombatRangeForPhase() {
return switch (this.phase()) {
case ONE -> 34.0D;
case TWO -> 29.0D;
case THREE -> 24.0D;
};
}
private int stompCooldownForPhase(boolean skyIsMine) {
double scale = switch (this.phase()) {
case ONE -> 1.0D;
case TWO -> 0.88D;
case THREE -> 0.72D;
};
int cooldown = Mth.floor(STOMP_COOLDOWN_TICKS * scale);
return skyIsMine ? cooldown / 2 : cooldown;
}
private int wingGustCooldownForPhase() {
double scale = switch (this.phase()) {
case ONE -> 1.0D;
case TWO -> 0.88D;
case THREE -> 0.72D;
};
return Mth.floor(WING_GUST_COOLDOWN_TICKS * scale);
}
private List<Mob> findRoyalMusterTargets(ServerLevel level) { private List<Mob> findRoyalMusterTargets(ServerLevel level) {
double radius = AntarchySettings.kingRoyalMusterRadius(); double radius = AntarchySettings.kingRoyalMusterRadius();
return level.getEntitiesOfClass(Mob.class, this.getBoundingBox().inflate(radius), mob -> return level.getEntitiesOfClass(Mob.class, this.getBoundingBox().inflate(radius), mob ->
@@ -1342,7 +1682,7 @@ public class KingEntity extends RoyalBossEntity {
} }
} }
if (applied > 0) { if (applied > 0) {
this.playRoyalSound(AntarchySoundEvents.KING_DECREE_CAST.get(), 0.95F); this.playRoyalSound(AntarchySoundEvents.KING_DECREE_CAST.get(), 0.34F, 0.95F);
} }
} }
@@ -1384,7 +1724,9 @@ public class KingEntity extends RoyalBossEntity {
RoyalElementalProjectileEntity fireball = RoyalElementalProjectileEntity.create(level, KingEntity.this, RoyalElementalProjectileEntity fireball = RoyalElementalProjectileEntity.create(level, KingEntity.this,
RoyalBeamElement.FIRE, origin, direction); RoyalBeamElement.FIRE, origin, direction);
level.addFreshEntity(fireball); level.addFreshEntity(fireball);
KingEntity.this.playRoyalSound(AntarchySoundEvents.KING_FIREBALL_SHOOT.get(), 0.9F); if (KingEntity.this.random.nextInt(5) == 0) {
KingEntity.this.playRoyalSound(AntarchySoundEvents.KING_FIREBALL_SHOOT.get(), 0.18F, 0.9F);
}
} }
})) return; })) return;
this.fireballCooldownTicks = AntarchySettings.kingFireballCooldownTicks(); this.fireballCooldownTicks = AntarchySettings.kingFireballCooldownTicks();
@@ -1414,7 +1756,7 @@ public class KingEntity extends RoyalBossEntity {
} }
@Override public void onActive(int elapsedTicks) { @Override public void onActive(int elapsedTicks) {
KingEntity.this.raiseIceSpikes(level, target); KingEntity.this.raiseIceSpikes(level, target);
KingEntity.this.playRoyalSound(AntarchySoundEvents.KING_ICE_SPIKES.get(), 0.8F); KingEntity.this.playRoyalSound(AntarchySoundEvents.KING_ICE_SPIKES.get(), 0.28F, 0.8F);
} }
})) return; })) return;
this.iceSpikeCooldownTicks = AntarchySettings.kingIceSpikeCooldownTicks(); this.iceSpikeCooldownTicks = AntarchySettings.kingIceSpikeCooldownTicks();
@@ -1449,7 +1791,9 @@ public class KingEntity extends RoyalBossEntity {
float falloff = (float) Math.max(0.2D, 1.0D - distance / (STOMP_RADIUS + 6.0D)); float falloff = (float) Math.max(0.2D, 1.0D - distance / (STOMP_RADIUS + 6.0D));
living.hurt(source, base * falloff); living.hurt(source, base * falloff);
Vec3 push = living.position().subtract(this.position()).normalize().scale(2.4D * falloff); Vec3 push = living.position().subtract(this.position()).normalize().scale(2.4D * falloff);
living.setDeltaMovement(living.getDeltaMovement().add(push.x, 0.85D, push.z)); Vec3 vertical = AntarchyGravityRotationUtil.vecPlayerToWorld(
0.0D, 0.85D, 0.0D, AntarchyGravityApi.getGravityDirection(living));
this.addGravityAwareImpulse(living, new Vec3(push.x, 0.0D, push.z).add(vertical));
living.hasImpulse = true; living.hasImpulse = true;
} }
RoyalBlockDestruction.destroySphere(level, this, RoyalBlockDestruction.destroySphere(level, this,
@@ -1475,9 +1819,25 @@ public class KingEntity extends RoyalBossEntity {
living.hasImpulse = true; living.hasImpulse = true;
living.hurt(this.damageSources().mobAttack(this), this.scaleRoyalDamage(6.0D)); living.hurt(this.damageSources().mobAttack(this), this.scaleRoyalDamage(6.0D));
} }
this.spawnKingWingPressure(level, forward);
level.sendParticles(ParticleTypes.SWEEP_ATTACK, this.getX() + forward.x * 6.0D, this.getY() + 4.0D, this.getZ() + forward.z * 6.0D, 12, 4.0D, 2.0D, 4.0D, 0.0D); level.sendParticles(ParticleTypes.SWEEP_ATTACK, this.getX() + forward.x * 6.0D, this.getY() + 4.0D, this.getZ() + forward.z * 6.0D, 12, 4.0D, 2.0D, 4.0D, 0.0D);
} }
private void spawnKingWingPressure(ServerLevel level, Vec3 forward) {
Vec3 side = new Vec3(-forward.z, 0.0D, forward.x);
Vec3 origin = this.position().add(0.0D, this.getBbHeight() * 0.55D, 0.0D);
for (int band = 1; band <= 5; band++) {
double distance = band * 3.0D;
double width = 1.2D + band * 1.05D;
for (int sample = -3; sample <= 3; sample++) {
Vec3 point = origin.add(forward.scale(distance)).add(side.scale(width * sample / 3.0D));
Vec3 velocity = forward.scale(0.18D + band * 0.015D).add(0.0D, 0.025D, 0.0D);
level.sendParticles(sample % 2 == 0 ? KING_WHITE_DUST : ParticleTypes.CLOUD,
point.x, point.y, point.z, 0, velocity.x, velocity.y, velocity.z, 1.0D);
}
}
}
private void tickDecree(ServerLevel level, LivingEntity target) { private void tickDecree(ServerLevel level, LivingEntity target) {
if (this.isRoyalRecoveryActive() && this.activeDecree == null) { if (this.isRoyalRecoveryActive() && this.activeDecree == null) {
return; return;
@@ -1502,31 +1862,41 @@ public class KingEntity extends RoyalBossEntity {
return; return;
} }
this.activeDecreeTicks = 300; this.activeDecreeTicks = 300;
this.decreeResponseGraceTicks = DECREE_RESPONSE_GRACE_TICKS;
this.activeDecree = this.pickDecree(target); this.activeDecree = this.pickDecree(target);
this.decreeRetreatPressure = false; this.decreeRetreatPressure = false;
this.closeQuartersEntered.clear(); this.closeQuartersEntered.clear();
this.closeQuartersOutsideTicks.clear(); this.closeQuartersOutsideTicks.clear();
this.decreeRecipients.clear(); this.decreeRecipients.clear();
this.playRoyalSound(AntarchySoundEvents.KING_DECREE_CAST.get(), 0.9F + this.random.nextFloat() * 0.15F); this.playRoyalSound(AntarchySoundEvents.KING_DECREE_CAST.get(), 0.34F,
0.9F + this.random.nextFloat() * 0.15F);
this.playRoyalSound(AntarchySoundEvents.KING_ROAR.get(), 0.8F + this.random.nextFloat() * 0.12F); this.playRoyalSound(AntarchySoundEvents.KING_ROAR.get(), 0.8F + this.random.nextFloat() * 0.12F);
this.projectRoyalSound(AntarchySoundEvents.KING_DECREE.get(), 4.0F, 1.0F, target); this.projectRoyalSound(AntarchySoundEvents.KING_DECREE.get(), 1.15F, 1.0F, target);
this.broadcastActiveDecree(level); this.broadcastActiveDecree(level);
} }
this.broadcastActiveDecree(level); this.broadcastActiveDecree(level);
boolean hasCompletableParticipants = false; boolean hasCompletableParticipants = false;
boolean everyParticipantComplete = true; boolean everyParticipantComplete = true;
for (ServerPlayer participant : participants) { for (ServerPlayer participant : participants) {
int countdown = this.activeDecree.countdownTicks(participant); if (this.activeDecreeTicks % 20 == 0) {
if (countdown > 0 && countdown <= 100 && this.tickCount % 20 == 0) { int deadlineTicks = this.activeDecree.countdownTicks(this, participant);
level.playSound(null, participant.blockPosition(), SoundEvents.BELL_BLOCK, int countdownTicks = deadlineTicks >= 0 ? deadlineTicks : this.activeDecreeTicks;
SoundSource.HOSTILE, 0.8F, 1.0F + countdown / 500.0F); int seconds = Math.max(0, (countdownTicks + 19) / 20);
ChatFormatting countdownColor = seconds <= 3 ? ChatFormatting.RED : ChatFormatting.GOLD;
participant.displayClientMessage(Component.empty()
.append(Component.translatable(this.activeDecree.translationKey())
.withStyle(ChatFormatting.GOLD, ChatFormatting.BOLD))
.append(Component.literal(" " + seconds).withStyle(countdownColor, ChatFormatting.BOLD)), true);
float volume = seconds <= 3 ? 0.8F : 0.45F;
float pitch = 1.0F + Math.max(0, 15 - seconds) * 0.035F;
participant.playNotifySound(SoundEvents.BELL_BLOCK, SoundSource.HOSTILE, volume, pitch);
} }
this.activeDecree.apply(level, this, participant); this.activeDecree.apply(level, this, participant);
if (this.activeDecree == null) { if (this.activeDecree == null) {
return; return;
} }
RoyalDecree.Evaluation evaluation = this.activeDecree.evaluate(level, this, participant); RoyalDecree.Evaluation evaluation = this.activeDecree.evaluate(level, this, participant);
if (evaluation == RoyalDecree.Evaluation.VIOLATED) { if (evaluation == RoyalDecree.Evaluation.VIOLATED && this.decreeResponseGraceTicks <= 0) {
this.failActiveDecree(participant); this.failActiveDecree(participant);
return; return;
} }
@@ -1541,6 +1911,9 @@ public class KingEntity extends RoyalBossEntity {
this.endDecree(target); this.endDecree(target);
return; return;
} }
if (this.decreeResponseGraceTicks > 0) {
this.decreeResponseGraceTicks--;
}
if (this.activeDecree != null && --this.activeDecreeTicks <= 0) { if (this.activeDecree != null && --this.activeDecreeTicks <= 0) {
this.projectRoyalSound(AntarchySoundEvents.KING_SUCCESS.get(), 4.0F, 1.0F, target); this.projectRoyalSound(AntarchySoundEvents.KING_SUCCESS.get(), 4.0F, 1.0F, target);
this.endDecree(target); this.endDecree(target);
@@ -1588,6 +1961,9 @@ public class KingEntity extends RoyalBossEntity {
} }
for (ServerPlayer participant : this.royalEncounterPlayers(level)) { for (ServerPlayer participant : this.royalEncounterPlayers(level)) {
if (this.decreeRecipients.add(participant.getUUID())) { if (this.decreeRecipients.add(participant.getUUID())) {
if (this.activeDecree instanceof ShowNoMercyDecree) {
this.lastPlayerDamageTime.put(participant.getUUID(), level.getGameTime());
}
this.sendDecreeTitle(participant); this.sendDecreeTitle(participant);
} }
} }
@@ -1608,6 +1984,7 @@ public class KingEntity extends RoyalBossEntity {
Player recipientPlayer = level.getPlayerByUUID(recipientId); Player recipientPlayer = level.getPlayerByUUID(recipientId);
if (recipientPlayer instanceof ServerPlayer recipient) { if (recipientPlayer instanceof ServerPlayer recipient) {
recipient.connection.send(new ClientboundClearTitlesPacket(false)); recipient.connection.send(new ClientboundClearTitlesPacket(false));
recipient.displayClientMessage(Component.empty(), true);
} }
} }
} }
@@ -1617,6 +1994,7 @@ public class KingEntity extends RoyalBossEntity {
} }
this.activeDecree = null; this.activeDecree = null;
this.activeDecreeTicks = 0; this.activeDecreeTicks = 0;
this.decreeResponseGraceTicks = 0;
this.decreeRetreatPressure = false; this.decreeRetreatPressure = false;
this.closeQuartersEntered.clear(); this.closeQuartersEntered.clear();
this.closeQuartersOutsideTicks.clear(); this.closeQuartersOutsideTicks.clear();
@@ -1639,8 +2017,13 @@ public class KingEntity extends RoyalBossEntity {
} }
this.attackScheduler.cancel(RoyalAttackLane.DECREE); this.attackScheduler.cancel(RoyalAttackLane.DECREE);
this.attackScheduler.cancel(RoyalAttackLane.HAZARD); this.attackScheduler.cancel(RoyalAttackLane.HAZARD);
if (target instanceof ServerPlayer player) {
player.addEffect(new MobEffectInstance(
AntarchyObjects.ROYAL_VITALITY_PENALTY_EFFECT.get(), 20 * 30, 0, false, false, false));
player.setHealth(Math.min(player.getHealth(), player.getMaxHealth()));
}
this.endDecree(target); this.endDecree(target);
this.projectRoyalSound(AntarchySoundEvents.KING_JUDGEMENT.get(), 4.0F, 1.0F, target); this.projectRoyalSound(AntarchySoundEvents.KING_JUDGEMENT.get(), 1.1F, 1.0F, target);
if (target instanceof ServerPlayer player) { if (target instanceof ServerPlayer player) {
this.sendDecreeViolationTitle(player); this.sendDecreeViolationTitle(player);
} }
@@ -1650,9 +2033,9 @@ public class KingEntity extends RoyalBossEntity {
private void sendDecreeViolationTitle(ServerPlayer player) { private void sendDecreeViolationTitle(ServerPlayer player) {
player.connection.send(new ClientboundSetTitlesAnimationPacket(5, 45, 10)); player.connection.send(new ClientboundSetTitlesAnimationPacket(5, 45, 10));
player.connection.send(new ClientboundSetTitleTextPacket(Component.translatable("decree.antarchy.violated") player.connection.send(new ClientboundSetTitleTextPacket(Component.translatable("decree.antarchy.violated")
.withStyle(style -> style.withColor(ChatFormatting.DARK_PURPLE).withBold(true)))); .withStyle(style -> style.withColor(ChatFormatting.GOLD).withBold(true))));
player.connection.send(new ClientboundSetSubtitleTextPacket(Component.translatable("decree.antarchy.violated.subtitle") player.connection.send(new ClientboundSetSubtitleTextPacket(Component.translatable("decree.antarchy.violated.subtitle")
.withStyle(style -> style.withColor(ChatFormatting.LIGHT_PURPLE)))); .withStyle(style -> style.withColor(ChatFormatting.YELLOW))));
} }
private void startRoyalJudgmentBarrage(LivingEntity target) { private void startRoyalJudgmentBarrage(LivingEntity target) {
@@ -1666,13 +2049,37 @@ public class KingEntity extends RoyalBossEntity {
for (RoyalHead.Slot slot : RoyalHead.Slot.values()) { for (RoyalHead.Slot slot : RoyalHead.Slot.values()) {
KingEntity.this.triggerAnim(slot.controllerName(), "shoot"); KingEntity.this.triggerAnim(slot.controllerName(), "shoot");
} }
level.sendParticles(ParticleTypes.WITCH, level.sendParticles(ParticleTypes.ELECTRIC_SPARK,
KingEntity.this.getX(), KingEntity.this.getY() + KingEntity.this.getBbHeight() * 0.7D, KingEntity.this.getX(), KingEntity.this.getY() + KingEntity.this.getBbHeight() * 0.7D,
KingEntity.this.getZ(), 70, 4.0D, 3.0D, 4.0D, 0.12D); KingEntity.this.getZ(), 70, 4.0D, 3.0D, 4.0D, 0.12D);
KingEntity.this.sendRoyalJudgmentTelegraph(level, target, 0);
Vec3 targetFlash = target.getEyePosition();
level.sendParticles(ParticleTypes.END_ROD, targetFlash.x, targetFlash.y, targetFlash.z,
24, 0.35D, 0.45D, 0.35D, 0.04D);
}
@Override
public void onWindup(int elapsedTicks, int remainingTicks) {
if (target.isAlive() && target.level() == level) {
KingEntity.this.sendRoyalJudgmentTelegraph(level, target, elapsedTicks);
}
} }
@Override @Override
public void onActive(int elapsedTicks) { public void onActive(int elapsedTicks) {
if (target.isAlive() && target.level() == level) {
if (elapsedTicks == 0) {
Vec3 targetFlash = target.getEyePosition();
level.sendParticles(ParticleTypes.END_ROD, targetFlash.x, targetFlash.y, targetFlash.z,
32, 0.45D, 0.55D, 0.45D, 0.05D);
if (target instanceof ServerPlayer player) {
KingJudgmentFlashSync.send(player, 12);
player.playNotifySound(SoundEvents.BELL_BLOCK, SoundSource.HOSTILE, 0.8F, 1.65F);
}
target.hurt(KingEntity.this.damageSources().magic(),
KingEntity.this.scaleRoyalDamage(6.0D));
}
}
if (elapsedTicks % 4 == 0 && target.isAlive() && target.level() == level) { if (elapsedTicks % 4 == 0 && target.isAlive() && target.level() == level) {
KingEntity.this.fireRoyalJudgmentWave(level, target); KingEntity.this.fireRoyalJudgmentWave(level, target);
} }
@@ -1680,6 +2087,35 @@ public class KingEntity extends RoyalBossEntity {
}); });
} }
private void sendRoyalJudgmentTelegraph(ServerLevel level, LivingEntity target, int elapsedTicks) {
Vec3 targetPoint = target.position().add(AntarchyGravityRotationUtil.vecPlayerToWorld(
0.0D, 0.35D, 0.0D, AntarchyGravityApi.getGravityDirection(target)));
double pulse = 0.8D + Math.min(1.0D, elapsedTicks / 20.0D) * 0.5D;
for (RoyalHead.Slot slot : RoyalHead.Slot.values()) {
RoyalHead head = this.royalHead(slot);
Vec3 origin = this.headAnchor(head);
Vec3 direction = targetPoint.subtract(origin);
double length = direction.length();
if (length < 0.01D) {
continue;
}
direction = direction.scale(1.0D / length);
int samples = Math.max(4, (int) (length / 2.0D));
for (int sample = 1; sample <= samples; sample++) {
Vec3 point = origin.add(direction.scale(length * sample / samples));
level.sendParticles(ParticleTypes.ELECTRIC_SPARK, point.x, point.y, point.z,
1, 0.02D, 0.02D, 0.02D, 0.0D);
}
}
double radius = 1.4D + pulse * 0.8D;
for (int index = 0; index < 20; index++) {
double angle = Mth.TWO_PI * index / 20.0D;
Vec3 point = targetPoint.add(Math.cos(angle) * radius, 0.0D, Math.sin(angle) * radius);
level.sendParticles(ParticleTypes.ELECTRIC_SPARK, point.x, point.y, point.z,
1, 0.0D, 0.03D, 0.0D, 0.0D);
}
}
private void fireRoyalJudgmentWave(ServerLevel level, LivingEntity target) { private void fireRoyalJudgmentWave(ServerLevel level, LivingEntity target) {
Vec3 predicted = target.getEyePosition().add(target.getDeltaMovement().scale(4.0D)); Vec3 predicted = target.getEyePosition().add(target.getDeltaMovement().scale(4.0D));
for (RoyalHead.Slot slot : RoyalHead.Slot.values()) { for (RoyalHead.Slot slot : RoyalHead.Slot.values()) {
@@ -1688,7 +2124,7 @@ public class KingEntity extends RoyalBossEntity {
Vec3 direction = predicted.subtract(origin).normalize(); Vec3 direction = predicted.subtract(origin).normalize();
RoyalBoltEntity bolt = new RoyalBoltEntity(AntarchyObjects.ROYAL_BOLT.get(), level); RoyalBoltEntity bolt = new RoyalBoltEntity(AntarchyObjects.ROYAL_BOLT.get(), level);
bolt.setOwner(this); bolt.setOwner(this);
bolt.setElement(RoyalBeamElement.QUEEN_PURPLE); bolt.setElement(RoyalBeamElement.KING_GOLD);
bolt.setPos(origin.x, origin.y, origin.z); bolt.setPos(origin.x, origin.y, origin.z);
bolt.shoot(direction.x, direction.y, direction.z, 1.55F, 1.0F); bolt.shoot(direction.x, direction.y, direction.z, 1.55F, 1.0F);
level.addFreshEntity(bolt); level.addFreshEntity(bolt);
@@ -1710,7 +2146,7 @@ public class KingEntity extends RoyalBossEntity {
long now = this.level().getGameTime(); long now = this.level().getGameTime();
if (this.judgmentCooldowns.getOrDefault(target.getUUID(), 0L) <= now) { if (this.judgmentCooldowns.getOrDefault(target.getUUID(), 0L) <= now) {
this.judgmentCooldowns.put(target.getUUID(), now + 20L); this.judgmentCooldowns.put(target.getUUID(), now + 20L);
this.projectRoyalSound(AntarchySoundEvents.KING_JUDGEMENT.get(), 4.0F, 1.0F, target); this.projectRoyalSound(AntarchySoundEvents.KING_JUDGEMENT.get(), 1.1F, 1.0F, target);
target.hurt(this.damageSources().magic(), this.scaleRoyalDamage(6.0D)); target.hurt(this.damageSources().magic(), this.scaleRoyalDamage(6.0D));
} }
} }
@@ -1734,7 +2170,7 @@ public class KingEntity extends RoyalBossEntity {
} }
boolean decreeApplies = attacker instanceof ServerPlayer boolean decreeApplies = attacker instanceof ServerPlayer
&& this.decreeRecipients.contains(attacker.getUUID()); && this.decreeRecipients.contains(attacker.getUUID());
if (decreeApplies) { if (decreeApplies && this.decreeResponseGraceTicks <= 0) {
if (this.activeDecree instanceof CloseQuartersDecree && projectile) { if (this.activeDecree instanceof CloseQuartersDecree && projectile) {
this.failActiveDecree(attacker); this.failActiveDecree(attacker);
} else if (this.activeDecree instanceof ComeNoCloserDecree && !projectile) { } else if (this.activeDecree instanceof ComeNoCloserDecree && !projectile) {
@@ -5,8 +5,10 @@ import com.craisinlord.antarchy.content.AntarchySoundEvents;
import com.craisinlord.antarchy.content.AntarchyObjects; import com.craisinlord.antarchy.content.AntarchyObjects;
import com.craisinlord.antarchy.content.AntarchyTags; import com.craisinlord.antarchy.content.AntarchyTags;
import com.craisinlord.antarchy.content.entity.ManticoreEntity; import com.craisinlord.antarchy.content.entity.ManticoreEntity;
import com.craisinlord.antarchy.content.entity.nightmare.NightmareBiteEntity;
import com.craisinlord.antarchy.content.gravity.AntarchyGravityApi; import com.craisinlord.antarchy.content.gravity.AntarchyGravityApi;
import com.craisinlord.antarchy.content.gravity.AntarchyGravityDirection; import com.craisinlord.antarchy.content.gravity.AntarchyGravityDirection;
import com.craisinlord.antarchy.content.gravity.AntarchyGravityRotationUtil;
import com.craisinlord.antarchy.content.gravity.AntarchyGravityTransition; import com.craisinlord.antarchy.content.gravity.AntarchyGravityTransition;
import com.craisinlord.antarchy.content.worldgen.thoraxis.ThoraxisUndersideManager; import com.craisinlord.antarchy.content.worldgen.thoraxis.ThoraxisUndersideManager;
import java.util.HashMap; import java.util.HashMap;
@@ -66,7 +68,6 @@ public class QueenEntity extends RoyalBossEntity {
private static final int MOMENTUM_LOCK_COOLDOWN = 420; private static final int MOMENTUM_LOCK_COOLDOWN = 420;
private static final int MOMENTUM_LOCK_DURATION = 90; private static final int MOMENTUM_LOCK_DURATION = 90;
private static final double MOMENTUM_LOCK_RADIUS = 26.0D; private static final double MOMENTUM_LOCK_RADIUS = 26.0D;
private static final float MOMENTUM_LOCK_EARLY_HEALTH_THRESHOLD = 0.80F;
private static final int CRUSHING_GRAVITY_COOLDOWN = 360; private static final int CRUSHING_GRAVITY_COOLDOWN = 360;
private static final int CRUSHING_GRAVITY_DURATION = 120; private static final int CRUSHING_GRAVITY_DURATION = 120;
private static final double CRUSHING_GRAVITY_RADIUS = 18.0D; private static final double CRUSHING_GRAVITY_RADIUS = 18.0D;
@@ -75,13 +76,23 @@ public class QueenEntity extends RoyalBossEntity {
private static final int ACCELERATION_DURATION = 140; private static final int ACCELERATION_DURATION = 140;
private static final int BLACK_HOLE_COOLDOWN = 340; private static final int BLACK_HOLE_COOLDOWN = 340;
private static final double QUEEN_BITE_COOLDOWN_MULTIPLIER = 1.5D; private static final double QUEEN_BITE_COOLDOWN_MULTIPLIER = 1.5D;
private static final int GRAVITY_STOMP_TELEGRAPH_TICKS = 22;
private static final int BLACK_HOLE_TELEGRAPH_TICKS = 24;
private static final int MOMENTUM_LOCK_TELEGRAPH_TICKS = 40; private static final int MOMENTUM_LOCK_TELEGRAPH_TICKS = 40;
private static final int CRUSHING_GRAVITY_TELEGRAPH_TICKS = 28;
private static final int ACCELERATION_TELEGRAPH_TICKS = 32;
private static final int LANDING_TELEGRAPH_TICKS = 30; private static final int LANDING_TELEGRAPH_TICKS = 30;
private static final int LANDING_IMPACT_TICKS = 18; private static final int LANDING_IMPACT_TICKS = 18;
private static final int GROUND_COMBAT_TICKS = 140; private static final int GROUND_COMBAT_TICKS = 140;
private static final int TAKEOFF_TICKS = 30; private static final int TAKEOFF_TICKS = 30;
private static final int LANDING_COOLDOWN_MIN = 360; private static final int LANDING_COOLDOWN_MIN = 360;
private static final int LANDING_COOLDOWN_VARIANCE = 240; private static final int LANDING_COOLDOWN_VARIANCE = 240;
private static final int BLACK_HOLE_FOLLOW_UP_DELAY = 22;
private static final int CRUSHING_LANDING_DELAY = 40;
private static final int MOMENTUM_VOLLEY_DELAY = MOMENTUM_LOCK_DURATION + 5;
private static final int ACCELERATION_VOLLEY_DELAY = 18;
private static final int ACCELERATION_LANDING_DELAY = 55;
private static final int DIRECTED_LANDING_RETRY_TICKS = 60;
private int manticoreSummonCooldownTicks; private int manticoreSummonCooldownTicks;
private int gravityStompCooldownTicks; private int gravityStompCooldownTicks;
@@ -89,7 +100,13 @@ public class QueenEntity extends RoyalBossEntity {
private int crushingGravityCooldownTicks; private int crushingGravityCooldownTicks;
private int accelerationCooldownTicks; private int accelerationCooldownTicks;
private int blackHoleCooldownTicks; private int blackHoleCooldownTicks;
private int gravityStompTelegraphTicks;
private int blackHoleTelegraphTicks;
private int momentumLockTelegraphTicks; private int momentumLockTelegraphTicks;
private int crushingGravityTelegraphTicks;
private int accelerationTelegraphTicks;
@Nullable
private Vec3 pendingBlackHoleAnchor;
private int queenLandingCooldownTicks = 300; private int queenLandingCooldownTicks = 300;
private int queenLandingStageTicks; private int queenLandingStageTicks;
private QueenLandingStage queenLandingStage = QueenLandingStage.AERIAL; private QueenLandingStage queenLandingStage = QueenLandingStage.AERIAL;
@@ -97,6 +114,12 @@ public class QueenEntity extends RoyalBossEntity {
private Vec3 queenLandingWaypoint; private Vec3 queenLandingWaypoint;
@Nullable @Nullable
private Phase pendingPhaseSignature; private Phase pendingPhaseSignature;
private QueenFollowUp queuedQueenFollowUp = QueenFollowUp.NONE;
private int queuedQueenFollowUpTicks;
private int queuedQueenLandingRetryTicks;
private int directedFireballVolleyTicks;
@Nullable
private String lastQueenMajorAttack;
private final int[] dreamFireballCooldownTicks = new int[3]; private final int[] dreamFireballCooldownTicks = new int[3];
private int idleWanderCooldownTicks; private int idleWanderCooldownTicks;
private boolean manticoreAnimationPending; private boolean manticoreAnimationPending;
@@ -115,6 +138,13 @@ public class QueenEntity extends RoyalBossEntity {
TAKEOFF TAKEOFF
} }
private enum QueenFollowUp {
NONE,
FIREBALL_VOLLEY,
LANDING,
ACCELERATION_VOLLEY
}
public QueenEntity(EntityType<? extends QueenEntity> entityType, Level level) { public QueenEntity(EntityType<? extends QueenEntity> entityType, Level level) {
super(entityType, level); super(entityType, level);
} }
@@ -148,13 +178,20 @@ public class QueenEntity extends RoyalBossEntity {
return this.queenLandingStage == QueenLandingStage.TELEGRAPH return this.queenLandingStage == QueenLandingStage.TELEGRAPH
|| this.queenLandingStage == QueenLandingStage.DESCENT || this.queenLandingStage == QueenLandingStage.DESCENT
|| this.queenLandingStage == QueenLandingStage.IMPACT || this.queenLandingStage == QueenLandingStage.IMPACT
|| this.queenLandingStage == QueenLandingStage.TAKEOFF; || this.queenLandingStage == QueenLandingStage.TAKEOFF
|| this.isQueenAbilityTelegraphActive();
}
@Override
protected boolean blocksRoyalBeamScheduling() {
return this.directedFireballVolleyTicks > 0;
} }
@Override @Override
protected void onRoyalPhaseTransitionStarted(Phase previousPhase, Phase nextPhase) { protected void onRoyalPhaseTransitionStarted(Phase previousPhase, Phase nextPhase) {
this.resetQueenLandingSequence(); this.resetQueenLandingSequence();
this.momentumLockTelegraphTicks = 0; this.clearQueenAbilityTelegraphs();
this.clearQueenAttackDirector();
this.pendingPhaseSignature = null; this.pendingPhaseSignature = null;
this.getNavigation().stop(); this.getNavigation().stop();
this.setDeltaMovement(this.getDeltaMovement().scale(0.35D)); this.setDeltaMovement(this.getDeltaMovement().scale(0.35D));
@@ -191,7 +228,7 @@ public class QueenEntity extends RoyalBossEntity {
this.queenLandingCooldownTicks = Math.min(this.queenLandingCooldownTicks, 100); this.queenLandingCooldownTicks = Math.min(this.queenLandingCooldownTicks, 100);
} else if (nextPhase == Phase.THREE) { } else if (nextPhase == Phase.THREE) {
this.accelerationCooldownTicks = 0; this.accelerationCooldownTicks = 0;
this.startRoyalAcceleration(); this.pendingPhaseSignature = Phase.THREE;
this.queenLandingCooldownTicks = 0; this.queenLandingCooldownTicks = 0;
} }
} }
@@ -334,12 +371,14 @@ public class QueenEntity extends RoyalBossEntity {
} }
LivingEntity target = this.getTarget(); LivingEntity target = this.getTarget();
if (target == null || !target.isAlive() || !this.canAttack(target)) { if (target == null || !target.isAlive() || !this.canAttack(target)) {
this.clearQueenAttackDirector();
this.resetQueenLandingSequence(); this.resetQueenLandingSequence();
this.tickIdleWander(); this.tickIdleWander();
} else { } else {
this.tickQueenAttackDirector(target);
this.tickQueenLandingSequence(target); this.tickQueenLandingSequence(target);
} }
this.tickMomentumLockTelegraph(); this.tickQueenAbilityTelegraphs();
if (this.blocksRoyalAttacksForMovement()) { if (this.blocksRoyalAttacksForMovement()) {
this.royalEffects.tick(); this.royalEffects.tick();
return; return;
@@ -347,13 +386,19 @@ public class QueenEntity extends RoyalBossEntity {
this.tickManticoreSummon(); this.tickManticoreSummon();
this.tickPendingManticoreAnimation(); this.tickPendingManticoreAnimation();
this.tickQueenAbilities(); this.tickQueenAbilities();
if (this.isQueenAbilityTelegraphActive()) {
this.royalEffects.tick();
return;
}
this.tickDreamFireballs(); this.tickDreamFireballs();
this.tickDirectedFireballVolley();
this.tickFinalAcceleration(); this.tickFinalAcceleration();
this.royalEffects.tick(); this.royalEffects.tick();
} }
private void tickQueenLandingSequence(LivingEntity target) { private void tickQueenLandingSequence(LivingEntity target) {
if (this.queenLandingStage == QueenLandingStage.AERIAL && this.isRoyalFlying() if (this.queenLandingStage == QueenLandingStage.AERIAL && this.isRoyalFlying()
&& !this.isQueenAbilityTelegraphActive()
&& this.queenLandingCooldownTicks-- <= 0 && target.onGround()) { && this.queenLandingCooldownTicks-- <= 0 && target.onGround()) {
this.beginQueenLanding(target); this.beginQueenLanding(target);
} }
@@ -435,7 +480,7 @@ public class QueenEntity extends RoyalBossEntity {
double x = target.getX() + Math.cos(angle) * radius; double x = target.getX() + Math.cos(angle) * radius;
double z = target.getZ() + Math.sin(angle) * radius; double z = target.getZ() + Math.sin(angle) * radius;
double y = inverted double y = inverted
? target.getY() + target.getBbHeight() - this.getBbHeight() ? target.position().y - 0.1D
: this.groundYBelow(x, z) + 0.1D; : this.groundYBelow(x, z) + 0.1D;
Vec3 candidate = new Vec3(x, y, z); Vec3 candidate = new Vec3(x, y, z);
AABB box = this.getBoundingBox().move( AABB box = this.getBoundingBox().move(
@@ -445,6 +490,15 @@ public class QueenEntity extends RoyalBossEntity {
} }
} }
} }
double fallbackY = inverted
? target.position().y - 0.1D
: this.groundYBelow(target.getX(), target.getZ()) + 0.1D;
Vec3 fallback = new Vec3(target.getX(), fallbackY, target.getZ());
AABB fallbackBox = this.getBoundingBox().move(
fallback.x - this.getX(), fallback.y - this.getY(), fallback.z - this.getZ());
if (this.level().noCollision(this, fallbackBox)) {
return fallback;
}
return null; return null;
} }
@@ -456,13 +510,13 @@ public class QueenEntity extends RoyalBossEntity {
double progress = this.queenLandingStage == QueenLandingStage.TELEGRAPH double progress = this.queenLandingStage == QueenLandingStage.TELEGRAPH
? 1.0D - this.queenLandingStageTicks / (double) LANDING_TELEGRAPH_TICKS : 1.0D; ? 1.0D - this.queenLandingStageTicks / (double) LANDING_TELEGRAPH_TICKS : 1.0D;
double radius = Mth.lerp(progress, 4.0D, 18.0D); double radius = Mth.lerp(progress, 4.0D, 18.0D);
double indicatorY = this.queenLandingWaypoint.y Vec3 indicator = this.queenLandingWaypoint.add(AntarchyGravityRotationUtil.vecPlayerToWorld(
+ (AntarchyGravityApi.isGravityInverted(this) ? this.getBbHeight() : 0.2D); 0.0D, 0.2D, 0.0D, AntarchyGravityApi.getGravityDirection(this)));
for (int index = 0; index < 40; index++) { for (int index = 0; index < 40; index++) {
double angle = Mth.TWO_PI * index / 40.0D; double angle = Mth.TWO_PI * index / 40.0D;
level.sendParticles(ParticleTypes.REVERSE_PORTAL, level.sendParticles(ParticleTypes.REVERSE_PORTAL,
this.queenLandingWaypoint.x + Math.cos(angle) * radius, this.queenLandingWaypoint.x + Math.cos(angle) * radius,
indicatorY, indicator.y,
this.queenLandingWaypoint.z + Math.sin(angle) * radius, this.queenLandingWaypoint.z + Math.sin(angle) * radius,
1, 0.0D, 0.0D, 0.0D, 0.0D); 1, 0.0D, 0.0D, 0.0D, 0.0D);
} }
@@ -474,13 +528,14 @@ public class QueenEntity extends RoyalBossEntity {
this.triggerAnim("wing_action", "wing_gust"); this.triggerAnim("wing_action", "wing_gust");
this.playRoyalSound(AntarchySoundEvents.QUEEN_FLY_LOOP.get(), 0.9F); this.playRoyalSound(AntarchySoundEvents.QUEEN_FLY_LOOP.get(), 0.9F);
if (this.level() instanceof ServerLevel level) { if (this.level() instanceof ServerLevel level) {
this.spawnQueenWingPressure(level);
level.sendParticles(ParticleTypes.REVERSE_PORTAL, level.sendParticles(ParticleTypes.REVERSE_PORTAL,
this.getX(), this.getY() + this.getBbHeight() * 0.4D, this.getZ(), this.getX(), this.getY() + this.getBbHeight() * 0.4D, this.getZ(),
60, 7.0D, 1.0D, 7.0D, 0.12D); 60, 7.0D, 1.0D, 7.0D, 0.12D);
} }
this.setRoyalFlying(true); this.setRoyalFlying(true);
double lift = AntarchyGravityApi.isGravityInverted(this) ? -0.42D : 0.42D; this.addGravityAwareImpulse(this, AntarchyGravityRotationUtil.vecPlayerToWorld(
this.setDeltaMovement(this.getDeltaMovement().add(0.0D, lift, 0.0D)); 0.0D, 0.42D, 0.0D, AntarchyGravityApi.getGravityDirection(this)));
this.hasImpulse = true; this.hasImpulse = true;
} }
@@ -507,9 +562,20 @@ public class QueenEntity extends RoyalBossEntity {
this.dreamFireballCooldownTicks[index]--; this.dreamFireballCooldownTicks[index]--;
continue; continue;
} }
if (!head.readyToAttack() || this.distanceTo(target) < 12.0D || this.distanceTo(target) > 52.0D) continue; double minimumRange = this.directedFireballVolleyTicks > 0 ? 6.0D : 12.0D;
double maximumRange = this.directedFireballVolleyTicks > 0 ? 64.0D : 52.0D;
if (!head.readyToAttack() || this.distanceTo(target) < minimumRange
|| this.distanceTo(target) > maximumRange) continue;
int cooldown = this.cooldown(180); int cooldown = this.cooldown(180);
RoyalHead.Slot slot = head.slot(); RoyalHead.Slot slot = head.slot();
LivingEntity fireballTarget = target;
if (this.directedFireballVolleyTicks > 0) {
LivingEntity assignedTarget = head.target(level);
if (assignedTarget != null && this.canAttack(assignedTarget)) {
fireballTarget = assignedTarget;
}
}
final LivingEntity committedTarget = fireballTarget;
if (!this.beginRoyalAttack("dream_fireball_" + slot.name(), this.headLane(head), cooldown, 12, 1, 5, if (!this.beginRoyalAttack("dream_fireball_" + slot.name(), this.headLane(head), cooldown, 12, 1, 5,
new com.craisinlord.antarchy.content.entity.royal.attack.RoyalAttackScheduler.Action() { new com.craisinlord.antarchy.content.entity.royal.attack.RoyalAttackScheduler.Action() {
@Override public void onStart() { @Override public void onStart() {
@@ -518,7 +584,7 @@ public class QueenEntity extends RoyalBossEntity {
} }
@Override public void onActive(int elapsedTicks) { @Override public void onActive(int elapsedTicks) {
Vec3 origin = QueenEntity.this.headAnchor(head); Vec3 origin = QueenEntity.this.headAnchor(head);
Vec3 direction = target.getEyePosition().subtract(origin).normalize(); Vec3 direction = committedTarget.getEyePosition().subtract(origin).normalize();
RoyalElementalProjectileEntity projectile = RoyalElementalProjectileEntity.create( RoyalElementalProjectileEntity projectile = RoyalElementalProjectileEntity.create(
level, QueenEntity.this, RoyalBeamElement.DREAM_FIRE, origin, direction); level, QueenEntity.this, RoyalBeamElement.DREAM_FIRE, origin, direction);
level.addFreshEntity(projectile); level.addFreshEntity(projectile);
@@ -533,6 +599,84 @@ public class QueenEntity extends RoyalBossEntity {
} }
} }
private void tickQueenAttackDirector(LivingEntity target) {
if (this.queuedQueenFollowUp == QueenFollowUp.NONE) {
return;
}
if (this.queuedQueenFollowUpTicks-- > 0) {
return;
}
QueenFollowUp followUp = this.queuedQueenFollowUp;
switch (followUp) {
case FIREBALL_VOLLEY -> {
this.clearQueuedQueenFollowUp();
this.primeDirectedFireballVolley();
}
case LANDING -> {
if (this.queenLandingStage == QueenLandingStage.AERIAL
&& this.isRoyalFlying() && target.onGround()) {
this.clearQueuedQueenFollowUp();
this.queenLandingCooldownTicks = 0;
} else if (--this.queuedQueenLandingRetryTicks <= 0) {
this.clearQueuedQueenFollowUp();
} else {
this.queuedQueenFollowUpTicks = 0;
}
}
case ACCELERATION_VOLLEY -> {
this.clearQueuedQueenFollowUp();
this.primeDirectedFireballVolley();
this.queueQueenFollowUp(QueenFollowUp.LANDING, ACCELERATION_LANDING_DELAY);
}
case NONE -> {
}
}
}
private void queueQueenFollowUp(QueenFollowUp followUp, int delayTicks) {
this.queuedQueenFollowUp = followUp;
this.queuedQueenFollowUpTicks = Math.max(0, delayTicks);
this.queuedQueenLandingRetryTicks = followUp == QueenFollowUp.LANDING
? DIRECTED_LANDING_RETRY_TICKS : 0;
}
private void clearQueuedQueenFollowUp() {
this.queuedQueenFollowUp = QueenFollowUp.NONE;
this.queuedQueenFollowUpTicks = 0;
this.queuedQueenLandingRetryTicks = 0;
}
private void clearQueenAttackDirector() {
this.clearQueuedQueenFollowUp();
this.directedFireballVolleyTicks = 0;
}
private void primeDirectedFireballVolley() {
this.directedFireballVolleyTicks = 60;
this.stopRoyalBeamsForDirectedAttack();
for (RoyalHead.Slot slot : RoyalHead.Slot.values()) {
int index = slot.ordinal();
this.dreamFireballCooldownTicks[index] = 0;
this.attackScheduler.resetCooldown("dream_fireball_" + slot.name());
}
}
private void tickDirectedFireballVolley() {
if (this.directedFireballVolleyTicks <= 0 || this.isRoyalRecoveryActive()) {
return;
}
boolean allCommitted = true;
for (int cooldown : this.dreamFireballCooldownTicks) {
if (cooldown <= 0) {
allCommitted = false;
break;
}
}
if (allCommitted || --this.directedFireballVolleyTicks <= 0) {
this.directedFireballVolleyTicks = 0;
}
}
private void tickIdleWander() { private void tickIdleWander() {
if (this.idleWanderCooldownTicks-- > 0) { if (this.idleWanderCooldownTicks-- > 0) {
return; return;
@@ -716,23 +860,27 @@ public class QueenEntity extends RoyalBossEntity {
if (target == null || !target.isAlive()) { if (target == null || !target.isAlive()) {
return; return;
} }
if (this.queuedQueenFollowUp != QueenFollowUp.NONE || this.directedFireballVolleyTicks > 0) {
return;
}
Phase phase = this.phase(); Phase phase = this.phase();
boolean majorBusy = this.royalEffects.active("momentum_lock") || this.royalEffects.active("crushing_gravity"); boolean majorBusy = this.royalEffects.active("momentum_lock")
boolean allowOverlap = phase != Phase.ONE; || this.royalEffects.active("crushing_gravity")
String[] candidates = phase == Phase.ONE && !this.isMomentumLockAvailableEarly() || this.royalEffects.active("acceleration");
? new String[] {"gravity_stomp", "black_hole"} String[] candidates = switch (phase) {
: new String[] {"gravity_stomp", "black_hole", "momentum_lock", "crushing_gravity", "acceleration"}; case ONE -> new String[] {"gravity_stomp", "black_hole"};
String selected = this.pendingPhaseSignature == Phase.TWO case TWO -> new String[] {"gravity_stomp", "black_hole", "momentum_lock", "crushing_gravity"};
&& this.queenAttackReady("momentum_lock", phase, majorBusy, allowOverlap) case THREE -> new String[] {"gravity_stomp", "black_hole", "momentum_lock", "crushing_gravity", "acceleration"};
? "momentum_lock"
: this.attackScheduler.chooseWeighted(candidates, id -> {
if (!this.queenAttackReady(id, phase, majorBusy, allowOverlap)) return 0;
return switch (id) {
case "black_hole", "crushing_gravity" -> phase == Phase.THREE ? 5 : 3;
case "momentum_lock", "acceleration" -> phase == Phase.THREE ? 4 : 2;
default -> 4;
}; };
String forcedAttack = this.pendingPhaseSignature == Phase.TWO ? "momentum_lock"
: this.pendingPhaseSignature == Phase.THREE ? "acceleration" : null;
String selected = forcedAttack != null
&& this.queenAttackReady(forcedAttack, phase, majorBusy)
? forcedAttack : this.attackScheduler.chooseWeighted(candidates, id -> {
if (!this.queenAttackReady(id, phase, majorBusy)
|| id.equals(this.lastQueenMajorAttack)) return 0;
return this.queenAttackWeight(id, phase);
}, this.random); }, this.random);
if (selected == null) return; if (selected == null) return;
int cooldown = switch (selected) { int cooldown = switch (selected) {
@@ -743,27 +891,30 @@ public class QueenEntity extends RoyalBossEntity {
default -> ACCELERATION_COOLDOWN; default -> ACCELERATION_COOLDOWN;
}; };
RoyalAttackLane lane = "black_hole".equals(selected) ? RoyalAttackLane.CENTER_HEAD : RoyalAttackLane.HAZARD; RoyalAttackLane lane = "black_hole".equals(selected) ? RoyalAttackLane.CENTER_HEAD : RoyalAttackLane.HAZARD;
int actionWindup = "momentum_lock".equals(selected) ? MOMENTUM_LOCK_TELEGRAPH_TICKS : 10; int actionWindup = this.queenAbilityWindup(selected);
int actionRecovery = "momentum_lock".equals(selected) int actionAnimationTicks = "momentum_lock".equals(selected)
? this.animationRecovery(55, actionWindup, 1, 14) || "crushing_gravity".equals(selected) || "acceleration".equals(selected) ? 55 : 25;
: this.animationRecovery(25, actionWindup, 1, 14); int actionRecovery = this.animationRecovery(actionAnimationTicks, actionWindup, 1, 14);
if (!this.beginRoyalAttack(selected, lane, this.cooldown(cooldown), if (!this.beginRoyalAttack(selected, lane, this.cooldown(cooldown),
actionWindup, 1, actionRecovery, new com.craisinlord.antarchy.content.entity.royal.attack.RoyalAttackScheduler.Action() { actionWindup, 1, actionRecovery, new com.craisinlord.antarchy.content.entity.royal.attack.RoyalAttackScheduler.Action() {
@Override public void onStart() { @Override public void onStart() {
if ("gravity_stomp".equals(selected)) { if ("gravity_stomp".equals(selected)) {
QueenEntity.this.triggerAnim("body_action", "stomp"); QueenEntity.this.triggerAnim("body_action", "stomp");
} else if ("momentum_lock".equals(selected)) { } else if ("momentum_lock".equals(selected)
|| "crushing_gravity".equals(selected)
|| "acceleration".equals(selected)) {
QueenEntity.this.triggerAnim("wing_action", "wing_gust"); QueenEntity.this.triggerAnim("wing_action", "wing_gust");
QueenEntity.this.momentumLockTelegraphTicks = MOMENTUM_LOCK_TELEGRAPH_TICKS;
QueenEntity.this.playRoyalSound(AntarchySoundEvents.QUEEN_ROAR.get(), 0.58F);
} else if ("black_hole".equals(selected)) { } else if ("black_hole".equals(selected)) {
QueenEntity.this.royalHead(RoyalHead.Slot.CENTER).startShoot(); QueenEntity.this.royalHead(RoyalHead.Slot.CENTER).startShoot();
QueenEntity.this.triggerAnim(RoyalHead.Slot.CENTER.controllerName(), "shoot"); QueenEntity.this.triggerAnim(RoyalHead.Slot.CENTER.controllerName(), "shoot");
} }
QueenEntity.this.startQueenAbilityTelegraph(selected, target);
} }
@Override public void onActive(int elapsedTicks) { @Override public void onActive(int elapsedTicks) {
if ("momentum_lock".equals(selected)) { QueenEntity.this.finishQueenAbilityTelegraph(selected);
QueenEntity.this.momentumLockTelegraphTicks = 0; if ("momentum_lock".equals(selected) || "crushing_gravity".equals(selected)
|| "acceleration".equals(selected)) {
QueenEntity.this.spawnQueenWingPressure(level);
} }
QueenEntity.this.performSelectedQueenAttack(selected, cooldown, level, target); QueenEntity.this.performSelectedQueenAttack(selected, cooldown, level, target);
} }
@@ -773,7 +924,8 @@ public class QueenEntity extends RoyalBossEntity {
} }
} }
})) return; })) return;
if ("momentum_lock".equals(selected)) { this.lastQueenMajorAttack = selected;
if (selected.equals(forcedAttack)) {
this.pendingPhaseSignature = null; this.pendingPhaseSignature = null;
} }
} }
@@ -781,23 +933,65 @@ public class QueenEntity extends RoyalBossEntity {
private void performSelectedQueenAttack(String selected, int cooldown, private void performSelectedQueenAttack(String selected, int cooldown,
ServerLevel level, LivingEntity target) { ServerLevel level, LivingEntity target) {
switch (selected) { switch (selected) {
case "gravity_stomp" -> { this.gravityStompCooldownTicks = cooldown; this.performGravityStomp(level, target); } case "gravity_stomp" -> {
case "black_hole" -> { this.blackHoleCooldownTicks = cooldown; this.castBlackHole(level, target); } this.gravityStompCooldownTicks = cooldown;
case "momentum_lock" -> { this.momentumLockCooldownTicks = cooldown; this.startMomentumLock(level); } this.performGravityStomp(level, target, false);
this.startRoyalRecovery(18);
}
case "black_hole" -> {
this.blackHoleCooldownTicks = cooldown;
this.castBlackHole(level, target);
this.startRoyalRecovery(16);
if (this.phase() == Phase.ONE) {
this.queueQueenFollowUp(QueenFollowUp.FIREBALL_VOLLEY, BLACK_HOLE_FOLLOW_UP_DELAY);
}
}
case "momentum_lock" -> {
this.momentumLockCooldownTicks = cooldown;
this.startMomentumLock(level);
this.queueQueenFollowUp(QueenFollowUp.FIREBALL_VOLLEY, MOMENTUM_VOLLEY_DELAY);
}
case "crushing_gravity" -> { case "crushing_gravity" -> {
this.crushingGravityCooldownTicks = cooldown; this.crushingGravityCooldownTicks = cooldown;
this.royalEffects.start("crushing_gravity", CRUSHING_GRAVITY_DURATION, this.royalEffects.start("crushing_gravity", CRUSHING_GRAVITY_DURATION,
() -> {}, this::tickCrushingGravity, () -> this.startRoyalRecovery(35)); () -> {}, this::tickCrushingGravity, () -> this.startRoyalRecovery(35));
this.playRoyalSound(AntarchySoundEvents.QUEEN_ROAR.get(), 0.6F); this.playRoyalSound(AntarchySoundEvents.QUEEN_ROAR.get(), 0.6F);
if (this.phase() == Phase.TWO) {
this.queueQueenFollowUp(QueenFollowUp.LANDING, CRUSHING_LANDING_DELAY);
}
}
case "acceleration" -> {
this.accelerationCooldownTicks = cooldown;
this.startRoyalAcceleration();
this.queueQueenFollowUp(QueenFollowUp.ACCELERATION_VOLLEY, ACCELERATION_VOLLEY_DELAY);
} }
case "acceleration" -> { this.accelerationCooldownTicks = cooldown; this.startRoyalAcceleration(); }
} }
} }
private boolean queenAttackReady(String id, Phase phase, boolean majorBusy, boolean allowOverlap) { private void spawnQueenWingPressure(ServerLevel level) {
if (!allowOverlap && majorBusy) return false; AntarchyGravityDirection gravity = AntarchyGravityApi.getGravityDirection(this);
for (int ring = 1; ring <= 3; ring++) {
double radius = ring * 3.2D;
int samples = 14 + ring * 4;
for (int sample = 0; sample < samples; sample++) {
double angle = Mth.TWO_PI * sample / samples;
double localX = Math.cos(angle) * radius;
double localZ = Math.sin(angle) * radius;
Vec3 point = this.position().add(AntarchyGravityRotationUtil.vecPlayerToWorld(
localX, this.getBbHeight() * 0.45D, localZ, gravity));
Vec3 velocity = AntarchyGravityRotationUtil.vecPlayerToWorld(
Math.cos(angle) * 0.2D, 0.025D, Math.sin(angle) * 0.2D, gravity);
level.sendParticles(ACCEL_DUST, point.x, point.y, point.z,
0, velocity.x, velocity.y, velocity.z, 1.0D);
}
}
}
private boolean queenAttackReady(String id, Phase phase, boolean majorBusy) {
if (majorBusy) return false;
return switch (id) { return switch (id) {
case "gravity_stomp" -> this.gravityStompCooldownTicks <= 0; case "gravity_stomp" -> this.gravityStompCooldownTicks <= 0
&& !this.isRoyalFlying() && this.queenLandingStage == QueenLandingStage.GROUND_COMBAT;
case "black_hole" -> this.blackHoleCooldownTicks <= 0 case "black_hole" -> this.blackHoleCooldownTicks <= 0
&& this.royalHead(RoyalHead.Slot.CENTER).readyToAttack() && this.royalHead(RoyalHead.Slot.CENTER).readyToAttack()
&& !this.attackScheduler.laneBusy(RoyalAttackLane.CENTER_HEAD); && !this.attackScheduler.laneBusy(RoyalAttackLane.CENTER_HEAD);
@@ -808,9 +1002,23 @@ public class QueenEntity extends RoyalBossEntity {
}; };
} }
private boolean isMomentumLockAvailableEarly() { private int queenAttackWeight(String id, Phase phase) {
return this.getMaxHealth() > 0.0F return switch (phase) {
&& this.getHealth() / this.getMaxHealth() <= MOMENTUM_LOCK_EARLY_HEALTH_THRESHOLD; case ONE -> "black_hole".equals(id) ? 6 : 3;
case TWO -> switch (id) {
case "momentum_lock", "crushing_gravity" -> 6;
case "gravity_stomp" -> 3;
case "black_hole" -> 2;
default -> 0;
};
case THREE -> switch (id) {
case "acceleration" -> 7;
case "black_hole", "crushing_gravity" -> 5;
case "gravity_stomp" -> 4;
case "momentum_lock" -> 2;
default -> 0;
};
};
} }
private void tickFinalAcceleration() { private void tickFinalAcceleration() {
@@ -818,12 +1026,18 @@ public class QueenEntity extends RoyalBossEntity {
return; return;
} }
if (!this.royalEffects.active("acceleration") && this.accelerationCooldownTicks <= 0) { if (!this.royalEffects.active("acceleration") && this.accelerationCooldownTicks <= 0) {
this.startRoyalAcceleration(); this.pendingPhaseSignature = Phase.THREE;
} }
} }
private void performGravityStomp(ServerLevel level, LivingEntity target) { private void performGravityStomp(ServerLevel level, LivingEntity target) {
this.performGravityStomp(level, target, true);
}
private void performGravityStomp(ServerLevel level, LivingEntity target, boolean triggerAnimation) {
if (triggerAnimation) {
this.triggerAnim("body_action", "stomp"); this.triggerAnim("body_action", "stomp");
}
this.playRoyalSound(AntarchySoundEvents.QUEEN_ROAR.get(), 0.7F); this.playRoyalSound(AntarchySoundEvents.QUEEN_ROAR.get(), 0.7F);
DamageSource source = this.damageSources().mobAttack(this); DamageSource source = this.damageSources().mobAttack(this);
for (LivingEntity living : level.getEntitiesOfClass(LivingEntity.class, for (LivingEntity living : level.getEntitiesOfClass(LivingEntity.class,
@@ -831,7 +1045,9 @@ public class QueenEntity extends RoyalBossEntity {
double distance = Math.max(1.0D, living.distanceTo(this)); double distance = Math.max(1.0D, living.distanceTo(this));
living.hurt(source, this.scaleRoyalDamage(12.0D * Math.max(0.25D, 1.0D - distance / 24.0D))); living.hurt(source, this.scaleRoyalDamage(12.0D * Math.max(0.25D, 1.0D - distance / 24.0D)));
Vec3 push = living.position().subtract(this.position()).normalize().scale(1.3D); Vec3 push = living.position().subtract(this.position()).normalize().scale(1.3D);
living.setDeltaMovement(living.getDeltaMovement().add(push.x, 0.65D, push.z)); Vec3 vertical = AntarchyGravityRotationUtil.vecPlayerToWorld(
0.0D, 0.65D, 0.0D, AntarchyGravityApi.getGravityDirection(living));
this.addGravityAwareImpulse(living, new Vec3(push.x, 0.0D, push.z).add(vertical));
living.hasImpulse = true; living.hasImpulse = true;
} }
level.sendParticles(ParticleTypes.EXPLOSION, this.getX(), this.getY() + 1.0D, this.getZ(), level.sendParticles(ParticleTypes.EXPLOSION, this.getX(), this.getY() + 1.0D, this.getZ(),
@@ -839,14 +1055,9 @@ public class QueenEntity extends RoyalBossEntity {
} }
private void castBlackHole(ServerLevel level, LivingEntity target) { private void castBlackHole(ServerLevel level, LivingEntity target) {
double vertical = 2.0D + this.random.nextDouble() * 2.0D; Vec3 anchor = this.pendingBlackHoleAnchor != null
if (AntarchyGravityApi.isGravityInverted(this)) { ? this.pendingBlackHoleAnchor : this.selectBlackHoleAnchor(target);
vertical = -vertical; this.pendingBlackHoleAnchor = null;
}
Vec3 anchor = target.position().add(
(this.random.nextDouble() - 0.5D) * 4.0D,
vertical,
(this.random.nextDouble() - 0.5D) * 4.0D);
Vec3 mouth = this.beamAnchor(this.royalHead(RoyalHead.Slot.CENTER)); Vec3 mouth = this.beamAnchor(this.royalHead(RoyalHead.Slot.CENTER));
RoyalBlackHoleEntity hole = RoyalBlackHoleEntity.createProjectile(level, mouth, anchor, this.getUUID()); RoyalBlackHoleEntity hole = RoyalBlackHoleEntity.createProjectile(level, mouth, anchor, this.getUUID());
level.addFreshEntity(hole); level.addFreshEntity(hole);
@@ -855,40 +1066,136 @@ public class QueenEntity extends RoyalBossEntity {
30, 0.6D, 0.6D, 0.6D, 0.25D); 30, 0.6D, 0.6D, 0.6D, 0.25D);
} }
private void tickMomentumLockTelegraph() { private Vec3 selectBlackHoleAnchor(LivingEntity target) {
if (this.momentumLockTelegraphTicks <= 0 || !(this.level() instanceof ServerLevel level)) { double vertical = 2.0D + this.random.nextDouble() * 2.0D;
if (AntarchyGravityApi.isGravityInverted(this)) {
vertical = -vertical;
}
return target.position().add(
(this.random.nextDouble() - 0.5D) * 4.0D,
vertical,
(this.random.nextDouble() - 0.5D) * 4.0D);
}
private int queenAbilityWindup(String ability) {
return switch (ability) {
case "gravity_stomp" -> GRAVITY_STOMP_TELEGRAPH_TICKS;
case "black_hole" -> BLACK_HOLE_TELEGRAPH_TICKS;
case "momentum_lock" -> MOMENTUM_LOCK_TELEGRAPH_TICKS;
case "crushing_gravity" -> CRUSHING_GRAVITY_TELEGRAPH_TICKS;
case "acceleration" -> ACCELERATION_TELEGRAPH_TICKS;
default -> 10;
};
}
private void startQueenAbilityTelegraph(String ability, LivingEntity target) {
switch (ability) {
case "gravity_stomp" -> {
this.gravityStompTelegraphTicks = GRAVITY_STOMP_TELEGRAPH_TICKS;
this.playRoyalSound(AntarchySoundEvents.QUEEN_ROAR.get(), 0.82F);
}
case "black_hole" -> {
this.pendingBlackHoleAnchor = this.selectBlackHoleAnchor(target);
this.blackHoleTelegraphTicks = BLACK_HOLE_TELEGRAPH_TICKS;
this.playRoyalSound(AntarchySoundEvents.QUEEN_ROAR.get(), 0.48F);
}
case "momentum_lock" -> {
this.momentumLockTelegraphTicks = MOMENTUM_LOCK_TELEGRAPH_TICKS;
this.playRoyalSound(AntarchySoundEvents.QUEEN_ROAR.get(), 0.58F);
}
case "crushing_gravity" -> {
this.crushingGravityTelegraphTicks = CRUSHING_GRAVITY_TELEGRAPH_TICKS;
this.playRoyalSound(AntarchySoundEvents.QUEEN_ROAR.get(), 0.42F);
}
case "acceleration" -> {
this.accelerationTelegraphTicks = ACCELERATION_TELEGRAPH_TICKS;
this.playRoyalSound(AntarchySoundEvents.QUEEN_ROAR.get(), 1.22F);
}
}
}
private void finishQueenAbilityTelegraph(String ability) {
switch (ability) {
case "gravity_stomp" -> this.gravityStompTelegraphTicks = 0;
case "black_hole" -> this.blackHoleTelegraphTicks = 0;
case "momentum_lock" -> this.momentumLockTelegraphTicks = 0;
case "crushing_gravity" -> this.crushingGravityTelegraphTicks = 0;
case "acceleration" -> this.accelerationTelegraphTicks = 0;
}
}
private boolean isQueenAbilityTelegraphActive() {
return this.gravityStompTelegraphTicks > 0 || this.blackHoleTelegraphTicks > 0
|| this.momentumLockTelegraphTicks > 0 || this.crushingGravityTelegraphTicks > 0
|| this.accelerationTelegraphTicks > 0;
}
private void clearQueenAbilityTelegraphs() {
this.gravityStompTelegraphTicks = 0;
this.blackHoleTelegraphTicks = 0;
this.momentumLockTelegraphTicks = 0;
this.crushingGravityTelegraphTicks = 0;
this.accelerationTelegraphTicks = 0;
this.pendingBlackHoleAnchor = null;
}
private void tickQueenAbilityTelegraphs() {
if (!this.isQueenAbilityTelegraphActive() || !(this.level() instanceof ServerLevel level)) {
return; return;
} }
this.getNavigation().stop(); this.getNavigation().stop();
this.setDeltaMovement(Vec3.ZERO); this.setDeltaMovement(Vec3.ZERO);
this.tickGravityStompTelegraph(level);
this.tickBlackHoleTelegraph(level);
this.tickMomentumLockTelegraph(level);
this.tickCrushingGravityTelegraph(level);
this.tickAccelerationTelegraph(level);
}
private void tickGravityStompTelegraph(ServerLevel level) {
if (this.gravityStompTelegraphTicks <= 0) return;
int elapsed = GRAVITY_STOMP_TELEGRAPH_TICKS - this.gravityStompTelegraphTicks;
if (this.tickCount % 2 == 0) {
double radius = Mth.lerp(elapsed / (double) GRAVITY_STOMP_TELEGRAPH_TICKS, 3.0D, 18.0D);
Vec3 surfaceCenter = this.position().add(AntarchyGravityRotationUtil.vecPlayerToWorld(
0.0D, 0.2D, 0.0D, AntarchyGravityApi.getGravityDirection(this)));
this.spawnQueenTelegraphSpokes(level, surfaceCenter, radius);
}
this.playQueenTelegraphBeat(level, this.gravityStompTelegraphTicks,
GRAVITY_STOMP_TELEGRAPH_TICKS, 0.72F);
this.gravityStompTelegraphTicks--;
}
private void tickBlackHoleTelegraph(ServerLevel level) {
if (this.blackHoleTelegraphTicks <= 0 || this.pendingBlackHoleAnchor == null) return;
int elapsed = BLACK_HOLE_TELEGRAPH_TICKS - this.blackHoleTelegraphTicks;
double progress = elapsed / (double) BLACK_HOLE_TELEGRAPH_TICKS;
double fieldRadius = Math.max(4.0D, AntarchySettings.queenBlackHoleRadius());
if (this.tickCount % 2 == 0) {
this.spawnQueenTelegraphRing(level, this.pendingBlackHoleAnchor, fieldRadius, 40, ParticleTypes.WITCH);
this.spawnBlackHoleFunnel(level, this.pendingBlackHoleAnchor, fieldRadius, progress);
level.sendParticles(AntarchyObjects.LUCID_BOLT_IMPACT_LARGE.get(),
this.pendingBlackHoleAnchor.x, this.pendingBlackHoleAnchor.y, this.pendingBlackHoleAnchor.z,
2, 0.12D, 0.12D, 0.12D, 0.01D);
}
this.playQueenTelegraphBeat(level, this.blackHoleTelegraphTicks,
BLACK_HOLE_TELEGRAPH_TICKS, 0.48F);
this.blackHoleTelegraphTicks--;
}
private void tickMomentumLockTelegraph(ServerLevel level) {
if (this.momentumLockTelegraphTicks <= 0) return;
int elapsed = MOMENTUM_LOCK_TELEGRAPH_TICKS - this.momentumLockTelegraphTicks; int elapsed = MOMENTUM_LOCK_TELEGRAPH_TICKS - this.momentumLockTelegraphTicks;
if (this.tickCount % 2 == 0) { if (this.tickCount % 2 == 0) {
double inwardRadius = Mth.lerp(elapsed / (double) MOMENTUM_LOCK_TELEGRAPH_TICKS, this.spawnMomentumLockCage(level, elapsed / (double) MOMENTUM_LOCK_TELEGRAPH_TICKS);
MOMENTUM_LOCK_RADIUS, 2.0D);
for (int index = 0; index < 48; index++) {
double angle = Mth.TWO_PI * index / 48.0D;
double cos = Math.cos(angle);
double sin = Math.sin(angle);
level.sendParticles(ParticleTypes.END_ROD,
this.getX() + cos * MOMENTUM_LOCK_RADIUS,
this.getY() + this.getBbHeight() * 0.45D,
this.getZ() + sin * MOMENTUM_LOCK_RADIUS,
1, 0.0D, 0.0D, 0.0D, 0.0D);
if (index % 2 == 0) {
level.sendParticles(ParticleTypes.REVERSE_PORTAL,
this.getX() + cos * inwardRadius,
this.getY() + this.getBbHeight() * 0.45D,
this.getZ() + sin * inwardRadius,
1, 0.0D, 0.0D, 0.0D, 0.0D);
} }
} if (this.momentumLockTelegraphTicks <= 14) {
}
if (this.momentumLockTelegraphTicks <= 10) {
for (LivingEntity living : level.getEntitiesOfClass(LivingEntity.class, for (LivingEntity living : level.getEntitiesOfClass(LivingEntity.class,
this.getBoundingBox().inflate(MOMENTUM_LOCK_RADIUS), this::canDamageWithRoyalAttack)) { this.getBoundingBox().inflate(MOMENTUM_LOCK_RADIUS), this::canDamageWithRoyalAttack)) {
level.sendParticles(ParticleTypes.END_ROD, this.spawnMomentumTargetCage(level, living);
living.getX(), living.getY() + living.getBbHeight() * 0.5D, living.getZ(), if (this.momentumLockTelegraphTicks == 12) {
2, 0.25D, 0.35D, 0.25D, 0.0D); NightmareBiteEntity.spawnTelegraphAt(level, living, true);
}
} }
} }
if (this.momentumLockTelegraphTicks == 40 || this.momentumLockTelegraphTicks == 20 if (this.momentumLockTelegraphTicks == 40 || this.momentumLockTelegraphTicks == 20
@@ -900,6 +1207,164 @@ public class QueenEntity extends RoyalBossEntity {
this.momentumLockTelegraphTicks--; this.momentumLockTelegraphTicks--;
} }
private void tickCrushingGravityTelegraph(ServerLevel level) {
if (this.crushingGravityTelegraphTicks <= 0) return;
int elapsed = CRUSHING_GRAVITY_TELEGRAPH_TICKS - this.crushingGravityTelegraphTicks;
double progress = elapsed / (double) CRUSHING_GRAVITY_TELEGRAPH_TICKS;
if (this.tickCount % 2 == 0) {
for (LivingEntity living : level.getEntitiesOfClass(LivingEntity.class,
this.getBoundingBox().inflate(CRUSHING_GRAVITY_RADIUS), this::canDamageWithRoyalAttack)) {
double downSign = AntarchyGravityApi.isGravityInverted(living) ? 1.0D : -1.0D;
this.spawnCrushingGravityArrow(level, living, downSign, progress);
}
}
if (elapsed % 7 == 0) {
level.playSound(null, this.blockPosition(), SoundEvents.BEACON_AMBIENT,
SoundSource.HOSTILE, 1.8F, 0.55F + (float) progress * 0.35F);
}
this.crushingGravityTelegraphTicks--;
}
private void tickAccelerationTelegraph(ServerLevel level) {
if (this.accelerationTelegraphTicks <= 0) return;
int elapsed = ACCELERATION_TELEGRAPH_TICKS - this.accelerationTelegraphTicks;
double progress = elapsed / (double) ACCELERATION_TELEGRAPH_TICKS;
this.spawnAccelerationHelix(level, progress);
this.playQueenTelegraphBeat(level, this.accelerationTelegraphTicks,
ACCELERATION_TELEGRAPH_TICKS, 1.0F);
this.accelerationTelegraphTicks--;
}
private void spawnQueenTelegraphSpokes(ServerLevel level, Vec3 center, double radius) {
int spokes = 10;
int segments = 6;
for (int spoke = 0; spoke < spokes; spoke++) {
double angle = Mth.TWO_PI * spoke / spokes;
for (int segment = 1; segment <= segments; segment++) {
double distance = radius * segment / segments;
level.sendParticles(ParticleTypes.CRIT,
center.x + Math.cos(angle) * distance, center.y,
center.z + Math.sin(angle) * distance,
1, 0.08D, 0.02D, 0.08D, 0.0D);
}
}
level.sendParticles(ParticleTypes.LARGE_SMOKE, center.x, center.y, center.z,
3, 0.7D, 0.05D, 0.7D, 0.015D);
}
private void spawnBlackHoleFunnel(ServerLevel level, Vec3 center, double fieldRadius, double progress) {
double rotation = this.tickCount * 0.35D;
AntarchyGravityDirection gravity = AntarchyGravityApi.getGravityDirection(this);
for (int tier = 0; tier < 4; tier++) {
double tierProgress = tier / 3.0D;
double radius = Mth.lerp(progress, fieldRadius * (0.25D + tierProgress * 0.5D), 0.7D);
double localY = -1.5D + tierProgress * 4.5D;
for (int point = 0; point < 10; point++) {
double angle = rotation + tier * 0.65D + Mth.TWO_PI * point / 10.0D;
Vec3 particlePos = center.add(AntarchyGravityRotationUtil.vecPlayerToWorld(
Math.cos(angle) * radius, localY, Math.sin(angle) * radius, gravity));
level.sendParticles(ParticleTypes.REVERSE_PORTAL,
particlePos.x, particlePos.y, particlePos.z,
1, 0.0D, 0.0D, 0.0D, 0.0D);
}
}
}
private void spawnMomentumLockCage(ServerLevel level, double progress) {
double halfHeight = Mth.lerp(progress, 1.5D, 6.0D);
double centerY = this.getY(0.45D);
for (int pillar = 0; pillar < 8; pillar++) {
double angle = Mth.TWO_PI * pillar / 8.0D;
double x = this.getX() + Math.cos(angle) * MOMENTUM_LOCK_RADIUS;
double z = this.getZ() + Math.sin(angle) * MOMENTUM_LOCK_RADIUS;
for (int segment = 0; segment < 7; segment++) {
double y = centerY - halfHeight + segment * halfHeight / 3.0D;
level.sendParticles(ParticleTypes.END_ROD, x, y, z,
1, 0.0D, 0.0D, 0.0D, 0.0D);
}
}
}
private void spawnMomentumTargetCage(ServerLevel level, LivingEntity living) {
double centerY = living.getY(0.5D);
double halfHeight = living.getBbHeight() * 0.7D + 0.6D;
for (int corner = 0; corner < 4; corner++) {
double angle = Math.PI / 4.0D + Math.PI / 2.0D * corner;
double x = living.getX() + Math.cos(angle) * 0.9D;
double z = living.getZ() + Math.sin(angle) * 0.9D;
for (int segment = -1; segment <= 1; segment++) {
level.sendParticles(ParticleTypes.END_ROD, x,
centerY + segment * halfHeight, z,
1, 0.0D, 0.0D, 0.0D, 0.0D);
}
}
}
private void spawnCrushingGravityArrow(ServerLevel level, LivingEntity living,
double downSign, double progress) {
double centerY = living.getY(0.5D);
double shaftStartY = centerY - downSign * Mth.lerp(progress, 7.0D, 4.5D);
for (int segment = 0; segment < 6; segment++) {
level.sendParticles(ParticleTypes.REVERSE_PORTAL, living.getX(),
shaftStartY + downSign * segment * 0.75D, living.getZ(),
1, 0.04D, 0.02D, 0.04D, 0.0D);
}
double tipY = shaftStartY + downSign * 4.5D;
double headY = tipY - downSign * 0.85D;
level.sendParticles(ParticleTypes.WITCH, living.getX() + 0.7D, headY, living.getZ(),
1, 0.0D, 0.0D, 0.0D, 0.0D);
level.sendParticles(ParticleTypes.WITCH, living.getX() - 0.7D, headY, living.getZ(),
1, 0.0D, 0.0D, 0.0D, 0.0D);
level.sendParticles(ParticleTypes.WITCH, living.getX(), headY, living.getZ() + 0.7D,
1, 0.0D, 0.0D, 0.0D, 0.0D);
level.sendParticles(ParticleTypes.WITCH, living.getX(), headY, living.getZ() - 0.7D,
1, 0.0D, 0.0D, 0.0D, 0.0D);
level.sendParticles(AntarchyObjects.LUCID_BOLT_IMPACT_SMALL.get(),
living.getX(), tipY, living.getZ(), 1, 0.05D, 0.05D, 0.05D, 0.0D);
}
private void spawnAccelerationHelix(ServerLevel level, double progress) {
double rotation = this.tickCount * Mth.lerp(progress, 0.24D, 0.62D);
double radius = Mth.lerp(progress, 2.8D, 0.75D);
AntarchyGravityDirection gravity = AntarchyGravityApi.getGravityDirection(this);
for (int strand = 0; strand < 2; strand++) {
for (int segment = 0; segment < 9; segment++) {
double heightProgress = segment / 8.0D;
double angle = rotation + strand * Math.PI + heightProgress * Mth.TWO_PI * 1.5D;
double localY = 0.4D + heightProgress * (this.getBbHeight() + 2.0D);
Vec3 particlePos = this.position().add(AntarchyGravityRotationUtil.vecPlayerToWorld(
Math.cos(angle) * radius, localY, Math.sin(angle) * radius, gravity));
level.sendParticles(AntarchyObjects.LUCID_BOLT_IMPACT_SMALL.get(),
particlePos.x, particlePos.y, particlePos.z,
1, 0.02D, 0.02D, 0.02D, 0.0D);
if (segment % 2 == 0) {
level.sendParticles(ACCEL_DUST, particlePos.x, particlePos.y, particlePos.z,
1, 0.0D, 0.0D, 0.0D, 0.0D);
}
}
}
}
private <T extends net.minecraft.core.particles.ParticleOptions> void spawnQueenTelegraphRing(
ServerLevel level, Vec3 center, double radius, int count, T particle) {
for (int index = 0; index < count; index++) {
double angle = Mth.TWO_PI * index / count;
level.sendParticles(particle,
center.x + Math.cos(angle) * radius, center.y,
center.z + Math.sin(angle) * radius,
1, 0.0D, 0.0D, 0.0D, 0.0D);
}
}
private void playQueenTelegraphBeat(ServerLevel level, int remainingTicks, int duration, float basePitch) {
if (remainingTicks == duration || remainingTicks == duration / 2
|| remainingTicks == 8 || remainingTicks == 3) {
float progress = 1.0F - remainingTicks / (float) duration;
level.playSound(null, this.blockPosition(), SoundEvents.BEACON_POWER_SELECT,
SoundSource.HOSTILE, 2.0F, basePitch + progress * 0.7F);
}
}
private void startMomentumLock(ServerLevel level) { private void startMomentumLock(ServerLevel level) {
this.momentumLockCooldownTicks = MOMENTUM_LOCK_COOLDOWN; this.momentumLockCooldownTicks = MOMENTUM_LOCK_COOLDOWN;
this.royalEffects.start("momentum_lock", MOMENTUM_LOCK_DURATION, () -> { this.royalEffects.start("momentum_lock", MOMENTUM_LOCK_DURATION, () -> {
@@ -1061,6 +1526,13 @@ public class QueenEntity extends RoyalBossEntity {
tag.putInt("AccelerationCooldownTicks", this.accelerationCooldownTicks); tag.putInt("AccelerationCooldownTicks", this.accelerationCooldownTicks);
tag.putInt("BlackHoleCooldownTicks", this.blackHoleCooldownTicks); tag.putInt("BlackHoleCooldownTicks", this.blackHoleCooldownTicks);
tag.putInt("QueenLandingCooldownTicks", this.queenLandingCooldownTicks); tag.putInt("QueenLandingCooldownTicks", this.queenLandingCooldownTicks);
tag.putString("QueuedQueenFollowUp", this.queuedQueenFollowUp.name());
tag.putInt("QueuedQueenFollowUpTicks", this.queuedQueenFollowUpTicks);
tag.putInt("QueuedQueenLandingRetryTicks", this.queuedQueenLandingRetryTicks);
tag.putInt("DirectedFireballVolleyTicks", this.directedFireballVolleyTicks);
if (this.lastQueenMajorAttack != null) {
tag.putString("LastQueenMajorAttack", this.lastQueenMajorAttack);
}
if (this.naturalTrailHome != null) { if (this.naturalTrailHome != null) {
tag.putLong("NaturalTrailSiteId", this.naturalTrailSiteId); tag.putLong("NaturalTrailSiteId", this.naturalTrailSiteId);
tag.putLong("NaturalTrailHome", this.naturalTrailHome.asLong()); tag.putLong("NaturalTrailHome", this.naturalTrailHome.asLong());
@@ -1078,6 +1550,21 @@ public class QueenEntity extends RoyalBossEntity {
this.blackHoleCooldownTicks = Math.max(0, tag.getInt("BlackHoleCooldownTicks")); this.blackHoleCooldownTicks = Math.max(0, tag.getInt("BlackHoleCooldownTicks"));
this.queenLandingCooldownTicks = tag.contains("QueenLandingCooldownTicks") this.queenLandingCooldownTicks = tag.contains("QueenLandingCooldownTicks")
? Math.max(0, tag.getInt("QueenLandingCooldownTicks")) : 300; ? Math.max(0, tag.getInt("QueenLandingCooldownTicks")) : 300;
if (tag.contains("QueuedQueenFollowUp")) {
try {
this.queuedQueenFollowUp = QueenFollowUp.valueOf(tag.getString("QueuedQueenFollowUp"));
} catch (IllegalArgumentException ignored) {
this.queuedQueenFollowUp = QueenFollowUp.NONE;
}
}
this.queuedQueenFollowUpTicks = Math.max(0, tag.getInt("QueuedQueenFollowUpTicks"));
this.queuedQueenLandingRetryTicks = Math.max(0, tag.getInt("QueuedQueenLandingRetryTicks"));
if (this.queuedQueenFollowUp == QueenFollowUp.LANDING && this.queuedQueenLandingRetryTicks == 0) {
this.queuedQueenLandingRetryTicks = DIRECTED_LANDING_RETRY_TICKS;
}
this.directedFireballVolleyTicks = Math.max(0, tag.getInt("DirectedFireballVolleyTicks"));
this.lastQueenMajorAttack = tag.contains("LastQueenMajorAttack")
? tag.getString("LastQueenMajorAttack") : null;
if (tag.contains("NaturalTrailHome")) { if (tag.contains("NaturalTrailHome")) {
this.naturalTrailSiteId = tag.getLong("NaturalTrailSiteId"); this.naturalTrailSiteId = tag.getLong("NaturalTrailSiteId");
this.naturalTrailHome = BlockPos.of(tag.getLong("NaturalTrailHome")); this.naturalTrailHome = BlockPos.of(tag.getLong("NaturalTrailHome"));
@@ -4,6 +4,7 @@ import com.craisinlord.antarchy.config.AntarchySettings;
import com.craisinlord.antarchy.content.entity.royal.beam.RoyalBeamElement; import com.craisinlord.antarchy.content.entity.royal.beam.RoyalBeamElement;
import net.minecraft.core.particles.ParticleOptions; import net.minecraft.core.particles.ParticleOptions;
import net.minecraft.core.particles.ParticleTypes; import net.minecraft.core.particles.ParticleTypes;
import net.minecraft.core.particles.DustParticleOptions;
import net.minecraft.nbt.CompoundTag; import net.minecraft.nbt.CompoundTag;
import net.minecraft.network.syncher.EntityDataAccessor; import net.minecraft.network.syncher.EntityDataAccessor;
import net.minecraft.network.syncher.EntityDataSerializers; import net.minecraft.network.syncher.EntityDataSerializers;
@@ -19,8 +20,11 @@ import net.minecraft.world.entity.projectile.AbstractHurtingProjectile;
import net.minecraft.world.level.Level; import net.minecraft.world.level.Level;
import net.minecraft.world.phys.EntityHitResult; import net.minecraft.world.phys.EntityHitResult;
import net.minecraft.world.phys.HitResult; import net.minecraft.world.phys.HitResult;
import org.joml.Vector3f;
public class RoyalBoltEntity extends AbstractHurtingProjectile { public class RoyalBoltEntity extends AbstractHurtingProjectile {
private static final DustParticleOptions KING_GOLD_TRAIL =
new DustParticleOptions(new Vector3f(1.0F, 0.72F, 0.12F), 1.35F);
private static final EntityDataAccessor<Integer> ELEMENT = private static final EntityDataAccessor<Integer> ELEMENT =
SynchedEntityData.defineId(RoyalBoltEntity.class, EntityDataSerializers.INT); SynchedEntityData.defineId(RoyalBoltEntity.class, EntityDataSerializers.INT);
@@ -78,6 +82,7 @@ public class RoyalBoltEntity extends AbstractHurtingProjectile {
case FIRE -> ParticleTypes.FLAME; case FIRE -> ParticleTypes.FLAME;
case LIGHTNING -> ParticleTypes.ELECTRIC_SPARK; case LIGHTNING -> ParticleTypes.ELECTRIC_SPARK;
case ICE -> ParticleTypes.SNOWFLAKE; case ICE -> ParticleTypes.SNOWFLAKE;
case KING_GOLD -> KING_GOLD_TRAIL;
default -> ParticleTypes.WITCH; default -> ParticleTypes.WITCH;
}; };
} }
@@ -258,6 +258,10 @@ public abstract class RoyalBossEntity extends Monster implements GeoEntity, Mult
return this.royalBeamStartSound(); return this.royalBeamStartSound();
} }
protected float royalBeamStartSoundVolume(RoyalHead head) {
return (float) AntarchySettings.royalBossSoundVolume();
}
protected void tickRoyalBeamEffects(RoyalHead head, Vec3 start, Vec3 end) { protected void tickRoyalBeamEffects(RoyalHead head, Vec3 start, Vec3 end) {
} }
@@ -307,6 +311,11 @@ public abstract class RoyalBossEntity extends Monster implements GeoEntity, Mult
return false; return false;
} }
/** Prevents new beam volleys while a bespoke attack has reserved the heads. */
protected boolean blocksRoyalBeamScheduling() {
return false;
}
protected int royalPhaseTransitionDuration(Phase nextPhase) { protected int royalPhaseTransitionDuration(Phase nextPhase) {
return 50; return 50;
} }
@@ -428,6 +437,10 @@ public abstract class RoyalBossEntity extends Monster implements GeoEntity, Mult
this.playSound(sound, (float) AntarchySettings.royalBossSoundVolume(), pitch); this.playSound(sound, (float) AntarchySettings.royalBossSoundVolume(), pitch);
} }
protected void playRoyalSound(SoundEvent sound, float volume, float pitch) {
this.playSound(sound, volume, pitch);
}
protected void projectRoyalSound(SoundEvent sound, float volume, float pitch, @Nullable LivingEntity focus) { protected void projectRoyalSound(SoundEvent sound, float volume, float pitch, @Nullable LivingEntity focus) {
if (this.level().isClientSide) { if (this.level().isClientSide) {
return; return;
@@ -540,7 +553,9 @@ public abstract class RoyalBossEntity extends Monster implements GeoEntity, Mult
this.getLookControl().setLookAt(primaryTarget, 30.0F, 30.0F); this.getLookControl().setLookAt(primaryTarget, 30.0F, 30.0F);
} }
this.assignHeadTargets(primaryTarget); this.assignHeadTargets(primaryTarget);
if (!this.blocksRoyalBeamScheduling()) {
this.tickRoyalBeam(primaryTarget); this.tickRoyalBeam(primaryTarget);
}
this.tickBodyCrush(); this.tickBodyCrush();
Phase phase = this.phase(); Phase phase = this.phase();
@@ -657,7 +672,8 @@ public abstract class RoyalBossEntity extends Monster implements GeoEntity, Mult
RoyalBossEntity.this.entityData.set(BEAM_ELEMENT[index], RoyalBossEntity.this.royalBeamElement(head).ordinal()); RoyalBossEntity.this.entityData.set(BEAM_ELEMENT[index], RoyalBossEntity.this.royalBeamElement(head).ordinal());
RoyalBossEntity.this.entityData.set(BEAM_ACTIVE[index], true); RoyalBossEntity.this.entityData.set(BEAM_ACTIVE[index], true);
RoyalBossEntity.this.beamLoopSoundTicks[index] = 20; RoyalBossEntity.this.beamLoopSoundTicks[index] = 20;
RoyalBossEntity.this.playRoyalSound(RoyalBossEntity.this.royalBeamStartSound(head), 1.0F); RoyalBossEntity.this.playRoyalSound(RoyalBossEntity.this.royalBeamStartSound(head),
RoyalBossEntity.this.royalBeamStartSoundVolume(head), 1.0F);
RoyalBossEntity.this.playRoyalSound(RoyalBossEntity.this.royalBeamShootSound(), 1.0F); RoyalBossEntity.this.playRoyalSound(RoyalBossEntity.this.royalBeamShootSound(), 1.0F);
} }
@@ -686,6 +702,11 @@ public abstract class RoyalBossEntity extends Monster implements GeoEntity, Mult
this.recoveryWindowTicks = Math.max(this.recoveryWindowTicks, ticks); this.recoveryWindowTicks = Math.max(this.recoveryWindowTicks, ticks);
} }
protected final void addGravityAwareImpulse(Entity entity, Vec3 worldImpulse) {
AntarchyGravityApi.setWorldVelocity(entity,
AntarchyGravityApi.getWorldVelocity(entity).add(worldImpulse));
}
protected final boolean isRoyalPhaseTransitionActive() { protected final boolean isRoyalPhaseTransitionActive() {
return this.phaseTransitionTicks > 0; return this.phaseTransitionTicks > 0;
} }
@@ -726,9 +747,10 @@ public abstract class RoyalBossEntity extends Monster implements GeoEntity, Mult
return; return;
} }
int maxPlayers = Math.max(1, AntarchySettings.royalBossScalingMaxPlayers()); int maxPlayers = Math.max(1, AntarchySettings.royalBossScalingMaxPlayers());
double encounterRange = Math.max(32.0D, this.getAttributeValue(Attributes.FOLLOW_RANGE));
List<ServerPlayer> players = level.getPlayers(player -> player.isAlive() List<ServerPlayer> players = level.getPlayers(player -> player.isAlive()
&& !player.isSpectator() && !player.isSpectator()
&& player.distanceToSqr(this) <= AntarchySettings.royalBossFollowRange() * AntarchySettings.royalBossFollowRange()); && player.distanceToSqr(this) <= encounterRange * encounterRange);
if (target instanceof ServerPlayer player && player.isAlive() && !players.contains(player)) { if (target instanceof ServerPlayer player && player.isAlive() && !players.contains(player)) {
players.add(player); players.add(player);
} }
@@ -761,6 +783,14 @@ public abstract class RoyalBossEntity extends Monster implements GeoEntity, Mult
this.setRoyalBeamEndPosition(head.slot(), null); this.setRoyalBeamEndPosition(head.slot(), null);
} }
protected final void stopRoyalBeamsForDirectedAttack() {
for (RoyalHead head : this.heads) {
if (head.beamActive() || head.shooting()) {
this.stopRoyalBeam(head);
}
}
}
public Phase phase() { public Phase phase() {
float fraction = this.getMaxHealth() <= 0.0F ? 1.0F : this.getHealth() / this.getMaxHealth(); float fraction = this.getMaxHealth() <= 0.0F ? 1.0F : this.getHealth() / this.getMaxHealth();
if (fraction > PHASE_TWO_THRESHOLD) { if (fraction > PHASE_TWO_THRESHOLD) {
@@ -1174,6 +1204,7 @@ public abstract class RoyalBossEntity extends Monster implements GeoEntity, Mult
-0.2D - this.random.nextDouble() * 0.15D, -0.2D - this.random.nextDouble() * 0.15D,
(this.random.nextDouble() - 0.5D) * 0.12D); (this.random.nextDouble() - 0.5D) * 0.12D);
itemEntity.setDefaultPickUpDelay(); itemEntity.setDefaultPickUpDelay();
RoyalLootDropGlow.start(itemEntity);
this.level().addFreshEntity(itemEntity); this.level().addFreshEntity(itemEntity);
return itemEntity; return itemEntity;
} }
@@ -11,6 +11,8 @@ import net.minecraft.network.syncher.EntityDataAccessor;
import net.minecraft.network.syncher.EntityDataSerializers; import net.minecraft.network.syncher.EntityDataSerializers;
import net.minecraft.network.syncher.SynchedEntityData; import net.minecraft.network.syncher.SynchedEntityData;
import net.minecraft.server.level.ServerLevel; import net.minecraft.server.level.ServerLevel;
import net.minecraft.sounds.SoundEvents;
import net.minecraft.sounds.SoundSource;
import net.minecraft.world.damagesource.DamageSource; import net.minecraft.world.damagesource.DamageSource;
import net.minecraft.world.entity.Entity; import net.minecraft.world.entity.Entity;
import net.minecraft.world.entity.EntityType; import net.minecraft.world.entity.EntityType;
@@ -143,6 +145,9 @@ public class RoyalElementalProjectileEntity extends ThrowableProjectile implemen
} }
igniteImpact(level, center, this.isDreamFireball()); igniteImpact(level, center, this.isDreamFireball());
level.sendParticles(ParticleTypes.EXPLOSION_EMITTER, center.x, center.y, center.z, 4, 1.5D, 1.5D, 1.5D, 0.0D); level.sendParticles(ParticleTypes.EXPLOSION_EMITTER, center.x, center.y, center.z, 4, 1.5D, 1.5D, 1.5D, 0.0D);
level.playSound(null, BlockPos.containing(center), SoundEvents.GENERIC_EXPLODE.value(),
SoundSource.HOSTILE, this.isDreamFireball() ? 1.15F : 1.0F,
this.isDreamFireball() ? 0.78F : 0.92F);
} }
private void igniteImpact(ServerLevel level, Vec3 center, boolean dream) { private void igniteImpact(ServerLevel level, Vec3 center, boolean dream) {
@@ -30,6 +30,12 @@ public class RoyalIceSpikeEntity extends Entity implements GeoEntity {
private boolean impacted; private boolean impacted;
@Nullable @Nullable
private UUID ownerId; private UUID ownerId;
private float customImpactDamage = -1.0F;
private double customImpactRadius = 2.2D;
private int customFreezeTicks = 100;
private int customSlowTicks;
private int customSlowAmplifier;
private double customKnockup = 0.35D;
public RoyalIceSpikeEntity(EntityType<? extends RoyalIceSpikeEntity> type, Level level) { public RoyalIceSpikeEntity(EntityType<? extends RoyalIceSpikeEntity> type, Level level) {
super(type, level); super(type, level);
@@ -49,6 +55,18 @@ public class RoyalIceSpikeEntity extends Entity implements GeoEntity {
return spike; return spike;
} }
public static RoyalIceSpikeEntity create(ServerLevel level, Vec3 position, LivingEntity owner,
float damage, double radius, int freezeTicks, int slowTicks, int slowAmplifier) {
RoyalIceSpikeEntity spike = create(level, position, owner);
spike.customImpactDamage = damage;
spike.customImpactRadius = radius;
spike.customFreezeTicks = freezeTicks;
spike.customSlowTicks = slowTicks;
spike.customSlowAmplifier = slowAmplifier;
spike.customKnockup = 0.1D;
return spike;
}
@Override @Override
protected void defineSynchedData(net.minecraft.network.syncher.SynchedEntityData.Builder builder) { protected void defineSynchedData(net.minecraft.network.syncher.SynchedEntityData.Builder builder) {
} }
@@ -73,25 +91,55 @@ public class RoyalIceSpikeEntity extends Entity implements GeoEntity {
private void impact(ServerLevel level) { private void impact(ServerLevel level) {
DamageSource source = this.damageSources().magic(); DamageSource source = this.damageSources().magic();
for (LivingEntity living : level.getEntitiesOfClass(LivingEntity.class,
new AABB(this.blockPosition()).inflate(2.2D), entity -> entity.isAlive())) {
Entity owner = this.ownerId == null ? null : level.getEntity(this.ownerId); Entity owner = this.ownerId == null ? null : level.getEntity(this.ownerId);
living.hurt(source, owner instanceof RoyalBossEntity royalBoss if (this.ownerId != null && owner == null) return;
for (LivingEntity living : level.getEntitiesOfClass(LivingEntity.class,
new AABB(this.blockPosition()).inflate(this.customImpactRadius), entity -> entity.isAlive()
&& entity.distanceToSqr(this.position()) <= this.customImpactRadius * this.customImpactRadius
&& canHitOwnerTarget(owner, entity))) {
float damage = this.customImpactDamage >= 0.0F ? this.customImpactDamage
: owner instanceof RoyalBossEntity royalBoss
? royalBoss.scaleRoyalDamage(AntarchySettings.kingIceSpikeDamage()) ? royalBoss.scaleRoyalDamage(AntarchySettings.kingIceSpikeDamage())
: (float) AntarchySettings.kingIceSpikeDamage()); : (float) AntarchySettings.kingIceSpikeDamage();
living.setTicksFrozen(Math.min(living.getTicksRequiredToFreeze() + 60, living.getTicksFrozen() + 100)); boolean hit = owner instanceof net.minecraft.world.entity.player.Player player && this.customImpactDamage >= 0.0F
living.setDeltaMovement(living.getDeltaMovement().add(0.0D, 0.35D, 0.0D)); ? com.craisinlord.antarchy.content.item.RoyalGuardianSwordAbilities.applySecondaryDamage(player, living, damage)
: living.hurt(source, damage);
if (!hit) continue;
living.setTicksFrozen(Math.min(living.getTicksRequiredToFreeze() + 60, living.getTicksFrozen() + this.customFreezeTicks));
if (this.customSlowTicks > 0) {
living.addEffect(new net.minecraft.world.effect.MobEffectInstance(
net.minecraft.world.effect.MobEffects.MOVEMENT_SLOWDOWN, this.customSlowTicks, this.customSlowAmplifier));
}
living.setDeltaMovement(living.getDeltaMovement().add(0.0D, this.customKnockup, 0.0D));
living.hasImpulse = true; living.hasImpulse = true;
} }
level.sendParticles(ParticleTypes.SNOWFLAKE, this.getX(), this.getY() + 1.5D, this.getZ(), 25, 1.5D, 1.0D, 1.5D, 0.04D); level.sendParticles(ParticleTypes.SNOWFLAKE, this.getX(), this.getY() + 1.5D, this.getZ(), 25, 1.5D, 1.0D, 1.5D, 0.04D);
} }
private boolean canHitOwnerTarget(@Nullable Entity owner, LivingEntity target) {
if (owner == null) return this.ownerId == null;
if (target == owner || target.isAlliedTo(owner) || owner.isAlliedTo(target)) return false;
if (owner instanceof RoyalBossEntity royalBoss) return royalBoss.canDamageWithRoyalAttack(target);
if (owner instanceof net.minecraft.world.entity.player.Player player) {
return com.craisinlord.antarchy.content.item.RoyalGuardianSwordAbilities.canDamageTarget(player, target, false);
}
return true;
}
@Override @Override
protected void readAdditionalSaveData(CompoundTag tag) { protected void readAdditionalSaveData(CompoundTag tag) {
this.impacted = tag.getBoolean("Impacted"); this.impacted = tag.getBoolean("Impacted");
if (tag.hasUUID("Owner")) { if (tag.hasUUID("Owner")) {
this.ownerId = tag.getUUID("Owner"); this.ownerId = tag.getUUID("Owner");
} }
if (tag.contains("CustomImpactDamage")) {
this.customImpactDamage = tag.getFloat("CustomImpactDamage");
this.customImpactRadius = tag.getDouble("CustomImpactRadius");
this.customFreezeTicks = tag.getInt("CustomFreezeTicks");
this.customSlowTicks = tag.getInt("CustomSlowTicks");
this.customSlowAmplifier = tag.getInt("CustomSlowAmplifier");
this.customKnockup = tag.getDouble("CustomKnockup");
}
} }
@Override @Override
@@ -100,6 +148,14 @@ public class RoyalIceSpikeEntity extends Entity implements GeoEntity {
if (this.ownerId != null) { if (this.ownerId != null) {
tag.putUUID("Owner", this.ownerId); tag.putUUID("Owner", this.ownerId);
} }
if (this.customImpactDamage >= 0.0F) {
tag.putFloat("CustomImpactDamage", this.customImpactDamage);
tag.putDouble("CustomImpactRadius", this.customImpactRadius);
tag.putInt("CustomFreezeTicks", this.customFreezeTicks);
tag.putInt("CustomSlowTicks", this.customSlowTicks);
tag.putInt("CustomSlowAmplifier", this.customSlowAmplifier);
tag.putDouble("CustomKnockup", this.customKnockup);
}
} }
@Override @Override
@@ -0,0 +1,37 @@
package com.craisinlord.antarchy.content.entity.royal;
import net.minecraft.world.entity.item.ItemEntity;
public final class RoyalLootDropGlow {
private static final String TAG_PREFIX = "antarchy_royal_loot_glow_until_";
public static final int DURATION_TICKS = 20 * 120;
private RoyalLootDropGlow() {
}
public static void start(ItemEntity itemEntity) {
long expiryTick = itemEntity.level().getGameTime() + DURATION_TICKS;
itemEntity.addTag(TAG_PREFIX + expiryTick);
itemEntity.setGlowingTag(true);
}
public static void tick(ItemEntity itemEntity) {
if (itemEntity.level().isClientSide) return;
for (String tag : itemEntity.getTags()) {
if (!tag.startsWith(TAG_PREFIX)) continue;
long expiryTick;
try {
expiryTick = Long.parseLong(tag.substring(TAG_PREFIX.length()));
} catch (NumberFormatException ignored) {
itemEntity.removeTag(tag);
continue;
}
if (itemEntity.level().getGameTime() < expiryTick) {
itemEntity.setGlowingTag(true);
return;
}
itemEntity.removeTag(tag);
itemEntity.setGlowingTag(false);
}
}
}
@@ -11,6 +11,7 @@ import net.minecraft.util.RandomSource;
public final class RoyalAttackScheduler { public final class RoyalAttackScheduler {
public interface Action { public interface Action {
default void onStart() {} default void onStart() {}
default void onWindup(int elapsedTicks, int remainingTicks) {}
default void onActive(int elapsedTicks) {} default void onActive(int elapsedTicks) {}
default void onComplete() {} default void onComplete() {}
} }
@@ -44,6 +45,9 @@ public final class RoyalAttackScheduler {
running.elapsedTicks++; running.elapsedTicks++;
int activeStart = running.windupTicks; int activeStart = running.windupTicks;
int activeEnd = activeStart + running.activeTicks; int activeEnd = activeStart + running.activeTicks;
if (running.elapsedTicks < activeStart) {
running.action.onWindup(running.elapsedTicks, activeStart - running.elapsedTicks);
}
if (running.elapsedTicks >= activeStart && running.elapsedTicks < activeEnd) { if (running.elapsedTicks >= activeStart && running.elapsedTicks < activeEnd) {
running.action.onActive(running.elapsedTicks - activeStart); running.action.onActive(running.elapsedTicks - activeStart);
} }
@@ -116,6 +120,10 @@ public final class RoyalAttackScheduler {
return cooldowns.getOrDefault(id, 0); return cooldowns.getOrDefault(id, 0);
} }
public void resetCooldown(String id) {
cooldowns.remove(id);
}
public void cancel(RoyalAttackLane lane) { public void cancel(RoyalAttackLane lane) {
active.remove(lane); active.remove(lane);
} }
@@ -8,5 +8,6 @@ public enum RoyalBeamElement {
QUEEN_PURPLE, QUEEN_PURPLE,
QUEEN_RED, QUEEN_RED,
QUEEN_BLACK, QUEEN_BLACK,
DREAM_FIRE DREAM_FIRE,
KING_GOLD
} }
@@ -12,6 +12,10 @@ public final class CloseQuartersDecree implements RoyalDecree {
return king.evaluateCloseQuarters(target); return king.evaluateCloseQuarters(target);
} }
@Override @Override
public int countdownTicks(KingEntity king, LivingEntity target) {
return king.closeQuartersCountdownTicks(target);
}
@Override
public int contextWeight(KingEntity king, LivingEntity target) { public int contextWeight(KingEntity king, LivingEntity target) {
return 4 + king.behaviorScore(KingEntity.Behavior.RANGED); return 4 + king.behaviorScore(KingEntity.Behavior.RANGED);
} }
@@ -1,12 +1,18 @@
package com.craisinlord.antarchy.content.entity.royal.decree; package com.craisinlord.antarchy.content.entity.royal.decree;
import com.craisinlord.antarchy.content.entity.royal.KingEntity; import com.craisinlord.antarchy.content.entity.royal.KingEntity;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
import net.minecraft.server.level.ServerLevel; import net.minecraft.server.level.ServerLevel;
import net.minecraft.world.entity.LivingEntity; import net.minecraft.world.entity.LivingEntity;
import net.minecraft.world.phys.Vec3; import net.minecraft.world.phys.Vec3;
public final class FightMeCowardDecree implements RoyalDecree { public final class FightMeCowardDecree implements RoyalDecree {
private static final int APPROACH_TICKS = 80;
private final Map<UUID, Integer> deadlines = new HashMap<>();
public String name() { return "FIGHT ME, COWARD!"; } public String name() { return "FIGHT ME, COWARD!"; }
public String translationKey() { return "decree.antarchy.fight_me_coward"; } public String translationKey() { return "decree.antarchy.fight_me_coward"; }
public void apply(ServerLevel level, KingEntity king, LivingEntity target) { public void apply(ServerLevel level, KingEntity king, LivingEntity target) {
this.deadlines.computeIfAbsent(target.getUUID(), ignored -> target.tickCount + APPROACH_TICKS);
if (target.distanceToSqr(king) > 784.0D) { if (target.distanceToSqr(king) > 784.0D) {
Vec3 pull = king.position().subtract(target.position()).normalize().scale(0.35D); Vec3 pull = king.position().subtract(target.position()).normalize().scale(0.35D);
target.setDeltaMovement(target.getDeltaMovement().add(pull.x, 0.05D, pull.z)); target.setDeltaMovement(target.getDeltaMovement().add(pull.x, 0.05D, pull.z));
@@ -15,6 +21,21 @@ public final class FightMeCowardDecree implements RoyalDecree {
} }
@Override @Override
public Evaluation evaluate(ServerLevel level, KingEntity king, LivingEntity target) { public Evaluation evaluate(ServerLevel level, KingEntity king, LivingEntity target) {
return target.distanceToSqr(king) > 784.0D ? Evaluation.VIOLATED : Evaluation.COMPLIANT; int deadline = this.deadlines.computeIfAbsent(target.getUUID(), ignored -> target.tickCount + APPROACH_TICKS);
return target.tickCount >= deadline && target.distanceToSqr(king) > 784.0D
? Evaluation.VIOLATED : Evaluation.COMPLIANT;
}
@Override
public int countdownTicks(KingEntity king, LivingEntity target) {
if (target.distanceToSqr(king) <= 784.0D) {
return -1;
}
return this.deadlines.getOrDefault(target.getUUID(), target.tickCount + APPROACH_TICKS) - target.tickCount;
}
@Override
public void onEnded() {
this.deadlines.clear();
} }
} }
@@ -23,7 +23,7 @@ public final class KneelDecree implements RoyalDecree {
} }
@Override @Override
public int countdownTicks(LivingEntity target) { public int countdownTicks(KingEntity king, LivingEntity target) {
return this.deadlines.getOrDefault(target.getUUID(), target.tickCount + 100) - target.tickCount; return this.deadlines.getOrDefault(target.getUUID(), target.tickCount + 100) - target.tickCount;
} }
@@ -18,7 +18,7 @@ public interface RoyalDecree {
return Evaluation.COMPLIANT; return Evaluation.COMPLIANT;
} }
default int countdownTicks(LivingEntity target) { default int countdownTicks(KingEntity king, LivingEntity target) {
return -1; return -1;
} }
@@ -35,7 +35,7 @@ public final class ComputerScientistTradeManager extends SimpleJsonResourceReloa
} }
public static int[] slotsPerLevel() { public static int[] slotsPerLevel() {
return new int[] {4, 2, 2, 3, 4}; return new int[] {4, 2, 2, 3, 5};
} }
public static VillagerTrades.ItemListing listing(int level, int index) { public static VillagerTrades.ItemListing listing(int level, int index) {
@@ -0,0 +1,13 @@
package com.craisinlord.antarchy.content.gravity;
import net.minecraft.world.phys.Vec3;
/** Converts a projectile launch vector from its shooter's gravity frame to world space. */
public final class ProjectileLaunchGravity {
private ProjectileLaunchGravity() {
}
public static Vec3 toWorld(Vec3 launchDirection, AntarchyGravityDirection gravityDirection) {
return AntarchyGravityRotationUtil.vecPlayerToWorld(launchDirection, gravityDirection);
}
}
@@ -1,5 +1,6 @@
package com.craisinlord.antarchy.content.guide; package com.craisinlord.antarchy.content.guide;
import com.craisinlord.antarchy.Antarchy;
import com.craisinlord.antarchy.config.AntarchySettings; import com.craisinlord.antarchy.config.AntarchySettings;
import com.google.gson.JsonArray; import com.google.gson.JsonArray;
import com.google.gson.JsonElement; import com.google.gson.JsonElement;
@@ -41,14 +42,14 @@ public final class ComputerGuideData extends SimplePreparableReloadListener<Comp
public static List<Entry> entriesFor(List<ResourceLocation> diskIds) { public static List<Entry> entriesFor(List<ResourceLocation> diskIds) {
if (AntarchySettings.unlockAllArchives()) { if (AntarchySettings.unlockAllArchives()) {
Map<ResourceLocation, Entry> result = new LinkedHashMap<>(); Map<ResourceLocation, Entry> result = new LinkedHashMap<>();
result.put(INTRODUCTION, new Entry(INTRODUCTION, "article", "general", "guide.antarchy.entry.introduction.title", "guide.antarchy.entry.introduction.subtitle", List.of("guide.antarchy.entry.introduction.description"), "", "", "", "", "")); result.put(INTRODUCTION, introduction());
result.putAll(entries); result.putAll(entries);
return result.values().stream().sorted(Comparator.comparing(entry -> entry.titleKey().toString())).toList(); return result.values().stream().sorted(Comparator.comparing(entry -> entry.titleKey().toString())).toList();
} }
Map<ResourceLocation, Entry> result = new LinkedHashMap<>(); Map<ResourceLocation, Entry> result = new LinkedHashMap<>();
for (ResourceLocation diskId : diskIds) { for (ResourceLocation diskId : diskIds) {
if (INTRODUCTION.equals(diskId)) { if (INTRODUCTION.equals(diskId)) {
result.put(INTRODUCTION, new Entry(INTRODUCTION, "article", "general", "guide.antarchy.entry.introduction.title", "guide.antarchy.entry.introduction.subtitle", List.of("guide.antarchy.entry.introduction.description"), "", "", "", "", "")); result.put(INTRODUCTION, introduction());
continue; continue;
} }
Disk disk = disks.get(diskId); Disk disk = disks.get(diskId);
@@ -65,6 +66,87 @@ public final class ComputerGuideData extends SimplePreparableReloadListener<Comp
return result.values().stream().sorted(Comparator.comparing(entry -> entry.titleKey().toString())).toList(); return result.values().stream().sorted(Comparator.comparing(entry -> entry.titleKey().toString())).toList();
} }
private static Entry introduction() {
return new Entry(INTRODUCTION, "article", "general", "guide.antarchy.entry.introduction.title",
"guide.antarchy.entry.introduction.subtitle", List.of("guide.antarchy.entry.introduction.description"),
"", "", "", "", "", "", "", 0, "", "", "", 0.0F, 1.0F);
}
/** Encodes the server data-pack view so remote clients do not need a duplicate asset copy. */
public static String encodeNetworkSnapshot() {
JsonObject root = new JsonObject();
JsonArray entryArray = new JsonArray();
entries.values().stream().sorted(Comparator.comparing(entry -> entry.id().toString())).forEach(entry -> {
JsonObject object = new JsonObject();
object.addProperty("id", entry.id().toString());
object.addProperty("type", entry.type());
object.addProperty("category", entry.category());
object.addProperty("title", entry.titleKey());
object.addProperty("subtitle", entry.subtitleKey());
JsonArray descriptions = new JsonArray();
entry.descriptionKeys().forEach(descriptions::add);
object.add("description", descriptions);
object.addProperty("item", entry.itemId());
object.addProperty("entity", entry.entityId());
object.addProperty("enchantment", entry.enchantmentId());
object.addProperty("recipe", entry.recipeId());
object.addProperty("structure", entry.structureId());
object.addProperty("structure_tag", entry.structureTagId());
object.addProperty("dimension", entry.dimensionId());
object.addProperty("search_radius", entry.searchRadius());
object.addProperty("cover_item", entry.coverItemId());
object.addProperty("cover_entity", entry.coverEntityId());
object.addProperty("cover_potion", entry.coverPotionId());
object.addProperty("rotation", entry.rotation());
object.addProperty("render_scale", entry.renderScale());
entryArray.add(object);
});
root.add("entries", entryArray);
JsonArray diskArray = new JsonArray();
disks.values().stream().sorted(Comparator.comparing(disk -> disk.id().toString())).forEach(disk -> {
JsonObject object = new JsonObject();
object.addProperty("id", disk.id().toString());
object.addProperty("category", disk.category());
object.addProperty("title", disk.titleKey());
JsonArray diskEntries = new JsonArray();
disk.entries().forEach(id -> diskEntries.add(id.toString()));
object.add("entries", diskEntries);
object.addProperty("wallpaper", disk.wallpaper());
diskArray.add(object);
});
root.add("disks", diskArray);
return root.toString();
}
/** Applies the authoritative server snapshot on a multiplayer client. */
public static void applyNetworkSnapshot(String json) {
try {
JsonObject root = JsonParser.parseString(json).getAsJsonObject();
Map<ResourceLocation, Entry> loadedEntries = new HashMap<>();
Map<ResourceLocation, Disk> loadedDisks = new HashMap<>();
JsonArray entryArray = root.getAsJsonArray("entries");
if (entryArray != null) {
for (JsonElement element : entryArray) {
JsonObject object = element.getAsJsonObject();
parseEntry(ResourceLocation.parse(string(object, "id", "")), object, loadedEntries);
}
}
JsonArray diskArray = root.getAsJsonArray("disks");
if (diskArray != null) {
for (JsonElement element : diskArray) {
JsonObject object = element.getAsJsonObject();
parseDisk(ResourceLocation.parse(string(object, "id", "")), object, loadedDisks);
}
}
entries = Map.copyOf(loadedEntries);
disks = Map.copyOf(loadedDisks);
Antarchy.LOGGER.info("Loaded {} computer archive entries and {} disks from the server", entries.size(), disks.size());
} catch (RuntimeException exception) {
Antarchy.LOGGER.error("Failed to decode computer archive data from the server", exception);
}
}
@Override @Override
protected LoadedData prepare(ResourceManager resourceManager, ProfilerFiller profiler) { protected LoadedData prepare(ResourceManager resourceManager, ProfilerFiller profiler) {
Map<ResourceLocation, Entry> loadedEntries = new HashMap<>(); Map<ResourceLocation, Entry> loadedEntries = new HashMap<>();
@@ -74,7 +156,8 @@ public final class ComputerGuideData extends SimplePreparableReloadListener<Comp
try { try {
JsonElement json = JsonParser.parseReader(resource.getValue().openAsReader()); JsonElement json = JsonParser.parseReader(resource.getValue().openAsReader());
parseEntry(id, json, loadedEntries); parseEntry(id, json, loadedEntries);
} catch (Exception ignored) { } catch (Exception exception) {
Antarchy.LOGGER.error("Failed to load computer archive entry {}", resource.getKey(), exception);
} }
} }
for (Map.Entry<ResourceLocation, net.minecraft.server.packs.resources.Resource> resource : resourceManager.listResources("computer/disk", path -> path.getPath().endsWith(".json")).entrySet()) { for (Map.Entry<ResourceLocation, net.minecraft.server.packs.resources.Resource> resource : resourceManager.listResources("computer/disk", path -> path.getPath().endsWith(".json")).entrySet()) {
@@ -82,7 +165,8 @@ public final class ComputerGuideData extends SimplePreparableReloadListener<Comp
try { try {
JsonElement json = JsonParser.parseReader(resource.getValue().openAsReader()); JsonElement json = JsonParser.parseReader(resource.getValue().openAsReader());
parseDisk(id, json, loadedDisks); parseDisk(id, json, loadedDisks);
} catch (Exception ignored) { } catch (Exception exception) {
Antarchy.LOGGER.error("Failed to load computer archive disk {}", resource.getKey(), exception);
} }
} }
return new LoadedData(Map.copyOf(loadedEntries), Map.copyOf(loadedDisks)); return new LoadedData(Map.copyOf(loadedEntries), Map.copyOf(loadedDisks));
@@ -115,10 +199,23 @@ public final class ComputerGuideData extends SimplePreparableReloadListener<Comp
List<String> description = strings(object.get("description")); List<String> description = strings(object.get("description"));
String item = string(object, "item", ""); String item = string(object, "item", "");
String entity = string(object, "entity", ""); String entity = string(object, "entity", "");
String enchantment = string(object, "enchantment", "");
String recipe = string(object, "recipe", ""); String recipe = string(object, "recipe", "");
String structure = string(object, "structure", ""); String structure = string(object, "structure", "");
String structureTag = string(object, "structure_tag", "");
String dimension = string(object, "dimension", ""); String dimension = string(object, "dimension", "");
target.put(id, new Entry(id, type, category, title, subtitle, description, item, entity, recipe, structure, dimension)); int searchRadius = object.has("search_radius") && object.get("search_radius").isJsonPrimitive()
? Math.max(0, Math.min(1000, object.get("search_radius").getAsInt())) : 0;
String coverItem = string(object, "cover_item", "");
String coverEntity = string(object, "cover_entity", "");
String coverPotion = string(object, "cover_potion", "");
float rotation = object.has("rotation") && object.get("rotation").isJsonPrimitive()
? object.get("rotation").getAsFloat() : 0.0F;
float renderScale = object.has("render_scale") && object.get("render_scale").isJsonPrimitive()
? object.get("render_scale").getAsFloat() : 1.0F;
renderScale = Math.max(0.1F, Math.min(3.0F, renderScale));
target.put(id, new Entry(id, type, category, title, subtitle, description, item, entity, enchantment, recipe, structure, structureTag, dimension, searchRadius,
coverItem, coverEntity, coverPotion, rotation, renderScale));
} }
private static void parseDisk(ResourceLocation id, JsonElement element, Map<ResourceLocation, Disk> target) { private static void parseDisk(ResourceLocation id, JsonElement element, Map<ResourceLocation, Disk> target) {
@@ -168,9 +265,11 @@ public final class ComputerGuideData extends SimplePreparableReloadListener<Comp
JsonObject render = value.getAsJsonObject(); JsonObject render = value.getAsJsonObject();
String item = string(render, "item", ""); String item = string(render, "item", "");
String entity = string(render, "entity", ""); String entity = string(render, "entity", "");
String enchantment = string(render, "enchantment", "");
String recipe = string(render, "recipe", ""); String recipe = string(render, "recipe", "");
if (!item.isBlank()) values.add("@item:" + item); if (!item.isBlank()) values.add("@item:" + item);
else if (!entity.isBlank()) values.add("@entity:" + entity); else if (!entity.isBlank()) values.add("@entity:" + entity);
else if (!enchantment.isBlank()) values.add("@enchantment:" + enchantment);
else if (!recipe.isBlank()) values.add("@recipe:" + recipe); else if (!recipe.isBlank()) values.add("@recipe:" + recipe);
} }
} }
@@ -178,7 +277,9 @@ public final class ComputerGuideData extends SimplePreparableReloadListener<Comp
} }
public record Entry(ResourceLocation id, String type, String category, String titleKey, String subtitleKey, public record Entry(ResourceLocation id, String type, String category, String titleKey, String subtitleKey,
List<String> descriptionKeys, String itemId, String entityId, String recipeId, String structureId, String dimensionId) { List<String> descriptionKeys, String itemId, String entityId, String enchantmentId,
String recipeId, String structureId, String structureTagId, String dimensionId, int searchRadius, String coverItemId,
String coverEntityId, String coverPotionId, float rotation, float renderScale) {
} }
public record Disk(ResourceLocation id, String category, String titleKey, List<ResourceLocation> entries, String wallpaper) { public record Disk(ResourceLocation id, String category, String titleKey, List<ResourceLocation> entries, String wallpaper) {
@@ -8,13 +8,19 @@ import net.minecraft.core.registries.BuiltInRegistries;
import net.minecraft.resources.ResourceLocation; import net.minecraft.resources.ResourceLocation;
import net.minecraft.network.chat.Component; import net.minecraft.network.chat.Component;
import net.minecraft.world.entity.EquipmentSlot; import net.minecraft.world.entity.EquipmentSlot;
import net.minecraft.world.entity.EquipmentSlotGroup;
import net.minecraft.world.entity.LivingEntity; import net.minecraft.world.entity.LivingEntity;
import net.minecraft.world.entity.ai.attributes.AttributeModifier;
import net.minecraft.world.entity.ai.attributes.Attributes;
import net.minecraft.world.item.ElytraItem; import net.minecraft.world.item.ElytraItem;
import net.minecraft.world.item.Item; import net.minecraft.world.item.Item;
import net.minecraft.world.item.ItemStack; import net.minecraft.world.item.ItemStack;
import net.minecraft.world.item.component.ItemAttributeModifiers;
public final class BrutalflyElytraItem extends ElytraItem { public final class BrutalflyElytraItem extends ElytraItem {
private static final ResourceLocation REPAIR_MATERIAL_ID = ResourceLocation.fromNamespaceAndPath("antarchy", "brutalfly_wing"); private static final ResourceLocation REPAIR_MATERIAL_ID = ResourceLocation.fromNamespaceAndPath("antarchy", "brutalfly_wing");
private static final ResourceLocation ARMOR_MODIFIER_ID = ResourceLocation.fromNamespaceAndPath("antarchy", "brutalfly_elytra_armor");
private static final ResourceLocation TOUGHNESS_MODIFIER_ID = ResourceLocation.fromNamespaceAndPath("antarchy", "brutalfly_elytra_toughness");
// Set by NeoForge client setup to return the actual bound key name // Set by NeoForge client setup to return the actual bound key name
public static Supplier<Component> FLAP_KEY_NAME = () -> Component.literal("Shift"); public static Supplier<Component> FLAP_KEY_NAME = () -> Component.literal("Shift");
@@ -27,6 +33,15 @@ public final class BrutalflyElytraItem extends ElytraItem {
return entity != null && entity.getItemBySlot(EquipmentSlot.CHEST).getItem() instanceof BrutalflyElytraItem; return entity != null && entity.getItemBySlot(EquipmentSlot.CHEST).getItem() instanceof BrutalflyElytraItem;
} }
@Override
public ItemAttributeModifiers getDefaultAttributeModifiers() {
return ItemAttributeModifiers.builder()
.add(Attributes.ARMOR,
new AttributeModifier(ARMOR_MODIFIER_ID, 3.0D, AttributeModifier.Operation.ADD_VALUE),
EquipmentSlotGroup.CHEST)
.build();
}
@Override @Override
public boolean isValidRepairItem(ItemStack toRepair, ItemStack repair) { public boolean isValidRepairItem(ItemStack toRepair, ItemStack repair) {
Optional<Item> repairMaterial = BuiltInRegistries.ITEM.getOptional(REPAIR_MATERIAL_ID); Optional<Item> repairMaterial = BuiltInRegistries.ITEM.getOptional(REPAIR_MATERIAL_ID);
@@ -10,16 +10,22 @@ import net.minecraft.core.registries.BuiltInRegistries;
import net.minecraft.network.chat.Component; import net.minecraft.network.chat.Component;
import net.minecraft.resources.ResourceLocation; import net.minecraft.resources.ResourceLocation;
import net.minecraft.world.entity.EquipmentSlot; import net.minecraft.world.entity.EquipmentSlot;
import net.minecraft.world.entity.EquipmentSlotGroup;
import net.minecraft.world.entity.LivingEntity; import net.minecraft.world.entity.LivingEntity;
import net.minecraft.world.entity.ai.attributes.AttributeModifier;
import net.minecraft.world.entity.ai.attributes.Attributes;
import net.minecraft.world.entity.player.Player; import net.minecraft.world.entity.player.Player;
import net.minecraft.world.item.ElytraItem; import net.minecraft.world.item.ElytraItem;
import net.minecraft.world.item.Item; import net.minecraft.world.item.Item;
import net.minecraft.world.item.ItemStack; import net.minecraft.world.item.ItemStack;
import net.minecraft.world.item.TooltipFlag; import net.minecraft.world.item.TooltipFlag;
import net.minecraft.world.item.component.ItemAttributeModifiers;
import net.minecraft.world.level.Level; import net.minecraft.world.level.Level;
public final class ManticoreWingsItem extends ElytraItem { public final class ManticoreWingsItem extends ElytraItem {
private static final ResourceLocation REPAIR_MATERIAL_ID = ResourceLocation.fromNamespaceAndPath("antarchy", "manticore_wing"); private static final ResourceLocation REPAIR_MATERIAL_ID = ResourceLocation.fromNamespaceAndPath("antarchy", "manticore_wing");
private static final ResourceLocation ARMOR_MODIFIER_ID = ResourceLocation.fromNamespaceAndPath("antarchy", "manticore_wings_armor");
private static final ResourceLocation TOUGHNESS_MODIFIER_ID = ResourceLocation.fromNamespaceAndPath("antarchy", "manticore_wings_toughness");
private static final int DAMAGE_COOLDOWN_TICKS = 10; private static final int DAMAGE_COOLDOWN_TICKS = 10;
private static final double MIN_COLLISION_SPEED = 0.2D; private static final double MIN_COLLISION_SPEED = 0.2D;
private static final float COLLISION_DAMAGE_PER_BLOCK_PER_TICK = 8.0F; private static final float COLLISION_DAMAGE_PER_BLOCK_PER_TICK = 8.0F;
@@ -62,6 +68,18 @@ public final class ManticoreWingsItem extends ElytraItem {
return entity != null && entity.getItemBySlot(EquipmentSlot.CHEST).getItem() instanceof ManticoreWingsItem; return entity != null && entity.getItemBySlot(EquipmentSlot.CHEST).getItem() instanceof ManticoreWingsItem;
} }
@Override
public ItemAttributeModifiers getDefaultAttributeModifiers() {
return ItemAttributeModifiers.builder()
.add(Attributes.ARMOR,
new AttributeModifier(ARMOR_MODIFIER_ID, 5.0D, AttributeModifier.Operation.ADD_VALUE),
EquipmentSlotGroup.CHEST)
.add(Attributes.ARMOR_TOUGHNESS,
new AttributeModifier(TOUGHNESS_MODIFIER_ID, 1.0D, AttributeModifier.Operation.ADD_VALUE),
EquipmentSlotGroup.CHEST)
.build();
}
@Override @Override
public void appendHoverText(ItemStack stack, Item.TooltipContext context, List<Component> tooltipComponents, TooltipFlag tooltipFlag) { public void appendHoverText(ItemStack stack, Item.TooltipContext context, List<Component> tooltipComponents, TooltipFlag tooltipFlag) {
tooltipComponents.add(Component.translatable("tooltip.antarchy.manticore_wings").withStyle(ChatFormatting.GRAY)); tooltipComponents.add(Component.translatable("tooltip.antarchy.manticore_wings").withStyle(ChatFormatting.GRAY));
@@ -34,9 +34,13 @@ public class RainbowSugarItem extends Item {
if (!level.isClientSide && livingEntity instanceof ServerPlayer serverPlayer && isInInfinityDimension(serverPlayer.serverLevel())) { if (!level.isClientSide && livingEntity instanceof ServerPlayer serverPlayer && isInInfinityDimension(serverPlayer.serverLevel())) {
ServerLevel returnLevel = AntTeleportHelper.resolveReturnDestinationLevel(serverPlayer); ServerLevel returnLevel = AntTeleportHelper.resolveReturnDestinationLevel(serverPlayer);
var returnPos = AntTeleportHelper.getDestinationPosition(serverPlayer, returnLevel); var returnPos = AntTeleportHelper.getDestinationPosition(serverPlayer, returnLevel);
if (returnPos == null) {
serverPlayer.displayClientMessage(Component.translatable("message.antarchy.teleport_arrival_failed"), true);
} else {
serverPlayer.teleportTo(returnLevel, returnPos.x, returnPos.y, returnPos.z, serverPlayer.getYRot(), serverPlayer.getXRot()); serverPlayer.teleportTo(returnLevel, returnPos.x, returnPos.y, returnPos.z, serverPlayer.getYRot(), serverPlayer.getXRot());
serverPlayer.playSound(SoundEvents.ENDERMAN_TELEPORT, 1.0F, 1.0F); serverPlayer.playSound(SoundEvents.ENDERMAN_TELEPORT, 1.0F, 1.0F);
} }
}
return result; return result;
} }
@@ -2,16 +2,21 @@ package com.craisinlord.antarchy.content.item;
import com.craisinlord.antarchy.config.AntarchySettings; import com.craisinlord.antarchy.config.AntarchySettings;
import com.craisinlord.antarchy.content.item.royal.RoyalGearHelper; import com.craisinlord.antarchy.content.item.royal.RoyalGearHelper;
import java.util.List;
import net.minecraft.core.Holder; import net.minecraft.core.Holder;
import net.minecraft.network.chat.Component;
import net.minecraft.resources.ResourceLocation; import net.minecraft.resources.ResourceLocation;
import net.minecraft.world.entity.Entity; import net.minecraft.world.entity.Entity;
import net.minecraft.world.entity.EquipmentSlot;
import net.minecraft.world.entity.EquipmentSlotGroup; import net.minecraft.world.entity.EquipmentSlotGroup;
import net.minecraft.world.entity.ai.attributes.AttributeModifier; import net.minecraft.world.entity.ai.attributes.AttributeModifier;
import net.minecraft.world.entity.ai.attributes.Attributes; import net.minecraft.world.entity.ai.attributes.Attributes;
import net.minecraft.world.entity.player.Player; import net.minecraft.world.entity.player.Player;
import net.minecraft.world.item.ArmorItem; import net.minecraft.world.item.ArmorItem;
import net.minecraft.world.item.ArmorMaterial; import net.minecraft.world.item.ArmorMaterial;
import net.minecraft.world.item.Item;
import net.minecraft.world.item.ItemStack; import net.minecraft.world.item.ItemStack;
import net.minecraft.world.item.TooltipFlag;
import net.minecraft.world.item.component.ItemAttributeModifiers; import net.minecraft.world.item.component.ItemAttributeModifiers;
import net.minecraft.world.level.Level; import net.minecraft.world.level.Level;
@@ -42,6 +47,40 @@ public class RoyalGuardianArmorItem extends ArmorItem {
return (float) AntarchySettings.royalGuardianArmorToughness(); return (float) AntarchySettings.royalGuardianArmorToughness();
} }
public static double judgmentDamageMultiplier(Player player) {
double bonus = 0.0D;
if (player.getItemBySlot(EquipmentSlot.HEAD).getItem() instanceof RoyalGuardianArmorItem) bonus += AntarchySettings.royalGuardianHelmetJudgmentBonus();
if (player.getItemBySlot(EquipmentSlot.CHEST).getItem() instanceof RoyalGuardianArmorItem) bonus += AntarchySettings.royalGuardianChestplateJudgmentBonus();
if (player.getItemBySlot(EquipmentSlot.LEGS).getItem() instanceof RoyalGuardianArmorItem) bonus += AntarchySettings.royalGuardianLeggingsJudgmentBonus();
if (player.getItemBySlot(EquipmentSlot.FEET).getItem() instanceof RoyalGuardianArmorItem) bonus += AntarchySettings.royalGuardianBootsJudgmentBonus();
return 1.0D + bonus;
}
private double judgmentBonus() {
return switch (this.armorType) {
case HELMET -> AntarchySettings.royalGuardianHelmetJudgmentBonus();
case CHESTPLATE, BODY -> AntarchySettings.royalGuardianChestplateJudgmentBonus();
case LEGGINGS -> AntarchySettings.royalGuardianLeggingsJudgmentBonus();
case BOOTS -> AntarchySettings.royalGuardianBootsJudgmentBonus();
};
}
@Override
public void appendHoverText(ItemStack stack, Item.TooltipContext context, List<Component> tooltipComponents, TooltipFlag tooltipFlag) {
String slotName = switch (this.armorType) {
case HELMET -> "helmet";
case CHESTPLATE, BODY -> "chestplate";
case LEGGINGS -> "leggings";
case BOOTS -> "boots";
};
tooltipComponents.add(Component.translatable("tooltip.antarchy.royal_guardian_" + slotName + ".judgment",
Math.round(judgmentBonus() * 100.0D)));
if (this.armorType == Type.CHESTPLATE) {
tooltipComponents.add(Component.translatable("tooltip.antarchy.royal_guardian_chestplate.boundary"));
}
super.appendHoverText(stack, context, tooltipComponents, tooltipFlag);
}
@Override @Override
public ItemAttributeModifiers getDefaultAttributeModifiers() { public ItemAttributeModifiers getDefaultAttributeModifiers() {
String prefix = "royal_guardian_" + this.armorType.getName(); String prefix = "royal_guardian_" + this.armorType.getName();
@@ -2,9 +2,15 @@ package com.craisinlord.antarchy.content.item;
import com.craisinlord.antarchy.content.effect.RoyalEffectEligibility; import com.craisinlord.antarchy.content.effect.RoyalEffectEligibility;
import com.craisinlord.antarchy.content.effect.RoyalEffectHooks; import com.craisinlord.antarchy.content.effect.RoyalEffectHooks;
import com.craisinlord.antarchy.content.effect.CommandedEntityAccess;
import com.craisinlord.antarchy.content.effect.CommandedBehavior;
import com.craisinlord.antarchy.config.AntarchySettings; import com.craisinlord.antarchy.config.AntarchySettings;
import java.util.List; import java.util.List;
import java.util.Set;
import java.util.HashSet;
import java.util.Comparator; import java.util.Comparator;
import java.util.UUID;
import java.util.stream.StreamSupport;
import java.util.function.Supplier; import java.util.function.Supplier;
import net.minecraft.core.Holder; import net.minecraft.core.Holder;
import net.minecraft.world.InteractionHand; import net.minecraft.world.InteractionHand;
@@ -12,6 +18,10 @@ import net.minecraft.world.InteractionResultHolder;
import net.minecraft.world.effect.MobEffect; import net.minecraft.world.effect.MobEffect;
import net.minecraft.world.effect.MobEffectInstance; import net.minecraft.world.effect.MobEffectInstance;
import net.minecraft.world.entity.Mob; import net.minecraft.world.entity.Mob;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.sounds.SoundEvents;
import net.minecraft.sounds.SoundSource;
import net.minecraft.core.particles.ParticleTypes;
import net.minecraft.world.entity.player.Player; import net.minecraft.world.entity.player.Player;
import net.minecraft.world.item.Item; import net.minecraft.world.item.Item;
import net.minecraft.world.item.ItemStack; import net.minecraft.world.item.ItemStack;
@@ -24,9 +34,6 @@ import net.minecraft.world.level.Level;
import net.minecraft.world.phys.AABB; import net.minecraft.world.phys.AABB;
public class RoyalGuardianShieldItem extends ShieldItem { public class RoyalGuardianShieldItem extends ShieldItem {
private static final int COMMAND_COOLDOWN_TICKS = 20 * 15;
private static final int COMMAND_DURATION_TICKS = 20 * 30;
private static final double COMMAND_RANGE = 16.0D;
private final Supplier<Ingredient> repairIngredient; private final Supplier<Ingredient> repairIngredient;
public RoyalGuardianShieldItem(Item.Properties properties, Supplier<Ingredient> repairIngredient) { public RoyalGuardianShieldItem(Item.Properties properties, Supplier<Ingredient> repairIngredient) {
@@ -54,6 +61,9 @@ public class RoyalGuardianShieldItem extends ShieldItem {
@Override @Override
public InteractionResultHolder<ItemStack> use(Level level, Player player, InteractionHand hand) { public InteractionResultHolder<ItemStack> use(Level level, Player player, InteractionHand hand) {
ItemStack stack = player.getItemInHand(hand); ItemStack stack = player.getItemInHand(hand);
if (!player.isShiftKeyDown()) {
return super.use(level, player, hand);
}
if (player.getCooldowns().isOnCooldown(this)) { if (player.getCooldowns().isOnCooldown(this)) {
return InteractionResultHolder.fail(stack); return InteractionResultHolder.fail(stack);
} }
@@ -64,16 +74,54 @@ public class RoyalGuardianShieldItem extends ShieldItem {
if (commanded == null) { if (commanded == null) {
return InteractionResultHolder.fail(stack); return InteractionResultHolder.fail(stack);
} }
Mob target = level.getEntitiesOfClass(Mob.class, if (!(level instanceof ServerLevel serverLevel)) {
new AABB(player.blockPosition()).inflate(COMMAND_RANGE),
mob -> RoyalEffectEligibility.canApplyCommanded(mob))
.stream()
.min(Comparator.comparingDouble((Mob mob) -> player.distanceToSqr(mob)).thenComparingInt(Mob::getId))
.orElse(null);
if (target == null || !target.addEffect(new MobEffectInstance(commanded, COMMAND_DURATION_TICKS), player)) {
return InteractionResultHolder.fail(stack); return InteractionResultHolder.fail(stack);
} }
player.getCooldowns().addCooldown(this, COMMAND_COOLDOWN_TICKS); int rosterSize = Math.max(0, AntarchySettings.royalGuardianMusterMobCap());
int duration = Math.max(1, AntarchySettings.royalGuardianMusterDurationTicks());
if (rosterSize == 0) return InteractionResultHolder.fail(stack);
Set<UUID> rosterIds = new HashSet<>();
List<Mob> existingRoster = StreamSupport.stream(serverLevel.getAllEntities().spliterator(), false)
.filter(Mob.class::isInstance).map(Mob.class::cast)
.filter(mob -> mob.hasEffect(commanded) && mob instanceof CommandedEntityAccess access
&& access.antarchy$isRoyalInvested() && player.getUUID().equals(access.antarchy$getCommanderUuid()))
.sorted(Comparator.comparingDouble((Mob mob) -> player.distanceToSqr(mob)).thenComparingInt(Mob::getId))
.limit(rosterSize).toList();
for (Mob mob : existingRoster) {
rosterIds.add(mob.getUUID());
mob.addEffect(new MobEffectInstance(commanded, duration), player);
}
int slotsRemaining = rosterSize - existingRoster.size();
int recruited = 0;
List<Mob> candidates = level.getEntitiesOfClass(Mob.class,
new AABB(player.blockPosition()).inflate(Math.max(0.0D, AntarchySettings.royalGuardianMusterRange())),
mob -> !rosterIds.contains(mob.getUUID()) && RoyalEffectEligibility.canApplyCommanded(mob))
.stream()
.sorted(Comparator.comparing((Mob mob) -> !isInCommandLookCone(player, mob))
.thenComparingDouble(player::distanceToSqr).thenComparingInt(Mob::getId))
.toList();
for (Mob target : candidates) {
if (recruited >= slotsRemaining) break;
if (!target.addEffect(new MobEffectInstance(commanded, duration), player)) continue;
CommandedEntityAccess access = (CommandedEntityAccess) target;
access.antarchy$setRoyalInvested(true);
CommandedBehavior.syncInvestiture(target, true);
rosterIds.add(target.getUUID());
recruited++;
}
if (existingRoster.isEmpty() && recruited == 0) {
return InteractionResultHolder.fail(stack);
}
player.getCooldowns().addCooldown(this, Math.max(0, AntarchySettings.royalGuardianMusterCooldownTicks()));
serverLevel.playSound(null, player.blockPosition(), SoundEvents.ILLUSIONER_CAST_SPELL, SoundSource.PLAYERS, 1.0F, 0.75F);
serverLevel.sendParticles(ParticleTypes.ENCHANT, player.getX(), player.getY() + 1.0D, player.getZ(), 32, 1.0D, 0.7D, 1.0D, 0.2D);
return InteractionResultHolder.success(stack); return InteractionResultHolder.success(stack);
} }
private static boolean isInCommandLookCone(Player player, Mob mob) {
var direction = mob.getBoundingBox().getCenter().subtract(player.getEyePosition()).normalize();
return player.getViewVector(1.0F).dot(direction) >= 0.9063078D; // 25-degree half-angle
}
} }
@@ -0,0 +1,211 @@
package com.craisinlord.antarchy.content.item;
import com.craisinlord.antarchy.config.AntarchySettings;
import com.craisinlord.antarchy.content.effect.CommandedEntityAccess;
import com.craisinlord.antarchy.content.entity.royal.RoyalIceSpikeEntity;
import java.util.HashSet;
import java.util.Set;
import java.util.UUID;
import net.minecraft.core.BlockPos;
import net.minecraft.core.particles.DustParticleOptions;
import net.minecraft.core.particles.ParticleTypes;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.sounds.SoundEvents;
import net.minecraft.sounds.SoundSource;
import net.minecraft.world.entity.EntityType;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.entity.LivingEntity;
import net.minecraft.world.entity.Mob;
import net.minecraft.world.entity.MobCategory;
import net.minecraft.world.entity.OwnableEntity;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.entity.LightningBolt;
import net.minecraft.world.level.GameRules;
import net.minecraft.world.level.block.Blocks;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.level.levelgen.Heightmap;
import net.minecraft.world.phys.AABB;
import net.minecraft.world.phys.Vec3;
import org.joml.Vector3f;
/** Player-safe kernels for the Royal Guardian Sword's King-derived elemental modes. */
public final class RoyalGuardianSwordAbilities {
private static final ThreadLocal<Integer> SECONDARY_DAMAGE_DEPTH = ThreadLocal.withInitial(() -> 0);
private static final ThreadLocal<MeleeAttackContext> MELEE_ATTACK = new ThreadLocal<>();
private static final DustParticleOptions FIRE_GOLD = new DustParticleOptions(new Vector3f(1.0F, 0.55F, 0.08F), 1.6F);
private static final DustParticleOptions STORM_GOLD = new DustParticleOptions(new Vector3f(1.0F, 0.86F, 0.25F), 1.25F);
private RoyalGuardianSwordAbilities() {
}
public static boolean isApplyingSecondaryDamage() {
return SECONDARY_DAMAGE_DEPTH.get() > 0;
}
public static void beginMeleeAttack(Player player, Entity target) {
boolean charged = player.getMainHandItem().getItem() instanceof RoyalGuardianSwordItem
&& player.getAttackStrengthScale(0.5F) >= 0.9F;
MELEE_ATTACK.set(new MeleeAttackContext(player.getUUID(), target.getUUID(), charged));
}
public static boolean isChargedMeleeAttack(Player player, LivingEntity target) {
MeleeAttackContext context = MELEE_ATTACK.get();
return context != null && context.charged && context.playerId.equals(player.getUUID())
&& context.targetId.equals(target.getUUID());
}
public static void endMeleeAttack(Player player) {
MeleeAttackContext context = MELEE_ATTACK.get();
if (context != null && context.playerId.equals(player.getUUID())) MELEE_ATTACK.remove();
}
public static boolean applySecondaryDamage(Player owner, LivingEntity target, float amount) {
if (amount <= 0.0F || !canDamageTarget(owner, target, false)) return false;
int depth = SECONDARY_DAMAGE_DEPTH.get();
SECONDARY_DAMAGE_DEPTH.set(depth + 1);
try {
return target.hurt(owner.damageSources().playerAttack(owner), amount);
} finally {
if (depth == 0) SECONDARY_DAMAGE_DEPTH.remove();
else SECONDARY_DAMAGE_DEPTH.set(depth);
}
}
public static void discharge(Player owner, LivingEntity primary, ItemStack swordStack) {
if (!(owner.level() instanceof ServerLevel level) || !canDamageTarget(owner, primary, true)
|| !(swordStack.getItem() instanceof RoyalGuardianSwordItem)
|| !RoyalGuardianSwordItem.isDischargeReady(swordStack, level.getGameTime())) return;
switch (RoyalGuardianSwordItem.getMode(swordStack)) {
case FIRE -> fireImpact(level, owner, primary);
case FROST -> frostFan(level, owner, primary);
case STORM -> chainLightning(level, owner, primary);
}
RoyalGuardianSwordItem.startDischargeCooldown(swordStack, level.getGameTime());
}
private static void fireImpact(ServerLevel level, Player owner, LivingEntity primary) {
Vec3 center = primary.position().add(0.0D, primary.getBbHeight() * 0.5D, 0.0D);
applySecondaryDamage(owner, primary, (float) AntarchySettings.royalGuardianSwordFirePrimaryDamage());
if (primary.isAlive()) primary.setRemainingFireTicks(Math.max(primary.getRemainingFireTicks(), 100));
for (LivingEntity target : level.getEntitiesOfClass(LivingEntity.class, new AABB(center, center).inflate(4.0D),
candidate -> candidate != primary && canDamageTarget(owner, candidate, false))) {
double distance = Math.sqrt(target.distanceToSqr(center));
float falloff = (float) Math.max(0.25D, 1.0D - distance / 5.0D);
applySecondaryDamage(owner, target, (float) AntarchySettings.royalGuardianSwordFireSplashDamage() * falloff);
target.setRemainingFireTicks(Math.max(target.getRemainingFireTicks(), 80));
}
if (level.getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING)) placeBoundedFire(level, center, 6);
level.sendParticles(ParticleTypes.FLAME, center.x, center.y, center.z, 55, 1.4D, 1.0D, 1.4D, 0.1D);
level.sendParticles(FIRE_GOLD, center.x, center.y, center.z, 28, 1.1D, 0.8D, 1.1D, 0.04D);
level.sendParticles(ParticleTypes.EXPLOSION, center.x, center.y, center.z, 3, 0.8D, 0.6D, 0.8D, 0.0D);
level.playSound(null, BlockPos.containing(center), SoundEvents.GENERIC_EXPLODE.value(), SoundSource.PLAYERS, 1.0F, 1.05F);
}
private static void placeBoundedFire(ServerLevel level, Vec3 center, int cap) {
int placed = 0;
BlockPos base = BlockPos.containing(center);
for (BlockPos pos : BlockPos.betweenClosed(base.offset(-2, -1, -2), base.offset(2, 2, 2))) {
if (placed >= cap) return;
if (pos.distToCenterSqr(center.x, center.y, center.z) > 7.0D || !level.isEmptyBlock(pos)) continue;
BlockPos below = pos.below();
if (!level.getBlockState(below).isFaceSturdy(level, below, net.minecraft.core.Direction.UP)) continue;
BlockState fire = Blocks.FIRE.defaultBlockState();
if (fire.canSurvive(level, pos) && level.setBlockAndUpdate(pos, fire)) placed++;
}
}
private static void frostFan(ServerLevel level, Player owner, LivingEntity primary) {
if (primary.isAlive()) {
primary.setTicksFrozen(Math.min(primary.getTicksRequiredToFreeze(), primary.getTicksFrozen() + 50));
primary.addEffect(new net.minecraft.world.effect.MobEffectInstance(net.minecraft.world.effect.MobEffects.MOVEMENT_SLOWDOWN, 60, 1), owner);
}
Vec3 forward = primary.position().subtract(owner.position()).multiply(1.0D, 0.0D, 1.0D);
if (forward.lengthSqr() < 1.0E-5D) forward = owner.getLookAngle().multiply(1.0D, 0.0D, 1.0D);
if (forward.lengthSqr() < 1.0E-5D) forward = new Vec3(0.0D, 0.0D, 1.0D);
forward = forward.normalize();
Vec3 side = new Vec3(-forward.z, 0.0D, forward.x);
for (int distanceStep = 1; distanceStep <= 4; distanceStep++) {
double distance = 2.5D + distanceStep * 2.7D;
for (int lane = -1; lane <= 1; lane++) {
Vec3 point = primary.position().add(forward.scale(distance)).add(side.scale(lane * 1.8D));
BlockPos surface = level.getHeightmapPos(Heightmap.Types.MOTION_BLOCKING_NO_LEAVES, BlockPos.containing(point));
Vec3 spawn = new Vec3(point.x, surface.getY() + 0.1D, point.z);
RoyalIceSpikeEntity spike = RoyalIceSpikeEntity.create(level, spawn, owner,
(float) AntarchySettings.royalGuardianSwordFrostSpikeDamage(), 1.55D, 100, 60, 1);
level.addFreshEntity(spike);
}
}
Vec3 center = primary.position().add(0.0D, 0.8D, 0.0D);
level.sendParticles(ParticleTypes.SNOWFLAKE, center.x, center.y, center.z, 70, 1.3D, 1.1D, 1.3D, 0.1D);
level.sendParticles(ParticleTypes.SNOWFLAKE, owner.getX(), owner.getY() + 1.0D, owner.getZ(), 18, 0.7D, 0.5D, 0.7D, 0.04D);
level.playSound(null, primary.blockPosition(), SoundEvents.GLASS_BREAK, SoundSource.PLAYERS, 1.0F, 0.6F);
}
private static void chainLightning(ServerLevel level, Player owner, LivingEntity primary) {
Set<UUID> struck = new HashSet<>();
struck.add(primary.getUUID());
strikeVisual(level, primary.position().add(0.0D, primary.getBbHeight() * 0.5D, 0.0D));
applySecondaryDamage(owner, primary, (float) AntarchySettings.royalGuardianSwordStormPrimaryDamage());
LivingEntity previous = primary;
float damage = (float) AntarchySettings.royalGuardianSwordStormJumpDamage();
double jumpRange = Math.max(0.0D, AntarchySettings.royalGuardianSwordStormJumpRange());
for (int jump = 0; jump < Math.max(0, AntarchySettings.royalGuardianSwordStormMaxJumps()); jump++) {
Vec3 origin = previous.position().add(0.0D, previous.getBbHeight() * 0.5D, 0.0D);
LivingEntity next = level.getEntitiesOfClass(LivingEntity.class, new AABB(origin, origin).inflate(jumpRange),
candidate -> !struck.contains(candidate.getUUID()) && canDamageTarget(owner, candidate, false)
&& candidate.distanceToSqr(origin) <= jumpRange * jumpRange)
.stream().min(java.util.Comparator.comparingDouble((LivingEntity candidate) -> candidate.distanceToSqr(origin))
.thenComparingInt(LivingEntity::getId)).orElse(null);
if (next == null) break;
struck.add(next.getUUID());
Vec3 end = next.position().add(0.0D, next.getBbHeight() * 0.5D, 0.0D);
drawLightning(level, origin, end);
applySecondaryDamage(owner, next, damage);
next.setRemainingFireTicks(Math.max(next.getRemainingFireTicks(), 20));
previous = next;
damage *= 0.76F;
}
level.playSound(null, primary.blockPosition(), SoundEvents.LIGHTNING_BOLT_THUNDER, SoundSource.PLAYERS, 0.8F, 1.45F);
}
private static void strikeVisual(ServerLevel level, Vec3 at) {
LightningBolt bolt = new LightningBolt(EntityType.LIGHTNING_BOLT, level);
bolt.moveTo(at.x, at.y, at.z);
bolt.setVisualOnly(true);
level.addFreshEntity(bolt);
level.sendParticles(STORM_GOLD, at.x, at.y, at.z, 20, 0.45D, 0.7D, 0.45D, 0.1D);
}
private static void drawLightning(ServerLevel level, Vec3 start, Vec3 end) {
LightningBolt bolt = new LightningBolt(EntityType.LIGHTNING_BOLT, level);
bolt.moveTo(end.x, end.y, end.z);
bolt.setVisualOnly(true);
level.addFreshEntity(bolt);
Vec3 direction = end.subtract(start);
double length = direction.length();
if (length > 0.001D) {
Vec3 step = direction.scale(1.0D / length);
for (double d = 0.0D; d < length; d += 1.2D) {
Vec3 point = start.add(step.scale(d));
level.sendParticles(STORM_GOLD, point.x, point.y, point.z, 2, 0.08D, 0.08D, 0.08D, 0.04D);
}
}
level.sendParticles(ParticleTypes.ELECTRIC_SPARK, end.x, end.y, end.z, 18, 0.25D, 0.4D, 0.25D, 0.08D);
}
public static boolean canDamageTarget(Player owner, LivingEntity target, boolean primary) {
if (target == null || target == owner || (!primary && !target.isAlive())) return false;
if (target.isAlliedTo(owner) || owner.isAlliedTo(target)) return false;
if (target instanceof OwnableEntity ownable && owner.getUUID().equals(ownable.getOwnerUUID())) return false;
if (target instanceof CommandedEntityAccess access && owner.getUUID().equals(access.antarchy$getCommanderUuid())) return false;
if (target instanceof Player other) return owner.canHarmPlayer(other);
if (primary) return true;
return target instanceof Mob mob && (mob.getType().getCategory() == MobCategory.MONSTER
|| mob.getTarget() == owner || mob.getLastHurtByMob() == owner);
}
private record MeleeAttackContext(UUID playerId, UUID targetId, boolean charged) {
}
}
@@ -4,14 +4,25 @@ import com.craisinlord.antarchy.config.AntarchySettings;
import com.craisinlord.antarchy.content.client.model.ResourceBackedGeoItemModel; import com.craisinlord.antarchy.content.client.model.ResourceBackedGeoItemModel;
import com.craisinlord.antarchy.content.client.renderer.AnimatedHeldItemRenderer; import com.craisinlord.antarchy.content.client.renderer.AnimatedHeldItemRenderer;
import java.util.function.Consumer; import java.util.function.Consumer;
import java.util.List;
import net.minecraft.ChatFormatting;
import net.minecraft.network.chat.Component;
import net.minecraft.resources.ResourceLocation; import net.minecraft.resources.ResourceLocation;
import net.minecraft.sounds.SoundEvents;
import net.minecraft.sounds.SoundSource;
import net.minecraft.world.InteractionHand;
import net.minecraft.world.InteractionResultHolder;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.entity.EquipmentSlotGroup; import net.minecraft.world.entity.EquipmentSlotGroup;
import net.minecraft.world.entity.ai.attributes.AttributeModifier; import net.minecraft.world.entity.ai.attributes.AttributeModifier;
import net.minecraft.world.entity.ai.attributes.Attributes; import net.minecraft.world.entity.ai.attributes.Attributes;
import net.minecraft.world.item.Item; import net.minecraft.world.item.Item;
import net.minecraft.world.item.ItemStack; import net.minecraft.world.item.ItemStack;
import net.minecraft.world.item.TooltipFlag;
import net.minecraft.world.item.SwordItem; import net.minecraft.world.item.SwordItem;
import net.minecraft.world.item.Tier; import net.minecraft.world.item.Tier;
import net.minecraft.world.item.component.CustomData;
import net.minecraft.core.component.DataComponents;
import net.minecraft.world.item.component.ItemAttributeModifiers; import net.minecraft.world.item.component.ItemAttributeModifiers;
import software.bernie.geckolib.animatable.GeoItem; import software.bernie.geckolib.animatable.GeoItem;
import software.bernie.geckolib.animatable.client.GeoRenderProvider; import software.bernie.geckolib.animatable.client.GeoRenderProvider;
@@ -25,6 +36,8 @@ public class RoyalGuardianSwordItem extends SwordItem implements GeoItem {
private static final ResourceLocation ANIMATION_LOCATION = ResourceLocation.fromNamespaceAndPath("antarchy", "animations/static_item.animation.json"); private static final ResourceLocation ANIMATION_LOCATION = ResourceLocation.fromNamespaceAndPath("antarchy", "animations/static_item.animation.json");
private static final ResourceLocation REACH_MODIFIER_ID = ResourceLocation.fromNamespaceAndPath("antarchy", "royal_guardian_sword_reach"); private static final ResourceLocation REACH_MODIFIER_ID = ResourceLocation.fromNamespaceAndPath("antarchy", "royal_guardian_sword_reach");
private static final ResourceLocation KNOCKBACK_MODIFIER_ID = ResourceLocation.fromNamespaceAndPath("antarchy", "royal_guardian_sword_knockback"); private static final ResourceLocation KNOCKBACK_MODIFIER_ID = ResourceLocation.fromNamespaceAndPath("antarchy", "royal_guardian_sword_knockback");
private static final String MODE_TAG = "antarchy.royal_guardian_sword.mode";
private static final String DISCHARGE_READY_AT_TAG = "antarchy.royal_guardian_sword.discharge_ready_at";
private final Tier tier; private final Tier tier;
private final AnimatableInstanceCache geoCache = GeckoLibUtil.createInstanceCache(this); private final AnimatableInstanceCache geoCache = GeckoLibUtil.createInstanceCache(this);
@@ -60,6 +73,79 @@ public class RoyalGuardianSwordItem extends SwordItem implements GeoItem {
return AntarchySettings.royalWeaponEnchantability(); return AntarchySettings.royalWeaponEnchantability();
} }
@Override
public InteractionResultHolder<ItemStack> use(net.minecraft.world.level.Level level, Player player, InteractionHand hand) {
ItemStack stack = player.getItemInHand(hand);
if (!player.isShiftKeyDown()) return InteractionResultHolder.pass(stack);
cycleMode(level, player, stack);
return InteractionResultHolder.sidedSuccess(stack, level.isClientSide);
}
private void cycleMode(net.minecraft.world.level.Level level, Player player, ItemStack stack) {
Mode next = getMode(stack).next();
setMode(stack, next);
if (!level.isClientSide) {
player.displayClientMessage(Component.translatable("message.antarchy.royal_guardian_sword.mode",
Component.translatable(next.translationKey)), true);
level.playSound(null, player.blockPosition(), SoundEvents.AMETHYST_BLOCK_CHIME, SoundSource.PLAYERS, 0.8F, 0.8F + next.ordinal() * 0.2F);
}
}
private static void setMode(ItemStack stack, Mode mode) {
CustomData.update(DataComponents.CUSTOM_DATA, stack, tag -> {
if (mode == Mode.FIRE) tag.remove(MODE_TAG);
else tag.putInt(MODE_TAG, mode.ordinal());
});
}
public boolean shouldCauseReequipAnimation(ItemStack oldStack, ItemStack newStack, boolean slotChanged) {
return slotChanged || !ItemStack.isSameItem(oldStack, newStack);
}
@Override
public void appendHoverText(ItemStack stack, Item.TooltipContext context, List<Component> tooltipComponents, TooltipFlag tooltipFlag) {
Mode mode = getMode(stack);
tooltipComponents.add(Component.translatable("tooltip.antarchy.royal_guardian_sword.mode",
Component.translatable(mode.translationKey)).withStyle(ChatFormatting.GOLD));
tooltipComponents.add(Component.translatable(mode.tooltipKey).withStyle(ChatFormatting.GRAY));
tooltipComponents.add(Component.translatable("tooltip.antarchy.royal_guardian_sword.controls",
AntarchySettings.royalGuardianSwordElementalCooldownTicks() / 20.0D).withStyle(ChatFormatting.DARK_GRAY));
super.appendHoverText(stack, context, tooltipComponents, tooltipFlag);
}
public static Mode getMode(ItemStack stack) {
int value = stack.getOrDefault(DataComponents.CUSTOM_DATA, CustomData.EMPTY).copyTag().getInt(MODE_TAG);
return Mode.values()[Math.floorMod(value, Mode.values().length)];
}
public static boolean isDischargeReady(ItemStack stack, long gameTime) {
return stack.getOrDefault(DataComponents.CUSTOM_DATA, CustomData.EMPTY).copyTag()
.getLong(DISCHARGE_READY_AT_TAG) <= gameTime;
}
public static void startDischargeCooldown(ItemStack stack, long gameTime) {
CustomData.update(DataComponents.CUSTOM_DATA, stack, tag -> tag.putLong(DISCHARGE_READY_AT_TAG,
gameTime + Math.max(0, AntarchySettings.royalGuardianSwordElementalCooldownTicks())));
}
public enum Mode {
FIRE("tooltip.antarchy.royal_guardian_sword.mode.fire", "tooltip.antarchy.royal_guardian_sword.fire"),
FROST("tooltip.antarchy.royal_guardian_sword.mode.frost", "tooltip.antarchy.royal_guardian_sword.frost"),
STORM("tooltip.antarchy.royal_guardian_sword.mode.storm", "tooltip.antarchy.royal_guardian_sword.storm");
private final String translationKey;
private final String tooltipKey;
Mode(String translationKey, String tooltipKey) {
this.translationKey = translationKey;
this.tooltipKey = tooltipKey;
}
public Mode next() {
return values()[(this.ordinal() + 1) % values().length];
}
}
@Override @Override
public void registerControllers(AnimatableManager.ControllerRegistrar controllers) { public void registerControllers(AnimatableManager.ControllerRegistrar controllers) {
} }
@@ -0,0 +1,18 @@
package com.craisinlord.antarchy.content.network;
import com.craisinlord.antarchy.Antarchy;
import io.netty.buffer.ByteBuf;
import net.minecraft.network.codec.StreamCodec;
import net.minecraft.network.protocol.common.custom.CustomPacketPayload;
import net.minecraft.resources.ResourceLocation;
public record ActivateRoyalBoundaryPayload() implements CustomPacketPayload {
public static final Type<ActivateRoyalBoundaryPayload> TYPE = new Type<>(
ResourceLocation.fromNamespaceAndPath(Antarchy.MODID, "activate_royal_boundary"));
public static final StreamCodec<ByteBuf, ActivateRoyalBoundaryPayload> STREAM_CODEC = StreamCodec.unit(new ActivateRoyalBoundaryPayload());
@Override
public Type<ActivateRoyalBoundaryPayload> type() {
return TYPE;
}
}
@@ -1,26 +1,32 @@
package com.craisinlord.antarchy.content.network; package com.craisinlord.antarchy.content.network;
import com.craisinlord.antarchy.content.antmail.AntmailAttachment; import com.craisinlord.antarchy.content.antmail.AntmailAttachment;
import com.craisinlord.antarchy.content.antmail.AntmailDebug;
import com.craisinlord.antarchy.content.antmail.AntmailDraft; import com.craisinlord.antarchy.content.antmail.AntmailDraft;
import com.craisinlord.antarchy.content.antmail.AntmailWire; import com.craisinlord.antarchy.content.antmail.AntmailWire;
import net.minecraft.core.BlockPos; import net.minecraft.core.BlockPos;
import java.util.List; import java.util.List;
import java.util.UUID; import java.util.UUID;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.function.Consumer; import java.util.function.Consumer;
public final class AntmailNetworking { public final class AntmailNetworking {
private static Consumer<Object> sender = payload -> { }; private static Consumer<Object> sender = payload -> { };
private static volatile boolean senderConfigured;
private static final AtomicBoolean senderErrorLogged = new AtomicBoolean();
private AntmailNetworking() { private AntmailNetworking() {
} }
public static void setSender(Consumer<Object> sender) { public static void setSender(Consumer<Object> sender) {
AntmailNetworking.sender = sender; AntmailNetworking.sender = sender;
senderConfigured = sender != null;
} }
public static void setup(BlockPos pos, String username) { public static void setup(BlockPos pos, String username) {
sender.accept(new AntmailSetupPayload(pos, username)); AntmailDebug.log("C2S setup request pos=" + pos + " usernameLength=" + username.length());
sendPayload(new AntmailSetupPayload(pos, username));
} }
public static void requestState(BlockPos pos) { public static void requestState(BlockPos pos) {
@@ -32,38 +38,46 @@ public final class AntmailNetworking {
} }
public static void requestState(BlockPos pos, int folder, int page, long knownVersion) { public static void requestState(BlockPos pos, int folder, int page, long knownVersion) {
sender.accept(new AntmailStateRequestPayload(pos, folder, page, knownVersion)); AntmailDebug.log("C2S state request pos=" + pos + " folder=" + folder + " page=" + page + " knownVersion=" + knownVersion);
sendPayload(new AntmailStateRequestPayload(pos, folder, page, knownVersion));
} }
public static void requestMessage(BlockPos pos, UUID messageId) { public static void requestMessage(BlockPos pos, UUID messageId) {
sender.accept(new AntmailMessageRequestPayload(pos, messageId.toString())); sendPayload(new AntmailMessageRequestPayload(pos, messageId.toString()));
} }
public static void send(BlockPos pos, String recipient, String subject, String body, List<AntmailAttachment> attachments) { public static void send(BlockPos pos, String recipient, String subject, String body, List<AntmailAttachment> attachments) {
sender.accept(new AntmailSendPayload(pos, recipient, subject, body, AntmailWire.encodeAttachments(attachments))); sendPayload(new AntmailSendPayload(pos, recipient, subject, body, AntmailWire.encodeAttachments(attachments)));
} }
public static void markRead(BlockPos pos, UUID messageId) { public static void markRead(BlockPos pos, UUID messageId) {
sender.accept(new AntmailReadPayload(pos, messageId.toString(), true)); sendPayload(new AntmailReadPayload(pos, messageId.toString(), true));
} }
public static void markUnread(BlockPos pos, UUID messageId) { public static void markUnread(BlockPos pos, UUID messageId) {
sender.accept(new AntmailReadPayload(pos, messageId.toString(), false)); sendPayload(new AntmailReadPayload(pos, messageId.toString(), false));
} }
public static void delete(BlockPos pos, UUID messageId, boolean sent) { public static void delete(BlockPos pos, UUID messageId, boolean sent) {
sender.accept(new AntmailDeletePayload(pos, messageId.toString(), sent)); sendPayload(new AntmailDeletePayload(pos, messageId.toString(), sent));
} }
public static void saveDraft(BlockPos pos, AntmailDraft draft) { public static void saveDraft(BlockPos pos, AntmailDraft draft) {
sender.accept(new AntmailDraftPayload(pos, AntmailDraftPayload.SAVE, AntmailWire.encodeTag(draft.toTag()))); sendPayload(new AntmailDraftPayload(pos, AntmailDraftPayload.SAVE, AntmailWire.encodeTag(draft.toTag())));
} }
public static void deleteDraft(BlockPos pos, UUID draftId) { public static void deleteDraft(BlockPos pos, UUID draftId) {
sender.accept(new AntmailDraftPayload(pos, AntmailDraftPayload.DELETE, draftId.toString())); sendPayload(new AntmailDraftPayload(pos, AntmailDraftPayload.DELETE, draftId.toString()));
} }
public static void retry(BlockPos pos, UUID messageId) { public static void retry(BlockPos pos, UUID messageId) {
sender.accept(new AntmailRetryPayload(pos, messageId.toString())); sendPayload(new AntmailRetryPayload(pos, messageId.toString()));
}
private static void sendPayload(Object payload) {
if (!senderConfigured) {
if (senderErrorLogged.compareAndSet(false, true)) AntmailDebug.error("Network sender is not configured; dropping " + payload.getClass().getSimpleName(), new IllegalStateException("AntmailNetworking.setSender was not called"));
}
else sender.accept(payload);
} }
} }
@@ -1,6 +1,7 @@
package com.craisinlord.antarchy.content.network; package com.craisinlord.antarchy.content.network;
import com.craisinlord.antarchy.content.antmail.AntmailAddress; import com.craisinlord.antarchy.content.antmail.AntmailAddress;
import com.craisinlord.antarchy.content.antmail.AntmailDebug;
import com.craisinlord.antarchy.content.antmail.AntmailAttachment; import com.craisinlord.antarchy.content.antmail.AntmailAttachment;
import com.craisinlord.antarchy.content.antmail.AntmailDeliveryResult; import com.craisinlord.antarchy.content.antmail.AntmailDeliveryResult;
import com.craisinlord.antarchy.content.antmail.AntmailDraft; import com.craisinlord.antarchy.content.antmail.AntmailDraft;
@@ -20,17 +21,22 @@ import java.util.function.BiConsumer;
public final class AntmailServerHandler { public final class AntmailServerHandler {
private static BiConsumer<ServerPlayer, AntmailResultPayload> resultSender = (player, payload) -> { }; private static BiConsumer<ServerPlayer, AntmailResultPayload> resultSender = (player, payload) -> { };
private static volatile boolean resultSenderConfigured;
private AntmailServerHandler() { private AntmailServerHandler() {
} }
public static void setResultSender(BiConsumer<ServerPlayer, AntmailResultPayload> sender) { public static void setResultSender(BiConsumer<ServerPlayer, AntmailResultPayload> sender) {
resultSender = sender; resultSender = sender;
resultSenderConfigured = sender != null;
} }
public static void handle(ServerPlayer player, AntmailSetupPayload payload) { public static void handle(ServerPlayer player, AntmailSetupPayload payload) {
AntmailDebug.log("C2S setup received player=" + player.getGameProfile().getName() + " pos=" + payload.pos()
+ " usernameLength=" + payload.username().length());
ComputerBlockEntity computer = computer(player, payload.pos()); ComputerBlockEntity computer = computer(player, payload.pos());
if (computer == null || !computer.canUseFileSystem(player)) { if (computer == null || !computer.canUseFileSystem(player)) {
AntmailDebug.log("setup rejected: computer missing, out of reach, unauthenticated, or owned by another user");
result(player, payload.pos(), AntmailDeliveryResult.Status.FAILED.ordinal(), "", "", "registration_failed:unauthorized", ""); result(player, payload.pos(), AntmailDeliveryResult.Status.FAILED.ordinal(), "", "", "registration_failed:unauthorized", "");
return; return;
} }
@@ -43,8 +49,11 @@ public final class AntmailServerHandler {
} }
public static void handle(ServerPlayer player, AntmailStateRequestPayload payload) { public static void handle(ServerPlayer player, AntmailStateRequestPayload payload) {
AntmailDebug.log("state request received player=" + player.getGameProfile().getName() + " pos=" + payload.pos()
+ " folder=" + payload.folder() + " page=" + payload.page() + " knownVersion=" + payload.knownVersion());
ComputerBlockEntity computer = authorizedComputer(player, payload.pos()); ComputerBlockEntity computer = authorizedComputer(player, payload.pos());
if (computer == null) { if (computer == null) {
AntmailDebug.log("state request rejected: computer missing, out of reach, unauthenticated, or owned by another user");
result(player, payload.pos(), AntmailDeliveryResult.Status.FAILED.ordinal(), "", "", "unauthorized", ""); result(player, payload.pos(), AntmailDeliveryResult.Status.FAILED.ordinal(), "", "", "unauthorized", "");
return; return;
} }
@@ -63,7 +72,13 @@ public final class AntmailServerHandler {
int folder = payload.folder() < AntmailStateRequestPayload.INBOX || payload.folder() > AntmailStateRequestPayload.DRAFTS int folder = payload.folder() < AntmailStateRequestPayload.INBOX || payload.folder() > AntmailStateRequestPayload.DRAFTS
? AntmailStateRequestPayload.INBOX : payload.folder(); ? AntmailStateRequestPayload.INBOX : payload.folder();
int page = Math.max(0, Math.min(payload.page(), AntmailValidation.MAX_MAILBOX_MESSAGES / AntmailMailbox.PAGE_SIZE)); int page = Math.max(0, Math.min(payload.page(), AntmailValidation.MAX_MAILBOX_MESSAGES / AntmailMailbox.PAGE_SIZE));
result(player, payload.pos(), AntmailDeliveryResult.Status.DELIVERED.ordinal(), address.fullAddress(), "", "", AntmailWire.encodeTag(mailbox.toPageTag(folder, page))); try {
String encoded = AntmailWire.encodeTag(mailbox.toPageTag(folder, page));
result(player, payload.pos(), AntmailDeliveryResult.Status.DELIVERED.ordinal(), address.fullAddress(), "", "", encoded);
} catch (RuntimeException exception) {
AntmailDebug.error("Failed to encode state for " + address.fullAddress() + " at " + payload.pos(), exception);
result(player, payload.pos(), AntmailDeliveryResult.Status.FAILED.ordinal(), address.fullAddress(), "", "state_encode_failed", "");
}
} }
public static void handle(ServerPlayer player, AntmailMessageRequestPayload payload) { public static void handle(ServerPlayer player, AntmailMessageRequestPayload payload) {
@@ -240,6 +255,12 @@ public final class AntmailServerHandler {
if (!address.isBlank()) version = AntmailServerData.access(player.server).mailboxVersion(AntmailAddress.parse(address)); if (!address.isBlank()) version = AntmailServerData.access(player.server).mailboxVersion(AntmailAddress.parse(address));
} catch (IllegalArgumentException ignored) { } catch (IllegalArgumentException ignored) {
} }
AntmailDebug.log("sending result player=" + player.getGameProfile().getName() + " pos=" + pos + " status=" + status
+ " detail=" + detail + " addressPresent=" + !address.isBlank() + " dataChars=" + data.length() + " version=" + version);
if (!resultSenderConfigured) {
AntmailDebug.error("Result sender is not configured; dropping response at " + pos, new IllegalStateException("AntmailServerHandler.setResultSender was not called"));
return;
}
resultSender.accept(player, new AntmailResultPayload(pos, status, address, messageId, detail, data, version)); resultSender.accept(player, new AntmailResultPayload(pos, status, address, messageId, detail, data, version));
} }
} }
@@ -5,6 +5,7 @@ import com.craisinlord.antarchy.content.computer.ComputerFileSystem;
import com.craisinlord.antarchy.content.computer.terminal.TerminalCommandService; import com.craisinlord.antarchy.content.computer.terminal.TerminalCommandService;
import com.craisinlord.antarchy.content.computer.terminal.TerminalFileSystem; import com.craisinlord.antarchy.content.computer.terminal.TerminalFileSystem;
import com.craisinlord.antarchy.content.computer.terminal.TerminalResult; import com.craisinlord.antarchy.content.computer.terminal.TerminalResult;
import com.craisinlord.antarchy.config.AntarchySettings;
import com.craisinlord.antarchy.content.antmail.AntmailWire; import com.craisinlord.antarchy.content.antmail.AntmailWire;
import com.craisinlord.antarchy.content.computer.blockle.BlockleAnswers; import com.craisinlord.antarchy.content.computer.blockle.BlockleAnswers;
import com.craisinlord.antarchy.content.computer.blockle.BlockleDictionary; import com.craisinlord.antarchy.content.computer.blockle.BlockleDictionary;
@@ -13,6 +14,9 @@ import net.minecraft.server.level.ServerPlayer;
import net.minecraft.server.level.ServerLevel; import net.minecraft.server.level.ServerLevel;
import net.minecraft.world.level.Level; import net.minecraft.world.level.Level;
import net.minecraft.resources.ResourceLocation; import net.minecraft.resources.ResourceLocation;
import net.minecraft.core.registries.Registries;
import net.minecraft.tags.TagKey;
import net.minecraft.world.level.levelgen.structure.Structure;
import net.minecraft.world.level.block.entity.BlockEntity; import net.minecraft.world.level.block.entity.BlockEntity;
import java.util.function.BiConsumer; import java.util.function.BiConsumer;
@@ -68,6 +72,7 @@ public final class ComputerAccessHandler {
case ComputerAccessPayload.ANTMAN_STATE -> antmanState(player, computer, payload); case ComputerAccessPayload.ANTMAN_STATE -> antmanState(player, computer, payload);
case ComputerAccessPayload.BLOCKLE_STATE -> blockleState(player, computer, payload, false); case ComputerAccessPayload.BLOCKLE_STATE -> blockleState(player, computer, payload, false);
case ComputerAccessPayload.BLOCKLE_GUESS -> blockleState(player, computer, payload, true); case ComputerAccessPayload.BLOCKLE_GUESS -> blockleState(player, computer, payload, true);
case ComputerAccessPayload.LOCATE_STRUCTURE -> locateStructure(player, computer, payload);
default -> send(player, payload, ComputerAccessResultPayload.INVALID); default -> send(player, payload, ComputerAccessResultPayload.INVALID);
} }
} }
@@ -75,6 +80,7 @@ public final class ComputerAccessHandler {
private static void open(ServerPlayer player, ComputerBlockEntity computer, ComputerAccessPayload payload) { private static void open(ServerPlayer player, ComputerBlockEntity computer, ComputerAccessPayload payload) {
if (computer.hasActiveUser() && !computer.isAuthenticatedBy(player)) { if (computer.hasActiveUser() && !computer.isAuthenticatedBy(player)) {
send(player, payload, ComputerAccessResultPayload.BUSY); send(player, payload, ComputerAccessResultPayload.BUSY);
sendArchive(player, computer, payload);
return; return;
} }
if (!computer.hasPassword()) { if (!computer.hasPassword()) {
@@ -86,6 +92,13 @@ public final class ComputerAccessHandler {
} else { } else {
send(player, payload, ComputerAccessResultPayload.READY); send(player, payload, ComputerAccessResultPayload.READY);
} }
sendArchive(player, computer, payload);
}
private static void sendArchive(ServerPlayer player, ComputerBlockEntity computer, ComputerAccessPayload payload) {
resultSender.accept(player, new ComputerAccessResultPayload(payload.pos(), ComputerAccessResultPayload.SUCCESS,
computer.hasPassword(), computer.isAuthenticated(), ComputerAccessPayload.ARCHIVE_STATE + "\0\0"
+ com.craisinlord.antarchy.content.guide.ComputerGuideData.encodeNetworkSnapshot()));
} }
private static void setup(ServerPlayer player, ComputerBlockEntity computer, ComputerAccessPayload payload) { private static void setup(ServerPlayer player, ComputerBlockEntity computer, ComputerAccessPayload payload) {
@@ -204,10 +217,6 @@ public final class ComputerAccessHandler {
} }
private static void terminalCommand(ServerPlayer player, ComputerBlockEntity computer, ComputerAccessPayload payload) { private static void terminalCommand(ServerPlayer player, ComputerBlockEntity computer, ComputerAccessPayload payload) {
if (!computer.canUseFileSystem(player)) {
send(player, payload, ComputerAccessResultPayload.INVALID);
return;
}
String[] request = splitFileRequest(payload.value()); String[] request = splitFileRequest(payload.value());
if (request == null) { if (request == null) {
sendTerminal(player, payload, TerminalResult.error("/", "ERROR: INVALID REQUEST")); sendTerminal(player, payload, TerminalResult.error("/", "ERROR: INVALID REQUEST"));
@@ -320,7 +329,7 @@ public final class ComputerAccessHandler {
return; return;
} }
ResourceLocation disk = ResourceLocation.fromNamespaceAndPath("antarchy", "blockle_game"); ResourceLocation disk = ResourceLocation.fromNamespaceAndPath("antarchy", "blockle_game");
if (!computer.diskIds().contains(disk)) { if (!AntarchySettings.unlockAllArchives() && !computer.diskIds().contains(disk)) {
sendFile(player, payload, false, "", "not_installed"); sendFile(player, payload, false, "", "not_installed");
return; return;
} }
@@ -332,6 +341,10 @@ public final class ComputerAccessHandler {
long day = overworld.getDayTime() / 24000L; long day = overworld.getDayTime() / 24000L;
computer.resetBlockle(day); computer.resetBlockle(day);
BlockleAnswers.Answer answer = BlockleAnswers.answerForDay(day); BlockleAnswers.Answer answer = BlockleAnswers.answerForDay(day);
if (answer == null) {
sendFile(player, payload, false, "", "answers_unavailable");
return;
}
if (submit) { if (submit) {
String guess = payload.value() == null ? "" : payload.value().toLowerCase(java.util.Locale.ROOT); String guess = payload.value() == null ? "" : payload.value().toLowerCase(java.util.Locale.ROOT);
if (guess.length() != 5 || !guess.chars().allMatch(value -> value >= 'a' && value <= 'z')) { if (guess.length() != 5 || !guess.chars().allMatch(value -> value >= 'a' && value <= 'z')) {
@@ -351,6 +364,49 @@ public final class ComputerAccessHandler {
sendFile(player, payload, true, encodeBlockle(day, answer, computer.blockleGuesses()), ""); sendFile(player, payload, true, encodeBlockle(day, answer, computer.blockleGuesses()), "");
} }
private static void locateStructure(ServerPlayer player, ComputerBlockEntity computer, ComputerAccessPayload payload) {
ResourceLocation entryId;
try {
entryId = ResourceLocation.parse(payload.value());
} catch (RuntimeException exception) {
sendFile(player, payload, false, "", "unauthorized");
return;
}
var entry = com.craisinlord.antarchy.content.guide.ComputerGuideData.entry(entryId);
boolean unlocked = com.craisinlord.antarchy.content.guide.ComputerGuideData.entriesFor(computer.diskIds()).stream()
.anyMatch(candidate -> candidate.id().equals(entryId));
if (!computer.canUseFileSystem(player) || !unlocked || entry == null || entry.structureId().isBlank()
|| entry.structureTagId().isBlank() || entry.dimensionId().isBlank() || entry.searchRadius() <= 0) {
sendFile(player, payload, false, "", "unauthorized");
return;
}
ResourceLocation dimensionId;
ResourceLocation structureTagId;
try {
dimensionId = ResourceLocation.parse(entry.dimensionId());
structureTagId = ResourceLocation.parse(entry.structureTagId());
} catch (RuntimeException exception) {
sendFile(player, payload, false, "", "dimension_unavailable");
return;
}
ServerLevel targetLevel = player.server.getLevel(net.minecraft.resources.ResourceKey.create(Registries.DIMENSION, dimensionId));
if (targetLevel == null) {
sendFile(player, payload, false, "", "dimension_unavailable");
return;
}
TagKey<Structure> structures = TagKey.create(Registries.STRUCTURE, structureTagId);
net.minecraft.core.BlockPos origin = computer.getBlockPos();
var nearest = targetLevel.findNearestMapStructure(structures, origin, entry.searchRadius(), false);
if (nearest == null) {
sendFile(player, payload, false, "", "not_found");
return;
}
sendFile(player, payload, true, nearest.getX() + ", " + nearest.getY() + ", " + nearest.getZ(), "");
}
private static String encodeBlockle(long day, BlockleAnswers.Answer answer, java.util.List<String> guesses) { private static String encodeBlockle(long day, BlockleAnswers.Answer answer, java.util.List<String> guesses) {
StringBuilder result = new StringBuilder(Long.toString(day)); StringBuilder result = new StringBuilder(Long.toString(day));
boolean solved = false; boolean solved = false;
@@ -29,6 +29,8 @@ public record ComputerAccessPayload(BlockPos pos, int action, String value) impl
public static final int ANTMAN_STATE = 17; public static final int ANTMAN_STATE = 17;
public static final int BLOCKLE_STATE = 18; public static final int BLOCKLE_STATE = 18;
public static final int BLOCKLE_GUESS = 19; public static final int BLOCKLE_GUESS = 19;
public static final int ARCHIVE_STATE = 20;
public static final int LOCATE_STRUCTURE = 21;
public static final Type<ComputerAccessPayload> TYPE = new Type<>(ResourceLocation.fromNamespaceAndPath(Antarchy.MODID, "computer_access")); public static final Type<ComputerAccessPayload> TYPE = new Type<>(ResourceLocation.fromNamespaceAndPath(Antarchy.MODID, "computer_access"));
public static final StreamCodec<RegistryFriendlyByteBuf, ComputerAccessPayload> STREAM_CODEC = StreamCodec.composite( public static final StreamCodec<RegistryFriendlyByteBuf, ComputerAccessPayload> STREAM_CODEC = StreamCodec.composite(
BlockPos.STREAM_CODEC, ComputerAccessPayload::pos, BlockPos.STREAM_CODEC, ComputerAccessPayload::pos,
@@ -103,4 +103,8 @@ public final class ComputerNetworking {
public static void submitBlockleGuess(BlockPos pos, String guess) { public static void submitBlockleGuess(BlockPos pos, String guess) {
sender.accept(new ComputerAccessPayload(pos, ComputerAccessPayload.BLOCKLE_GUESS, guess)); sender.accept(new ComputerAccessPayload(pos, ComputerAccessPayload.BLOCKLE_GUESS, guess));
} }
public static void locateStructure(BlockPos pos, ResourceLocation entryId) {
sender.accept(new ComputerAccessPayload(pos, ComputerAccessPayload.LOCATE_STRUCTURE, entryId.toString()));
}
} }
@@ -0,0 +1,20 @@
package com.craisinlord.antarchy.content.network;
import com.craisinlord.antarchy.Antarchy;
import io.netty.buffer.ByteBuf;
import net.minecraft.network.codec.ByteBufCodecs;
import net.minecraft.network.codec.StreamCodec;
import net.minecraft.network.protocol.common.custom.CustomPacketPayload;
import net.minecraft.resources.ResourceLocation;
public record KingJudgmentFlashPayload(int durationTicks) implements CustomPacketPayload {
public static final Type<KingJudgmentFlashPayload> TYPE =
new Type<>(ResourceLocation.fromNamespaceAndPath(Antarchy.MODID, "king_judgment_flash"));
public static final StreamCodec<ByteBuf, KingJudgmentFlashPayload> STREAM_CODEC =
ByteBufCodecs.VAR_INT.map(KingJudgmentFlashPayload::new, KingJudgmentFlashPayload::durationTicks);
@Override
public Type<KingJudgmentFlashPayload> type() {
return TYPE;
}
}
@@ -0,0 +1,22 @@
package com.craisinlord.antarchy.content.network;
import java.util.function.BiConsumer;
import net.minecraft.server.level.ServerPlayer;
public final class KingJudgmentFlashSync {
private static final BiConsumer<ServerPlayer, Integer> NOOP = (player, ticks) -> {
};
private static volatile BiConsumer<ServerPlayer, Integer> sink = NOOP;
private KingJudgmentFlashSync() {
}
public static void setSink(BiConsumer<ServerPlayer, Integer> sink) {
KingJudgmentFlashSync.sink = sink == null ? NOOP : sink;
}
public static void send(ServerPlayer player, int ticks) {
sink.accept(player, ticks);
}
}
@@ -5,6 +5,7 @@ import java.util.LinkedHashSet;
import java.util.Set; import java.util.Set;
import net.minecraft.core.BlockPos; import net.minecraft.core.BlockPos;
import net.minecraft.core.Direction; import net.minecraft.core.Direction;
import net.minecraft.tags.BlockTags;
import net.minecraft.resources.ResourceKey; import net.minecraft.resources.ResourceKey;
import net.minecraft.server.level.ServerLevel; import net.minecraft.server.level.ServerLevel;
import net.minecraft.server.level.ServerPlayer; import net.minecraft.server.level.ServerPlayer;
@@ -46,6 +47,12 @@ public final class PermanentPortalTeleporter {
} }
Vec3 arrival = findArrivalPosition(entity, destination, portalPos, type); Vec3 arrival = findArrivalPosition(entity, destination, portalPos, type);
if (arrival == null) {
if (entity instanceof ServerPlayer player) {
player.displayClientMessage(net.minecraft.network.chat.Component.translatable("message.antarchy.teleport_arrival_failed"), true);
}
return new DimensionTransition(sourceLevel, entity, DimensionTransition.DO_NOTHING);
}
return new DimensionTransition(destination, arrival, Vec3.ZERO, entity.getYRot(), entity.getXRot(), DimensionTransition.PLAY_PORTAL_SOUND); return new DimensionTransition(destination, arrival, Vec3.ZERO, entity.getYRot(), entity.getXRot(), DimensionTransition.PLAY_PORTAL_SOUND);
} }
@@ -57,17 +64,24 @@ public final class PermanentPortalTeleporter {
return sourceLevel.getServer().getLevel(type.primaryDimension()); return sourceLevel.getServer().getLevel(type.primaryDimension());
} }
@Nullable
private static Vec3 findArrivalPosition(Entity entity, ServerLevel destination, BlockPos preferredPos, PermanentPortalType type) { private static Vec3 findArrivalPosition(Entity entity, ServerLevel destination, BlockPos preferredPos, PermanentPortalType type) {
PermanentPortalShape active = PermanentPortalShape.findActiveNear(destination, preferredPos, type); PermanentPortalShape active = PermanentPortalShape.findActiveNear(destination, preferredPos, type);
if (active != null) { if (active != null) {
return active.center(); Vec3 safePortalPosition = tryFindSafePosition(entity, destination, BlockPos.containing(active.center()));
if (safePortalPosition != null) {
return safePortalPosition;
}
} }
Vec3 safe = findSafeArrivalPosition(entity, destination, preferredPos, type); Vec3 safe = findSafeArrivalPosition(entity, destination, preferredPos, type);
if (safe != null) { if (safe != null) {
PermanentPortalShape nearbyActive = findActiveNearby(destination, BlockPos.containing(safe), type); PermanentPortalShape nearbyActive = findActiveNearby(destination, BlockPos.containing(safe), type);
if (nearbyActive != null) { if (nearbyActive != null) {
return nearbyActive.center(); Vec3 safePortalPosition = tryFindSafePosition(entity, destination, BlockPos.containing(nearbyActive.center()));
if (safePortalPosition != null) {
return safePortalPosition;
}
} }
PermanentPortalShape created = createReturnPortal(destination, safe, type); PermanentPortalShape created = createReturnPortal(destination, safe, type);
@@ -77,11 +91,7 @@ public final class PermanentPortalTeleporter {
return createFallbackPortal(destination, BlockPos.containing(safe), type); return createFallbackPortal(destination, BlockPos.containing(safe), type);
} }
BlockPos fallbackPos = preferredPos; return null;
if (type == PermanentPortalType.ELYTHIA && destination.hasChunkAt(preferredPos)) {
fallbackPos = destination.getHeightmapPos(Heightmap.Types.MOTION_BLOCKING_NO_LEAVES, preferredPos);
}
return createFallbackPortal(destination, fallbackPos, type);
} }
@Nullable @Nullable
@@ -119,7 +129,8 @@ public final class PermanentPortalTeleporter {
} }
Set<BlockPos> candidates = new LinkedHashSet<>(); Set<BlockPos> candidates = new LinkedHashSet<>();
if (type != PermanentPortalType.ELYTHIA) { boolean elythia = destination.dimension() == PermanentPortalType.ELYTHIA.primaryDimension();
if (!elythia) {
addCandidate(candidates, preferredPos); addCandidate(candidates, preferredPos);
} }
@@ -132,9 +143,16 @@ public final class PermanentPortalTeleporter {
BlockPos horizontalPos = preferredPos.offset(xOff, 0, zOff); BlockPos horizontalPos = preferredPos.offset(xOff, 0, zOff);
if (destination.hasChunkAt(horizontalPos)) { if (destination.hasChunkAt(horizontalPos)) {
addCandidate(candidates, destination.getHeightmapPos(Heightmap.Types.MOTION_BLOCKING_NO_LEAVES, horizontalPos)); BlockPos surface = destination.getHeightmapPos(Heightmap.Types.MOTION_BLOCKING_NO_LEAVES, horizontalPos);
if (elythia) {
candidates.add(surface);
candidates.add(surface.above());
} else {
addCandidate(candidates, surface);
addCandidate(candidates, destination.getHeightmapPos(Heightmap.Types.WORLD_SURFACE, horizontalPos)); addCandidate(candidates, destination.getHeightmapPos(Heightmap.Types.WORLD_SURFACE, horizontalPos));
} }
}
if (!elythia) {
for (int yOff = 0; yOff <= VERTICAL_SEARCH; yOff++) { for (int yOff = 0; yOff <= VERTICAL_SEARCH; yOff++) {
addCandidate(candidates, horizontalPos.above(yOff)); addCandidate(candidates, horizontalPos.above(yOff));
if (yOff > 0) { if (yOff > 0) {
@@ -144,8 +162,9 @@ public final class PermanentPortalTeleporter {
} }
} }
} }
}
if (type == PermanentPortalType.ELYTHIA) { if (elythia) {
addCandidate(candidates, preferredPos); addCandidate(candidates, preferredPos);
} }
@@ -300,6 +319,36 @@ public final class PermanentPortalTeleporter {
private static boolean isValidArrivalPosition(ServerLevel destination, Vec3 safePos) { private static boolean isValidArrivalPosition(ServerLevel destination, Vec3 safePos) {
BlockPos standPos = BlockPos.containing(safePos); BlockPos standPos = BlockPos.containing(safePos);
if (destination.dimension() == PermanentPortalType.ELYTHIA.primaryDimension()) {
BlockPos supportPos = standPos.below();
if (!destination.hasChunkAt(standPos) || !destination.hasChunkAt(supportPos)) {
return false;
}
BlockState support = destination.getBlockState(supportPos);
if (!support.isFaceSturdy(destination, supportPos, Direction.UP)
|| support.is(BlockTags.LEAVES)
|| !support.getFluidState().isEmpty()) {
return false;
}
int nearbySupports = 0;
for (Direction direction : Direction.Plane.HORIZONTAL) {
BlockPos neighbor = supportPos.relative(direction);
if (!destination.hasChunkAt(neighbor)) {
continue;
}
for (int yOff = -2; yOff <= 2; yOff++) {
BlockPos nearby = neighbor.offset(0, yOff, 0);
BlockState nearbyState = destination.getBlockState(nearby);
if (nearbyState.isFaceSturdy(destination, nearby, Direction.UP)
&& !nearbyState.is(BlockTags.LEAVES)
&& nearbyState.getFluidState().isEmpty()) {
nearbySupports++;
break;
}
}
}
return nearbySupports >= 2;
}
if (destination.dimension() == AntarchySettings.termiteDestinationDimension() if (destination.dimension() == AntarchySettings.termiteDestinationDimension()
|| destination.dimension() == AntarchySettings.redAntDestinationDimension()) { || destination.dimension() == AntarchySettings.redAntDestinationDimension()) {
if (!destination.hasChunkAt(standPos) || !destination.hasChunkAt(standPos.below())) { if (!destination.hasChunkAt(standPos) || !destination.hasChunkAt(standPos.below())) {
@@ -28,11 +28,11 @@ public final class ThoraxisTransitionThroatFeature extends Feature<NoneFeatureCo
for (int y = undersideSurface; y <= upperSurface; y++) { for (int y = undersideSurface; y <= upperSurface; y++) {
int layerOffsetX = noise(seed, Math.floorDiv(y, 5), 17) * 2; int layerOffsetX = noise(seed, Math.floorDiv(y, 5), 17) * 2;
int layerOffsetZ = noise(seed, Math.floorDiv(y, 5), 31) * 2; int layerOffsetZ = noise(seed, Math.floorDiv(y, 5), 31) * 2;
double radiusX = 3.5D + noise01(seed, Math.floorDiv(y, 11), 43) * 2.0D; double radiusX = 5.0D + noise01(seed, Math.floorDiv(y, 11), 43) * 2.5D;
double radiusZ = 3.5D + noise01(seed, Math.floorDiv(y, 13), 59) * 2.0D; double radiusZ = 5.0D + noise01(seed, Math.floorDiv(y, 13), 59) * 2.5D;
for (int x = -7; x <= 7; x++) { for (int x = -9; x <= 9; x++) {
for (int z = -7; z <= 7; z++) { for (int z = -9; z <= 9; z++) {
double dx = x - layerOffsetX; double dx = x - layerOffsetX;
double dz = z - layerOffsetZ; double dz = z - layerOffsetZ;
double distance = Math.sqrt((dx * dx) / (radiusX * radiusX) + (dz * dz) / (radiusZ * radiusZ)); double distance = Math.sqrt((dx * dx) / (radiusX * radiusX) + (dz * dz) / (radiusZ * radiusZ));
@@ -32,7 +32,7 @@ public final class ThoraxisUndersideManager {
public static final int GRAVITY_FLIP_Y = 0; public static final int GRAVITY_FLIP_Y = 0;
private static final net.minecraft.resources.ResourceLocation THORAXIS_DIMENSION = net.minecraft.resources.ResourceLocation.fromNamespaceAndPath(Antarchy.MODID, "thoraxis"); private static final net.minecraft.resources.ResourceLocation THORAXIS_DIMENSION = net.minecraft.resources.ResourceLocation.fromNamespaceAndPath(Antarchy.MODID, "thoraxis");
private static final AntarchyGravityTransition TRANSITION = new AntarchyGravityTransition(12); private static final AntarchyGravityTransition TRANSITION = new AntarchyGravityTransition(12);
private static final AntarchyGravityTransition THROAT_TRANSITION = new AntarchyGravityTransition(20); private static final AntarchyGravityTransition THROAT_TRANSITION = new AntarchyGravityTransition(12);
private static final int EFFECT_DURATION_TICKS = 50; private static final int EFFECT_DURATION_TICKS = 50;
private static final int EFFECT_REFRESH_THRESHOLD_TICKS = 10; private static final int EFFECT_REFRESH_THRESHOLD_TICKS = 10;
private static final int DISCOVERY_INTERVAL_TICKS = 5; private static final int DISCOVERY_INTERVAL_TICKS = 5;
@@ -43,7 +43,7 @@ public final class ThoraxisUndersideManager {
private static final int ENTER_UNDERSIDE_Y = GRAVITY_FLIP_Y - 4; private static final int ENTER_UNDERSIDE_Y = GRAVITY_FLIP_Y - 4;
private static final int EXIT_UNDERSIDE_Y = GRAVITY_FLIP_Y + 4; private static final int EXIT_UNDERSIDE_Y = GRAVITY_FLIP_Y + 4;
private static final int FLIP_COOLDOWN_TICKS = 40; private static final int FLIP_COOLDOWN_TICKS = 40;
private static final int CROSSING_TIMEOUT_TICKS = 30; private static final int CROSSING_TIMEOUT_TICKS = 50;
private static final int SETTLE_TIMEOUT_TICKS = 70; private static final int SETTLE_TIMEOUT_TICKS = 70;
private static final double MIN_CROSSING_SPEED = 0.48D; private static final double MIN_CROSSING_SPEED = 0.48D;
private static final Map<ServerLevel, TrackingState> STATES = new WeakHashMap<>(); private static final Map<ServerLevel, TrackingState> STATES = new WeakHashMap<>();
@@ -231,9 +231,7 @@ public final class ThoraxisUndersideManager {
if (!crossing.toUnderside) { if (!crossing.toUnderside) {
refreshInvertedEffect(player); refreshInvertedEffect(player);
} }
if (crossing.ticks > CROSSING_TIMEOUT_TICKS if (crossing.ticks > CROSSING_TIMEOUT_TICKS) {
|| (crossing.toUnderside && velocity.y > 0.08D)
|| (!crossing.toUnderside && velocity.y < -0.08D)) {
tracking.playerCrossings.remove(player.getUUID()); tracking.playerCrossings.remove(player.getUUID());
return; return;
} }
@@ -248,6 +246,11 @@ public final class ThoraxisUndersideManager {
? AntarchyGravityDirection.UP ? AntarchyGravityDirection.UP
: AntarchyGravityDirection.DOWN; : AntarchyGravityDirection.DOWN;
AntarchyGravityApi.setAirborneGravityDirection(player, destination, crossing.toUnderside, THROAT_TRANSITION); AntarchyGravityApi.setAirborneGravityDirection(player, destination, crossing.toUnderside, THROAT_TRANSITION);
double assistedVerticalVelocity = crossing.toUnderside
? Math.min(velocity.y, -0.7D)
: Math.max(velocity.y, 0.7D);
AntarchyGravityApi.setWorldVelocity(player,
new Vec3(velocity.x, assistedVerticalVelocity, velocity.z));
player.resetFallDistance(); player.resetFallDistance();
crossing.flipped = true; crossing.flipped = true;
crossing.ticks = 0; crossing.ticks = 0;
@@ -25,10 +25,23 @@ public final class AntarchyMixinPlugin implements IMixinConfigPlugin {
@Override @Override
public boolean shouldApplyMixin(String targetClassName, String mixinClassName) { public boolean shouldApplyMixin(String targetClassName, String mixinClassName) {
String resourcePath = targetClassName.replace('.', '/') + ".class"; if (!classPresent(targetClassName)) {
if (this.getClass().getClassLoader().getResource(resourcePath) == null) {
return false; return false;
} }
if (mixinClassName.startsWith("com.craisinlord.antarchy.mixins.compat.scguns.")) {
return classPresent("top.ribs.scguns.entity.projectile.ProjectileEntity");
}
if (mixinClassName.startsWith("com.craisinlord.antarchy.mixins.compat.irons_spellbooks.")) {
return classPresent("io.redspace.ironsspellbooks.entity.spells.AbstractMagicProjectile");
}
if (mixinClassName.startsWith("com.craisinlord.antarchy.mixins.compat.ars_nouveau.")) {
return classPresent("com.hollingsworth.arsnouveau.common.entity.EntityProjectileSpell");
}
if (mixinClassName.startsWith("com.craisinlord.antarchy.mixins.compat.alexscaves.")) {
// The target class was found above. Checking the target itself avoids depending on
// whether a loader exposes Alex's Caves' entrypoint resource to this classloader.
return true;
}
if (mixinClassName.endsWith(".VillagerTradesMixin")) { if (mixinClassName.endsWith(".VillagerTradesMixin")) {
// Trade registration is platform-driven on every supported loader now. Keeping the // Trade registration is platform-driven on every supported loader now. Keeping the
// static VillagerTrades class-init mixin around risks touching AntarchyObjects before // static VillagerTrades class-init mixin around risks touching AntarchyObjects before
@@ -38,6 +51,16 @@ public final class AntarchyMixinPlugin implements IMixinConfigPlugin {
return true; return true;
} }
private static boolean classPresent(String className) {
String resourcePath = className.replace('.', '/') + ".class";
ClassLoader contextClassLoader = Thread.currentThread().getContextClassLoader();
if (contextClassLoader != null && contextClassLoader.getResource(resourcePath) != null) {
return true;
}
ClassLoader mixinClassLoader = AntarchyMixinPlugin.class.getClassLoader();
return mixinClassLoader != null && mixinClassLoader.getResource(resourcePath) != null;
}
@Override @Override
public void acceptTargets(Set<String> myTargets, Set<String> otherTargets) { public void acceptTargets(Set<String> myTargets, Set<String> otherTargets) {
} }
@@ -13,6 +13,8 @@ public abstract class CommandedMobMixin implements CommandedEntityAccess {
private UUID antarchy$commanderUuid; private UUID antarchy$commanderUuid;
@Unique @Unique
private boolean antarchy$commandedTargetOwned; private boolean antarchy$commandedTargetOwned;
@Unique
private boolean antarchy$royalInvested;
@Override @Override
public UUID antarchy$getCommanderUuid() { public UUID antarchy$getCommanderUuid() {
@@ -34,11 +36,22 @@ public abstract class CommandedMobMixin implements CommandedEntityAccess {
antarchy$commandedTargetOwned = owned; antarchy$commandedTargetOwned = owned;
} }
@Override
public boolean antarchy$isRoyalInvested() {
return antarchy$royalInvested;
}
@Override
public void antarchy$setRoyalInvested(boolean invested) {
antarchy$royalInvested = invested;
}
@org.spongepowered.asm.mixin.injection.Inject(method = "addAdditionalSaveData", at = @org.spongepowered.asm.mixin.injection.At("TAIL")) @org.spongepowered.asm.mixin.injection.Inject(method = "addAdditionalSaveData", at = @org.spongepowered.asm.mixin.injection.At("TAIL"))
private void antarchy$saveCommandedData(CompoundTag tag, org.spongepowered.asm.mixin.injection.callback.CallbackInfo ci) { private void antarchy$saveCommandedData(CompoundTag tag, org.spongepowered.asm.mixin.injection.callback.CallbackInfo ci) {
if (antarchy$commanderUuid != null) { if (antarchy$commanderUuid != null) {
tag.putUUID("AntarchyCommander", antarchy$commanderUuid); tag.putUUID("AntarchyCommander", antarchy$commanderUuid);
tag.putBoolean("AntarchyCommandedTargetOwned", antarchy$commandedTargetOwned); tag.putBoolean("AntarchyCommandedTargetOwned", antarchy$commandedTargetOwned);
tag.putBoolean("AntarchyRoyalInvested", antarchy$royalInvested);
} }
} }
@@ -47,6 +60,7 @@ public abstract class CommandedMobMixin implements CommandedEntityAccess {
if (tag.hasUUID("AntarchyCommander")) { if (tag.hasUUID("AntarchyCommander")) {
antarchy$commanderUuid = tag.getUUID("AntarchyCommander"); antarchy$commanderUuid = tag.getUUID("AntarchyCommander");
antarchy$commandedTargetOwned = tag.getBoolean("AntarchyCommandedTargetOwned"); antarchy$commandedTargetOwned = tag.getBoolean("AntarchyCommandedTargetOwned");
antarchy$royalInvested = tag.getBoolean("AntarchyRoyalInvested");
} }
} }
} }
@@ -0,0 +1,42 @@
package com.craisinlord.antarchy.mixins;
import com.craisinlord.antarchy.content.effect.JudgmentMarkAccess;
import net.minecraft.network.syncher.EntityDataAccessor;
import net.minecraft.network.syncher.EntityDataSerializers;
import net.minecraft.network.syncher.SynchedEntityData;
import net.minecraft.world.entity.LivingEntity;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Unique;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
@Mixin(LivingEntity.class)
public abstract class JudgmentMarkEntityMixin implements JudgmentMarkAccess {
@Unique
private static final EntityDataAccessor<Boolean> ANTARCHY_JUDGMENT_MARKED =
SynchedEntityData.defineId(LivingEntity.class, EntityDataSerializers.BOOLEAN);
@Inject(method = "defineSynchedData", at = @At("TAIL"))
private void antarchy$defineJudgmentState(SynchedEntityData.Builder builder, CallbackInfo ci) {
builder.define(ANTARCHY_JUDGMENT_MARKED, false);
}
@Inject(method = "tick", at = @At("TAIL"))
private void antarchy$tickJudgmentState(CallbackInfo ci) {
com.craisinlord.antarchy.content.effect.JudgmentMarkManager.tickTarget((LivingEntity) (Object) this);
}
@Override
public boolean antarchy$isJudgmentMarked() {
return ((LivingEntity) (Object) this).getEntityData().get(ANTARCHY_JUDGMENT_MARKED);
}
@Override
public void antarchy$setJudgmentMarked(boolean marked) {
LivingEntity entity = (LivingEntity) (Object) this;
if (!entity.level().isClientSide && entity.getEntityData().get(ANTARCHY_JUDGMENT_MARKED) != marked) {
entity.getEntityData().set(ANTARCHY_JUDGMENT_MARKED, marked);
}
}
}
@@ -0,0 +1,21 @@
package com.craisinlord.antarchy.mixins;
import com.craisinlord.antarchy.content.effect.RoyalBoundaryManager;
import net.minecraft.world.entity.projectile.Projectile;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
@Mixin(Projectile.class)
public abstract class ProjectileRoyalBoundaryMixin {
@Inject(method = "tick", at = @At("HEAD"))
private void antarchy$prepareRoyalBoundary(CallbackInfo ci) {
RoyalBoundaryManager.prepareProjectile((Projectile) (Object) this);
}
@Inject(method = "tick", at = @At("TAIL"))
private void antarchy$processRoyalBoundary(CallbackInfo ci) {
RoyalBoundaryManager.tickProjectile((Projectile) (Object) this);
}
}
@@ -0,0 +1,17 @@
package com.craisinlord.antarchy.mixins;
import com.craisinlord.antarchy.content.effect.RoyalBoundaryManager;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.world.entity.Entity;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
@Mixin(Entity.class)
public abstract class RoyalBoundaryOwnerCleanupMixin {
@Inject(method = "remove", at = @At("HEAD"))
private void antarchy$removeRoyalBoundary(Entity.RemovalReason reason, CallbackInfo ci) {
if ((Object) this instanceof ServerPlayer player) RoyalBoundaryManager.clearOwner(player);
}
}
@@ -0,0 +1,23 @@
package com.craisinlord.antarchy.mixins;
import com.craisinlord.antarchy.content.item.RoyalGuardianSwordAbilities;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.entity.player.Player;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
/** Captures attack charge before vanilla resets the player's attack-strength ticker. */
@Mixin(Player.class)
public abstract class RoyalGuardianPlayerAttackMixin {
@Inject(method = "attack", at = @At("HEAD"))
private void antarchy$beginGuardianAttack(Entity target, CallbackInfo ci) {
RoyalGuardianSwordAbilities.beginMeleeAttack((Player) (Object) this, target);
}
@Inject(method = "attack", at = @At("RETURN"))
private void antarchy$endGuardianAttack(Entity target, CallbackInfo ci) {
RoyalGuardianSwordAbilities.endMeleeAttack((Player) (Object) this);
}
}
@@ -0,0 +1,27 @@
package com.craisinlord.antarchy.mixins;
import com.craisinlord.antarchy.content.item.RoyalGuardianSwordAbilities;
import com.craisinlord.antarchy.content.item.RoyalGuardianSwordItem;
import net.minecraft.world.damagesource.DamageSource;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.entity.LivingEntity;
import net.minecraft.world.entity.player.Player;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
@Mixin(LivingEntity.class)
public abstract class RoyalGuardianSwordCombatMixin {
@Inject(method = "hurt", at = @At("RETURN"))
private void antarchy$releaseGuardianElement(DamageSource source, float amount,
CallbackInfoReturnable<Boolean> cir) {
if (!cir.getReturnValue() || amount <= 0.0F || RoyalGuardianSwordAbilities.isApplyingSecondaryDamage()) return;
Entity attacker = source.getEntity();
if (!(attacker instanceof Player player) || source.getDirectEntity() != player
|| !(player.getMainHandItem().getItem() instanceof RoyalGuardianSwordItem)
|| !RoyalGuardianSwordAbilities.isChargedMeleeAttack(player, (LivingEntity) (Object) this)
|| !RoyalGuardianSwordItem.isDischargeReady(player.getMainHandItem(), player.level().getGameTime())) return;
RoyalGuardianSwordAbilities.discharge(player, (LivingEntity) (Object) this, player.getMainHandItem());
}
}
@@ -0,0 +1,59 @@
package com.craisinlord.antarchy.mixins;
import com.craisinlord.antarchy.config.AntarchySettings;
import com.craisinlord.antarchy.content.effect.JudgmentMarkManager;
import com.craisinlord.antarchy.content.effect.CommandedEntityAccess;
import com.craisinlord.antarchy.content.item.RoyalGuardianArmorItem;
import net.minecraft.world.damagesource.DamageSource;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.entity.LivingEntity;
import net.minecraft.world.entity.projectile.Projectile;
import net.minecraft.world.entity.player.Player;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.ModifyVariable;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
@Mixin(LivingEntity.class)
public abstract class RoyalJudgmentCombatMixin {
@ModifyVariable(method = "hurt", at = @At("HEAD"), argsOnly = true, ordinal = 0)
private float antarchy$boostMarkedTargetDamage(float amount, DamageSource source) {
if (com.craisinlord.antarchy.content.item.RoyalGuardianSwordAbilities.isApplyingSecondaryDamage()) return amount;
LivingEntity target = (LivingEntity) (Object) this;
if (target instanceof CommandedEntityAccess access && access.antarchy$isRoyalInvested()) {
double reduction = Math.max(0.0D, Math.min(1.0D, AntarchySettings.royalGuardianMusterDamageReduction()));
amount *= (float) (1.0D - reduction);
}
Player attacker = antarchy$resolvePlayer(source);
if (attacker != null && amount > 0.0F && JudgmentMarkManager.isMarkedBy(attacker, target)) {
return amount * (float) RoyalGuardianArmorItem.judgmentDamageMultiplier(attacker);
}
return amount;
}
@Inject(method = "hurt", at = @At("RETURN"))
private void antarchy$markAfterHit(DamageSource source, float amount,
CallbackInfoReturnable<Boolean> cir) {
if (!cir.getReturnValue() || amount <= 0.0F
|| com.craisinlord.antarchy.content.item.RoyalGuardianSwordAbilities.isApplyingSecondaryDamage()) {
return;
}
LivingEntity target = (LivingEntity) (Object) this;
Player attacker = antarchy$resolvePlayer(source);
if (attacker != null) {
JudgmentMarkManager.recordSuccessfulHit(attacker, target);
}
}
private static Player antarchy$resolvePlayer(DamageSource source) {
Entity attacker = source.getEntity();
if (attacker instanceof Player player) {
return player;
}
if (source.getDirectEntity() instanceof Projectile projectile && projectile.getOwner() instanceof Player player) {
return player;
}
return null;
}
}
@@ -0,0 +1,16 @@
package com.craisinlord.antarchy.mixins;
import com.craisinlord.antarchy.content.entity.royal.RoyalLootDropGlow;
import net.minecraft.world.entity.item.ItemEntity;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
@Mixin(ItemEntity.class)
public abstract class RoyalLootItemGlowMixin {
@Inject(method = "tick", at = @At("HEAD"))
private void antarchy$tickRoyalLootGlow(CallbackInfo ci) {
RoyalLootDropGlow.tick((ItemEntity) (Object) this);
}
}
@@ -0,0 +1,16 @@
package com.craisinlord.antarchy.mixins;
import com.craisinlord.antarchy.content.effect.RoyalBoundaryManager;
import net.minecraft.server.level.ServerPlayer;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
@Mixin(ServerPlayer.class)
public abstract class ServerPlayerRoyalBoundaryMixin {
@Inject(method = "tick", at = @At("TAIL"))
private void antarchy$tickRoyalBoundary(CallbackInfo ci) {
RoyalBoundaryManager.tickOwner((ServerPlayer) (Object) this);
}
}
@@ -64,4 +64,17 @@ public abstract class HoverboardRiderPoseMixin<T extends LivingEntity, M extends
humanoidModel.body.zRot += (float) Math.toRadians(HOVERBOARD_RIDER_TORSO_ROLL); humanoidModel.body.zRot += (float) Math.toRadians(HOVERBOARD_RIDER_TORSO_ROLL);
} }
} }
@Inject(
method = "render(Lnet/minecraft/world/entity/LivingEntity;FFLcom/mojang/blaze3d/vertex/PoseStack;Lnet/minecraft/client/renderer/MultiBufferSource;I)V",
at = @At("RETURN")
)
private void antarchy$restoreHoverboardRiderPose(T entity, float entityYaw, float partialTick, PoseStack poseStack, MultiBufferSource bufferSource, int packedLight, CallbackInfo ci) {
if (entity.getVehicle() instanceof HoverboardEntity && this.getModel() instanceof HumanoidModel<?> humanoidModel) {
float correction = (float) Math.toRadians(HOVERBOARD_RIDER_RENDER_YAW);
humanoidModel.head.yRot -= correction;
humanoidModel.hat.yRot -= correction;
humanoidModel.body.zRot -= (float) Math.toRadians(HOVERBOARD_RIDER_TORSO_ROLL);
}
}
} }
@@ -0,0 +1,52 @@
package com.craisinlord.antarchy.mixins.compat.alexscaves;
import com.craisinlord.antarchy.content.gravity.AntarchyGravityApi;
import com.craisinlord.antarchy.content.gravity.AntarchyGravityDirection;
import com.craisinlord.antarchy.content.gravity.AntarchyGravityRotationUtil;
import com.craisinlord.antarchy.content.worldgen.thoraxis.ThoraxisUndersideManager;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.entity.projectile.Projectile;
import net.minecraft.world.phys.Vec3;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.ModifyArg;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
/** Handles Alex's Caves' manually-launched Candy Cane Hook. */
@Mixin(targets = "com.github.alexmodguy.alexscaves.server.entity.item.CandyCaneHookEntity")
public abstract class AlexsCavesCandyCaneHookMixin {
private boolean antarchy$launchAdjusted;
@Inject(method = "tick", at = @At("HEAD"))
private void antarchy$rotateManualLaunch(CallbackInfo ci) {
Projectile self = (Projectile) (Object) this;
if (this.antarchy$launchAdjusted) {
return;
}
this.antarchy$launchAdjusted = true;
Entity owner = self.getOwner();
boolean ownerInverted = owner != null && AntarchyGravityApi.isGravityInverted(owner);
if (ownerInverted || ThoraxisUndersideManager.isInUnderside(self)) {
AntarchyGravityDirection direction = ownerInverted
? AntarchyGravityApi.getGravityDirection(owner)
: AntarchyGravityDirection.UP;
Vec3 launch = AntarchyGravityRotationUtil.vecPlayerToWorld(
self.getDeltaMovement(), direction);
self.setDeltaMovement(launch);
}
}
@ModifyArg(
method = "fling",
at = @At(value = "INVOKE", target = "Lnet/minecraft/world/phys/Vec3;add(DDD)Lnet/minecraft/world/phys/Vec3;"),
index = 1
)
private double antarchy$invertFlingBoost(double y) {
Projectile self = (Projectile) (Object) this;
Entity owner = self.getOwner();
return y != 0.0D && (ThoraxisUndersideManager.isInUnderside(self)
|| (owner != null && AntarchyGravityApi.isGravityInverted(owner))) ? -y : y;
}
}
@@ -0,0 +1,25 @@
package com.craisinlord.antarchy.mixins.compat.alexscaves;
import com.craisinlord.antarchy.content.gravity.AntarchyGravityApi;
import com.craisinlord.antarchy.content.worldgen.thoraxis.ThoraxisUndersideManager;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.entity.projectile.Projectile;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.ModifyArg;
/** Corrects Depth Charge's custom tick, which bypasses ThrowableProjectile.tick. */
@Mixin(targets = "com.github.alexmodguy.alexscaves.server.entity.item.DepthChargeEntity")
public abstract class AlexsCavesDepthChargeMixin {
@ModifyArg(
method = "tick",
at = @At(value = "INVOKE", target = "Lnet/minecraft/world/phys/Vec3;add(DDD)Lnet/minecraft/world/phys/Vec3;"),
index = 1
)
private double antarchy$invertManualGravity(double y) {
Projectile self = (Projectile) (Object) this;
Entity owner = self.getOwner();
return !self.isNoGravity() && (ThoraxisUndersideManager.isInUnderside(self)
|| (owner != null && AntarchyGravityApi.isGravityInverted(owner))) ? -y : y;
}
}
@@ -0,0 +1,25 @@
package com.craisinlord.antarchy.mixins.compat.alexscaves;
import com.craisinlord.antarchy.content.gravity.AntarchyGravityApi;
import com.craisinlord.antarchy.content.worldgen.thoraxis.ThoraxisUndersideManager;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.entity.projectile.Projectile;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.ModifyArg;
/** Corrects Tephra's explicit -0.1 Y gravity adjustment. */
@Mixin(targets = "com.github.alexmodguy.alexscaves.server.entity.item.TephraEntity")
public abstract class AlexsCavesTephraMixin {
@ModifyArg(
method = "tick",
at = @At(value = "INVOKE", target = "Lnet/minecraft/world/phys/Vec3;add(DDD)Lnet/minecraft/world/phys/Vec3;", ordinal = 1),
index = 1
)
private double antarchy$invertManualGravity(double y) {
Projectile self = (Projectile) (Object) this;
Entity owner = self.getOwner();
return !self.isNoGravity() && (ThoraxisUndersideManager.isInUnderside(self)
|| (owner != null && AntarchyGravityApi.isGravityInverted(owner))) ? -y : y;
}
}
@@ -0,0 +1,25 @@
package com.craisinlord.antarchy.mixins.compat.alexscaves;
import com.craisinlord.antarchy.content.gravity.AntarchyGravityApi;
import com.craisinlord.antarchy.content.worldgen.thoraxis.ThoraxisUndersideManager;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.entity.projectile.Projectile;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.ModifyArg;
/** Corrects Water Bolt's explicit -0.07 Y gravity adjustment. */
@Mixin(targets = "com.github.alexmodguy.alexscaves.server.entity.item.WaterBoltEntity")
public abstract class AlexsCavesWaterBoltMixin {
@ModifyArg(
method = "tick",
at = @At(value = "INVOKE", target = "Lnet/minecraft/world/phys/Vec3;add(DDD)Lnet/minecraft/world/phys/Vec3;", ordinal = 1),
index = 1
)
private double antarchy$invertManualGravity(double y) {
Projectile self = (Projectile) (Object) this;
Entity owner = self.getOwner();
return !self.isNoGravity() && (ThoraxisUndersideManager.isInUnderside(self)
|| (owner != null && AntarchyGravityApi.isGravityInverted(owner))) ? -y : y;
}
}
@@ -0,0 +1,43 @@
package com.craisinlord.antarchy.mixins.compat.ars_nouveau;
import com.craisinlord.antarchy.content.gravity.AntarchyGravityApi;
import com.craisinlord.antarchy.content.gravity.AntarchyGravityDirection;
import com.craisinlord.antarchy.content.gravity.AntarchyGravityTransition;
import com.craisinlord.antarchy.content.worldgen.thoraxis.ThoraxisUndersideManager;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.entity.projectile.Projectile;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
/** Handles Ars Nouveau's explicit tickNextPosition gravity adjustment. */
@Mixin(targets = "com.hollingsworth.arsnouveau.common.entity.EntityProjectileSpell")
public abstract class ArsNouveauProjectileGravityMixin {
@Inject(method = "tick", at = @At("HEAD"))
private void antarchy$prepareArsProjectile(CallbackInfo ci) {
Projectile self = (Projectile) (Object) this;
Entity owner = self.getOwner();
if (owner != null && (ThoraxisUndersideManager.isInUnderside(self)
|| AntarchyGravityApi.isGravityInverted(owner))) {
AntarchyGravityApi.setAirborneGravityDirection(
self,
AntarchyGravityDirection.UP,
false,
AntarchyGravityTransition.INSTANT
);
}
}
@Inject(method = "tickNextPosition", at = @At("TAIL"))
private void antarchy$invertArsProjectileGravity(CallbackInfo ci) {
Projectile self = (Projectile) (Object) this;
if (!AntarchyGravityApi.isGravityInverted(self) || self.isNoGravity()) {
return;
}
// Ars applies -0.03 directly in tickNextPosition(). Correct that one
// application to +0.03 without changing damping or spell effects.
self.setDeltaMovement(self.getDeltaMovement().add(0.0D, 0.06D, 0.0D));
}
}
@@ -0,0 +1,65 @@
package com.craisinlord.antarchy.mixins.compat.irons_spellbooks;
import com.craisinlord.antarchy.content.gravity.AntarchyGravityApi;
import com.craisinlord.antarchy.content.gravity.AntarchyGravityDirection;
import com.craisinlord.antarchy.content.gravity.AntarchyGravityRotationUtil;
import com.craisinlord.antarchy.content.gravity.AntarchyGravityTransition;
import com.craisinlord.antarchy.content.worldgen.thoraxis.ThoraxisUndersideManager;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.entity.projectile.Projectile;
import net.minecraft.world.phys.Vec3;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Shadow;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
/** Handles Iron's Spellbooks' custom travel() gravity path. */
@Mixin(targets = "io.redspace.ironsspellbooks.entity.spells.AbstractMagicProjectile")
public abstract class IronsSpellbooksProjectileGravityMixin {
@Shadow public abstract float getSpeed();
@Shadow protected abstract double getDefaultGravity();
@Inject(method = "tick", at = @At("HEAD"))
private void antarchy$prepareMagicProjectile(CallbackInfo ci) {
Projectile self = (Projectile) (Object) this;
Entity owner = self.getOwner();
boolean inverted = ThoraxisUndersideManager.isInUnderside(self)
|| (owner != null && AntarchyGravityApi.isGravityInverted(owner));
if (!inverted) {
return;
}
AntarchyGravityApi.setAirborneGravityDirection(
self,
AntarchyGravityDirection.UP,
false,
AntarchyGravityTransition.INSTANT
);
}
@Inject(method = "shoot", at = @At("HEAD"), cancellable = true)
private void antarchy$rotateMagicProjectileLaunch(Vec3 rotation, CallbackInfo ci) {
Projectile self = (Projectile) (Object) this;
Entity owner = self.getOwner();
if (owner == null || (!ThoraxisUndersideManager.isInUnderside(self)
&& !AntarchyGravityApi.isGravityInverted(owner))) {
return;
}
self.setDeltaMovement(AntarchyGravityRotationUtil.vecPlayerToWorld(
rotation,
AntarchyGravityApi.getGravityDirection(owner)
).scale(this.getSpeed()));
ci.cancel();
}
@Inject(method = "travel", at = @At("TAIL"))
private void antarchy$invertMagicProjectileGravity(CallbackInfo ci) {
Projectile self = (Projectile) (Object) this;
if (!AntarchyGravityApi.isGravityInverted(self) || self.isNoGravity()) {
return;
}
// AbstractMagicProjectile.travel() already subtracted default gravity.
self.setDeltaMovement(self.getDeltaMovement().add(0.0D, 2.0D * this.getDefaultGravity(), 0.0D));
}
}
@@ -0,0 +1,26 @@
package com.craisinlord.antarchy.mixins.compat.scguns;
import com.craisinlord.antarchy.content.gravity.AntarchyGravityApi;
import com.craisinlord.antarchy.content.worldgen.thoraxis.ThoraxisUndersideManager;
import net.minecraft.world.entity.projectile.AbstractArrow;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
/** Corrects Scorched Guns arrow-derived projectiles with custom tick gravity. */
@Mixin(targets = "top.ribs.scguns.entity.projectile.BrassBoltEntity")
public abstract class ScgunsArrowGravityMixin {
@Inject(method = "tick", at = @At("TAIL"))
private void antarchy$invertScgunsArrowGravity(CallbackInfo ci) {
AbstractArrow self = (AbstractArrow) (Object) this;
if (self.isNoGravity() || (!AntarchyGravityApi.isGravityInverted(self)
&& !ThoraxisUndersideManager.isInUnderside(self))) {
return;
}
// Brass Bolt adds +0.01 after AbstractArrow.tick(); invert that custom
// correction to -0.01, without touching normal arrows.
self.setDeltaMovement(self.getDeltaMovement().add(0.0D, -0.02D, 0.0D));
}
}
@@ -0,0 +1,82 @@
package com.craisinlord.antarchy.mixins.compat.scguns;
import com.craisinlord.antarchy.content.gravity.AntarchyGravityApi;
import com.craisinlord.antarchy.content.gravity.AntarchyGravityDirection;
import com.craisinlord.antarchy.content.gravity.AntarchyGravityRotationUtil;
import com.craisinlord.antarchy.content.gravity.AntarchyGravityTransition;
import com.craisinlord.antarchy.content.worldgen.thoraxis.ThoraxisUndersideManager;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.entity.LivingEntity;
import net.minecraft.world.phys.Vec3;
import org.jetbrains.annotations.Nullable;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Shadow;
import org.spongepowered.asm.mixin.Unique;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
/** Bridges Scorched Guns' Entity-based projectile physics into Antarchy gravity. */
@Mixin(targets = {
"top.ribs.scguns.entity.projectile.ProjectileEntity",
"top.ribs.scguns.entity.projectile.BearPackShellProjectileEntity",
"top.ribs.scguns.entity.projectile.KrahgRoundProjectileEntity",
"top.ribs.scguns.entity.projectile.LightningProjectileEntity",
"top.ribs.scguns.entity.projectile.OsborneSlugProjectileEntity",
"top.ribs.scguns.entity.projectile.ShotballProjectileEntity"
})
public abstract class ScgunsProjectileEntityGravityMixin {
@Shadow @Nullable protected LivingEntity shooter;
@Shadow protected double modifiedGravity;
@Shadow public abstract Vec3 getDeltaMovement();
@Shadow public abstract void setDeltaMovement(Vec3 movement);
@Unique private boolean antarchy$launchDirectionAdjusted;
@Inject(method = "tick", at = @At("HEAD"))
private void antarchy$prepareScgunsProjectile(CallbackInfo ci) {
Entity self = (Entity) (Object) this;
boolean inverted = ThoraxisUndersideManager.isInUnderside(self)
|| (this.shooter != null && AntarchyGravityApi.isGravityInverted(this.shooter));
if (!inverted) {
if (AntarchyGravityApi.isGravityInverted(self) && !AntarchyGravityApi.isGravityForced(self)) {
AntarchyGravityApi.setAirborneGravityDirection(
self,
AntarchyGravityDirection.DOWN,
false,
AntarchyGravityTransition.INSTANT
);
}
return;
}
AntarchyGravityApi.setAirborneGravityDirection(
self,
AntarchyGravityDirection.UP,
false,
AntarchyGravityTransition.INSTANT
);
if (!this.antarchy$launchDirectionAdjusted && self.tickCount <= 1) {
Vec3 motion = this.getDeltaMovement();
if (motion.lengthSqr() > 1.0E-8D) {
this.setDeltaMovement(AntarchyGravityRotationUtil.vecPlayerToWorld(
motion,
AntarchyGravityDirection.UP
));
}
this.antarchy$launchDirectionAdjusted = true;
}
}
@Inject(method = "tick", at = @At("TAIL"))
private void antarchy$invertScgunsGravity(CallbackInfo ci) {
Entity self = (Entity) (Object) this;
if (!AntarchyGravityApi.isGravityInverted(self) || this.modifiedGravity == 0.0D) {
return;
}
// Scorched Guns already adds modifiedGravity once near tick end. Replace
// that downward world-Y acceleration with its upward counterpart.
this.setDeltaMovement(this.getDeltaMovement().add(0.0D, -2.0D * this.modifiedGravity, 0.0D));
}
}
@@ -1,7 +1,7 @@
package com.craisinlord.antarchy.mixins.gravity; package com.craisinlord.antarchy.mixins.gravity;
import com.craisinlord.antarchy.content.gravity.AntarchyGravityApi; import com.craisinlord.antarchy.content.gravity.AntarchyGravityApi;
import com.craisinlord.antarchy.content.gravity.AntarchyGravityRotationUtil; import com.craisinlord.antarchy.content.gravity.ProjectileLaunchGravity;
import com.craisinlord.antarchy.content.worldgen.thoraxis.ThoraxisUndersideManager; import com.craisinlord.antarchy.content.worldgen.thoraxis.ThoraxisUndersideManager;
import com.llamalad7.mixinextras.injector.wrapoperation.Operation; import com.llamalad7.mixinextras.injector.wrapoperation.Operation;
import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation; import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation;
@@ -58,7 +58,7 @@ public abstract class ProjectileGravityMixin {
) { ) {
Vec3 direction = new Vec3(x, y, z); Vec3 direction = new Vec3(x, y, z);
if (AntarchyGravityApi.isGravityInverted(shooter)) { if (AntarchyGravityApi.isGravityInverted(shooter)) {
direction = AntarchyGravityRotationUtil.vecPlayerToWorld( direction = ProjectileLaunchGravity.toWorld(
direction, direction,
AntarchyGravityApi.getGravityDirection(shooter) AntarchyGravityApi.getGravityDirection(shooter)
); );
@@ -31,8 +31,13 @@
"LivingEntityJumpingAccessor", "LivingEntityJumpingAccessor",
"CommandedMobMixin", "CommandedMobMixin",
"LivingEntityRoyalEffectsMixin", "LivingEntityRoyalEffectsMixin",
"JudgmentMarkEntityMixin",
"RoyalJudgmentCombatMixin",
"RoyalGuardianPlayerAttackMixin",
"RoyalGuardianSwordCombatMixin",
"MobCommandedTargetMixin", "MobCommandedTargetMixin",
"RoyalNoRespiteMixin", "RoyalNoRespiteMixin",
"RoyalLootItemGlowMixin",
"MantisClawWallClimbMixin", "MantisClawWallClimbMixin",
"MilkBucketSizeResetMixin", "MilkBucketSizeResetMixin",
"MinecartItemMixin", "MinecartItemMixin",
@@ -49,6 +54,9 @@
"VillagerTradesMixin", "VillagerTradesMixin",
"WaterlilyLumenMixin", "WaterlilyLumenMixin",
"ServerPlayerGameModeMixin", "ServerPlayerGameModeMixin",
"ServerPlayerRoyalBoundaryMixin",
"ProjectileRoyalBoundaryMixin",
"RoyalBoundaryOwnerCleanupMixin",
"PlayerAdvancementsMixin", "PlayerAdvancementsMixin",
"SpongeAntiwaterMixin", "SpongeAntiwaterMixin",
"SquidzookaEnchantMixin", "SquidzookaEnchantMixin",
@@ -87,6 +95,14 @@
"gravity.ServerPlayerGravityMixin", "gravity.ServerPlayerGravityMixin",
"gravity.ThrowableProjectileGravityMixin", "gravity.ThrowableProjectileGravityMixin",
"gravity.TrackedEntityAccessor", "gravity.TrackedEntityAccessor",
"compat.scguns.ScgunsProjectileEntityGravityMixin",
"compat.scguns.ScgunsArrowGravityMixin",
"compat.irons_spellbooks.IronsSpellbooksProjectileGravityMixin",
"compat.ars_nouveau.ArsNouveauProjectileGravityMixin",
"compat.alexscaves.AlexsCavesCandyCaneHookMixin",
"compat.alexscaves.AlexsCavesWaterBoltMixin",
"compat.alexscaves.AlexsCavesTephraMixin",
"compat.alexscaves.AlexsCavesDepthChargeMixin",
"multipart.EntityMultipartLifecycleMixin", "multipart.EntityMultipartLifecycleMixin",
"time.EntityDeltaMovementTimeDilationMixin", "time.EntityDeltaMovementTimeDilationMixin",
"time.EntityMoveTimeDilationMixin", "time.EntityMoveTimeDilationMixin",
@@ -543,6 +543,15 @@
"item.antarchy.royal_guardian_boots": "Royal Guardian Boots", "item.antarchy.royal_guardian_boots": "Royal Guardian Boots",
"item.antarchy.royal_guardian_sword": "Royal Guardian Sword", "item.antarchy.royal_guardian_sword": "Royal Guardian Sword",
"item.antarchy.royal_guardian_shield": "Royal Guardian Shield", "item.antarchy.royal_guardian_shield": "Royal Guardian Shield",
"tooltip.antarchy.royal_guardian_sword.mode": "Attunement: %s",
"tooltip.antarchy.royal_guardian_sword.mode.fire": "Fire",
"tooltip.antarchy.royal_guardian_sword.mode.frost": "Frost",
"tooltip.antarchy.royal_guardian_sword.mode.storm": "Storm",
"tooltip.antarchy.royal_guardian_sword.fire": "Fully charged hits erupt in royal flame, burning the target and nearby enemies.",
"tooltip.antarchy.royal_guardian_sword.frost": "Fully charged hits freeze the target and raise a fan of royal ice spikes.",
"tooltip.antarchy.royal_guardian_sword.storm": "Fully charged hits call royal lightning that chains between nearby enemies.",
"tooltip.antarchy.royal_guardian_sword.controls": "Sneak-use to cycle modes. Fully charged hits unleash the selected element (%s-second cooldown).",
"message.antarchy.royal_guardian_sword.mode": "Royal Guardian attuned to %s",
"item.antarchy.royal_assailant_helmet": "Royal Assailant Helmet", "item.antarchy.royal_assailant_helmet": "Royal Assailant Helmet",
"item.antarchy.royal_assailant_chestplate": "Royal Assailant Chestplate", "item.antarchy.royal_assailant_chestplate": "Royal Assailant Chestplate",
"item.antarchy.royal_assailant_leggings": "Royal Assailant Leggings", "item.antarchy.royal_assailant_leggings": "Royal Assailant Leggings",
@@ -552,7 +561,12 @@
"tooltip.antarchy.royal_assailant_staff": "Opens paired tears that summon four allied Manticores for 30 seconds.", "tooltip.antarchy.royal_assailant_staff": "Opens paired tears that summon four allied Manticores for 30 seconds.",
"tooltip.antarchy.royal_assailant_battleaxe.black_hole": "Use to create a Black Hole", "tooltip.antarchy.royal_assailant_battleaxe.black_hole": "Use to create a Black Hole",
"tooltip.antarchy.royal_assailant_battleaxe.dilation": "Hits apply Temporal Dilation", "tooltip.antarchy.royal_assailant_battleaxe.dilation": "Hits apply Temporal Dilation",
"tooltip.antarchy.royal_guardian_shield.command": "Use to command the nearest mob", "tooltip.antarchy.royal_guardian_shield.command": "Sneak-use to muster nearby mobs. Royal Investiture empowers their offense, speed, and resilience.",
"tooltip.antarchy.royal_guardian_helmet.judgment": "Marked for Judgment: +%s%% damage against marked targets",
"tooltip.antarchy.royal_guardian_chestplate.judgment": "Marked for Judgment: +%s%% damage against marked targets",
"tooltip.antarchy.royal_guardian_chestplate.boundary": "Royal Boundary (B by default): Freezes incoming hostile projectiles and grants Strength to allies.",
"tooltip.antarchy.royal_guardian_leggings.judgment": "Marked for Judgment: +%s%% damage against marked targets",
"tooltip.antarchy.royal_guardian_boots.judgment": "Marked for Judgment: +%s%% damage against marked targets",
"tooltip.antarchy.royal_assailant_chestplate.invert": "Press the inversion key to toggle inverted gravity on and off.", "tooltip.antarchy.royal_assailant_chestplate.invert": "Press the inversion key to toggle inverted gravity on and off.",
"tooltip.antarchy.royal_assailant_boots.fall": "Cancels fall damage while falling upward.", "tooltip.antarchy.royal_assailant_boots.fall": "Cancels fall damage while falling upward.",
"item.antarchy.mantis_claw": "Mantis Claw", "item.antarchy.mantis_claw": "Mantis Claw",
@@ -562,6 +576,9 @@
"item.antarchy.jerry_nucleus": "Jerry Nucleus", "item.antarchy.jerry_nucleus": "Jerry Nucleus",
"tooltip.antarchy.jerry_nucleus.hatch": "While holding, interact with any bug egg or turtle egg to hatch it instantly.", "tooltip.antarchy.jerry_nucleus.hatch": "While holding, interact with any bug egg or turtle egg to hatch it instantly.",
"key.antarchy.mount_special": "Mount: Special / Charge", "key.antarchy.mount_special": "Mount: Special / Charge",
"key.antarchy.royal_boundary": "Royal Boundary",
"message.antarchy.royal_boundary.activated": "Royal Boundary raised",
"message.antarchy.royal_boundary.recharging": "Royal Boundary is recharging",
"key.antarchy.mount_flight_toggle": "Mount: Toggle Flight", "key.antarchy.mount_flight_toggle": "Mount: Toggle Flight",
"item.antarchy.springbug_leg": "Springbug Leg", "item.antarchy.springbug_leg": "Springbug Leg",
"item.antarchy.springy_boots": "Springy Boots", "item.antarchy.springy_boots": "Springy Boots",
@@ -733,6 +750,7 @@
"message.antarchy.rainbow_ant_failed_dimension_create": "Failed to create dimension. This likely means something is wrong in your infinity config. Restart/Relog and try again.", "message.antarchy.rainbow_ant_failed_dimension_create": "Failed to create dimension. This likely means something is wrong in your infinity config. Restart/Relog and try again.",
"message.antarchy.rainbow_ant_infinity_missing": "Infinity must be installed for rainbow ant dimensions to work.", "message.antarchy.rainbow_ant_infinity_missing": "Infinity must be installed for rainbow ant dimensions to work.",
"message.antarchy.rainbow_ant_needs_reagent": "This rainbow ant needs a reagent before it can open a dimension.", "message.antarchy.rainbow_ant_needs_reagent": "This rainbow ant needs a reagent before it can open a dimension.",
"message.antarchy.teleport_arrival_failed": "No safe arrival spot was found. Teleportation was canceled.",
"message.antarchy.red_ant_activated": "The red ant is ready to open its destination.", "message.antarchy.red_ant_activated": "The red ant is ready to open its destination.",
"message.antarchy.red_ant_needs_reagent": "This red ant needs a reagent before it can open its destination.", "message.antarchy.red_ant_needs_reagent": "This red ant needs a reagent before it can open its destination.",
"message.antarchy.termite_activated": "The termite is ready to open its destination.", "message.antarchy.termite_activated": "The termite is ready to open its destination.",
@@ -1425,7 +1443,8 @@
,"decree.antarchy.close_quarters": "CLOSE QUARTERS" ,"decree.antarchy.close_quarters": "CLOSE QUARTERS"
,"decree.antarchy.close_quarters.subtitle": "Enter the royal circle. Projectiles are forbidden." ,"decree.antarchy.close_quarters.subtitle": "Enter the royal circle. Projectiles are forbidden."
,"decree.antarchy.violated": "DECREE VIOLATED" ,"decree.antarchy.violated": "DECREE VIOLATED"
,"decree.antarchy.violated.subtitle": "Face the King's judgment." ,"decree.antarchy.violated.subtitle": "Your hearts are halved for 30. Face the King's judgment."
,"effect.antarchy.royal_vitality_penalty": "Royal Vitality Diminished"
,"decree.antarchy.come_no_closer": "COME NO CLOSER" ,"decree.antarchy.come_no_closer": "COME NO CLOSER"
,"decree.antarchy.come_no_closer.subtitle": "Keep out of the King's inner radius." ,"decree.antarchy.come_no_closer.subtitle": "Keep out of the King's inner radius."
,"decree.antarchy.fight_me_coward": "FIGHT ME, COWARD!" ,"decree.antarchy.fight_me_coward": "FIGHT ME, COWARD!"
@@ -1475,9 +1494,9 @@
,"guide.antarchy.entry.chronosphere.title": "Chronosphere" ,"guide.antarchy.entry.chronosphere.title": "Chronosphere"
,"guide.antarchy.entry.chronosphere.subtitle": "Temporal equipment record" ,"guide.antarchy.entry.chronosphere.subtitle": "Temporal equipment record"
,"guide.antarchy.entry.chronosphere.description": "A record for the Chronosphere enchantment will be expanded here. Further data is pending." ,"guide.antarchy.entry.chronosphere.description": "A record for the Chronosphere enchantment will be expanded here. Further data is pending."
,"guide.antarchy.entry.toreterror_arena.title": "Toreterror Arena" ,"guide.antarchy.entry.toreterror.title": "Toreterror"
,"guide.antarchy.entry.toreterror_arena.subtitle": "Structure location fragment" ,"guide.antarchy.entry.toreterror_arena.subtitle": "Elythia Ocean boss"
,"guide.antarchy.entry.toreterror_arena.description": "The nearest arena is rumored to lie beyond the current horizon. Directional analysis is pending in this prototype." ,"guide.antarchy.entry.toreterror_arena.description": "The nearest Toreterror Arena within search range is listed below. It is always placed in an Elythia Ocean."
,"guide.antarchy.entry.elythia.title": "Elythia" ,"guide.antarchy.entry.elythia.title": "Elythia"
,"guide.antarchy.entry.elythia.subtitle": "Dimension and portal access record" ,"guide.antarchy.entry.elythia.subtitle": "Dimension and portal access record"
,"guide.antarchy.entry.elythia.description": "Elythia is reached through an Elythia portal made with a valid frame. A sacrificed Brown Ant activates the portal. The dimension contains oceans, meadows, forests, coral spikes, and other surface biomes." ,"guide.antarchy.entry.elythia.description": "Elythia is reached through an Elythia portal made with a valid frame. A sacrificed Brown Ant activates the portal. The dimension contains oceans, meadows, forests, coral spikes, and other surface biomes."
@@ -1505,6 +1524,27 @@
,"guide.antarchy.entry.high_fructose_corn_syrup.title": "High Fructose Corn Syrup" ,"guide.antarchy.entry.high_fructose_corn_syrup.title": "High Fructose Corn Syrup"
,"guide.antarchy.entry.high_fructose_corn_syrup.subtitle": "Nutritional and operational notes" ,"guide.antarchy.entry.high_fructose_corn_syrup.subtitle": "Nutritional and operational notes"
,"guide.antarchy.entry.high_fructose_corn_syrup.description": "A sweet substance of uncertain strategic value. Doctoral enthusiasm surrounding it is statistically suspicious." ,"guide.antarchy.entry.high_fructose_corn_syrup.description": "A sweet substance of uncertain strategic value. Doctoral enthusiasm surrounding it is statistically suspicious."
,"guide.antarchy.entry.rainbow_ant.title": "Rainbow Ant"
,"guide.antarchy.entry.rainbow_ant.subtitle": "Entity record"
,"guide.antarchy.entry.rainbow_ant.description": "When given an activation reagent, a Rainbow Ant can open a portal to its assigned dimension."
,"guide.antarchy.entry.easter_bunny.title": "Easter Bunny"
,"guide.antarchy.entry.easter_bunny.subtitle": "Entity record"
,"guide.antarchy.entry.easter_bunny.description": "Easter Bunnies periodically lay random spawn eggs."
,"guide.antarchy.entry.duplicator_tree.title": "Duplicator Tree"
,"guide.antarchy.entry.duplicator_tree.subtitle": "Block record"
,"guide.antarchy.entry.duplicator_tree.description": "Duplicator Trees copy nearby eligible blocks into open spaces around them."
,"guide.antarchy.disk.kraken.title": "KRAKEN SIGHTING NOTES"
,"guide.antarchy.entry.kraken.title": "Kraken"
,"guide.antarchy.entry.kraken.subtitle": "Overworld aquatic encounter"
,"guide.antarchy.entry.kraken.description": "Search open Overworld waters; the Kraken can only spawn in water. Some encounters require Bad Omen."
,"guide.antarchy.disk.king.title": "KING'S TREE FIELD NOTES"
,"guide.antarchy.entry.king.title": "King"
,"guide.antarchy.entry.king.subtitle": "Elythia royal entity record"
,"guide.antarchy.entry.king.description": "Seek the massive King's Tree in Elythia. The King spawns near its center and patrols the surrounding area."
,"guide.antarchy.disk.queen.title": "QUEEN HAVOC TRAIL NOTES"
,"guide.antarchy.entry.queen.title": "Queen"
,"guide.antarchy.entry.queen.subtitle": "Thoraxis royal entity record"
,"guide.antarchy.entry.queen.description": "Follow a Queen Havoc Trail through Thoraxis's underside to its chamber; the Queen appears when you approach."
,"screen.antarchy.computer.first_use.title": "INITIAL PASSWORD SETUP" ,"screen.antarchy.computer.first_use.title": "INITIAL PASSWORD SETUP"
,"screen.antarchy.computer.first_use.prompt": "CREATE A PASSWORD TO CONTINUE" ,"screen.antarchy.computer.first_use.prompt": "CREATE A PASSWORD TO CONTINUE"
,"screen.antarchy.computer.first_use.confirm": "CONFIRM PASSWORD" ,"screen.antarchy.computer.first_use.confirm": "CONFIRM PASSWORD"
@@ -3,8 +3,8 @@
"credit": "Made with Blockbench", "credit": "Made with Blockbench",
"texture_size": [128, 128], "texture_size": [128, 128],
"textures": { "textures": {
"0": "antarchy/item/computer", "0": "antarchy:item/computer",
"particle": "antarchy/item/computer" "particle": "antarchy:item/computer"
}, },
"elements": [ "elements": [
{ {
@@ -0,0 +1,7 @@
The pencil, eraser, trash, undo, and redo icons are adapted from Pixelarticons
by Gerrit Halfmann (https://github.com/halfmage/pixelarticons), version 2.4.1.
The source SVG files are included in this directory. The PNGs were rasterized
at 24x24, recolored to Antarchy green, and given a one-pixel black outline.
These icons are licensed under the MIT License. See LICENSE-pixelarticons.txt.
@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2019 Gerrit Halfmann
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
Binary file not shown.

After

Width:  |  Height:  |  Size: 233 B

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" fill="currentColor" viewBox="0 0 24 24"><path d="M15 18h6v2H7v-2h6v-2h2v2Zm-8 0H5v-2h2v2Zm-2-2H3v-2h2v2Zm12 0h-2v-2h2v2ZM7 14H5v-2h2v2Zm8 0h-2v-2h2v2Zm4 0h-2v-2h2v2ZM9 12H7v-2h2v2Zm4 0h-2v-2h2v2Zm8 0h-2v-2h2v2Zm-10-2H9V8h2v2Zm8 0h-2V8h2v2Zm-6-2h-2V6h2v2Zm4 0h-2V6h2v2Zm-2-2h-2V4h2v2Z"/></svg>

After

Width:  |  Height:  |  Size: 332 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 252 B

Loaded 100 of 158 files, more files were not shown because too many files have changed in this diff. Show more