diff --git a/.gradle-codex/caches/9.4.1/fileContent/fileContent.lock b/.gradle-codex/caches/9.4.1/fileContent/fileContent.lock new file mode 100644 index 00000000..5179b480 Binary files /dev/null and b/.gradle-codex/caches/9.4.1/fileContent/fileContent.lock differ diff --git a/.gradle-codex/caches/9.4.1/fileHashes/fileHashes.bin b/.gradle-codex/caches/9.4.1/fileHashes/fileHashes.bin new file mode 100644 index 00000000..21dae325 Binary files /dev/null and b/.gradle-codex/caches/9.4.1/fileHashes/fileHashes.bin differ diff --git a/.gradle-codex/caches/9.4.1/fileHashes/fileHashes.lock b/.gradle-codex/caches/9.4.1/fileHashes/fileHashes.lock new file mode 100644 index 00000000..7feee322 Binary files /dev/null and b/.gradle-codex/caches/9.4.1/fileHashes/fileHashes.lock differ diff --git a/.gradle-codex/caches/9.4.1/fileHashes/resourceHashesCache.bin b/.gradle-codex/caches/9.4.1/fileHashes/resourceHashesCache.bin new file mode 100644 index 00000000..22063717 Binary files /dev/null and b/.gradle-codex/caches/9.4.1/fileHashes/resourceHashesCache.bin differ diff --git a/.gradle-codex/caches/9.4.1/gc.properties b/.gradle-codex/caches/9.4.1/gc.properties new file mode 100644 index 00000000..e69de29b diff --git a/.gradle-codex/caches/9.4.1/transforms/.internal/gc.properties b/.gradle-codex/caches/9.4.1/transforms/.internal/gc.properties new file mode 100644 index 00000000..e69de29b diff --git a/.gradle-codex/wrapper/dists/gradle-9.4.1-bin/arn2x92ynaizyzdaamcbpbhtj/gradle-9.4.1-bin.zip.lck b/.gradle-codex/wrapper/dists/gradle-9.4.1-bin/arn2x92ynaizyzdaamcbpbhtj/gradle-9.4.1-bin.zip.lck new file mode 100644 index 00000000..e69de29b diff --git a/.gradle-codex/wrapper/dists/gradle-9.4.1-bin/arn2x92ynaizyzdaamcbpbhtj/gradle-9.4.1-bin.zip.part b/.gradle-codex/wrapper/dists/gradle-9.4.1-bin/arn2x92ynaizyzdaamcbpbhtj/gradle-9.4.1-bin.zip.part new file mode 100644 index 00000000..e69de29b diff --git a/common-1.21.1/src/main/java/com/craisinlord/antarchy/config/AntarchyConfigCatalog.java b/common-1.21.1/src/main/java/com/craisinlord/antarchy/config/AntarchyConfigCatalog.java index 1df735f3..c4aeb24e 100644 --- a/common-1.21.1/src/main/java/com/craisinlord/antarchy/config/AntarchyConfigCatalog.java +++ b/common-1.21.1/src/main/java/com/craisinlord/antarchy/config/AntarchyConfigCatalog.java @@ -182,6 +182,10 @@ public final class AntarchyConfigCatalog { || name.startsWith("american") || name.startsWith("mogglesVision") || name.equals("ductTapeRepairPercentPerUse") - || name.startsWith("fallenKingCrown"); + || name.startsWith("fallenKingCrown") + || name.startsWith("royalArmor") + || name.startsWith("royalGuardian") + || name.startsWith("royalAssailant") + || name.startsWith("royalWeapon"); } } diff --git a/common-1.21.1/src/main/java/com/craisinlord/antarchy/config/AntarchySettings.java b/common-1.21.1/src/main/java/com/craisinlord/antarchy/config/AntarchySettings.java index cff0ae71..8a985ec1 100644 --- a/common-1.21.1/src/main/java/com/craisinlord/antarchy/config/AntarchySettings.java +++ b/common-1.21.1/src/main/java/com/craisinlord/antarchy/config/AntarchySettings.java @@ -212,6 +212,11 @@ public final class AntarchySettings { private static int royalGuardianBootsArmorValue = 5; private static double royalGuardianArmorToughness = 4.0D; 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 royalAssailantChestplateArmorValue = 10; private static int royalAssailantLeggingsArmorValue = 8; @@ -224,6 +229,14 @@ public final class AntarchySettings { private static double royalAssailantBootsDoubleDamageChance = 0.05D; private static double royalGuardianSwordAttackDamage = 111.0D; 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 royalAssailantBattleAxeAttackSpeed = -2.9D; private static double royalWeaponAttackReachBonus = 1.0D; @@ -231,6 +244,21 @@ public final class AntarchySettings { private static int royalWeaponDurability = 4608; private static int royalWeaponEnchantability = 30; 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 int royalEggHatchChance = 3; private static double princeHealth = 120.0D; @@ -1957,6 +1985,17 @@ public final class AntarchySettings { public static double royalGuardianArmorKnockbackResistance() { return royalGuardianArmorKnockbackResistance; } 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 void setRoyalAssailantHelmetArmorValue(int v) { royalAssailantHelmetArmorValue = v; } @@ -1993,6 +2032,23 @@ public final class AntarchySettings { public static double royalGuardianSwordAttackSpeed() { return royalGuardianSwordAttackSpeed; } 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 void setRoyalAssailantBattleAxeAttackDamage(double v) { royalAssailantBattleAxeAttackDamage = v; } @@ -2014,6 +2070,37 @@ public final class AntarchySettings { public static int royalGuardianShieldDurability() { return royalGuardianShieldDurability; } 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 void setRoyalBoltDamage(double v) { royalBoltDamage = v; } diff --git a/common-1.21.1/src/main/java/com/craisinlord/antarchy/content/AntarchyObjects.java b/common-1.21.1/src/main/java/com/craisinlord/antarchy/content/AntarchyObjects.java index fa96587a..817c76e2 100644 --- a/common-1.21.1/src/main/java/com/craisinlord/antarchy/content/AntarchyObjects.java +++ b/common-1.21.1/src/main/java/com/craisinlord/antarchy/content/AntarchyObjects.java @@ -329,6 +329,7 @@ public final class AntarchyObjects { public static Supplier> STINKY_EFFECT = (Supplier>) UNBOUND; @SuppressWarnings("unchecked") public static Supplier> GLIMMERING_EFFECT = (Supplier>) UNBOUND; + public static Supplier> ROYAL_VITALITY_PENALTY_EFFECT = (Supplier>) UNBOUND; @SuppressWarnings("unchecked") public static Supplier LUMEN = (Supplier) UNBOUND; @SuppressWarnings("unchecked") @@ -823,6 +824,10 @@ public final class AntarchyObjects { GLIMMERING_EFFECT = supplier; } + public static void setRoyalVitalityPenaltyEffect(Supplier> supplier) { + ROYAL_VITALITY_PENALTY_EFFECT = supplier; + } + public static void setElka(Supplier> supplier) { ELKA = supplier; } diff --git a/common-1.21.1/src/main/java/com/craisinlord/antarchy/content/antmail/AntmailDebug.java b/common-1.21.1/src/main/java/com/craisinlord/antarchy/content/antmail/AntmailDebug.java new file mode 100644 index 00000000..cceeac19 --- /dev/null +++ b/common-1.21.1/src/main/java/com/craisinlord/antarchy/content/antmail/AntmailDebug.java @@ -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); + } +} diff --git a/common-1.21.1/src/main/java/com/craisinlord/antarchy/content/antmail/AntmailServerData.java b/common-1.21.1/src/main/java/com/craisinlord/antarchy/content/antmail/AntmailServerData.java index 494ce7c6..60686fe9 100644 --- a/common-1.21.1/src/main/java/com/craisinlord/antarchy/content/antmail/AntmailServerData.java +++ b/common-1.21.1/src/main/java/com/craisinlord/antarchy/content/antmail/AntmailServerData.java @@ -13,8 +13,10 @@ import net.minecraft.world.level.saveddata.SavedData; import java.util.ArrayList; import java.util.LinkedHashMap; +import java.util.LinkedHashSet; import java.util.List; import java.util.Map; +import java.util.Set; import java.util.UUID; public final class AntmailServerData extends SavedData { @@ -301,15 +303,20 @@ public final class AntmailServerData extends SavedData { public synchronized void drain(MinecraftServer server, AntmailAddress address) { AntmailMailbox mailbox = mailboxes.getOrCreate(address); final boolean[] delivered = {false}; + Set deliveredSenders = new LinkedHashSet<>(); pending.removeIf(pendingMessage -> { if (!pendingMessage.recipient().equals(address) || mailbox.inbox().size() >= AntmailValidation.MAX_MAILBOX_MESSAGES) return false; if (!mailbox.addIncoming(pendingMessage.message())) return false; pendingMessage.message().setDeliveryStatus("DELIVERED"); delivered[0] = true; + deliveredSenders.add(pendingMessage.message().sender()); return true; }); - if (delivered[0]) touchMailbox(address); - setDirty(); + if (delivered[0]) { + touchMailbox(address); + for (AntmailAddress sender : deliveredSenders) touchMailbox(sender); + setDirty(); + } } public synchronized AntmailDeliveryResult retry(MinecraftServer server, AntmailAddress sender, UUID messageId) { diff --git a/common-1.21.1/src/main/java/com/craisinlord/antarchy/content/antmail/AntmailWire.java b/common-1.21.1/src/main/java/com/craisinlord/antarchy/content/antmail/AntmailWire.java index 858c8857..07f6b92b 100644 --- a/common-1.21.1/src/main/java/com/craisinlord/antarchy/content/antmail/AntmailWire.java +++ b/common-1.21.1/src/main/java/com/craisinlord/antarchy/content/antmail/AntmailWire.java @@ -2,13 +2,19 @@ package com.craisinlord.antarchy.content.antmail; import net.minecraft.nbt.CompoundTag; 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.Base64; import java.util.List; 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() { } @@ -19,18 +25,30 @@ public final class AntmailWire { ListTag list = new ListTag(); for (AntmailAttachment attachment : attachments) list.add(attachment.toTag()); 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"); return encoded; } 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) { 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) { 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.length() > AntmailValidation.MAX_ATTACHMENT_WIRE_CHARS) throw new IllegalArgumentException("Attachment payload is too large"); try { - String snbt = new String(Base64.getDecoder().decode(encoded), java.nio.charset.StandardCharsets.UTF_8); - CompoundTag root = TagParser.parseTag(snbt); + CompoundTag root = decodeTag(encoded); ListTag list = root.getList("Attachments", 10); List result = new ArrayList<>(); for (int i = 0; i < list.size(); i++) result.add(AntmailAttachment.fromTag(list.getCompound(i))); diff --git a/common-1.21.1/src/main/java/com/craisinlord/antarchy/content/block/entity/QueenTrailSpawnMarkerBlockEntity.java b/common-1.21.1/src/main/java/com/craisinlord/antarchy/content/block/entity/QueenTrailSpawnMarkerBlockEntity.java index fe5e04cc..fba75d35 100644 --- a/common-1.21.1/src/main/java/com/craisinlord/antarchy/content/block/entity/QueenTrailSpawnMarkerBlockEntity.java +++ b/common-1.21.1/src/main/java/com/craisinlord/antarchy/content/block/entity/QueenTrailSpawnMarkerBlockEntity.java @@ -49,6 +49,10 @@ public final class QueenTrailSpawnMarkerBlockEntity extends BlockEntity { return this.configured; } + public boolean isConsumed() { + return this.consumed; + } + public static void serverTick(ServerLevel level, QueenTrailSpawnMarkerBlockEntity marker) { if (!marker.configured || marker.consumed || level.getDifficulty() == Difficulty.PEACEFUL) { return; @@ -88,6 +92,22 @@ public final class QueenTrailSpawnMarkerBlockEntity extends BlockEntity { } } 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) { this.consumed = true; this.queenUuid = queen.getUUID(); diff --git a/common-1.21.1/src/main/java/com/craisinlord/antarchy/content/client/AntmailClientState.java b/common-1.21.1/src/main/java/com/craisinlord/antarchy/content/client/AntmailClientState.java index 1d829aa8..6057fc03 100644 --- a/common-1.21.1/src/main/java/com/craisinlord/antarchy/content/client/AntmailClientState.java +++ b/common-1.21.1/src/main/java/com/craisinlord/antarchy/content/client/AntmailClientState.java @@ -1,6 +1,7 @@ package com.craisinlord.antarchy.content.client; import com.craisinlord.antarchy.content.network.AntmailResultPayload; +import com.craisinlord.antarchy.content.antmail.AntmailDebug; import com.craisinlord.antarchy.content.antmail.AntmailMessage; import net.minecraft.core.BlockPos; @@ -18,9 +19,19 @@ public final class AntmailClientState { } 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); if ("unchanged".equals(payload.detail())) { - VERSIONS.put(payload.pos(), payload.version()); + if (MAILBOXES.containsKey(payload.pos())) { + 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; } else if ("message_detail".equals(payload.detail()) && !payload.data().isBlank()) { try { diff --git a/common-1.21.1/src/main/java/com/craisinlord/antarchy/content/client/ComputerAccessClientState.java b/common-1.21.1/src/main/java/com/craisinlord/antarchy/content/client/ComputerAccessClientState.java index f826ee01..18c550ad 100644 --- a/common-1.21.1/src/main/java/com/craisinlord/antarchy/content/client/ComputerAccessClientState.java +++ b/common-1.21.1/src/main/java/com/craisinlord/antarchy/content/client/ComputerAccessClientState.java @@ -13,6 +13,13 @@ public final class ComputerAccessClientState { } 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")) { BlockleClientState.update(result); } diff --git a/common-1.21.1/src/main/java/com/craisinlord/antarchy/content/client/ComputerFileSystemClientState.java b/common-1.21.1/src/main/java/com/craisinlord/antarchy/content/client/ComputerFileSystemClientState.java index 2ca89adb..9442bb73 100644 --- a/common-1.21.1/src/main/java/com/craisinlord/antarchy/content/client/ComputerFileSystemClientState.java +++ b/common-1.21.1/src/main/java/com/craisinlord/antarchy/content/client/ComputerFileSystemClientState.java @@ -25,6 +25,16 @@ public final class ComputerFileSystemClientState { } catch (NumberFormatException ignored) { 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.success = payload.result() == ComputerAccessResultPayload.SUCCESS; state.error = envelope[1]; diff --git a/common-1.21.1/src/main/java/com/craisinlord/antarchy/content/client/ComputerStructureLocatorClientState.java b/common-1.21.1/src/main/java/com/craisinlord/antarchy/content/client/ComputerStructureLocatorClientState.java new file mode 100644 index 00000000..03697861 --- /dev/null +++ b/common-1.21.1/src/main/java/com/craisinlord/antarchy/content/client/ComputerStructureLocatorClientState.java @@ -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 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"); + } +} diff --git a/common-1.21.1/src/main/java/com/craisinlord/antarchy/content/client/KingJudgmentFlashClientState.java b/common-1.21.1/src/main/java/com/craisinlord/antarchy/content/client/KingJudgmentFlashClientState.java new file mode 100644 index 00000000..07113176 --- /dev/null +++ b/common-1.21.1/src/main/java/com/craisinlord/antarchy/content/client/KingJudgmentFlashClientState.java @@ -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; + } +} diff --git a/common-1.21.1/src/main/java/com/craisinlord/antarchy/content/client/KingJudgmentFlashHudRenderer.java b/common-1.21.1/src/main/java/com/craisinlord/antarchy/content/client/KingJudgmentFlashHudRenderer.java new file mode 100644 index 00000000..00e0ea17 --- /dev/null +++ b/common-1.21.1/src/main/java/com/craisinlord/antarchy/content/client/KingJudgmentFlashHudRenderer.java @@ -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); + } +} diff --git a/common-1.21.1/src/main/java/com/craisinlord/antarchy/content/client/renderer/RoyalIndicatorGeoLayer.java b/common-1.21.1/src/main/java/com/craisinlord/antarchy/content/client/renderer/RoyalIndicatorGeoLayer.java new file mode 100644 index 00000000..e4934986 --- /dev/null +++ b/common-1.21.1/src/main/java/com/craisinlord/antarchy/content/client/renderer/RoyalIndicatorGeoLayer.java @@ -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 extends GeoRenderLayer { + public RoyalIndicatorGeoLayer(GeoRenderer 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); + } +} diff --git a/common-1.21.1/src/main/java/com/craisinlord/antarchy/content/client/renderer/RoyalIndicatorLivingLayer.java b/common-1.21.1/src/main/java/com/craisinlord/antarchy/content/client/renderer/RoyalIndicatorLivingLayer.java new file mode 100644 index 00000000..9840fbd6 --- /dev/null +++ b/common-1.21.1/src/main/java/com/craisinlord/antarchy/content/client/renderer/RoyalIndicatorLivingLayer.java @@ -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> extends RenderLayer { + public RoyalIndicatorLivingLayer(RenderLayerParent 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); + } +} diff --git a/common-1.21.1/src/main/java/com/craisinlord/antarchy/content/client/renderer/RoyalIndicatorRenderer.java b/common-1.21.1/src/main/java/com/craisinlord/antarchy/content/client/renderer/RoyalIndicatorRenderer.java new file mode 100644 index 00000000..d8c45925 --- /dev/null +++ b/common-1.21.1/src/main/java/com/craisinlord/antarchy/content/client/renderer/RoyalIndicatorRenderer.java @@ -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); + } +} diff --git a/common-1.21.1/src/main/java/com/craisinlord/antarchy/content/client/screen/ComputerScreen.java b/common-1.21.1/src/main/java/com/craisinlord/antarchy/content/client/screen/ComputerScreen.java index 7a8d688c..60f3c40a 100644 --- a/common-1.21.1/src/main/java/com/craisinlord/antarchy/content/client/screen/ComputerScreen.java +++ b/common-1.21.1/src/main/java/com/craisinlord/antarchy/content/client/screen/ComputerScreen.java @@ -30,17 +30,31 @@ import com.craisinlord.antarchy.content.client.game.AntmanProgram; import com.craisinlord.antarchy.content.client.game.BlockleProgram; import com.craisinlord.antarchy.content.computer.terminal.TerminalCommandParser; import com.craisinlord.antarchy.content.computer.terminal.TerminalResult; +import com.craisinlord.antarchy.config.AntarchySettings; +import com.craisinlord.antarchy.content.entity.nightmare.NightmareEntity; import org.lwjgl.glfw.GLFW; import net.minecraft.client.Minecraft; import net.minecraft.client.gui.GuiGraphics; import net.minecraft.client.gui.screens.Screen; import net.minecraft.network.chat.Component; import net.minecraft.resources.ResourceLocation; +import net.minecraft.resources.ResourceKey; import net.minecraft.core.registries.BuiltInRegistries; +import net.minecraft.core.registries.Registries; +import net.minecraft.core.component.DataComponents; import net.minecraft.world.entity.LivingEntity; import net.minecraft.client.gui.screens.inventory.InventoryScreen; import net.minecraft.world.item.ItemStack; +import net.minecraft.world.item.Items; +import net.minecraft.world.item.alchemy.Potion; +import net.minecraft.world.item.alchemy.PotionContents; import net.minecraft.world.item.crafting.Ingredient; +import net.minecraft.world.item.crafting.RecipeHolder; +import net.minecraft.world.item.crafting.ShapedRecipe; +import net.minecraft.world.item.enchantment.Enchantment; +import net.minecraft.world.item.enchantment.ItemEnchantments; +import org.joml.Quaternionf; +import org.joml.Vector3f; import java.util.ArrayList; import java.util.LinkedHashMap; @@ -49,6 +63,7 @@ import java.util.Locale; import java.util.Map; import java.util.Base64; import java.util.HashSet; +import java.util.HashMap; import java.util.Set; import java.util.UUID; @@ -59,6 +74,10 @@ public final class ComputerScreen extends Screen { private static final int PALE_GREEN = 0xFFB8FFB8; private static final int DARK_GREEN = 0xFF102010; private static final int BLACK = 0xFF030603; + private static final int TITLE_BAR_HEIGHT = 20; + private static final int WINDOW_CONTROL_WIDTH = 18; + private static final int WINDOW_CONTROL_COUNT = 3; + private static final int TRASH_ICON_INDEX = 8; private static final String[] BOOT_MESSAGES = {"ANTS MARCHING...", "COMPUTER COMPUTING...", "HCFS BREWING...", "WAKING THE QUEEN..."}; private static final long SESSION_TIMEOUT = 300_000L; private static final Map SESSIONS = new LinkedHashMap<>(); @@ -76,6 +95,10 @@ public final class ComputerScreen extends Screen { private ComputerAccessResultPayload accessResult; private int observedResult = -1; private Window dragging; + private boolean paintStrokeActive; + private int paintStrokeButton = -1; + private int lastPaintPixelX = -1; + private int lastPaintPixelY = -1; private String draggedDisk; private ResourceLocation selectedDisk; private int dragX; @@ -85,6 +108,8 @@ public final class ComputerScreen extends Screen { private final AntmanProgram antman; private final BlockleProgram blockle; private final Set archiveRenderLog = new HashSet<>(); + private final Map archiveEntityPreviews = new HashMap<>(); + private final Set unavailableArchiveEntities = new HashSet<>(); public ComputerScreen(net.minecraft.core.BlockPos position) { super(Component.translatable("screen.antarchy.computer")); @@ -137,10 +162,13 @@ public final class ComputerScreen extends Screen { } } AntmailResultPayload mailResult = AntmailClientState.get(position); + syncAntmailPaintAttachment(); if (loggedIn && --session.antmailRefreshTicks <= 0) { requestAntmailState(); session.antmailRefreshTicks = 40; } + if (loggedIn && AntmailClientState.getMailbox(position) == null) session.antmailStateWaitTicks++; + else session.antmailStateWaitTicks = 0; if (session.antmailRegistrationPending && mailResult != null) { String expectedAddress = session.antmailUsername + "@antmail.com"; if (mailResult.address().equalsIgnoreCase(expectedAddress)) { @@ -153,6 +181,21 @@ public final class ComputerScreen extends Screen { session.antmailRegistrationPending = false; } } + if (!session.antmailRetryMessageId.isBlank() && mailResult != null + && mailResult != session.antmailRetryBaseline + && session.antmailRetryMessageId.equals(mailResult.messageId()) + && !"message_detail".equals(mailResult.detail())) { + session.antmailStatus = antmailStatus(mailResult); + session.antmailRetryMessageId = ""; + session.antmailRetryBaseline = null; + session.antmailRetryTicks = 0; + requestAntmailState(true); + } else if (!session.antmailRetryMessageId.isBlank() && --session.antmailRetryTicks <= 0) { + session.antmailStatus = "RETRY TIMED OUT // TRY AGAIN"; + session.antmailRetryMessageId = ""; + session.antmailRetryBaseline = null; + requestAntmailState(true); + } if (!loggedIn && accessResult != null && accessResult.authenticated()) { loggedIn = true; session.authenticated = true; @@ -211,10 +254,11 @@ public final class ComputerScreen extends Screen { session.terminalOutput.add("ERROR: CORRUPTED SERVER RESPONSE"); } } - basilisk.setInstalled(physicalDisks().stream().anyMatch(id -> id.getPath().equals("basilisk_game"))); - antman.setInstalled(physicalDisks().stream().anyMatch(id -> id.getPath().equals("antman_game"))); + boolean unlockAllGames = AntarchySettings.unlockAllArchives(); + basilisk.setInstalled(unlockAllGames || physicalDisks().stream().anyMatch(id -> id.getPath().equals("basilisk_game"))); + antman.setInstalled(unlockAllGames || physicalDisks().stream().anyMatch(id -> id.getPath().equals("antman_game"))); boolean blockleWasInstalled = blockle.installed(); - blockle.setInstalled(physicalDisks().stream().anyMatch(id -> id.getPath().equals("blockle_game"))); + blockle.setInstalled(unlockAllGames || physicalDisks().stream().anyMatch(id -> id.getPath().equals("blockle_game"))); if (loggedIn && blockle.installed() && !blockleWasInstalled) { ComputerNetworking.requestBlockleState(position); session.blockleStateRequested = true; @@ -315,18 +359,26 @@ public final class ComputerScreen extends Screen { boolean hover = inside(x - 6, y - 6, 76, 57, mouseX, mouseY); if (hover) g.fill(x - 7, y - 7, x + 69, y + 51, 0xFF173817); icon(g, ICONS[i], x + 25, y + 2); - if (ICONS[i].equals("ANTMAIL") && antmailHasUnreadMessages()) drawNotificationBadge(g, x + 47, y - 3); + if (ICONS[i].equals("ANTMAIL") && antmailHasUnreadMessages()) drawNotificationBadge(g, x + 35, y + 6); g.drawString(font, Component.literal(ICONS[i]), x, y + 35, GREEN, false); } List disks = physicalDisks().stream().filter(id -> !id.getPath().equals("blockle_game")).toList(); for (int i = 0; i < disks.size() && i < 3; i++) { - int x = l + 24 + i % 4 * 94; + int x = l + 24 + (i + 1) * 94; int y = t + 200 + i / 4 * 54; icon(g, "DISK", x + 25, y + 2); String name = disks.get(i).getPath(); g.drawString(font, Component.literal(name.length() > 12 ? name.substring(0, 12) : name), x, y + 35, GREEN, false); } - for (Window window : session.windows) if (!window.minimized) renderWindow(g, window, l, t, mouseX, mouseY); + // Commit the desktop before compositing the independent window layers. + g.flush(); + if (activeWindow != null && !activeWindow.minimized && session.windows.remove(activeWindow)) { + session.windows.add(activeWindow); + } + int layer = 1; + for (Window window : session.windows) { + if (!window.minimized) renderWindow(g, window, l, t, mouseX, mouseY, layer++); + } } private boolean antmailHasUnreadMessages() { @@ -336,10 +388,32 @@ public final class ComputerScreen extends Screen { } private void drawNotificationBadge(GuiGraphics g, int x, int y) { - g.fill(x - 3, y - 1, x + 4, y + 6, PALE_GREEN); - g.fill(x - 1, y - 3, x + 2, y + 8, PALE_GREEN); - g.fill(x - 5, y + 1, x + 6, y + 4, PALE_GREEN); - g.drawString(font, Component.literal("!"), x - 1, y - 1, BLACK, false); + String[] circle = { + "....#####....", + "..##+++++##..", + ".#+++++++++#.", + "#+++++++++++#", + "#+++++++++++#", + "#+++++++++++#", + "#+++++++++++#", + "#+++++++++++#", + "#+++++++++++#", + "#+++++++++++#", + ".#+++++++++#.", + "..##+++++##..", + "....#####...." + }; + int badgeFill = GREEN; + for (int row = 0; row < circle.length; row++) { + for (int column = 0; column < circle[row].length(); column++) { + char pixel = circle[row].charAt(column); + if (pixel == '#') g.fill(x - 6 + column, y - 6 + row, x - 5 + column, y - 5 + row, BLACK); + else if (pixel == '+') g.fill(x - 6 + column, y - 6 + row, x - 5 + column, y - 5 + row, badgeFill); + } + } + // Two-pixel stem and a centered two-pixel dot keep the exclamation bold and symmetrical. + g.fill(x - 1, y - 4, x + 1, y + 2, BLACK); + g.fill(x - 1, y + 3, x + 1, y + 5, BLACK); } private void renderWallpaper(GuiGraphics g, int l, int t) { @@ -383,138 +457,197 @@ public final class ComputerScreen extends Screen { private void icon(GuiGraphics g, String type, int x, int y) { if (type.equals("ARCHIVE") || type.equals("DISK")) { - g.fill(x - 11, y - 2, x + 11, y + 19, BLACK); + // Chunky drive/archive with an inset label window and lower status slot. + g.fill(x - 11, y - 2, x + 11, y + 20, BLACK); g.fill(x - 8, y + 1, x + 8, y + 16, GREEN); g.fill(x - 5, y + 3, x + 5, y + 8, BLACK); - g.fill(x - 4, y + 12, x + 4, y + 16, BLACK); + g.fill(x - 4, y + 4, x + 4, y + 7, PALE_GREEN); + g.fill(x - 5, y + 11, x + 5, y + 13, BLACK); + g.fill(x + 5, y + 14, x + 7, y + 16, BLACK); return; } if (type.equals("FILES")) { - // Windows-style file silhouette: the raised tab starts flush at the left edge. - g.fill(x - 11, y + 3, x + 11, y + 20, BLACK); - g.fill(x - 11, y, x - 1, y + 6, BLACK); - g.fill(x - 8, y + 6, x + 8, y + 17, GREEN); - g.fill(x - 8, y + 3, x - 2, y + 6, GREEN); + // Folder silhouette with a clearly outlined tab and a second document peeking out. + g.fill(x - 8, y - 2, x + 9, y + 14, BLACK); + g.fill(x - 5, y + 1, x + 6, y + 11, PALE_GREEN); + g.fill(x - 10, y + 4, x + 11, y + 20, BLACK); + g.fill(x - 7, y + 7, x + 8, y + 17, GREEN); + g.fill(x - 8, y + 4, x - 2, y + 7, GREEN); + g.fill(x - 4, y + 10, x + 5, y + 12, BLACK); + g.fill(x - 4, y + 14, x + 3, y + 16, BLACK); return; } if (type.equals("SETTINGS")) { - // Eight-tooth gear: a solid stepped silhouette with a clearly cut-out hub. - g.fill(x - 4, y - 3, x + 4, y + 20, BLACK); - g.fill(x - 12, y + 6, x + 12, y + 13, BLACK); - g.fill(x - 9, y, x - 4, y + 5, BLACK); - g.fill(x + 4, y, x + 9, y + 5, BLACK); - g.fill(x - 9, y + 14, x - 4, y + 19, BLACK); - g.fill(x + 4, y + 14, x + 9, y + 19, BLACK); + // One continuous cog silhouette, with broad square teeth and an open center. + g.fill(x - 4, y - 2, x + 4, y + 22, BLACK); + g.fill(x - 12, y + 6, x + 12, y + 14, BLACK); + g.fill(x - 10, y + 1, x - 5, y + 5, BLACK); + g.fill(x + 5, y + 1, x + 10, y + 5, BLACK); + g.fill(x - 10, y + 15, x - 5, y + 19, BLACK); + g.fill(x + 5, y + 15, x + 10, y + 19, BLACK); + g.fill(x - 2, y, x + 2, y + 20, GREEN); + g.fill(x - 10, y + 8, x + 10, y + 12, GREEN); g.fill(x - 7, y + 3, x + 7, y + 17, GREEN); - g.fill(x - 10, y + 7, x + 10, y + 12, GREEN); g.fill(x - 3, y + 7, x + 3, y + 13, BLACK); + g.fill(x - 1, y + 9, x + 1, y + 11, PALE_GREEN); return; } if (type.equals("TERMINAL")) { - g.fill(x - 12, y - 1, x + 12, y + 18, BLACK); - g.fill(x - 9, y + 2, x + 9, y + 15, GREEN); - g.fill(x - 6, y + 6, x - 2, y + 9, BLACK); - g.fill(x - 2, y + 9, x + 4, y + 12, BLACK); + g.fill(x - 12, y - 1, x + 12, y + 19, BLACK); + g.fill(x - 9, y + 2, x + 9, y + 14, GREEN); + g.fill(x - 6, y + 5, x - 3, y + 8, BLACK); + g.fill(x - 3, y + 8, x + 1, y + 11, BLACK); + g.fill(x - 6, y + 11, x - 3, y + 14, BLACK); + g.fill(x + 3, y + 11, x + 7, y + 13, BLACK); + g.fill(x - 5, y + 16, x + 5, y + 18, BLACK); return; } if (type.equals("TEXT")) { g.fill(x - 9, y - 2, x + 10, y + 20, BLACK); g.fill(x - 6, y + 1, x + 7, y + 17, GREEN); - g.fill(x + 5, y + 1, x + 7, y + 4, BLACK); - g.fill(x - 3, y + 5, x + 5, y + 7, BLACK); - g.fill(x - 3, y + 9, x + 5, y + 11, BLACK); - g.fill(x - 3, y + 13, x + 3, y + 15, BLACK); + g.fill(x + 4, y + 1, x + 7, y + 4, BLACK); + g.fill(x - 3, y + 5, x + 4, y + 7, BLACK); + g.fill(x - 3, y + 9, x + 4, y + 11, BLACK); + g.fill(x - 3, y + 13, x + 2, y + 15, BLACK); return; } if (type.equals("PAINT")) { - // Paint palette first, then a stepped diagonal brush over it. - g.fill(x - 11, y + 2, x + 8, y + 19, BLACK); - g.fill(x - 8, y + 5, x + 5, y + 16, GREEN); - g.fill(x - 5, y + 7, x - 2, y + 10, BLACK); - g.fill(x, y + 6, x + 3, y + 9, BLACK); - g.fill(x + 1, y + 12, x + 4, y + 15, BLACK); - g.fill(x + 7, y - 4, x + 12, y + 3, BLACK); - g.fill(x + 5, y + 1, x + 10, y + 8, BLACK); - g.fill(x + 3, y + 6, x + 8, y + 13, BLACK); - g.fill(x + 1, y + 11, x + 6, y + 16, BLACK); - g.fill(x + 8, y - 2, x + 10, y + 2, PALE_GREEN); - g.fill(x + 6, y + 2, x + 8, y + 7, PALE_GREEN); - g.fill(x + 3, y + 8, x + 5, y + 12, PALE_GREEN); + // Squared palette with a true thumb notch and a distinct, outlined brush. + g.fill(x - 11, y + 2, x + 8, y + 20, BLACK); + g.fill(x - 8, y + 5, x + 5, y + 17, GREEN); + g.fill(x - 11, y + 5, x - 8, y + 7, GREEN); + g.fill(x - 6, y + 8, x - 3, y + 11, BLACK); + g.fill(x, y + 7, x + 3, y + 10, BLACK); + g.fill(x - 1, y + 13, x + 2, y + 16, BLACK); + g.fill(x + 6, y - 3, x + 11, y + 1, BLACK); + g.fill(x + 4, y, x + 9, y + 6, BLACK); + g.fill(x + 2, y + 5, x + 7, y + 11, BLACK); + g.fill(x, y + 10, x + 5, y + 16, BLACK); + g.fill(x + 7, y - 1, x + 9, y + 2, PALE_GREEN); + g.fill(x + 5, y + 3, x + 7, y + 7, PALE_GREEN); + g.fill(x + 3, y + 8, x + 5, y + 11, PALE_GREEN); return; } if (type.equals("ANTMAIL")) { - g.fill(x - 12, y + 2, x + 12, y + 17, BLACK); - g.fill(x - 9, y + 5, x + 9, y + 14, GREEN); - // Sealed envelope: the stepped black V is the closed flap. - g.fill(x - 8, y + 5, x - 5, y + 7, BLACK); - g.fill(x + 5, y + 5, x + 8, y + 7, BLACK); - g.fill(x - 6, y + 7, x - 3, y + 9, BLACK); - g.fill(x + 3, y + 7, x + 6, y + 9, BLACK); - g.fill(x - 4, y + 9, x - 1, y + 11, BLACK); - g.fill(x + 1, y + 9, x + 4, y + 11, BLACK); - g.fill(x - 1, y + 11, x + 2, y + 13, BLACK); + g.fill(x - 12, y + 1, x + 12, y + 18, BLACK); + g.fill(x - 9, y + 4, x + 9, y + 15, GREEN); + // Envelope flap and two symmetric folded corners remain legible at icon scale. + g.fill(x - 9, y + 4, x + 9, y + 6, BLACK); + g.fill(x - 7, y + 6, x - 4, y + 8, BLACK); + g.fill(x + 4, y + 6, x + 7, y + 8, BLACK); + g.fill(x - 4, y + 8, x + 4, y + 10, BLACK); + g.fill(x - 9, y + 11, x - 6, y + 13, BLACK); + g.fill(x + 6, y + 11, x + 9, y + 13, BLACK); + g.fill(x - 6, y + 13, x - 3, y + 15, BLACK); + g.fill(x + 3, y + 13, x + 6, y + 15, BLACK); return; } if (type.equals("GAMES")) { - // Simple rectangular retro controller, sized to match the other icons. - g.fill(x - 12, y + 3, x + 12, y + 18, BLACK); - g.fill(x - 9, y + 6, x + 9, y + 15, GREEN); - // Controls only: one pixel down and one pixel left; the shell stays put. - g.fill(x - 7, y + 9, x - 1, y + 11, BLACK); - g.fill(x - 5, y + 7, x - 3, y + 13, BLACK); - g.fill(x + 3, y + 8, x + 5, y + 10, BLACK); - g.fill(x + 6, y + 11, x + 8, y + 13, BLACK); + // Pixel controller with tapered grips, a bold D-pad and paired buttons. + g.fill(x - 7, y + 2, x + 7, y + 5, BLACK); + g.fill(x - 11, y + 5, x + 11, y + 15, BLACK); + g.fill(x - 9, y + 15, x - 5, y + 19, BLACK); + g.fill(x + 5, y + 15, x + 9, y + 19, BLACK); + g.fill(x - 8, y + 5, x + 8, y + 13, GREEN); + g.fill(x - 7, y + 8, x - 1, y + 10, BLACK); + g.fill(x - 5, y + 6, x - 3, y + 12, BLACK); + g.fill(x + 3, y + 7, x + 5, y + 9, BLACK); + g.fill(x + 6, y + 10, x + 8, y + 12, BLACK); return; } if (type.equals("TRASH")) { - // Paint the complete black silhouette first so every green panel sits above it. - g.fill(x - 11, y + 2, x + 11, y + 5, BLACK); - g.fill(x - 8, y + 5, x + 8, y + 20, BLACK); - g.fill(x - 5, y - 1, x + 5, y + 2, BLACK); - g.fill(x - 5, y + 8, x + 5, y + 17, GREEN); - g.fill(x - 8, y + 5, x + 8, y + 7, GREEN); - g.fill(x - 3, y, x + 3, y + 1, GREEN); + g.fill(x - 8, y + 3, x + 8, y + 20, BLACK); + g.fill(x - 11, y + 1, x + 11, y + 4, BLACK); + g.fill(x - 5, y - 1, x + 5, y + 1, BLACK); + g.fill(x - 6, y + 4, x + 6, y + 18, GREEN); + g.fill(x - 3, y + 7, x - 1, y + 16, BLACK); + g.fill(x + 2, y + 7, x + 4, y + 16, BLACK); return; } g.fill(x - 10, y, x + 10, y + 18, BLACK); g.fill(x - 8, y + 2, x + 8, y + 16, GREEN); } - private void renderWindow(GuiGraphics g, Window window, int l, int t, int mouseX, int mouseY) { + private void renderWindow(GuiGraphics g, Window window, int l, int t, int mouseX, int mouseY, int layer) { + g.flush(); + g.pose().pushPose(); + g.pose().translate(0.0F, 0.0F, layer * 10.0F); + try { + renderWindowLayer(g, window, l, t, mouseX, mouseY); + } finally { + g.flush(); + g.pose().popPose(); + } + } + + private void renderWindowLayer(GuiGraphics g, Window window, int l, int t, int mouseX, int mouseY) { if (!window.maximized) { window.x = Math.max(12, Math.min(WIDTH - window.width - 12, window.x)); window.y = Math.max(34, Math.min(HEIGHT - window.height - 12, window.y)); - } else { - window.x = 14; - window.y = 34; } - int x = l + window.x; - int y = t + window.y; - int w = window.maximized ? WIDTH - 28 : window.width; - int h = window.maximized ? HEIGHT - 48 : window.height; + int x = l + windowLocalX(window); + int y = t + windowLocalY(window); + int w = windowWidth(window); + int h = windowHeight(window); window.renderWidth = w; window.renderHeight = h; g.fill(x - 2, y - 2, x + w + 2, y + h + 2, GREEN); g.fill(x, y, x + w, y + h, BLACK); - g.fill(x, y, x + w, y + 19, 0xFF173817); - g.drawString(font, Component.literal(trimToWidth(window.title, Math.max(1, w - 78))), x + 7, y + 6, GREEN, false); - g.drawString(font, Component.literal("_"), x + w - 39, y + 5, PALE_GREEN, false); - g.drawString(font, Component.literal(window.maximized ? "❐" : "□"), x + w - 27, y + 5, PALE_GREEN, false); - g.drawString(font, Component.literal("X"), x + w - 13, y + 6, GREEN, false); + g.fill(x, y, x + w, y + TITLE_BAR_HEIGHT, 0xFF173817); + int controlsLeft = x + w - WINDOW_CONTROL_WIDTH * WINDOW_CONTROL_COUNT; + g.drawString(font, Component.literal(trimToWidth(window.title, Math.max(1, controlsLeft - x - 12))), x + 7, y + 6, GREEN, false); + g.fill(controlsLeft, y, x + w, y + TITLE_BAR_HEIGHT, 0xFF173817); + g.fill(controlsLeft, y, controlsLeft + 1, y + TITLE_BAR_HEIGHT, GREEN); + g.fill(controlsLeft + WINDOW_CONTROL_WIDTH, y, controlsLeft + WINDOW_CONTROL_WIDTH + 1, y + TITLE_BAR_HEIGHT, GREEN); + g.fill(controlsLeft + WINDOW_CONTROL_WIDTH * 2, y, controlsLeft + WINDOW_CONTROL_WIDTH * 2 + 1, y + TITLE_BAR_HEIGHT, GREEN); + g.drawCenteredString(font, Component.literal("_"), controlsLeft + WINDOW_CONTROL_WIDTH / 2, y + 5, PALE_GREEN); + g.drawCenteredString(font, Component.literal(window.maximized ? "❐" : "□"), controlsLeft + WINDOW_CONTROL_WIDTH + WINDOW_CONTROL_WIDTH / 2, y + 5, PALE_GREEN); + g.drawCenteredString(font, Component.literal("X"), controlsLeft + WINDOW_CONTROL_WIDTH * 2 + WINDOW_CONTROL_WIDTH / 2, y + 6, GREEN); int cx = x + 8; int cy = y + 28; - if (window.type.equals("ARCHIVE")) renderArchive(g, cx, cy, w - 16, h - 34); - else if (window.type.equals("FILES")) renderFileExplorer(g, cx, cy, w - 16, h - 34); - else if (window.type.equals("SETTINGS")) renderSettings(g, cx, cy, h - 34); - else if (window.type.equals("TERMINAL")) renderTerminal(g, cx, cy, h - 34); - else if (window.type.equals("TEXT")) renderTextEditor(g, cx, cy, w - 16, h - 34); - else if (window.type.equals("PAINT")) renderPaint(g, cx, cy, w - 16, h - 34); - else if (window.type.equals("ANTMAIL")) renderAntmail(g, cx, cy, h - 34); - else if (window.type.equals("GAMES")) renderGames(g, window, cx, cy, mouseX, mouseY); - else renderTrash(g, cx, cy); + enableComputerScissor(g, x + 1, y + TITLE_BAR_HEIGHT, x + w - 1, y + h - 1); + try { + if (window.type.equals("ARCHIVE")) renderArchive(g, cx, cy, w - 16, h - 34, window.maximized); + else if (window.type.equals("FILES")) renderFileExplorer(g, cx, cy, w - 16, h - 34); + else if (window.type.equals("SETTINGS")) renderSettings(g, cx, cy, h - 34); + else if (window.type.equals("TERMINAL")) renderTerminal(g, cx, cy, h - 34); + else if (window.type.equals("TEXT")) renderTextEditor(g, cx, cy, w - 16, h - 34); + else if (window.type.equals("PAINT")) renderPaint(g, cx, cy, w - 16, h - 34); + else if (window.type.equals("ANTMAIL")) renderAntmail(g, cx, cy, h - 34); + else if (window.type.equals("GAMES")) renderGames(g, window, cx, cy, mouseX, mouseY); + else renderTrash(g, cx, cy); + } finally { + g.disableScissor(); + } } - private void renderArchive(GuiGraphics g, int x, int y, int w, int h) { + private void enableComputerScissor(GuiGraphics g, int left, int top, int right, int bottom) { + float scale = uiScale(); + int screenLeft = (int) Math.floor(width / 2.0F + left * scale); + int screenTop = (int) Math.floor(height / 2.0F + top * scale); + int screenRight = (int) Math.ceil(width / 2.0F + right * scale); + int screenBottom = (int) Math.ceil(height / 2.0F + bottom * scale); + g.enableScissor(screenLeft, screenTop, screenRight, screenBottom); + } + + private static int windowLocalX(Window window) { return window.maximized ? 14 : window.x; } + private static int windowLocalY(Window window) { return window.maximized ? 34 : window.y; } + private static int windowWidth(Window window) { return window.maximized ? WIDTH - 28 : window.width; } + private static int windowHeight(Window window) { return window.maximized ? HEIGHT - 48 : window.height; } + + private static void toggleMaximized(Window window) { + if (window.maximized) { + window.maximized = false; + window.x = window.restoreX; + window.y = window.restoreY; + } else { + window.restoreX = window.x; + window.restoreY = window.y; + window.maximized = true; + } + } + + private void renderArchive(GuiGraphics g, int x, int y, int w, int h, boolean maximized) { List entries = List.of(); if (Minecraft.getInstance().level != null && Minecraft.getInstance().level.getBlockEntity(position) instanceof ComputerBlockEntity computer) entries = ComputerGuideData.entriesFor(computer.diskIds()); @@ -527,19 +660,22 @@ public final class ComputerScreen extends Screen { if (selected != null) { g.drawString(font, Component.literal("< BACK // RECOVERED FILES"), x, y, GREEN, false); g.fill(x, y + 16, x + w, y + 18, GREEN); - g.drawString(font, Component.translatable(selected.titleKey()), x, y + 27, GREEN, false); + g.drawString(font, Component.literal(trimToWidth(Component.translatable(selected.titleKey()).getString(), w)), x, y + 27, GREEN, false); int contentBottom = y + h - 20; - g.enableScissor(x, y + 36, x + w, contentBottom); + enableComputerScissor(g, x, y + 36, x + w, contentBottom); int line = y + 43 - session.archiveDetailScroll * 11; - if (!selected.subtitleKey().isBlank()) line = wrap(g, Component.translatable(selected.subtitleKey()), x, line, w, PALE_GREEN) + 3; line = wrap(g, selected.type().toUpperCase() + " // " + selected.category().toUpperCase(), x, line, w, PALE_GREEN) + 5; - if (!selected.entityId().isBlank()) line = renderArchivePreview(g, "", selected.entityId(), x, line, w) + 2; + if (!selected.entityId().isBlank()) line = renderArchivePreview(g, "", selected.entityId(), "", x, line, w, selected.rotation(), selected.renderScale()) + 2; for (String descriptionPart : selected.descriptionKeys()) { line = renderArchiveDescriptionPart(g, descriptionPart, x, line, w); } - if (!selected.itemId().isBlank()) line = renderArchivePreview(g, selected.itemId(), "", x, line, w) + 2; + if (!selected.itemId().isBlank()) line = renderArchivePreview(g, selected.itemId(), "", "", x, line, w) + 2; + if (!selected.enchantmentId().isBlank()) line = renderArchivePreview(g, "", "", selected.enchantmentId(), x, line, w) + 2; if (!selected.recipeId().isBlank()) line = renderArchiveRecipe(g, selected.recipeId(), x, line, w) + 2; - if (!selected.structureId().isBlank()) line = wrap(g, "STRUCTURE // " + selected.structureId(), x, line, w, PALE_GREEN) + 2; + if (!selected.structureId().isBlank()) { + line = wrap(g, "STRUCTURE // " + selected.structureId(), x, line, w, PALE_GREEN) + 2; + line = wrap(g, com.craisinlord.antarchy.content.client.ComputerStructureLocatorClientState.get(position), x, line, w, GREEN) + 2; + } if (!selected.dimensionId().isBlank()) line = wrap(g, "DIMENSION // " + selected.dimensionId(), x, line, w, PALE_GREEN); g.disableScissor(); session.archiveDetailMaxScroll = Math.max(0, (line + session.archiveDetailScroll * 11 - contentBottom + 10) / 11); @@ -553,7 +689,7 @@ public final class ComputerScreen extends Screen { g.drawString(font, Component.literal("RECOVERED FILES // " + filteredEntries.size() + "/" + entries.size()), x, y, GREEN, false); g.fill(x, y + 14, x + w - 8, y + 35, BLACK); box(g, x, y + 14, x + w - 8, y + 35, session.archiveSearchFocused ? GREEN : PALE_GREEN); - String searchText = session.archiveSearch.isBlank() && !session.archiveSearchFocused ? "SEARCH TITLE / SUBTITLE" : session.archiveSearch; + String searchText = session.archiveSearch.isBlank() && !session.archiveSearchFocused ? "SEARCH TITLE" : session.archiveSearch; g.drawString(font, Component.literal(trimToWidth(searchText + (session.archiveSearchFocused ? "_" : ""), w - 16)), x + 6, y + 20, session.archiveSearch.isBlank() ? PALE_GREEN : GREEN, false); if (entries.isEmpty()) { @@ -564,45 +700,62 @@ public final class ComputerScreen extends Screen { g.drawString(font, Component.literal("NO MATCHING ENTRIES"), x, y + 52, PALE_GREEN, false); return; } - int row = y + 40; - int visible = Math.max(1, (h - 56) / 62); - int start = Math.max(0, Math.min(session.archiveScroll, Math.max(0, filteredEntries.size() - visible))); - for (int i = start; i < filteredEntries.size() && i < start + visible; i++) { - ComputerGuideData.Entry entry = filteredEntries.get(i); - int rowTop = row + (i - start) * 62; - boolean hover = inside(x, rowTop, w - 8, 56, session.archiveMouseX, session.archiveMouseY); - if (hover) g.fill(x, rowTop, x + w - 8, rowTop + 56, 0xFF173817); - g.fill(x + 3, rowTop + 3, x + 51, rowTop + 51, BLACK); - renderThumbnail(g, entry, x + 27, rowTop + 27, hover); - String title = trimToWidth(Component.translatable(entry.titleKey()).getString(), w - 78); - g.drawString(font, Component.literal(title), x + 58, rowTop + 9, GREEN, false); - if (!entry.subtitleKey().isBlank()) g.drawString(font, Component.literal(trimToWidth(Component.translatable(entry.subtitleKey()).getString(), w - 78)), x + 58, rowTop + 23, PALE_GREEN, false); - g.drawString(font, Component.literal(entry.type().toUpperCase() + " // " + entry.category().toUpperCase()), x + 58, rowTop + 39, PALE_GREEN, false); + int columns = maximized ? 3 : 1; + int cellHeight = maximized ? 72 : 38; + int thumbnailSize = maximized ? 44 : 28; + int listTop = y + 40; + int visibleRows = Math.max(1, (h - 58) / cellHeight); + int totalRows = (filteredEntries.size() + columns - 1) / columns; + int startRow = Math.max(0, Math.min(session.archiveScroll, Math.max(0, totalRows - visibleRows))); + for (int rowIndex = startRow; rowIndex < totalRows && rowIndex < startRow + visibleRows; rowIndex++) { + int rowTop = listTop + (rowIndex - startRow) * cellHeight; + for (int column = 0; column < columns; column++) { + int index = rowIndex * columns + column; + if (index >= filteredEntries.size()) break; + ComputerGuideData.Entry entry = filteredEntries.get(index); + int cellWidth = (w - 8) / columns; + int cellX = x + column * cellWidth; + int thumbX = maximized ? cellX + (cellWidth - thumbnailSize) / 2 : cellX + 3; + int thumbY = maximized ? rowTop + 2 : rowTop + 4; + int textX = maximized ? cellX + 3 : cellX + thumbnailSize + 10; + int textY = maximized ? rowTop + thumbnailSize + 5 : rowTop + 7; + boolean hover = inside(cellX, rowTop, cellWidth - 2, cellHeight - 2, session.archiveMouseX, session.archiveMouseY); + if (hover) g.fill(cellX, rowTop, cellX + cellWidth - 2, rowTop + cellHeight - 2, 0xFF173817); + g.fill(thumbX, thumbY, thumbX + thumbnailSize, thumbY + thumbnailSize, BLACK); + renderThumbnail(g, entry, thumbX + thumbnailSize / 2, thumbY + thumbnailSize / 2, hover, thumbnailSize); + int textWidth = maximized ? cellWidth - 8 : cellWidth - thumbnailSize - 12; + g.drawString(font, Component.literal(trimToWidth(Component.translatable(entry.titleKey()).getString(), textWidth)), textX, textY, GREEN, false); + g.drawString(font, Component.literal(trimToWidth(entry.category().toUpperCase(), textWidth)), textX, textY + 12, PALE_GREEN, false); + } } - if (filteredEntries.size() > visible) g.drawString(font, Component.literal("SCROLL " + (start + 1) + "-" + Math.min(filteredEntries.size(), start + visible) + " / " + filteredEntries.size()), x, y + h - 12, PALE_GREEN, false); + int shownStart = startRow * columns; + int shownEnd = Math.min(filteredEntries.size(), (startRow + visibleRows) * columns); + if (totalRows > visibleRows) g.drawString(font, Component.literal("SCROLL " + (shownStart + 1) + "-" + shownEnd + " / " + filteredEntries.size()), x, y + h - 12, PALE_GREEN, false); else g.drawString(font, Component.literal("[ CLICK ENTRY TO OPEN ]"), x, y + h - 12, PALE_GREEN, false); } private List filterArchiveEntries(List entries) { String query = session.archiveSearch.trim().toLowerCase(Locale.ROOT); if (query.isEmpty()) return entries; - return entries.stream().filter(entry -> Component.translatable(entry.titleKey()).getString().toLowerCase(Locale.ROOT).contains(query) - || (!entry.subtitleKey().isBlank() && Component.translatable(entry.subtitleKey()).getString().toLowerCase(Locale.ROOT).contains(query))).toList(); + return entries.stream().filter(entry -> Component.translatable(entry.titleKey()).getString().toLowerCase(Locale.ROOT).contains(query)).toList(); } - private void renderThumbnail(GuiGraphics g, ComputerGuideData.Entry entry, int centerX, int centerY, boolean hover) { + private void renderThumbnail(GuiGraphics g, ComputerGuideData.Entry entry, int centerX, int centerY, boolean hover, int size) { int background = hover ? 0xFF173817 : 0xFF102010; - g.fill(centerX - 18, centerY - 18, centerX + 18, centerY + 18, background); - if (!entry.recipeId().isBlank() && renderArchiveRecipe(g, entry.recipeId(), centerX - 18, centerY - 12, 36, 8)) return; - if (!renderArchiveAsset(g, entry.itemId(), entry.entityId(), centerX, centerY, 36)) { - String fallback = !entry.itemId().isBlank() ? entry.itemId() : entry.entityId(); - g.drawString(font, Component.literal(trimToWidth(fallback.isBlank() ? "NO RENDER" : fallback, 32)), centerX - 16, centerY - 4, hover ? GREEN : PALE_GREEN, false); + g.fill(centerX - size / 2, centerY - size / 2, centerX + size / 2, centerY + size / 2, background); + if (!entry.recipeId().isBlank() && renderArchiveRecipe(g, entry.recipeId(), centerX - size / 2, centerY - size / 2 + size / 5, size, Math.max(6, size / 4))) return; + String itemId = entry.coverItemId().isBlank() ? entry.itemId() : entry.coverItemId(); + String entityId = entry.coverEntityId().isBlank() ? entry.entityId() : entry.coverEntityId(); + if (!renderArchiveAsset(g, itemId, entityId, entry.enchantmentId(), entry.coverPotionId(), centerX, centerY, size, entry.rotation(), entry.renderScale())) { + String fallback = !itemId.isBlank() ? itemId : !entry.enchantmentId().isBlank() ? entry.enchantmentId() : entityId; + g.drawString(font, Component.literal(trimToWidth(fallback.isBlank() ? "NO RENDER" : fallback, size - 4)), centerX - size / 2 + 2, centerY - 4, hover ? GREEN : PALE_GREEN, false); } } private int renderArchiveDescriptionPart(GuiGraphics g, String part, int x, int y, int w) { - if (part.startsWith("@item:")) return renderArchivePreview(g, part.substring(6), "", x, y, w) + 2; - if (part.startsWith("@entity:")) return renderArchivePreview(g, "", part.substring(8), x, y, w) + 2; + if (part.startsWith("@item:")) return renderArchivePreview(g, part.substring(6), "", "", x, y, w) + 2; + if (part.startsWith("@entity:")) return renderArchivePreview(g, "", part.substring(8), "", x, y, w) + 2; + if (part.startsWith("@enchantment:")) return renderArchivePreview(g, "", "", part.substring(13), x, y, w) + 2; if (part.startsWith("@recipe:")) return renderArchiveRecipe(g, part.substring(8), x, y, w) + 2; return wrap(g, Component.translatable(part), x, y, w, GREEN) + 3; } @@ -619,50 +772,85 @@ public final class ComputerScreen extends Screen { private boolean renderArchiveRecipe(GuiGraphics g, String recipeId, int x, int y, int width, int slot) { if (Minecraft.getInstance().level == null) return false; try { - var holder = Minecraft.getInstance().level.getRecipeManager().byKey(ResourceLocation.parse(recipeId)).orElse(null); + var recipeManager = Minecraft.getInstance().level.getRecipeManager(); + ResourceLocation requestedId = ResourceLocation.parse(recipeId); + RecipeHolder holder = recipeManager.byKey(requestedId).orElse(null); + if (holder == null) { + var requestedItem = BuiltInRegistries.ITEM.getOptional(requestedId).orElse(null); + if (requestedItem != null && requestedItem != Items.AIR) { + holder = recipeManager.getRecipes().stream() + .filter(candidate -> candidate.value().getResultItem(Minecraft.getInstance().level.registryAccess()).is(requestedItem)) + .findFirst().orElse(null); + } + } if (holder == null) return false; var recipe = holder.value(); List ingredients = recipe.getIngredients(); - int gridWidth = slot * 3; - int outputX = x + gridWidth + 12; - int outputY = y + slot; + int actualSlot = Math.min(slot, Math.max(4, (width - 8) / 4)); + int gridWidth = actualSlot * 3; + int outputX = x + gridWidth + 8; + int outputY = y + actualSlot; + int shapedWidth = recipe instanceof ShapedRecipe shaped ? shaped.getWidth() : 3; + int shapedHeight = recipe instanceof ShapedRecipe shaped ? shaped.getHeight() : Math.min(3, (ingredients.size() + 2) / 3); for (int index = 0; index < 9; index++) { - int cellX = x + (index % 3) * slot; - int cellY = y + (index / 3) * slot; - g.fill(cellX, cellY, cellX + slot - 1, cellY + slot - 1, 0xFF173817); - if (index < ingredients.size()) { - ItemStack[] choices = ingredients.get(index).getItems(); - if (choices.length > 0) g.renderItem(choices[0], cellX + (slot - 16) / 2, cellY + (slot - 16) / 2); + int column = index % 3; + int row = index / 3; + int cellX = x + column * actualSlot; + int cellY = y + row * actualSlot; + g.fill(cellX, cellY, cellX + actualSlot - 1, cellY + actualSlot - 1, 0xFF173817); + int ingredientIndex = recipe instanceof ShapedRecipe + ? (column < shapedWidth && row < shapedHeight ? row * shapedWidth + column : -1) + : index; + if (ingredientIndex >= 0 && ingredientIndex < ingredients.size()) { + ItemStack[] choices = ingredients.get(ingredientIndex).getItems(); + if (choices.length > 0) renderArchiveItem(g, choices[0], cellX, cellY, actualSlot); } } - g.fill(x + gridWidth + 3, y + slot + 2, x + gridWidth + 9, y + slot + 4, PALE_GREEN); - g.fill(x + gridWidth + 7, y + slot, x + gridWidth + 9, y + slot + 6, PALE_GREEN); + g.fill(x + gridWidth + 2, y + actualSlot + actualSlot / 2 - 1, x + gridWidth + 6, y + actualSlot + actualSlot / 2 + 1, PALE_GREEN); + g.fill(x + gridWidth + 5, y + actualSlot + actualSlot / 2 - 3, x + gridWidth + 7, y + actualSlot + actualSlot / 2 + 3, PALE_GREEN); ItemStack result = recipe.getResultItem(Minecraft.getInstance().level.registryAccess()); if (!result.isEmpty()) { - g.fill(outputX, outputY, outputX + slot - 1, outputY + slot - 1, 0xFF173817); - g.renderItem(result, outputX + (slot - 16) / 2, outputY + (slot - 16) / 2); + g.fill(outputX, outputY, outputX + actualSlot - 1, outputY + actualSlot - 1, 0xFF173817); + renderArchiveItem(g, result, outputX, outputY, actualSlot); } return true; - } catch (RuntimeException ignored) { + } catch (RuntimeException exception) { + logArchivePreviewOnce("recipe.error." + recipeId, "Archive recipe preview failed id={} error={}", recipeId, exception.toString()); return false; } } - private int renderArchivePreview(GuiGraphics g, String itemId, String entityId, int x, int y, int w) { - int boxWidth = Math.min(48, w); - g.fill(x, y, x + boxWidth, y + 48, 0xFF102010); - int centerX = x + boxWidth / 2; - int centerY = y + 24; - if (!renderArchiveAsset(g, itemId, entityId, centerX, centerY, boxWidth)) { - String fallback = !itemId.isBlank() ? itemId : entityId; - g.drawString(font, Component.literal(trimToWidth(fallback, Math.max(1, w - boxWidth - 6))), x + boxWidth + 6, y + 18, PALE_GREEN, false); - } else if (!itemId.isBlank() || !entityId.isBlank()) { - g.drawString(font, Component.literal(trimToWidth(!itemId.isBlank() ? "ITEM // " + itemId : "MOB // " + entityId, Math.max(1, w - boxWidth - 6))), x + boxWidth + 6, y + 18, PALE_GREEN, false); - } - return y + 48; + private int renderArchivePreview(GuiGraphics g, String itemId, String entityId, String enchantmentId, int x, int y, int w) { + return renderArchivePreview(g, itemId, entityId, enchantmentId, x, y, w, 0.0F, 1.0F); } - private boolean renderArchiveAsset(GuiGraphics g, String itemId, String entityId, int centerX, int centerY, int size) { + private int renderArchivePreview(GuiGraphics g, String itemId, String entityId, String enchantmentId, int x, int y, int w, float rotation) { + return renderArchivePreview(g, itemId, entityId, enchantmentId, x, y, w, rotation, 1.0F); + } + + private int renderArchivePreview(GuiGraphics g, String itemId, String entityId, String enchantmentId, int x, int y, int w, float rotation, float renderScale) { + int previewSize = entityId.isBlank() ? 48 : 96; + int boxWidth = Math.min(previewSize, w); + g.fill(x, y, x + boxWidth, y + previewSize, 0xFF102010); + int centerX = x + boxWidth / 2; + int centerY = y + previewSize / 2; + if (!renderArchiveAsset(g, itemId, entityId, enchantmentId, "", centerX, centerY, boxWidth, rotation, renderScale)) { + String fallback = !itemId.isBlank() ? itemId : !enchantmentId.isBlank() ? enchantmentId : entityId; + g.drawString(font, Component.literal(trimToWidth(fallback, Math.max(1, w - boxWidth - 6))), x + boxWidth + 6, y + previewSize / 2 - 4, PALE_GREEN, false); + } else if (!itemId.isBlank() || !entityId.isBlank() || !enchantmentId.isBlank()) { + String label = !itemId.isBlank() ? "ITEM // " + itemId + : !enchantmentId.isBlank() ? "ENCHANTMENT // " + enchantmentId : "MOB // " + archiveMobName(entityId); + g.drawString(font, Component.literal(trimToWidth(label, Math.max(1, w - boxWidth - 6))), x + boxWidth + 6, y + previewSize / 2 - 4, PALE_GREEN, false); + } + return y + previewSize; + } + + private String archiveMobName(String entityId) { + int separator = entityId.indexOf(':'); + return (separator >= 0 ? entityId.substring(separator + 1) : entityId).replace('_', ' ').toUpperCase(Locale.ROOT); + } + + private boolean renderArchiveAsset(GuiGraphics g, String itemId, String entityId, String enchantmentId, String potionId, int centerX, int centerY, int size, float rotation, float renderScale) { if (!itemId.isBlank()) { try { ResourceLocation resourceId = ResourceLocation.parse(itemId); @@ -670,7 +858,8 @@ public final class ComputerScreen extends Screen { logArchivePreviewOnce("item.lookup." + itemId, "Archive item preview lookup id={} found={} registryName={}", itemId, item != null, item == null ? "" : BuiltInRegistries.ITEM.getKey(item)); if (item != null && !item.equals(net.minecraft.world.item.Items.AIR)) { ItemStack stack = new ItemStack(item); - g.renderItem(stack, centerX - 8, centerY - 8); + int iconSize = Math.min(32, Math.max(16, size - 4)); + renderArchiveItem(g, stack, centerX - iconSize / 2, centerY - iconSize / 2, iconSize); logArchivePreviewOnce("item.render." + itemId, "Archive item preview rendered id={} descriptionId={} center=({}, {})", itemId, stack.getDescriptionId(), centerX, centerY); return true; } @@ -679,6 +868,30 @@ public final class ComputerScreen extends Screen { logArchivePreviewOnce("item.error." + itemId, "Archive item preview failed id={} error={}", itemId, exception.toString()); } } + if (!enchantmentId.isBlank()) { + ItemStack stack = createArchiveEnchantedBook(enchantmentId); + if (!stack.isEmpty()) { + int iconSize = Math.min(32, Math.max(16, size - 4)); + renderArchiveItem(g, stack, centerX - iconSize / 2, centerY - iconSize / 2, iconSize); + logArchivePreviewOnce("enchantment.render." + enchantmentId, "Archive enchantment preview rendered id={}", enchantmentId); + return true; + } + logArchivePreviewOnce("enchantment.unrenderable." + enchantmentId, "Archive enchantment preview unavailable id={}", enchantmentId); + } + if (!potionId.isBlank() && Minecraft.getInstance().level != null) { + try { + ResourceLocation potionLocation = ResourceLocation.parse(potionId); + ResourceKey potionKey = ResourceKey.create(Registries.POTION, potionLocation); + var potion = Minecraft.getInstance().level.registryAccess().lookupOrThrow(Registries.POTION).get(potionKey).orElse(null); + if (potion != null) { + int iconSize = Math.min(32, Math.max(16, size - 4)); + renderArchiveItem(g, PotionContents.createItemStack(Items.POTION, potion), centerX - iconSize / 2, centerY - iconSize / 2, iconSize); + return true; + } + } catch (RuntimeException exception) { + logArchivePreviewOnce("potion.error." + potionId, "Archive potion preview failed id={} error={}", potionId, exception.toString()); + } + } if (!entityId.isBlank() && Minecraft.getInstance().level == null) { logArchivePreviewOnce("entity.no_level." + entityId, "Archive mob preview skipped id={} because the client level is null", entityId); } @@ -687,17 +900,49 @@ public final class ComputerScreen extends Screen { ResourceLocation resourceId = ResourceLocation.parse(entityId); var entityType = BuiltInRegistries.ENTITY_TYPE.getOptional(resourceId).orElse(null); logArchivePreviewOnce("entity.lookup." + entityId, "Archive mob preview lookup id={} found={} registryName={}", entityId, entityType != null, entityType == null ? "" : BuiltInRegistries.ENTITY_TYPE.getKey(entityType)); - var entity = entityType == null ? null : entityType.create(Minecraft.getInstance().level); - if (entity instanceof LivingEntity living && !living.isRemoved()) { + LivingEntity living = archiveEntityPreviews.get(entityId); + if (living == null && entityType != null && !unavailableArchiveEntities.contains(entityId)) { + var entity = entityType.create(Minecraft.getInstance().level); + if (entity instanceof LivingEntity created && !created.isRemoved()) { + living = created; + archiveEntityPreviews.put(entityId, created); + } else { + unavailableArchiveEntities.add(entityId); + logArchivePreviewOnce("entity.unrenderable." + entityId, "Archive mob preview unavailable id={} createdClass={} isLiving={}", entityId, entity == null ? "" : entity.getClass().getName(), entity instanceof LivingEntity); + } + } + if (living != null && !living.isRemoved()) { + if (living instanceof NightmareEntity nightmare) nightmare.setArchivePreviewFlying(); living.setPos(0.0D, 0.0D, 0.0D); - living.setYRot(180.0F); - living.yBodyRot = 180.0F; - living.yHeadRot = 180.0F; - InventoryScreen.renderEntityInInventoryFollowsMouse(g, centerX - size / 2, centerY - size / 2, centerX + size / 2, centerY + size / 2, Math.max(18, size / 2), 0.15F, centerX, centerY, living); + float yaw = 180.0F + rotation; + living.setYRot(yaw); + living.yRotO = yaw; + living.yBodyRot = yaw; + living.yBodyRotO = yaw; + living.yHeadRot = yaw; + living.yHeadRotO = yaw; + living.tickCount = (int) (Minecraft.getInstance().level.getGameTime() & Integer.MAX_VALUE); + float largestDimension = Math.max(0.45F, Math.max(living.getBbWidth(), living.getBbHeight())); + // Leave room for wide silhouettes and configured yaw rotations (notably the flying squirrel). + int entityRenderScale = Math.max(4, Math.min(size, Math.round(size * 0.52F * renderScale / largestDimension))); + float entityScale = Math.max(0.01F, living.getScale()); + Vector3f translation = new Vector3f(0.0F, living.getBbHeight() / 2.0F + 0.0625F * entityScale, 0.0F); + Quaternionf pose = new Quaternionf().rotateZ((float) Math.PI); + Quaternionf camera = new Quaternionf(); + g.flush(); + enableComputerScissor(g, centerX - size / 2, centerY - size / 2, centerX + size / 2, centerY + size / 2); + g.setColor(0.0F, 1.0F, 0.0F, 1.0F); + try { + InventoryScreen.renderEntityInInventory(g, centerX, centerY, entityRenderScale / entityScale, + translation, pose, camera, living); + g.flush(); + } finally { + g.setColor(1.0F, 1.0F, 1.0F, 1.0F); + g.disableScissor(); + } logArchivePreviewOnce("entity.render." + entityId, "Archive mob preview rendered id={} class={} size={} center=({}, {})", entityId, living.getClass().getName(), size, centerX, centerY); return true; } - logArchivePreviewOnce("entity.unrenderable." + entityId, "Archive mob preview unavailable id={} createdClass={} isLiving={}", entityId, entity == null ? "" : entity.getClass().getName(), entity instanceof LivingEntity); } catch (RuntimeException exception) { logArchivePreviewOnce("entity.error." + entityId, "Archive mob preview failed id={} error={}", entityId, exception.toString()); } @@ -705,6 +950,45 @@ public final class ComputerScreen extends Screen { return false; } + private void renderArchiveItem(GuiGraphics g, ItemStack stack, int x, int y) { + renderArchiveItem(g, stack, x, y, 16); + } + + private void renderArchiveItem(GuiGraphics g, ItemStack stack, int x, int y, int size) { + g.flush(); + // Keep only the green channel, matching the archive mob treatment and preventing source hues from bleeding through. + g.setColor(0.0F, 1.0F, 0.0F, 1.0F); + g.pose().pushPose(); + try { + g.pose().translate(x, y, 0.0F); + float scale = size / 16.0F; + g.pose().scale(scale, scale, 1.0F); + g.renderItem(stack, 0, 0); + g.flush(); + } finally { + g.pose().popPose(); + g.setColor(1.0F, 1.0F, 1.0F, 1.0F); + } + } + + private ItemStack createArchiveEnchantedBook(String enchantmentId) { + if (Minecraft.getInstance().level == null) return ItemStack.EMPTY; + try { + ResourceLocation id = ResourceLocation.parse(enchantmentId); + ResourceKey key = ResourceKey.create(Registries.ENCHANTMENT, id); + var holder = Minecraft.getInstance().level.registryAccess().lookupOrThrow(Registries.ENCHANTMENT).get(key).orElse(null); + if (holder == null) return ItemStack.EMPTY; + ItemEnchantments.Mutable enchantments = new ItemEnchantments.Mutable(ItemEnchantments.EMPTY); + enchantments.set(holder, 1); + ItemStack stack = new ItemStack(Items.ENCHANTED_BOOK); + stack.set(DataComponents.STORED_ENCHANTMENTS, enchantments.toImmutable().withTooltip(true)); + return stack; + } catch (RuntimeException exception) { + logArchivePreviewOnce("enchantment.error." + enchantmentId, "Archive enchantment preview failed id={} error={}", enchantmentId, exception.toString()); + return ItemStack.EMPTY; + } + } + private void logArchivePreviewOnce(String key, String message, Object... arguments) { if (archiveRenderLog.add(key)) Antarchy.LOGGER.info(message, arguments); } @@ -719,7 +1003,7 @@ public final class ComputerScreen extends Screen { for (String file : files) { String[] fields = file.split("\\t", 3); if (fields.length < 3 || !isDirectChild(fields[1], session.fileExplorerDirectory)) continue; - if (fileIndex++ < session.fileExplorerScroll || line > y + h - 20) continue; + if (fileIndex++ < session.fileExplorerScroll || line > y + h - 32) continue; boolean selected = fields[1].equals(session.fileExplorerSelected); if (selected) g.fill(x - 3, line - 2, x + w - 3, line + 11, 0xFF173817); g.drawString(font, Component.literal(fileIcon(fields[0]) + " " + trimToWidth(fields[0], 30)), x, line, PALE_GREEN, false); @@ -733,7 +1017,7 @@ public final class ComputerScreen extends Screen { String footer = session.fileExplorerCreatingDirectory ? "NEW FOLDER: " + session.fileExplorerRename + "_" : session.fileExplorerMoving ? "MOVE TO PATH: " + session.fileExplorerRename + "_" : session.fileExplorerRenaming ? "RENAME: " + session.fileExplorerRename + "_" - : session.fileExplorerDeleteConfirm ? "DELETE SELECTED FILE? [ ENTER / ESC ]" : "[ F6 NEW FOLDER ] [ F7 MOVE ] [ DELETE ] [ F2 RENAME ]"; + : session.fileExplorerDeleteConfirm ? "DELETE SELECTED FILE? [ ENTER / ESC ]" : "[ CTRL+N NEW ] [ CTRL+M MOVE ] [ DEL ] [ CTRL+R RENAME ]"; if (state.lastMutationAction() == com.craisinlord.antarchy.content.network.ComputerAccessPayload.FILE_DELETE || state.lastMutationAction() == com.craisinlord.antarchy.content.network.ComputerAccessPayload.FILE_MOVE) { footer += state.lastMutationSuccess() ? " // OK" : " // ERR: " + state.lastMutationError(); @@ -786,7 +1070,7 @@ public final class ComputerScreen extends Screen { } g.drawString(font, Component.literal("[ EJECT SELECTED ]"), x, y + 162, selectedDisk == null ? 0xFF4A754A : GREEN, false); } - g.drawString(font, Component.literal("LOG OUT"), x, y + h - 17, GREEN, false); + g.drawString(font, Component.literal("LOG OUT"), x, y + 74, GREEN, false); } private void drawWallpaperPreview(GuiGraphics g, int x, int y, int w, int h, String id) { @@ -805,14 +1089,12 @@ public final class ComputerScreen extends Screen { } private void renderTerminal(GuiGraphics g, int x, int y, int h) { - var fileState = ComputerFileSystemClientState.get(position); g.drawString(font, Component.literal("ANTINTOSH TERMINAL [READY]"), x, y, GREEN, false); int line = y + 17; for (String output : session.terminalOutput) { line = wrapLimited(g, output, x, line, 208, y + h - 48, PALE_GREEN) + 1; if (line >= y + h - 48) break; } - if (!fileState.error().isEmpty()) g.drawString(font, Component.literal("RESPONSE: " + fileState.error()), x, y + h - 40, PALE_GREEN, false); g.drawString(font, Component.literal(trimToWidth(session.terminalDirectory + "> " + session.terminalInput + "_", 208)), x, y + h - 26, GREEN, false); } @@ -822,15 +1104,16 @@ public final class ComputerScreen extends Screen { try { AntPaintFile file = AntPaintFileCodec.decode(Base64.getDecoder().decode(fileState.openedContents())); session.paintName = file.filename(); + session.antmailPaintPath = fileState.openedPath(); session.paintCanvas = file.canvas().copy(); } catch (IllegalArgumentException ignored) { } } - g.drawString(font, Component.literal("ANTPAINT // " + session.paintName + (session.paintDirty ? " *" : "")), x, y, GREEN, false); + g.drawString(font, Component.literal(trimToWidth("ANTPAINT // " + session.paintName + (session.paintDirty ? " *" : ""), w)), x, y, GREEN, false); renderPaintToolbar(g, x, y); // w and h are already the window's content dimensions; keep rendering in // lockstep with paintAt(), which receives these same adjusted dimensions. - int canvasHeight = h; + int canvasHeight = Math.max(1, h - 32); int size = Math.max(1, Math.min(w / AntPaintCanvas.WIDTH, canvasHeight / AntPaintCanvas.HEIGHT)); int cx = x + (w - size * AntPaintCanvas.WIDTH) / 2; int cy = y + 30 + Math.max(0, (canvasHeight - size * AntPaintCanvas.HEIGHT) / 2); @@ -862,111 +1145,137 @@ public final class ComputerScreen extends Screen { } private void paintToolbarIcon(GuiGraphics g, int index, int x, int y) { + String externalIcon = switch (index) { + case 0 -> "pencil"; + case 1 -> "eraser"; + case 3 -> "trash"; + case 4 -> "undo"; + case 5 -> "redo"; + default -> null; + }; + if (externalIcon != null) { + ResourceLocation texture = ResourceLocation.fromNamespaceAndPath( + "antarchy", "textures/gui/antpaint/" + externalIcon + ".png"); + g.blit(texture, x - 9, y - 9, 0, 0, 18, 18, 24, 24); + return; + } int color = PALE_GREEN; String[][] sprites = { + // Pencil: graphite point at lower-left, long barrel, and capped eraser at upper-right. { - "...............", - ".....##........", - "....#++#.......", - "...#++++##.....", - "...#+++++#.....", - "....#++++##....", - ".....#++++##...", - "......#++++##..", - ".......#++++##.", - "........#++++#.", - ".........#++#..", - "..........##...", - ".........#.....", - "...............", - "..............." + ".............##.", + "............####", + "...........##++#", + "..........##+++##", + ".........##+++##.", + "........##+++##..", + ".......##+++##...", + "......##+++##....", + ".....##+++##.....", + "....##+++##......", + "...##+++##.......", + "..##+++##........", + ".##++##..........", + ".####............", + "..##.............", + "...t............." }, + // Eraser: angled block with a contrasting paper edge and colored rubber. { - "...............", - ".....######....", - "....#++++++#...", - "...#++gg+++##..", - "..#++gg++++++#.", - ".#+++++++++++#.", - "#++++++++++++#.", - "#+++++++++++##.", - "#+++++++++##...", - ".#########.....", - "...............", - "...............", - "...............", - "...............", - "..............." + "................", + "...........####.", + ".........###++##", + ".......###+++##.", + ".....###+++##...", + "...###+++##.....", + ".###+++##.......", + "##+++###........", + "#++###...........", + "####.............", + "................", + "................", + "................", + "................", + "................", + "................" }, + // Bucket: pale arched handle, wide rim, tapered body, and visible paint. { - "....######.....", - "...##....##....", - "..##......##...", - "..#........#...", - ".############..", - ".#++++++++++#..", - ".#++gg++++++#..", - ".#++++++++++#..", - ".#++++++++++#..", - "..#++++++++#...", - "..#++++++++#...", - "...#++++++#....", - "...#++++++#....", - "....######.....", - "...............", - "..............." + "....######......", + "...##....##.....", + "..##......##....", + "..#........#....", + ".#############..", + ".#+++++++++++##.", + ".#++gggggg+++##.", + ".#+++++++++++##.", + "..#+++++++++##..", + "..##++++++++##..", + "...##++++++##...", + "...##++++++##...", + "....########....", + "................", + "................", + "................" }, + // Clear: trash bin with lid and vertical slats. { - "...............", - "....########...", - "..##++++++++#..", - ".#++#########..", - "#++##.........", - "#++#...........", - "#++##..........", - ".#++#########..", - "..##++++++++#..", - "....########...", - "...............", - "...............", - "...............", - "...............", - "..............." + ".....######.....", + "...##########...", + "....##.##.##....", + "..############..", + "..##++++++++##..", + "..##++##++##++##.", + "..##++##++##++##.", + "..##++##++##++##.", + "...##++++++++##..", + "...############..", + "....##......##...", + "................", + "................", + "................", + "................", + "................" }, + // Undo: left-facing hooked arrow. { - "...............", - "....########...", - "..##++++++++#..", - ".#++#########..", - "#++#...........", - "#++##..........", - "#++#...........", - ".#++#########..", - "..##++++++++#..", - "....########...", - "...............", - "...............", - "...............", - "...............", - "..............." + "......####......", + "................", + "................", + "....##..........", + "...####.........", + "..##++##........", + ".##++++##.......", + "##++++++++++##..", + ".##++++##.......", + "..##++##........", + "...####.........", + "....##..........", + "................", + "................", + "................", + "................" }, + // Redo: right-facing hooked arrow, mirrored at render time. { - "...............", - "...#########...", - "..#+++++++++#..", - ".#++#######++#.", - "#++##.....##++#", - "#++#.......#++#", - "#++#.......#++#", - ".#++#######++#.", - "..#+++++++++#..", - "...#########...", - "...............", - "...............", - "...............", - "...............", - "..............." + "......####......", + "................", + "................", + "....##..........", + "...####.........", + "..##++##........", + ".##++++##.......", + "##++++++++++##..", + ".##++++##.......", + "..##++##........", + "...####.........", + "....##..........", + "................", + "................", + "................", + "................" }, + // Save icon intentionally retained. { ".#############.", "#+++++++++++++#", @@ -986,6 +1295,30 @@ public final class ComputerScreen extends Screen { } }; String[] sprite = sprites[Math.max(0, Math.min(index, sprites.length - 1))]; + if (index == 5) { + sprite = sprite.clone(); + for (int row = 0; row < sprite.length; row++) { + sprite[row] = new StringBuilder(sprite[row].substring(0, Math.min(16, sprite[row].length()))).reverse().toString(); + } + } + for (int row = 0; row < 16; row++) { + String line = row < sprite.length ? sprite[row] : ""; + if (line.length() > 16) line = line.substring(0, 16); + for (int column = 0; column < 16; column++) { + char pixel = column < line.length() ? line.charAt(column) : '.'; + if (pixel != '#' && pixel != '+' && pixel != 'g' && pixel != 't') continue; + for (int[] offset : new int[][]{{0, -1}, {0, 1}, {-1, 0}, {1, 0}}) { + int dx = offset[0]; + int dy = offset[1]; + int nx = column + dx; + int ny = row + dy; + if (nx < 0 || nx >= 16 || ny < 0 || ny >= 16) continue; + String neighborLine = ny < sprite.length ? sprite[ny] : ""; + char neighbor = nx < neighborLine.length() ? neighborLine.charAt(nx) : '.'; + if (neighbor == '.' || neighbor == ' ') g.fill(x - 8 + nx, y - 8 + ny, x - 7 + nx, y - 7 + ny, 0xFF78A878); + } + } + } for (int row = 0; row < 16; row++) { String line = row < sprite.length ? sprite[row] : ""; for (int column = 0; column < 16; column++) { @@ -993,6 +1326,7 @@ public final class ComputerScreen extends Screen { if (pixel == '#') g.fill(x - 8 + column, y - 8 + row, x - 7 + column, y - 7 + row, BLACK); else if (pixel == '+') g.fill(x - 8 + column, y - 8 + row, x - 7 + column, y - 7 + row, color); else if (pixel == 'g') g.fill(x - 8 + column, y - 8 + row, x - 7 + column, y - 7 + row, GREEN); + else if (pixel == 't') g.fill(x - 8 + column, y - 8 + row, x - 7 + column, y - 7 + row, 0xFF657465); } } } @@ -1145,12 +1479,13 @@ public final class ComputerScreen extends Screen { session.textJustSavedAs = false; String editorTitle = session.textRenaming ? "RENAME // " + session.textRename + "_" : "ANTTEXT // " + session.textPath + (session.textDirty ? " *" : ""); g.drawString(font, Component.literal(trimToWidth(editorTitle, Math.max(1, w - 104))), x, y, GREEN, false); - g.drawString(font, Component.literal("NEW OPEN WRITE SAVE AS"), x, y + 18, PALE_GREEN, false); + g.drawString(font, Component.literal(trimToWidth("NEW OPEN WRITE SAVE AS", w)), x, y + 18, PALE_GREEN, false); if (fileState.lastMutationAction() == com.craisinlord.antarchy.content.network.ComputerAccessPayload.FILE_CREATE || fileState.lastMutationAction() == com.craisinlord.antarchy.content.network.ComputerAccessPayload.FILE_SAVE || fileState.lastMutationAction() == com.craisinlord.antarchy.content.network.ComputerAccessPayload.FILE_MOVE) { String status = fileState.lastMutationSuccess() ? "OPERATION OK" : "OPERATION ERROR // " + fileState.lastMutationError(); - g.drawString(font, Component.literal(status), x + w - 100, y, fileState.lastMutationSuccess() ? PALE_GREEN : GREEN, false); + String displayStatus = trimToWidth(status, Math.min(100, w)); + g.drawString(font, Component.literal(displayStatus), x + w - font.width(displayStatus), y, fileState.lastMutationSuccess() ? PALE_GREEN : GREEN, false); } g.fill(x + 4, y + 36, x + w - 4, y + h - 4, BLACK); if (session.textListing) { @@ -1161,7 +1496,7 @@ public final class ComputerScreen extends Screen { for (String file : fileState.files()) { String[] fields = file.split("\\t", 3); if (fields.length < 3 || !fields[0].equals("TEXT")) continue; - if (textIndex++ < session.textPickerScroll || line > y + h - 28) continue; + if (textIndex++ < session.textPickerScroll || line > y + h - 50) continue; g.drawString(font, Component.literal(fields[1]), x + 10, line, PALE_GREEN, false); line += 14; visibleFiles++; @@ -1172,7 +1507,7 @@ public final class ComputerScreen extends Screen { if (session.textFocused) content = content.substring(0, Math.min(session.textCursor, content.length())) + "|" + content.substring(Math.min(session.textCursor, content.length())); List lines = font.split(Component.literal(content), w - 28); - int maxLines = Math.max(1, (h - 56) / 11); + int maxLines = Math.max(1, (h - 88) / 11); session.textScroll = Math.max(0, Math.min(session.textScroll, Math.max(0, lines.size() - maxLines))); for (int i = session.textScroll; i < lines.size() && i < session.textScroll + maxLines; i++) { g.drawString(font, lines.get(i), x + 10, y + 46 + (i - session.textScroll) * 11, GREEN, false); @@ -1181,17 +1516,33 @@ public final class ComputerScreen extends Screen { if (session.textDeleteConfirm) { g.drawString(font, Component.literal("DELETE CURRENT FILE? [ ENTER / ESC ]"), x, y + h - 28, PALE_GREEN, false); } else { - g.drawString(font, Component.literal("[CTRL+S] SAVE [CTRL+SHIFT+S] SAVE AS"), x, y + h - 39, PALE_GREEN, false); - g.drawString(font, Component.literal("[F2] RENAME [F8] DELETE [ESC] CLOSE"), x, y + h - 26, PALE_GREEN, false); + g.drawString(font, Component.literal(trimToWidth("[CTRL+S] SAVE [CTRL+SHIFT+S] SAVE AS", w)), x, y + h - 39, PALE_GREEN, false); + g.drawString(font, Component.literal(trimToWidth("[CTRL+R] RENAME [CTRL+DEL] DELETE [ESC] CLOSE", w)), x, y + h - 26, PALE_GREEN, false); } } private void renderAntmail(GuiGraphics g, int x, int y, int h) { var result = AntmailClientState.getMailbox(position); + AntmailResultPayload lastResponse = AntmailClientState.get(position); + boolean unconfigured = (result != null && result.address().isBlank()) + || (result == null && lastResponse != null && ("unconfigured".equals(lastResponse.detail()) + || "address_not_found".equals(lastResponse.detail()) || lastResponse.detail().startsWith("registration_failed:"))); g.drawString(font, Component.literal("ANTMAIL // COMPUTER ADDRESS"), x, y, GREEN, false); - boolean unconfigured = result == null || result.address().isBlank(); - if (unconfigured) { - g.drawString(font, Component.literal("NO ADDRESS CONFIGURED"), x, y + 20, PALE_GREEN, false); + session.antmailAttachmentStartLine = -1; + session.antmailVisibleAttachmentCount = 0; + if (result == null && !unconfigured) { + g.drawString(font, Component.literal("CONTACTING ANTMAIL NETWORK"), x, y + 20, GREEN, false); + String waitStatus = lastResponse != null && !lastResponse.detail().isBlank() + ? "SERVER // " + lastResponse.detail().toUpperCase() + : session.antmailStateWaitTicks >= 100 ? "NO RESPONSE // CHECK CLIENT + SERVER LOGS" : "PLEASE WAIT // RETRYING STATE SYNC"; + g.drawString(font, Component.literal(trimToWidth(waitStatus, 208)), x, y + 40, PALE_GREEN, false); + box(g, x, y + 56, x + 150, y + 79, GREEN); + g.drawString(font, Component.literal("[ RETRY STATE LINK ]"), x + 6, y + 63, GREEN, false); + } else if (unconfigured) { + String registrationStatus = lastResponse != null && lastResponse.detail().startsWith("registration_failed:") + ? "REGISTRATION FAILED // " + lastResponse.detail().substring("registration_failed:".length()).toUpperCase() + : "NO ADDRESS CONFIGURED"; + g.drawString(font, Component.literal(trimToWidth(registrationStatus, 208)), x, y + 20, PALE_GREEN, false); g.drawString(font, Component.literal("USERNAME // 3-16 CHARACTERS"), x, y + 40, GREEN, false); g.fill(x, y + 56, x + 206, y + 79, BLACK); box(g, x, y + 56, x + 206, y + 79, GREEN); @@ -1199,9 +1550,14 @@ public final class ComputerScreen extends Screen { g.drawString(font, Component.literal("[ ENTER ] REGISTER ADDRESS"), x, y + 88, GREEN, false); } else { g.drawString(font, Component.literal(trimToWidth(result.address(), 208)), x, y + 20, PALE_GREEN, false); - g.drawString(font, Component.literal("[ INBOX ] [ SENT ] [ DRAFTS ]"), x, y + 40, GREEN, false); - box(g, x + 158, y + 30, x + 214, y + 53, GREEN); - g.drawString(font, Component.literal("COMPOSE"), x + 163, y + 38, GREEN, false); + if (session.antmailMode.equals("inbox")) box(g, x, y + 30, x + 54, y + 53, PALE_GREEN); + if (session.antmailMode.equals("sent")) box(g, x + 54, y + 30, x + 100, y + 53, PALE_GREEN); + if (session.antmailMode.equals("drafts")) box(g, x + 100, y + 30, x + 154, y + 53, PALE_GREEN); + g.drawString(font, Component.literal("INBOX"), x + 6, y + 38, session.antmailMode.equals("inbox") ? PALE_GREEN : GREEN, false); + g.drawString(font, Component.literal("SENT"), x + 60, y + 38, session.antmailMode.equals("sent") ? PALE_GREEN : GREEN, false); + g.drawString(font, Component.literal("DRAFTS"), x + 106, y + 38, session.antmailMode.equals("drafts") ? PALE_GREEN : GREEN, false); + box(g, x + 158, y + 30, x + 214, y + 53, session.antmailMode.equals("compose") ? PALE_GREEN : GREEN); + g.drawString(font, Component.literal("COMPOSE"), x + 163, y + 38, session.antmailMode.equals("compose") ? PALE_GREEN : GREEN, false); if (session.antmailMode.equals("compose")) { g.drawString(font, Component.literal(trimToWidth(session.antmailRecipient, 180)), x + 28, y + 52, PALE_GREEN, false); g.drawString(font, Component.literal(trimToWidth(session.antmailSubject, 156)), x + 52, y + 66, PALE_GREEN, false); @@ -1226,15 +1582,25 @@ public final class ComputerScreen extends Screen { } else if (session.antmailMode.equals("message")) { AntmailMessage message = selectedAntmailMessage(result); if (message != null) { + boolean showRetry = session.antmailSent && !message.deliveryStatus().equals("DELIVERED"); + int attachmentBottom = y + h - (showRetry ? 54 : 36); g.drawString(font, Component.literal("< BACK // " + (session.antmailSent ? "SENT" : "INBOX")), x, y + 40, GREEN, false); g.drawString(font, Component.literal(trimToWidth(session.antmailSent ? message.recipient().fullAddress() : message.sender().fullAddress(), 208)), x, y + 62, PALE_GREEN, false); g.drawString(font, Component.literal(trimToWidth(message.subject(), 208)), x, y + 78, GREEN, false); - int messageLine = wrapLimited(g, message.body(), x, y + 98, 208, y + h - 52, PALE_GREEN) + 4; - for (AntmailAttachment attachment : message.attachments()) { - if (messageLine > y + h - 38) break; - messageLine = wrap(g, "[ SAVE ATTACHMENT ] " + attachment.fileName(), x, messageLine, 208, GREEN) + 2; + int messageLine = wrapLimited(g, message.body(), x, y + 98, 208, attachmentBottom, PALE_GREEN) + 4; + if (!message.attachments().isEmpty() && messageLine + 9 <= attachmentBottom) { + session.antmailAttachmentStartLine = messageLine; + for (AntmailAttachment attachment : message.attachments()) { + if (messageLine + 9 > attachmentBottom) break; + g.drawString(font, Component.literal(trimToWidth("[ SAVE ATTACHMENT ] " + attachment.fileName(), 208)), x, messageLine, GREEN, false); + session.antmailVisibleAttachmentCount++; + messageLine += 14; + } + } + if (showRetry) { + String retryLabel = session.antmailRetryMessageId.equals(message.id().toString()) ? "[ RETRYING... ]" : "[ RETRY DELIVERY ]"; + g.drawString(font, Component.literal(retryLabel), x, y + h - 46, GREEN, false); } - if (session.antmailSent && !message.deliveryStatus().equals("DELIVERED")) g.drawString(font, Component.literal("[ RETRY DELIVERY ]"), x, y + h - 46, GREEN, false); g.drawString(font, Component.literal("[ DELETE MESSAGE ]"), x, y + h - 28, GREEN, false); } } else if (session.antmailMode.equals("drafts")) { @@ -1262,7 +1628,33 @@ public final class ComputerScreen extends Screen { } } AntmailResultPayload operation = AntmailClientState.get(position); - String displayStatus = operation != null && !operation.messageId().isBlank() ? antmailStatus(operation) : session.antmailStatus; + if (session.antmailSendPending && operation != null && operation != session.antmailSendBaseline && !operation.messageId().isBlank()) { + session.antmailSendPending = false; + session.antmailSendBaseline = null; + if (operation.status() == 0 || operation.status() == 1) { + if (session.antmailDraftId != null) AntmailNetworking.deleteDraft(position, session.antmailDraftId); + session.antmailDraftId = null; + session.antmailDraftLoaded = false; + session.antmailStatus = antmailStatus(operation); + session.antmailMode = "inbox"; + session.antmailFocused = false; + session.antmailRecipient = ""; + session.antmailSubject = ""; + session.antmailBody = ""; + session.antmailAttachText = false; + session.antmailAttachPaint = false; + session.antmailPaintAttachment = null; + session.antmailPaintLoadPendingPath = ""; + session.antmailTextPath = ""; + session.antmailPaintPath = ""; + session.antmailPage = 0; + requestAntmailState(true); + } else { + session.antmailFocused = true; + session.antmailStatus = antmailStatus(operation); + } + } + String displayStatus = session.antmailStatus; if (!displayStatus.isEmpty()) drawBottomWrapped(g, displayStatus, x, y, h, 208, PALE_GREEN); } @@ -1321,7 +1713,7 @@ public final class ComputerScreen extends Screen { } if (games.isEmpty()) { g.drawString(font, Component.literal("NO GAMES INSTALLED"), x, y + 22, PALE_GREEN, false); - g.drawString(font, Component.literal("FIND GAME DISKS TO INSTALL PROGRAMS"), x, y + 40, PALE_GREEN, false); + g.drawString(font, Component.literal(trimToWidth("FIND GAME DISKS TO INSTALL PROGRAMS", 214)), x, y + 40, PALE_GREEN, false); } } @@ -1396,17 +1788,21 @@ public final class ComputerScreen extends Screen { for (int i = session.windows.size() - 1; i >= 0; i--) { Window window = session.windows.get(i); if (window.minimized) continue; - int x = l + window.x; - int y = t + window.y; - int w = window.maximized ? WIDTH - 28 : window.width; - int h = window.maximized ? HEIGHT - 48 : window.height; - if (inside(x, y + 20, w, h - 20, mouseX, mouseY)) { + int x = l + windowLocalX(window); + int y = t + windowLocalY(window); + int w = windowWidth(window); + int h = windowHeight(window); + int contentX = x + 8; + int contentY = y + 28; + int contentW = w - 16; + int contentH = h - 34; + if (inside(x, y + TITLE_BAR_HEIGHT, w, h - TITLE_BAR_HEIGHT, mouseX, mouseY)) { activeWindow = window; session.windows.remove(i); session.windows.add(window); - if (window.type.equals("SETTINGS") && inside(x + 8, y + 105, 205, 51, mouseX, mouseY)) { + if (window.type.equals("SETTINGS") && inside(contentX, contentY + 105, Math.min(205, contentW), 51, mouseX, mouseY)) { List disks = physicalDisks(); - int index = ((int) mouseY - (y + 105)) / 17; + int index = ((int) mouseY - (contentY + 105)) / 17; if (index >= 0 && index < disks.size() && index < 3) selectedDisk = disks.get(index); return true; } @@ -1425,20 +1821,34 @@ public final class ComputerScreen extends Screen { return true; } session.archiveSearchFocused = false; - int visible = Math.max(1, (h - 56) / 62); - int start = Math.max(0, Math.min(session.archiveScroll, Math.max(0, entries.size() - visible))); - int index = start + ((int) mouseY - (archiveY + 38)) / 62; - int rowIndex = index - start; - if (rowIndex >= 0 && rowIndex < visible && index < entries.size() && inside(archiveX, archiveY + 38 + rowIndex * 62, w - 16, 56, mouseX, mouseY)) { - session.archiveEntryId = entries.get(index).id(); + int columns = window.maximized ? 3 : 1; + int cellHeight = window.maximized ? 72 : 38; + int visibleRows = Math.max(1, (contentH - 58) / cellHeight); + int totalRows = (entries.size() + columns - 1) / columns; + int startRow = Math.max(0, Math.min(session.archiveScroll, Math.max(0, totalRows - visibleRows))); + int gridTop = archiveY + 40; + int relativeRow = ((int) mouseY - gridTop) / cellHeight; + int relativeColumn = Math.min(columns - 1, Math.max(0, ((int) mouseX - archiveX) / Math.max(1, (contentW - 8) / columns))); + int index = (startRow + relativeRow) * columns + relativeColumn; + int cellWidth = (contentW - 8) / columns; + int cellX = archiveX + relativeColumn * cellWidth; + int rowTop = gridTop + relativeRow * cellHeight; + if (relativeRow >= 0 && relativeRow < visibleRows && index < entries.size() + && inside(cellX, rowTop, cellWidth - 2, cellHeight - 2, mouseX, mouseY)) { + ComputerGuideData.Entry openedEntry = entries.get(index); + session.archiveEntryId = openedEntry.id(); session.archiveDetailScroll = 0; + if (!openedEntry.structureId().isBlank()) { + com.craisinlord.antarchy.content.client.ComputerStructureLocatorClientState.searching(position, openedEntry.dimensionId()); + ComputerNetworking.locateStructure(position, openedEntry.id()); + } } } return true; } if (window.type.equals("FILES")) { var files = ComputerFileSystemClientState.get(position).files(); - if (!session.fileExplorerDirectory.equals("/") && mouseY < y + 36 && mouseX > x + w - 48) { + if (!session.fileExplorerDirectory.equals("/") && mouseY >= contentY && mouseY < contentY + 12 && mouseX > x + w - 48) { session.fileExplorerDirectory = parentDirectory(session.fileExplorerDirectory); session.fileExplorerScroll = 0; return true; @@ -1449,9 +1859,9 @@ public final class ComputerScreen extends Screen { if (fields.length < 3 || !isDirectChild(fields[1], session.fileExplorerDirectory)) continue; int rowIndex = row++; if (rowIndex < session.fileExplorerScroll) continue; - int rowTop = y + 34 + (rowIndex - session.fileExplorerScroll) * 14; - if (rowTop > y + h - 20) break; - if (!inside(x, rowTop - 2, w, 14, mouseX, mouseY)) continue; + int rowTop = contentY + 34 + (rowIndex - session.fileExplorerScroll) * 14; + if (rowTop > contentY + contentH - 32) break; + if (!inside(contentX, rowTop - 2, contentW, 14, mouseX, mouseY)) continue; session.fileExplorerSelected = fields[1]; if (fields[0].equals("DIRECTORY")) { session.fileExplorerDirectory = fields[1]; @@ -1466,17 +1876,28 @@ public final class ComputerScreen extends Screen { return true; } if (window.type.equals("PAINT")) { - int contentX = x + 8; - int contentY = y + 58; - if (inside(contentX, contentY, w - 16, h - 34, mouseX, mouseY)) paintAt(mouseX, mouseY, contentX, contentY, w - 16, h - 34, button == 1); - else if (mouseY >= y + 39 && mouseY < y + 58 && mouseX >= contentX && mouseX < contentX + 196) { + int canvasX = contentX; + int canvasY = contentY + 30; + int canvasHeight = Math.max(1, contentH - 32); + if (inside(canvasX, canvasY, contentW, canvasHeight, mouseX, mouseY)) { + if (button == 0 || button == 1) { + paintStrokeActive = session.paintTool == AntPaintTool.PENCIL || session.paintTool == AntPaintTool.ERASER; + paintStrokeButton = button; + lastPaintPixelX = -1; + lastPaintPixelY = -1; + if (paintStrokeActive) session.paintHistory.record(session.paintCanvas); + } + if (button == 0 || button == 1) paintAt(mouseX, mouseY, canvasX, canvasY, contentW, canvasHeight); + } + else if (mouseY >= contentY + 11 && mouseY < contentY + 30 && mouseX >= contentX && mouseX < contentX + Math.min(196, contentW)) { + paintStrokeActive = false; paintToolbarAction(((int) mouseX - contentX) / 28); } return true; } if (window.type.equals("TEXT")) { - if (mouseY < y + 66) { - int toolbarX = (int) mouseX - (x + 8); + if (inside(contentX, contentY + 12, contentW, 26, mouseX, mouseY)) { + int toolbarX = (int) mouseX - contentX; if (toolbarX < 30) newText(); else if (toolbarX < 60) { session.textListing = true; session.textPickerScroll = 0; ComputerNetworking.listFiles(position); } else if (toolbarX < 102) writeText(); @@ -1484,12 +1905,11 @@ public final class ComputerScreen extends Screen { else if (toolbarX < 210) beginSaveAs(); } else if (session.textListing) { var files = ComputerFileSystemClientState.get(position).files(); - int index = ((int) mouseY - (y + 62)) / 14; + int index = session.textPickerScroll + ((int) mouseY - (contentY + 62)) / 14; int textIndex = 0; for (String file : files) { String[] fields = file.split("\\t", 3); if (fields.length < 3 || !fields[0].equals("TEXT")) continue; - if (textIndex < session.textPickerScroll) { textIndex++; continue; } if (textIndex++ == index) { session.textListing = false; ComputerNetworking.openFile(position, fields[1]); @@ -1498,65 +1918,71 @@ public final class ComputerScreen extends Screen { } } else { session.textFocused = true; - setTextCursorFromMouse(mouseX - (x + 10), mouseY - (y + 46), w - 28); + setTextCursorFromMouse(mouseX - (contentX + 10), mouseY - (contentY + 46), contentW - 12); } return true; } if (window.type.equals("ANTMAIL")) { var result = AntmailClientState.getMailbox(position); - if (result == null || result.address().isBlank()) { - if (inside(x + 8, y + 56, 214, 25, mouseX, mouseY)) { + if (result == null && !antmailUnconfigured()) { + if (inside(contentX, contentY + 56, Math.min(150, contentW), 23, mouseX, mouseY)) { + session.antmailStateWaitTicks = 0; + requestAntmailState(true); + } + return true; + } else if (result == null || result.address().isBlank()) { + if (inside(contentX, contentY + 56, Math.min(206, contentW), 23, mouseX, mouseY)) { session.antmailFocused = true; return true; } - if (inside(x + 8, y + 112, 214, 24, mouseX, mouseY)) { + if (inside(contentX, contentY + 84, contentW, 24, mouseX, mouseY)) { setupAntmail(); return true; } - } else if (inside(x + 8, y + 30, 54, 24, mouseX, mouseY)) { + } else if (inside(contentX, contentY + 30, 54, 24, mouseX, mouseY)) { session.antmailMode = "inbox"; session.antmailSent = false; session.antmailPage = 0; session.antmailMessageIndex = -1; requestAntmailState(true); - } else if (inside(x + 62, y + 30, 38, 24, mouseX, mouseY)) { + } else if (inside(contentX + 54, contentY + 30, 46, 24, mouseX, mouseY)) { session.antmailMode = "sent"; session.antmailSent = true; session.antmailPage = 0; session.antmailMessageIndex = -1; requestAntmailState(true); - } else if (inside(x + 102, y + 30, 52, 24, mouseX, mouseY)) { + } else if (inside(contentX + 100, contentY + 30, 54, 24, mouseX, mouseY)) { session.antmailMode = "drafts"; session.antmailSent = false; session.antmailPage = 0; session.antmailMessageIndex = -1; requestAntmailState(true); - } else if (inside(x + 158, y + 30, 56, 24, mouseX, mouseY)) { + } else if (inside(contentX + 158, contentY + 30, Math.min(56, Math.max(0, contentW - 158)), 24, mouseX, mouseY)) { session.antmailMode = "compose"; session.antmailFocused = true; session.antmailField = 1; restoreLatestDraft(result); } else if (session.antmailMode.equals("inbox") || session.antmailMode.equals("sent")) { - if (mouseY >= y + h - 54 && mouseY < y + h - 32) { - if (mouseX < x + 60 && session.antmailPage > 0) { + if (mouseY >= contentY + contentH - 48 && mouseY < contentY + contentH - 28) { + if (mouseX < contentX + 52 && session.antmailPage > 0) { session.antmailPage--; requestAntmailState(true); - } else if (mouseX >= x + 150 && (session.antmailPage + 1) * AntmailMailbox.PAGE_SIZE < antmailTotal(result, session.antmailSent)) { + } else if (mouseX >= contentX + 142 && (session.antmailPage + 1) * AntmailMailbox.PAGE_SIZE < antmailTotal(result, session.antmailSent)) { session.antmailPage++; requestAntmailState(true); } return true; } - int index = ((int) mouseY - (y + 80)) / 14; + int index = ((int) mouseY - (contentY + 80)) / 14; List messages = mailboxMessages(result, session.antmailSent); if (index >= 0 && index < messages.size()) { session.antmailMessageIndex = index; session.antmailMode = "message"; AntmailNetworking.requestMessage(position, messages.get(index).id()); - AntmailNetworking.markRead(position, messages.get(index).id()); + if (!session.antmailSent) AntmailNetworking.markRead(position, messages.get(index).id()); } } else if (session.antmailMode.equals("drafts")) { - int index = ((int) mouseY - (y + 80)) / 14; + int index = ((int) mouseY - (contentY + 80)) / 14; AntmailMailbox mailbox = mailbox(result); if (mailbox != null && index >= 0 && index < mailbox.drafts().size()) { restoreDraft(mailbox.drafts().get(index)); @@ -1566,23 +1992,31 @@ public final class ComputerScreen extends Screen { } } else if (session.antmailMode.equals("message")) { AntmailMessage message = selectedAntmailMessage(result); - if (session.antmailSent && message != null && !message.deliveryStatus().equals("DELIVERED") && mouseY >= y + h - 54 && mouseY < y + h - 34) { + if (session.antmailSent && message != null && !message.deliveryStatus().equals("DELIVERED") + && session.antmailRetryMessageId.isBlank() + && mouseY >= contentY + contentH - 50 && mouseY < contentY + contentH - 32) { + session.antmailRetryBaseline = AntmailClientState.get(position); + session.antmailRetryMessageId = message.id().toString(); + session.antmailRetryTicks = 100; AntmailNetworking.retry(position, message.id()); session.antmailStatus = "RETRYING DELIVERY"; return true; } - if (mouseY >= y + h - 32 && mouseY < y + h - 8) { + if (mouseY >= contentY + contentH - 32 && mouseY < contentY + contentH - 8) { if (message != null) AntmailNetworking.delete(position, message.id(), session.antmailSent); session.antmailMode = session.antmailSent ? "sent" : "inbox"; session.antmailMessageIndex = -1; return true; } - if (message != null && mouseY >= y + 90 && mouseY < y + h - 38 && !message.attachments().isEmpty()) { - int attachmentIndex = Math.max(0, Math.min(message.attachments().size() - 1, ((int) mouseY - (y + 98)) / 14)); + if (message != null && session.antmailAttachmentStartLine >= 0 + && mouseY >= session.antmailAttachmentStartLine + && mouseY < session.antmailAttachmentStartLine + session.antmailVisibleAttachmentCount * 14 + && !message.attachments().isEmpty()) { + int attachmentIndex = ((int) mouseY - session.antmailAttachmentStartLine) / 14; saveAntmailAttachment(message.attachments().get(attachmentIndex)); return true; } - if (mouseY < y + 72) { + if (mouseY < contentY + 72) { session.antmailMode = session.antmailSent ? "sent" : "inbox"; session.antmailMessageIndex = -1; } @@ -1592,7 +2026,8 @@ public final class ComputerScreen extends Screen { for (String file : ComputerFileSystemClientState.get(position).files()) { String[] fields = file.split("\\t", 3); if (fields.length < 2 || (!fields[0].equals("TEXT") && !fields[0].equals("IMAGE"))) continue; - int rowTop = y + 64 + row++ * 14; + int rowTop = contentY + 64 + row++ * 14; + if (rowTop > contentY + contentH - 52) break; if (mouseY >= rowTop - 2 && mouseY < rowTop + 12) { session.fileExplorerSelected = fields[1]; session.antmailAttachText = fields[0].equals("TEXT"); @@ -1603,6 +2038,9 @@ public final class ComputerScreen extends Screen { session.antmailTextPath = fields[1]; } else { session.antmailPaintPath = fields[1]; + session.paintName = fields[1].substring(fields[1].lastIndexOf('/') + 1); + session.antmailPaintAttachment = null; + session.antmailPaintLoadPendingPath = fields[1]; } ComputerNetworking.openFile(position, fields[1]); return true; @@ -1610,20 +2048,20 @@ public final class ComputerScreen extends Screen { } return true; } - if (mouseY >= y + 116 && mouseY < y + 141) { + if (mouseY >= contentY + 116 && mouseY < contentY + 141) { session.antmailPickingAttachment = true; return true; } - if (mouseY >= y + 42 && mouseY < y + 59) { + if (mouseY >= contentY + 42 && mouseY < contentY + 59) { session.antmailField = 1; session.antmailFocused = true; - } else if (mouseY >= y + 59 && mouseY < y + 75) { + } else if (mouseY >= contentY + 59 && mouseY < contentY + 75) { session.antmailField = 2; session.antmailFocused = true; - } else if (mouseY >= y + 75 && mouseY < y + 116) { + } else if (mouseY >= contentY + 75 && mouseY < contentY + 116) { session.antmailField = 3; session.antmailFocused = true; - } else if (mouseY >= y + h - 38 && mouseY < y + h - 8) { + } else if (mouseY >= contentY + contentH - 38 && mouseY < contentY + contentH - 8) { sendAntmail(); } } @@ -1631,8 +2069,8 @@ public final class ComputerScreen extends Screen { } if (window.type.equals("GAMES") && !window.gameOpen) { List games = installedGameIds(); - int index = ((int) mouseY - (y + 20)) / 16; - if (index >= 0 && index < games.size() && mouseY < y + 20 + games.size() * 16) { + int index = ((int) mouseY - (contentY + 18)) / 16; + if (index >= 0 && index < games.size() && mouseY < contentY + 18 + games.size() * 16) { window.gameId = games.get(index); window.gameOpen = true; activeWindow = window; @@ -1641,19 +2079,11 @@ public final class ComputerScreen extends Screen { } if (window.type.equals("TERMINAL")) { session.terminalFocused = true; return true; } } - if (window.type.equals("SETTINGS") && inside(x + 8, y + 105, 205, 51, mouseX, mouseY)) { - List disks = physicalDisks(); - int index = ((int) mouseY - (y + 105)) / 17; - if (index >= 0 && index < disks.size() && index < 3) { - selectedDisk = disks.get(index); - return true; - } - } - if (window.type.equals("SETTINGS") && inside(x + 8, y + 155, 205, 25, mouseX, mouseY)) { + if (window.type.equals("SETTINGS") && inside(contentX, contentY + 156, Math.min(205, contentW), 20, mouseX, mouseY)) { ejectSelected(); return true; } - if (window.type.equals("SETTINGS") && inside(x, y + h - 30, 90, 25, mouseX, mouseY)) { + if (window.type.equals("SETTINGS") && inside(contentX, contentY + 70, 90, 18, mouseX, mouseY)) { ComputerNetworking.logout(position); loggedIn = false; session.authenticated = false; @@ -1661,19 +2091,20 @@ public final class ComputerScreen extends Screen { activeWindow = null; return true; } - if (inside(x, y + 20, w, h - 20, mouseX, mouseY)) return true; - if (inside(x, y, w, 20, mouseX, mouseY)) { - if (inside(x + w - 18, y, 18, 20, mouseX, mouseY)) { + if (inside(x, y + TITLE_BAR_HEIGHT, w, h - TITLE_BAR_HEIGHT, mouseX, mouseY)) return true; + if (inside(x, y, w, TITLE_BAR_HEIGHT, mouseX, mouseY)) { + int controlsLeft = x + w - WINDOW_CONTROL_WIDTH * WINDOW_CONTROL_COUNT; + if (inside(controlsLeft + WINDOW_CONTROL_WIDTH * 2, y, WINDOW_CONTROL_WIDTH, TITLE_BAR_HEIGHT, mouseX, mouseY)) { if (window.type.equals("ANTMAIL") && session.antmailMode.equals("compose")) saveAntmailDraft(); session.windows.remove(i); if (activeWindow == window) activeWindow = null; } - else if (inside(x + w - 48, y, 16, 20, mouseX, mouseY)) window.maximized = !window.maximized; - else if (inside(x + w - 63, y, 16, 20, mouseX, mouseY)) { + else if (inside(controlsLeft + WINDOW_CONTROL_WIDTH, y, WINDOW_CONTROL_WIDTH, TITLE_BAR_HEIGHT, mouseX, mouseY)) toggleMaximized(window); + else if (inside(controlsLeft, y, WINDOW_CONTROL_WIDTH, TITLE_BAR_HEIGHT, mouseX, mouseY)) { window.minimized = true; if (activeWindow == window) activeWindow = null; } - else { dragging = window; dragX = (int) mouseX - x; dragY = (int) mouseY - y; session.windows.remove(i); session.windows.add(window); } + else if (!window.maximized) { dragging = window; dragX = (int) mouseX - x; dragY = (int) mouseY - y; session.windows.remove(i); session.windows.add(window); } return true; } } @@ -1684,7 +2115,7 @@ public final class ComputerScreen extends Screen { } List disks = physicalDisks().stream().filter(id -> !id.getPath().equals("blockle_game")).toList(); for (int i = 0; i < disks.size() && i < 3; i++) { - int x = l + 24 + i % 4 * 94; + int x = l + 24 + (i + 1) * 94; int y = t + 200 + i / 4 * 54; if (inside(x - 6, y - 6, 76, 48, mouseX, mouseY)) { draggedDisk = disks.get(i).toString(); @@ -1706,12 +2137,12 @@ public final class ComputerScreen extends Screen { dragging.x = Math.max(12, Math.min(WIDTH - dragging.renderWidth - 12, (int) mouseX - l - this.dragX)); dragging.y = Math.max(34, Math.min(HEIGHT - dragging.renderHeight - 12, (int) mouseY - t - this.dragY)); } - if (activeWindow != null && activeWindow.type.equals("PAINT") && button != 0) { + if (paintStrokeActive && button == paintStrokeButton && activeWindow != null && activeWindow.type.equals("PAINT")) { int l = -WIDTH / 2; int t = -HEIGHT / 2; - int x = l + activeWindow.x + 8; - int y = t + activeWindow.y + 58; - paintAt(mouseX, mouseY, x, y, activeWindow.renderWidth - 16, activeWindow.renderHeight - 34, button == 1); + int x = l + windowLocalX(activeWindow) + 8; + int y = t + windowLocalY(activeWindow) + 58; + paintAt(mouseX, mouseY, x, y, activeWindow.renderWidth - 16, Math.max(1, activeWindow.renderHeight - 66)); } return true; } @@ -1724,34 +2155,41 @@ public final class ComputerScreen extends Screen { if (draggedDisk != null) { int l = -WIDTH / 2; int t = -HEIGHT / 2; - int trashX = l + 24 + (6 % 4) * 94; - int trashY = t + 48 + (6 / 4) * 76; + int trashX = l + 24 + (TRASH_ICON_INDEX % 4) * 94; + int trashY = t + 48 + (TRASH_ICON_INDEX / 4) * 76; if (inside(trashX - 6, trashY - 6, 76, 57, mouseX, mouseY)) ejectSelected(); draggedDisk = null; } dragging = null; + if (button == paintStrokeButton) { + paintStrokeActive = false; + paintStrokeButton = -1; + lastPaintPixelX = -1; + lastPaintPixelY = -1; + } return true; } @Override public boolean mouseScrolled(double mouseX, double mouseY, double scrollX, double scrollY) { Window hoveredWindow = windowAt(mouseX, mouseY); - if (hoveredWindow != null) activeWindow = hoveredWindow; - if (loggedIn && activeWindow != null && activeWindow.type.equals("FILES")) { + if (!loggedIn || hoveredWindow == null) return super.mouseScrolled(mouseX, mouseY, scrollX, scrollY); + activeWindow = hoveredWindow; + if (activeWindow.type.equals("FILES")) { session.fileExplorerScroll = Math.max(0, session.fileExplorerScroll - (int) Math.signum(scrollY)); return true; } - if (loggedIn && activeWindow != null && activeWindow.type.equals("TEXT") && session.textListing) { + if (activeWindow.type.equals("TEXT") && session.textListing) { session.textPickerScroll = Math.max(0, session.textPickerScroll - (int) Math.signum(scrollY)); return true; } - if (loggedIn && activeWindow != null && activeWindow.type.equals("TEXT")) { + if (activeWindow.type.equals("TEXT")) { session.textScroll = Math.max(0, session.textScroll - (int) Math.signum(scrollY)); return true; } - if (loggedIn && activeWindow != null && activeWindow.type.equals("ARCHIVE")) { + if (activeWindow.type.equals("ARCHIVE")) { if (session.archiveEntryId == null) session.archiveScroll = Math.max(0, session.archiveScroll - (int) Math.signum(scrollY)); - else session.archiveDetailScroll = Math.max(0, Math.min(session.archiveDetailMaxScroll, session.archiveDetailScroll - (int) Math.signum(scrollY))); + else session.archiveDetailScroll = Math.max(0, session.archiveDetailScroll - (int) Math.signum(scrollY)); return true; } return super.mouseScrolled(mouseX, mouseY, scrollX, scrollY); @@ -1875,17 +2313,17 @@ public final class ComputerScreen extends Screen { renameSelectedFile(); return true; } - } else if (keyCode == GLFW.GLFW_KEY_F2) { + } else if (keyCode == GLFW.GLFW_KEY_R && hasControl(modifiers)) { beginRenameSelectedFile(); return true; } else if (keyCode == GLFW.GLFW_KEY_DELETE) { session.fileExplorerDeleteConfirm = true; return true; - } else if (keyCode == GLFW.GLFW_KEY_F6) { + } else if (keyCode == GLFW.GLFW_KEY_N && hasControl(modifiers)) { session.fileExplorerCreatingDirectory = true; session.fileExplorerRename = ""; return true; - } else if (keyCode == GLFW.GLFW_KEY_F7 && !session.fileExplorerSelected.isBlank()) { + } else if (keyCode == GLFW.GLFW_KEY_M && hasControl(modifiers) && !session.fileExplorerSelected.isBlank()) { session.fileExplorerMoving = true; session.fileExplorerRename = ""; return true; @@ -1898,6 +2336,10 @@ public final class ComputerScreen extends Screen { } if (activeWindow != null && activeWindow.type.equals("ANTMAIL") && session.antmailFocused) { + if (keyCode == GLFW.GLFW_KEY_ESCAPE && session.antmailPickingAttachment) { + session.antmailPickingAttachment = false; + return true; + } if (keyCode == GLFW.GLFW_KEY_BACKSPACE) { if (session.antmailMode.equals("compose")) { if (session.antmailField == 1 && !session.antmailRecipient.isEmpty()) session.antmailRecipient = session.antmailRecipient.substring(0, session.antmailRecipient.length() - 1); @@ -1914,7 +2356,7 @@ public final class ComputerScreen extends Screen { } if (activeWindow != null && activeWindow.type.equals("TEXT") && session.textFocused) { if (keyCode == GLFW.GLFW_KEY_BACKSPACE) { if (session.textCursor > 0) { session.textContent = session.textContent.substring(0, session.textCursor - 1) + session.textContent.substring(session.textCursor); session.textCursor--; session.textDirty = true; } return true; } - if (keyCode == GLFW.GLFW_KEY_DELETE) { if (session.textCursor < session.textContent.length()) { session.textContent = session.textContent.substring(0, session.textCursor) + session.textContent.substring(session.textCursor + 1); session.textDirty = true; } return true; } + if (keyCode == GLFW.GLFW_KEY_DELETE && !hasControl(modifiers)) { if (session.textCursor < session.textContent.length()) { session.textContent = session.textContent.substring(0, session.textCursor) + session.textContent.substring(session.textCursor + 1); session.textDirty = true; } return true; } if (keyCode == GLFW.GLFW_KEY_LEFT) { session.textCursor = Math.max(0, session.textCursor - 1); return true; } if (keyCode == GLFW.GLFW_KEY_RIGHT) { session.textCursor = Math.min(session.textContent.length(), session.textCursor + 1); return true; } if (keyCode == GLFW.GLFW_KEY_UP || keyCode == GLFW.GLFW_KEY_DOWN) { moveTextCursorVertically(keyCode == GLFW.GLFW_KEY_UP); return true; } @@ -1929,12 +2371,12 @@ public final class ComputerScreen extends Screen { if (keyCode == GLFW.GLFW_KEY_ESCAPE) { session.textDeleteConfirm = false; return true; } if (keyCode == GLFW.GLFW_KEY_ENTER || keyCode == GLFW.GLFW_KEY_KP_ENTER) { session.textDeleteConfirm = false; deleteTextFile(); return true; } } - if (keyCode == GLFW.GLFW_KEY_F8) { session.textDeleteConfirm = true; session.textFocused = false; return true; } + if (keyCode == GLFW.GLFW_KEY_DELETE && hasControl(modifiers)) { session.textDeleteConfirm = true; session.textFocused = false; return true; } if (keyCode == GLFW.GLFW_KEY_S && hasControl(modifiers) && (modifiers & GLFW.GLFW_MOD_SHIFT) != 0) { beginSaveAs(); return true; } - if (keyCode == GLFW.GLFW_KEY_F2 && !session.textPath.isBlank()) { + if (keyCode == GLFW.GLFW_KEY_R && hasControl(modifiers) && !session.textPath.isBlank()) { int slash = session.textPath.lastIndexOf('/'); session.textRename = session.textPath.substring(slash + 1); session.textRenaming = true; @@ -2017,11 +2459,21 @@ public final class ComputerScreen extends Screen { requestAntmailState(false); } + private boolean antmailUnconfigured() { + AntmailResultPayload mailbox = AntmailClientState.getMailbox(position); + if (mailbox != null) return mailbox.address().isBlank(); + AntmailResultPayload response = AntmailClientState.get(position); + return response != null && ("unconfigured".equals(response.detail()) + || "address_not_found".equals(response.detail()) + || response.detail().startsWith("registration_failed:")); + } + private void requestAntmailState(boolean force) { int folder = session.antmailMode.equals("sent") ? com.craisinlord.antarchy.content.network.AntmailStateRequestPayload.SENT : session.antmailMode.equals("drafts") ? com.craisinlord.antarchy.content.network.AntmailStateRequestPayload.DRAFTS : com.craisinlord.antarchy.content.network.AntmailStateRequestPayload.INBOX; AntmailNetworking.requestState(position, folder, session.antmailPage, force ? 0L : AntmailClientState.getVersion(position)); + if (force) session.antmailStateWaitTicks = 0; } private void setupAntmail() { @@ -2054,23 +2506,22 @@ public final class ComputerScreen extends Screen { session.antmailStatus = "SEND ERROR // SAVE TEXT FILE FIRST"; return; } - if (session.antmailAttachPaint && (session.paintDirty || session.paintName.isBlank() || session.paintName.startsWith("UNTITLED") || !session.antmailPaintPath.endsWith(session.paintName))) { + if (session.antmailAttachPaint && !session.antmailPaintLoadPendingPath.isBlank()) { + session.antmailStatus = "ATTACHMENT STILL LOADING // TRY AGAIN"; + return; + } + if (session.antmailAttachPaint && session.antmailPaintAttachment == null + && (session.paintDirty || session.paintName.isBlank() || session.paintName.startsWith("UNTITLED") + || !session.antmailPaintPath.endsWith(session.paintName))) { session.antmailStatus = "SEND ERROR // SAVE PAINTING FIRST"; return; } List attachments = composeAttachments(); + session.antmailSendBaseline = AntmailClientState.get(position); AntmailNetworking.send(position, session.antmailRecipient, session.antmailSubject, session.antmailBody, attachments); - if (session.antmailDraftId != null) AntmailNetworking.deleteDraft(position, session.antmailDraftId); - session.antmailStatus = "MESSAGE SENT"; - session.antmailMode = "inbox"; + session.antmailStatus = "SENDING MESSAGE"; + session.antmailSendPending = true; session.antmailFocused = false; - session.antmailRecipient = ""; - session.antmailSubject = ""; - session.antmailBody = ""; - session.antmailAttachText = false; - session.antmailAttachPaint = false; - session.antmailTextPath = ""; - session.antmailPaintPath = ""; session.antmailDraftId = null; session.antmailDraftLoaded = false; session.antmailPickingAttachment = false; @@ -2106,6 +2557,8 @@ public final class ComputerScreen extends Screen { session.antmailAttachPaint = true; session.paintName = paint.fileName(); session.antmailPaintPath = "/pictures/" + paint.fileName(); + session.antmailPaintAttachment = paint; + session.antmailPaintLoadPendingPath = ""; AntPaintCanvas canvas = new AntPaintCanvas(); byte[] pixels = paint.pixels(); for (int index = 0; index < pixels.length; index++) if (pixels[index] == 1) canvas.set(index % AntPaintCanvas.WIDTH, index / AntPaintCanvas.WIDTH, true); @@ -2124,13 +2577,16 @@ public final class ComputerScreen extends Screen { session.antmailRecipient.isBlank() ? null : AntmailAddress.parse(session.antmailRecipient), session.antmailSubject, session.antmailBody, composeAttachments()); AntmailNetworking.saveDraft(position, draft); } catch (RuntimeException ignored) { + session.antmailStatus = "DRAFT SAVE ERROR // CHECK RECIPIENT"; } } private List composeAttachments() { List attachments = new ArrayList<>(); if (session.antmailAttachText) attachments.add(new AntmailAttachment.TextFile(session.textPath.substring(session.textPath.lastIndexOf('/') + 1), session.textContent)); - if (session.antmailAttachPaint) { + if (session.antmailAttachPaint && session.antmailPaintAttachment != null) { + attachments.add(session.antmailPaintAttachment); + } else if (session.antmailAttachPaint) { byte[] pixels = new byte[AntPaintCanvas.WIDTH * AntPaintCanvas.HEIGHT]; for (int py = 0; py < AntPaintCanvas.HEIGHT; py++) for (int px = 0; px < AntPaintCanvas.WIDTH; px++) pixels[py * AntPaintCanvas.WIDTH + px] = (byte) (session.paintCanvas.get(px, py) ? 1 : 0); attachments.add(new AntmailAttachment.PaintImage(session.paintName, AntPaintCanvas.WIDTH, AntPaintCanvas.HEIGHT, pixels)); @@ -2138,6 +2594,24 @@ public final class ComputerScreen extends Screen { return attachments; } + private void syncAntmailPaintAttachment() { + String requestedPath = session.antmailPaintLoadPendingPath; + if (requestedPath.isBlank()) return; + ComputerFileSystemClientState.State fileState = ComputerFileSystemClientState.get(position); + if (!requestedPath.equals(fileState.openedPath()) || fileState.openedContents().isBlank()) return; + try { + AntPaintFile file = AntPaintFileCodec.decode(Base64.getDecoder().decode(fileState.openedContents())); + session.paintName = file.filename(); + session.antmailPaintPath = requestedPath; + session.antmailPaintAttachment = AntmailAttachmentFiles.fromPaintFile(file); + session.antmailPaintLoadPendingPath = ""; + session.antmailStatus = "PAINTING ATTACHED // " + file.filename(); + } catch (RuntimeException exception) { + session.antmailPaintLoadPendingPath = ""; + session.antmailStatus = "ATTACHMENT ERROR // INVALID PAINT FILE"; + } + } + private void saveAntmailAttachment(AntmailAttachment attachment) { try { if (attachment instanceof AntmailAttachment.TextFile text) { @@ -2257,6 +2731,9 @@ public final class ComputerScreen extends Screen { String savedPath = path; boolean exists = ComputerFileSystemClientState.get(position).files().stream().anyMatch(entry -> entry.contains("\t" + savedPath + "\t")); if (exists) ComputerNetworking.saveFile(position, savedPath, encoded); else ComputerNetworking.createFile(position, savedPath, encoded); + session.antmailPaintPath = savedPath; + session.antmailPaintAttachment = null; + session.antmailPaintLoadPendingPath = ""; session.paintDirty = false; ComputerNetworking.listFiles(position); } @@ -2284,19 +2761,48 @@ public final class ComputerScreen extends Screen { } } - private void paintAt(double mouseX, double mouseY, int x, int y, int w, int h, boolean draw) { + private void paintAt(double mouseX, double mouseY, int x, int y, int w, int h) { int size = Math.min(w / AntPaintCanvas.WIDTH, h / AntPaintCanvas.HEIGHT); if (size <= 0) return; int canvasX = x + (w - size * AntPaintCanvas.WIDTH) / 2; int px = (int) ((mouseX - canvasX) / size); int canvasY = y + Math.max(0, (h - size * AntPaintCanvas.HEIGHT) / 2); int py = (int) ((mouseY - canvasY) / size); - if (px < 0 || py < 0 || px >= AntPaintCanvas.WIDTH || py >= AntPaintCanvas.HEIGHT) return; - session.paintHistory.record(session.paintCanvas); - if (session.paintTool == AntPaintTool.PENCIL) session.paintCanvas.draw(px, py); - else if (session.paintTool == AntPaintTool.ERASER) session.paintCanvas.erase(px, py); - else session.paintCanvas.floodFill(px, py, true); - session.paintDirty = true; + if (px < 0 || py < 0 || px >= AntPaintCanvas.WIDTH || py >= AntPaintCanvas.HEIGHT) { + lastPaintPixelX = -1; + lastPaintPixelY = -1; + return; + } + if (session.paintTool == AntPaintTool.FILL) { + session.paintHistory.record(session.paintCanvas); + if (session.paintCanvas.floodFill(px, py, true) > 0) session.paintDirty = true; + return; + } + if (lastPaintPixelX < 0 || lastPaintPixelY < 0) applyPaintPixel(px, py); + else { + int dx = Math.abs(px - lastPaintPixelX); + int dy = Math.abs(py - lastPaintPixelY); + int stepX = lastPaintPixelX < px ? 1 : -1; + int stepY = lastPaintPixelY < py ? 1 : -1; + int error = dx - dy; + int currentX = lastPaintPixelX; + int currentY = lastPaintPixelY; + while (currentX != px || currentY != py) { + int twiceError = error * 2; + if (twiceError > -dy) { error -= dy; currentX += stepX; } + if (twiceError < dx) { error += dx; currentY += stepY; } + applyPaintPixel(currentX, currentY); + } + } + lastPaintPixelX = px; + lastPaintPixelY = py; + } + + private void applyPaintPixel(int x, int y) { + boolean changed = session.paintTool == AntPaintTool.PENCIL + ? session.paintCanvas.draw(x, y) + : session.paintCanvas.erase(x, y); + if (changed) session.paintDirty = true; } private boolean hasControl(int modifiers) { @@ -2361,14 +2867,19 @@ public final class ComputerScreen extends Screen { } private void open(String type) { - for (Window window : session.windows) if (window.type.equals(type)) { + for (int index = 0; index < session.windows.size(); index++) { + Window window = session.windows.get(index); + if (!window.type.equals(type)) continue; window.minimized = false; - session.windows.remove(window); + session.windows.remove(index); session.windows.add(window); + activeWindow = window; if (type.equals("FILES")) ComputerNetworking.listFiles(position); return; } - session.windows.add(new Window(type, TITLES.get(type), 112 + session.windows.size() * 12, 52 + session.windows.size() * 10)); + Window window = new Window(type, TITLES.get(type), 112 + session.windows.size() * 12, 52 + session.windows.size() * 10); + session.windows.add(window); + activeWindow = window; if (type.equals("ANTMAIL")) requestAntmailState(); else if (type.equals("FILES")) ComputerNetworking.listFiles(position); } @@ -2382,11 +2893,11 @@ public final class ComputerScreen extends Screen { for (int index = session.windows.size() - 1; index >= 0; index--) { Window window = session.windows.get(index); if (window.minimized) continue; - int windowX = left + window.x; - int windowY = top + window.y; - int windowWidth = window.maximized ? WIDTH - 28 : window.width; - int windowHeight = window.maximized ? HEIGHT - 48 : window.height; - if (inside(windowX, windowY + 20, windowWidth, windowHeight - 20, localMouseX, localMouseY)) return window; + int windowX = left + windowLocalX(window); + int windowY = top + windowLocalY(window); + int renderedWidth = windowWidth(window); + int renderedHeight = windowHeight(window); + if (inside(windowX, windowY + TITLE_BAR_HEIGHT, renderedWidth, renderedHeight - TITLE_BAR_HEIGHT, localMouseX, localMouseY)) return window; } return null; } @@ -2447,8 +2958,14 @@ public final class ComputerScreen extends Screen { private boolean paintDirty; private String antmailUsername = ""; private String antmailStatus = ""; + private boolean antmailSendPending; + private AntmailResultPayload antmailSendBaseline; + private String antmailRetryMessageId = ""; + private AntmailResultPayload antmailRetryBaseline; + private int antmailRetryTicks; private boolean antmailRegistrationPending; private int antmailRefreshTicks; + private int antmailStateWaitTicks; private boolean antmailFocused; private String antmailMode = "inbox"; private String antmailRecipient = ""; @@ -2458,10 +2975,14 @@ public final class ComputerScreen extends Screen { private boolean antmailSent; private int antmailMessageIndex = -1; private int antmailPage; + private int antmailAttachmentStartLine = -1; + private int antmailVisibleAttachmentCount; private boolean antmailAttachText; private boolean antmailAttachPaint; private String antmailTextPath = ""; private String antmailPaintPath = ""; + private AntmailAttachment.PaintImage antmailPaintAttachment; + private String antmailPaintLoadPendingPath = ""; private UUID antmailDraftId; private boolean antmailDraftLoaded; private boolean antmailPickingAttachment; @@ -2487,6 +3008,8 @@ public final class ComputerScreen extends Screen { private final int height = 210; private int x; private int y; + private int restoreX; + private int restoreY; private int renderWidth = width; private int renderHeight = height; private boolean minimized; @@ -2494,6 +3017,13 @@ public final class ComputerScreen extends Screen { private boolean gameOpen; private String gameId = "basilisk"; - private Window(String type, String title, int x, int y) { this.type = type; this.title = title; this.x = x; this.y = y; } + private Window(String type, String title, int x, int y) { + this.type = type; + this.title = title; + this.x = x; + this.y = y; + this.restoreX = x; + this.restoreY = y; + } } } diff --git a/common-1.21.1/src/main/java/com/craisinlord/antarchy/content/command/QueenLocateCommand.java b/common-1.21.1/src/main/java/com/craisinlord/antarchy/content/command/QueenLocateCommand.java index fb5fba4a..5d25b404 100644 --- a/common-1.21.1/src/main/java/com/craisinlord/antarchy/content/command/QueenLocateCommand.java +++ b/common-1.21.1/src/main/java/com/craisinlord/antarchy/content/command/QueenLocateCommand.java @@ -69,6 +69,10 @@ public final class QueenLocateCommand { continue; } 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(); if (queen != null) { spawn = BlockPos.containing(queen.position()); @@ -77,7 +81,8 @@ public final class QueenLocateCommand { String coordinates = "[" + spawn.getX() + ", " + spawn.getY() + ", " + spawn.getZ() + "]"; String teleportCommand = "/execute in " + THORAXIS.location() + " run tp @s " + 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) .withStyle(Style.EMPTY .withColor(net.minecraft.ChatFormatting.AQUA) diff --git a/common-1.21.1/src/main/java/com/craisinlord/antarchy/content/computer/blockle/BlockleAnswers.java b/common-1.21.1/src/main/java/com/craisinlord/antarchy/content/computer/blockle/BlockleAnswers.java index c33e009a..a0ed05a2 100644 --- a/common-1.21.1/src/main/java/com/craisinlord/antarchy/content/computer/blockle/BlockleAnswers.java +++ b/common-1.21.1/src/main/java/com/craisinlord/antarchy/content/computer/blockle/BlockleAnswers.java @@ -1,76 +1,98 @@ package com.craisinlord.antarchy.content.computer.blockle; import com.craisinlord.antarchy.Antarchy; +import com.google.gson.JsonElement; +import com.google.gson.JsonParser; 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.Map; -public final class BlockleAnswers { +public final class BlockleAnswers extends SimplePreparableReloadListener> { public static final int VERSION = 1; private static final int SALT = 0x4A17; - private static final List ANSWERS = List.of( - new Answer("anvil", id("anvil")), - new Answer("apple", id("apple")), - new Answer("arrow", id("arrow")), - new Answer("bread", id("bread")), - new Answer("brick", id("brick")), - new Answer("chain", id("chain")), - new Answer("chest", id("chest")), - new Answer("clock", id("clock")), - new Answer("cocoa", id("cocoa")), - new Answer("flint", id("flint")), - new Answer("glass", id("glass")), - new Answer("melon", id("melon")), - new Answer("paper", id("paper")), - new Answer("poppy", id("poppy")), - new Answer("sculk", id("sculk")), - new Answer("stone", id("stone")), - new Answer("stick", id("stick")), - new Answer("sugar", id("sugar")), - new Answer("torch", id("torch")), - new Answer("vault", id("vault")), - new Answer("wheat", id("wheat")), - new Answer("allay", id("allay_spawn_egg")), - new Answer("blaze", id("blaze_spawn_egg")), - new Answer("camel", id("camel_spawn_egg")), - 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 final ResourceLocation ANSWERS_FILE = ResourceLocation.fromNamespaceAndPath(Antarchy.MODID, "blockle/answers.json"); + private static final BlockleAnswers INSTANCE = new BlockleAnswers(); + private static final Map ANSWER_ITEMS = Map.ofEntries( + Map.entry("anvil", vanilla("anvil")), Map.entry("apple", vanilla("apple")), + Map.entry("arrow", vanilla("arrow")), Map.entry("bread", vanilla("bread")), + Map.entry("brick", vanilla("brick")), Map.entry("chain", vanilla("chain")), + Map.entry("chest", vanilla("chest")), Map.entry("clock", vanilla("clock")), + Map.entry("cocoa", vanilla("cocoa")), Map.entry("flint", vanilla("flint")), + Map.entry("glass", vanilla("glass")), Map.entry("melon", vanilla("melon")), + Map.entry("paper", vanilla("paper")), Map.entry("poppy", vanilla("poppy")), + Map.entry("sculk", vanilla("sculk")), Map.entry("stone", vanilla("stone")), + Map.entry("stick", vanilla("stick")), Map.entry("sugar", vanilla("sugar")), + Map.entry("torch", vanilla("torch")), Map.entry("vault", vanilla("vault")), + Map.entry("wheat", vanilla("wheat")), Map.entry("allay", vanilla("allay_spawn_egg")), + Map.entry("blaze", vanilla("blaze_spawn_egg")), Map.entry("camel", vanilla("camel_spawn_egg")), + Map.entry("drown", vanilla("drowned_spawn_egg")), Map.entry("ghast", vanilla("ghast_spawn_egg")), + Map.entry("horse", vanilla("horse_spawn_egg")), Map.entry("llama", vanilla("llama_spawn_egg")), + Map.entry("panda", vanilla("panda_spawn_egg")), Map.entry("sheep", vanilla("sheep_spawn_egg")), + Map.entry("slime", vanilla("slime_spawn_egg")), Map.entry("squid", vanilla("squid_spawn_egg")), + Map.entry("stray", vanilla("stray_spawn_egg")), Map.entry("witch", vanilla("witch_spawn_egg")), + Map.entry("lever", vanilla("lever")), Map.entry("sword", vanilla("iron_sword")), + Map.entry("cheep", antarchy("cheep_spawn_egg")), Map.entry("jerry", antarchy("jerry_spawn_egg")), + Map.entry("lucid", antarchy("lucid_spawn_egg")), Map.entry("queen", antarchy("queen_spawn_egg")), + Map.entry("ichor", antarchy("ichor")), Map.entry("lotus", antarchy("lotus")), + Map.entry("mucus", antarchy("mucus")), Map.entry("peach", antarchy("peach")) ); + private static volatile List answers = List.of(); private BlockleAnswers() { } + public static BlockleAnswers instance() { + return INSTANCE; + } + public static Answer answerForDay(long day) { - int index = (int) Math.floorMod(day + SALT, ANSWERS.size()); - return ANSWERS.get(index); + List currentAnswers = answers; + 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() { - return ANSWERS.size(); + return answers.size(); + } + + @Override + protected List 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 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 loadedAnswers, ResourceManager resourceManager, ProfilerFiller profiler) { + answers = loadedAnswers; + Antarchy.LOGGER.info("Loaded {} valid Blockle answers", answers.size()); } public record Answer(String word, ResourceLocation itemId) { } - private static ResourceLocation id(String path) { + private static ResourceLocation vanilla(String path) { return ResourceLocation.withDefaultNamespace(path); } diff --git a/common-1.21.1/src/main/java/com/craisinlord/antarchy/content/effect/CommandedBehavior.java b/common-1.21.1/src/main/java/com/craisinlord/antarchy/content/effect/CommandedBehavior.java index 431e316a..754b9494 100644 --- a/common-1.21.1/src/main/java/com/craisinlord/antarchy/content/effect/CommandedBehavior.java +++ b/common-1.21.1/src/main/java/com/craisinlord/antarchy/content/effect/CommandedBehavior.java @@ -1,13 +1,18 @@ package com.craisinlord.antarchy.content.effect; +import com.craisinlord.antarchy.config.AntarchySettings; import java.util.UUID; import net.minecraft.world.entity.Entity; import net.minecraft.world.entity.LivingEntity; import net.minecraft.world.entity.Mob; import net.minecraft.server.level.ServerLevel; 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 { + 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() { } @@ -26,6 +31,7 @@ public final class CommandedBehavior { clear(mob); return; } + syncInvestiture(mob, access.antarchy$isRoyalInvested()); if (mob.tickCount % 10 == 0) { LivingEntity commandedCandidate = findCommanderTarget(mob, commander); if (commandedCandidate != null) { @@ -100,6 +106,11 @@ public final class CommandedBehavior { } 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; if (isValidTarget(mob, commander, target)) { return target; @@ -130,7 +141,9 @@ public final class CommandedBehavior { if (mob.tickCount % 20 != 0 || mob.distanceToSqr(target) > 4.0D) { 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); } } @@ -151,6 +164,28 @@ public final class CommandedBehavior { mob.setTarget(null); } access.antarchy$setCommandedTargetOwned(false); + access.antarchy$setRoyalInvested(false); + syncInvestiture(mob, false); 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 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); + } + } } diff --git a/common-1.21.1/src/main/java/com/craisinlord/antarchy/content/effect/CommandedEntityAccess.java b/common-1.21.1/src/main/java/com/craisinlord/antarchy/content/effect/CommandedEntityAccess.java index 6907148d..18904cbd 100644 --- a/common-1.21.1/src/main/java/com/craisinlord/antarchy/content/effect/CommandedEntityAccess.java +++ b/common-1.21.1/src/main/java/com/craisinlord/antarchy/content/effect/CommandedEntityAccess.java @@ -7,4 +7,6 @@ public interface CommandedEntityAccess { void antarchy$setCommanderUuid(UUID uuid); boolean antarchy$isCommandedTargetOwned(); void antarchy$setCommandedTargetOwned(boolean owned); + boolean antarchy$isRoyalInvested(); + void antarchy$setRoyalInvested(boolean invested); } diff --git a/common-1.21.1/src/main/java/com/craisinlord/antarchy/content/effect/JudgmentMarkAccess.java b/common-1.21.1/src/main/java/com/craisinlord/antarchy/content/effect/JudgmentMarkAccess.java new file mode 100644 index 00000000..3026c5bf --- /dev/null +++ b/common-1.21.1/src/main/java/com/craisinlord/antarchy/content/effect/JudgmentMarkAccess.java @@ -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); +} diff --git a/common-1.21.1/src/main/java/com/craisinlord/antarchy/content/effect/JudgmentMarkManager.java b/common-1.21.1/src/main/java/com/craisinlord/antarchy/content/effect/JudgmentMarkManager.java new file mode 100644 index 00000000..934cd075 --- /dev/null +++ b/common-1.21.1/src/main/java/com/craisinlord/antarchy/content/effect/JudgmentMarkManager.java @@ -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 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 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 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 byOwner = new HashMap<>(); + private final Map> ownersByTarget = new HashMap<>(); + } +} diff --git a/common-1.21.1/src/main/java/com/craisinlord/antarchy/content/effect/RoyalBoundaryManager.java b/common-1.21.1/src/main/java/com/craisinlord/antarchy/content/effect/RoyalBoundaryManager.java new file mode 100644 index 00000000..dda752e4 --- /dev/null +++ b/common-1.21.1/src/main/java/com/craisinlord/antarchy/content/effect/RoyalBoundaryManager.java @@ -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> BOUNDARIES = new WeakHashMap<>(); + private static final Map> 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 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 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 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 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 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 boundaries = BOUNDARIES.get(level); + if (boundaries == null || boundaries.isEmpty() || projectile.isRemoved()) return; + var boundaryIterator = boundaries.entrySet().iterator(); + while (boundaryIterator.hasNext()) { + Map.Entry 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 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 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; + } + } +} diff --git a/common-1.21.1/src/main/java/com/craisinlord/antarchy/content/effect/RoyalVitalityPenaltyEffect.java b/common-1.21.1/src/main/java/com/craisinlord/antarchy/content/effect/RoyalVitalityPenaltyEffect.java new file mode 100644 index 00000000..9df462f3 --- /dev/null +++ b/common-1.21.1/src/main/java/com/craisinlord/antarchy/content/effect/RoyalVitalityPenaltyEffect.java @@ -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); + } +} diff --git a/common-1.21.1/src/main/java/com/craisinlord/antarchy/content/entity/ant/AntTeleportHelper.java b/common-1.21.1/src/main/java/com/craisinlord/antarchy/content/entity/ant/AntTeleportHelper.java index 389ba6a5..58fc5bbc 100644 --- a/common-1.21.1/src/main/java/com/craisinlord/antarchy/content/entity/ant/AntTeleportHelper.java +++ b/common-1.21.1/src/main/java/com/craisinlord/antarchy/content/entity/ant/AntTeleportHelper.java @@ -113,6 +113,9 @@ public final class AntTeleportHelper { } Vec3 destinationPos = getDestinationPosition(player, destination); + if (destinationPos == null) { + return failArrival(player); + } teleportPlayerWithCompanions(player, destination, destinationPos); player.playSound(SoundEvents.ENDERMAN_TELEPORT, 1.0F, 1.0F); return InteractionResult.CONSUME; @@ -121,6 +124,9 @@ public final class AntTeleportHelper { public static InteractionResult teleportPlayerToReturnDestination(ServerPlayer player) { ServerLevel destination = resolveReturnDestinationLevel(player); Vec3 destinationPos = getDestinationPosition(player, destination); + if (destinationPos == null) { + return failArrival(player); + } teleportPlayerWithCompanions(player, destination, destinationPos); player.playSound(SoundEvents.ENDERMAN_TELEPORT, 1.0F, 1.0F); return InteractionResult.CONSUME; @@ -160,6 +166,9 @@ public final class AntTeleportHelper { } Vec3 destinationPos = getDestinationPosition(player, destination); + if (destinationPos == null) { + return failArrival(player); + } teleportPlayerWithCompanions(player, destination, destinationPos); player.playSound(SoundEvents.ENDERMAN_TELEPORT, 1.0F, 1.0F); return InteractionResult.CONSUME; @@ -245,6 +254,7 @@ public final class AntTeleportHelper { return player.server.overworld(); } + @Nullable public static Vec3 getDestinationPosition(ServerPlayer player, ServerLevel destination) { BlockPos respawnPos = player.getRespawnPosition(); BlockPos preferredPos; @@ -255,11 +265,7 @@ public final class AntTeleportHelper { } Vec3 safeArrivalPos = findSafeArrivalPosition(player, destination, preferredPos); - if (safeArrivalPos != null) { - return safeArrivalPos; - } - - return createEmergencyArrivalPlatform(destination, findEmergencyPlatformPosition(destination, preferredPos)); + return safeArrivalPos; } @Nullable @@ -310,7 +316,7 @@ public final class AntTeleportHelper { /** * Arrival validation needs neighboring chunks for collision checks. Load a * 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) { ChunkPos center = new ChunkPos(preferredPos); @@ -343,6 +349,7 @@ public final class AntTeleportHelper { @Nullable private static Vec3 findSafeArrivalPositionInYRange(ServerPlayer player, ServerLevel destination, BlockPos preferredPos, int minY, int maxY) { Set surfaceCandidates = new LinkedHashSet<>(); + boolean elythia = destination.dimension() == PermanentPortalType.ELYTHIA.primaryDimension(); for (int radius = 0; radius <= ARRIVAL_SEARCH_RADIUS; radius++) { for (int xOff = -radius; xOff <= radius; xOff++) { for (int zOff = -radius; zOff <= radius; zOff++) { @@ -357,12 +364,19 @@ public final class AntTeleportHelper { } BlockPos surface = destination.getHeightmapPos(Heightmap.Types.MOTION_BLOCKING_NO_LEAVES, column); - addArrivalCandidate(surfaceCandidates, surface); - addArrivalCandidate(surfaceCandidates, destination.getHeightmapPos(Heightmap.Types.WORLD_SURFACE, column)); + if (elythia) { + surfaceCandidates.add(surface); + surfaceCandidates.add(surface.above()); + } else { + addArrivalCandidate(surfaceCandidates, surface); + addArrivalCandidate(surfaceCandidates, destination.getHeightmapPos(Heightmap.Types.WORLD_SURFACE, column)); + } for (int yOffset = 1; yOffset <= ARRIVAL_VERTICAL_SEARCH; yOffset++) { surfaceCandidates.add(surface.above(yOffset)); - surfaceCandidates.add(surface.below(yOffset)); + if (!elythia) { + surfaceCandidates.add(surface.below(yOffset)); + } } } } @@ -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 - // 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 xOff = -radius; xOff <= radius; xOff++) { for (int zOff = -radius; zOff <= radius; zOff++) { @@ -442,6 +463,37 @@ public final class AntTeleportHelper { } 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()) { return true; } @@ -469,98 +521,8 @@ public final class AntTeleportHelper { return true; } - private static BlockPos getClampedFallbackPosition(ServerLevel destination, BlockPos preferredPos) { - int minY = destination.getMinBuildHeight() + 1; - int maxY = destination.getMaxBuildHeight() - 1; - 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(); + private static InteractionResult failArrival(ServerPlayer player) { + player.displayClientMessage(Component.translatable("message.antarchy.teleport_arrival_failed"), true); + return InteractionResult.CONSUME; } } diff --git a/common-1.21.1/src/main/java/com/craisinlord/antarchy/content/entity/ant/BaseAntEntity.java b/common-1.21.1/src/main/java/com/craisinlord/antarchy/content/entity/ant/BaseAntEntity.java index c760ab23..0d3df195 100644 --- a/common-1.21.1/src/main/java/com/craisinlord/antarchy/content/entity/ant/BaseAntEntity.java +++ b/common-1.21.1/src/main/java/com/craisinlord/antarchy/content/entity/ant/BaseAntEntity.java @@ -1323,6 +1323,7 @@ public abstract class BaseAntEntity extends Animal implements GeoEntity { return AntTeleportHelper.resolveReturnDestinationLevel(player); } + @Nullable protected Vec3 getDestinationPosition(ServerLevel destination, ServerPlayer player) { return AntTeleportHelper.getDestinationPosition(player, destination); } diff --git a/common-1.21.1/src/main/java/com/craisinlord/antarchy/content/entity/ant/RainbowAntEntity.java b/common-1.21.1/src/main/java/com/craisinlord/antarchy/content/entity/ant/RainbowAntEntity.java index e642d24c..8c368c31 100644 --- a/common-1.21.1/src/main/java/com/craisinlord/antarchy/content/entity/ant/RainbowAntEntity.java +++ b/common-1.21.1/src/main/java/com/craisinlord/antarchy/content/entity/ant/RainbowAntEntity.java @@ -123,6 +123,10 @@ public class RainbowAntEntity extends BaseAntEntity implements GeoEntity { if (this.isInInfinityDimension(serverPlayer.serverLevel())) { ServerLevel returnLevel = this.resolveReturnDestinationLevel(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.playSound(SoundEvents.ENDERMAN_TELEPORT, 1.0F, 1.0F); return InteractionResult.CONSUME; diff --git a/common-1.21.1/src/main/java/com/craisinlord/antarchy/content/entity/nightmare/NightmareBiteEntity.java b/common-1.21.1/src/main/java/com/craisinlord/antarchy/content/entity/nightmare/NightmareBiteEntity.java index 54a7f75a..c371e131 100644 --- a/common-1.21.1/src/main/java/com/craisinlord/antarchy/content/entity/nightmare/NightmareBiteEntity.java +++ b/common-1.21.1/src/main/java/com/craisinlord/antarchy/content/entity/nightmare/NightmareBiteEntity.java @@ -3,6 +3,9 @@ package com.craisinlord.antarchy.content.entity.nightmare; import com.craisinlord.antarchy.config.AntarchySettings; import com.craisinlord.antarchy.content.AntarchySoundEvents; 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.network.syncher.EntityDataAccessor; import net.minecraft.network.syncher.EntityDataSerializers; @@ -30,6 +33,7 @@ import java.util.UUID; public class NightmareBiteEntity extends Entity implements GeoEntity { private static final EntityDataAccessor PHASE_TWO = SynchedEntityData.defineId(NightmareBiteEntity.class, EntityDataSerializers.BOOLEAN); + private static final EntityDataAccessor TELEGRAPH_ONLY = SynchedEntityData.defineId(NightmareBiteEntity.class, EntityDataSerializers.BOOLEAN); private static final RawAnimation BITE_ANIM = RawAnimation.begin().thenPlay("bite"); private static final int LIFETIME_TICKS = 12; private static final int SOUND_TICK = 7; @@ -64,9 +68,21 @@ public class NightmareBiteEntity extends Entity implements GeoEntity { 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 protected void defineSynchedData(SynchedEntityData.Builder builder) { builder.define(PHASE_TWO, false); + builder.define(TELEGRAPH_ONLY, false); } @Override @@ -106,6 +122,14 @@ public class NightmareBiteEntity extends Entity implements GeoEntity { 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 public void tick() { super.tick(); @@ -113,7 +137,8 @@ public class NightmareBiteEntity extends Entity implements GeoEntity { return; } 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(); return; } @@ -126,7 +151,9 @@ public class NightmareBiteEntity extends Entity implements GeoEntity { } if (!this.biteDamageApplied && nextAge >= DAMAGE_TICK) { this.biteDamageApplied = true; - this.performBite(target); + if (!this.isTelegraphOnly()) { + this.performBite(target); + } } if (nextAge >= LIFETIME_TICKS) { this.discard(); @@ -143,6 +170,10 @@ public class NightmareBiteEntity extends Entity implements GeoEntity { } 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); if (forward.lengthSqr() < 1.0E-4D) { 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 = 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 backOffset = START_BACK_OFFSET + (END_BACK_OFFSET - START_BACK_OFFSET) * progress; - Vec3 desiredPos = target.position() - .add(0.0D, target.getBbHeight() * 0.55D, 0.0D) + Vec3 bodyCenter = target.position().add(AntarchyGravityRotationUtil.vecPlayerToWorld( + 0.0D, target.getBbHeight() * 0.55D, 0.0D, targetGravity)); + Vec3 desiredPos = bodyCenter .add(right.scale(this.approachSide * sideOffset)) .subtract(forward.scale(backOffset)); - Vec3 toTarget = target.getEyePosition().subtract(desiredPos); + Vec3 toTarget = bodyCenter.subtract(desiredPos); this.setPos(desiredPos.x, desiredPos.y, desiredPos.z); 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); } } @@ -177,6 +212,7 @@ public class NightmareBiteEntity extends Entity implements GeoEntity { @Override public void addAdditionalSaveData(CompoundTag tag) { tag.putBoolean("PhaseTwo", this.isPhaseTwo()); + tag.putBoolean("TelegraphOnly", this.isTelegraphOnly()); tag.putInt("AgeTicks", this.ageTicks); tag.putInt("ApproachSide", this.approachSide); tag.putBoolean("BiteSoundPlayed", this.biteSoundPlayed); @@ -189,6 +225,7 @@ public class NightmareBiteEntity extends Entity implements GeoEntity { @Override public void readAdditionalSaveData(CompoundTag tag) { this.setPhaseTwo(tag.getBoolean("PhaseTwo")); + this.setTelegraphOnly(tag.getBoolean("TelegraphOnly")); this.ageTicks = tag.getInt("AgeTicks"); this.approachSide = tag.getInt("ApproachSide"); this.biteSoundPlayed = tag.getBoolean("BiteSoundPlayed"); diff --git a/common-1.21.1/src/main/java/com/craisinlord/antarchy/content/entity/nightmare/NightmareEntity.java b/common-1.21.1/src/main/java/com/craisinlord/antarchy/content/entity/nightmare/NightmareEntity.java index d4b9b94a..87ee2f9d 100644 --- a/common-1.21.1/src/main/java/com/craisinlord/antarchy/content/entity/nightmare/NightmareEntity.java +++ b/common-1.21.1/src/main/java/com/craisinlord/antarchy/content/entity/nightmare/NightmareEntity.java @@ -642,6 +642,11 @@ public class NightmareEntity extends Monster implements GeoEntity { 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() { return this.entityData.get(ACTIVE_SPECIAL_ANIMATION); } diff --git a/common-1.21.1/src/main/java/com/craisinlord/antarchy/content/entity/royal/KingEntity.java b/common-1.21.1/src/main/java/com/craisinlord/antarchy/content/entity/royal/KingEntity.java index e59619e5..49129815 100644 --- a/common-1.21.1/src/main/java/com/craisinlord/antarchy/content/entity/royal/KingEntity.java +++ b/common-1.21.1/src/main/java/com/craisinlord/antarchy/content/entity/royal/KingEntity.java @@ -5,12 +5,16 @@ import com.craisinlord.antarchy.content.AntarchyObjects; import com.craisinlord.antarchy.content.AntarchySoundEvents; import com.craisinlord.antarchy.content.effect.RoyalEffectEligibility; 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.SoundEvents; import net.minecraft.sounds.SoundSource; import net.minecraft.network.chat.Component; import net.minecraft.ChatFormatting; 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.ClientboundSetSubtitleTextPacket; 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.Vec3; 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.RoyalBeamTerrainMode; import com.craisinlord.antarchy.content.entity.royal.beam.RoyalBeamElement; @@ -73,6 +78,10 @@ import java.util.Set; import java.util.UUID; 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 { 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_WINDUP_TICKS = 14; 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_JUMPS = 4; 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_ANGLE_STEP = 0.014D; 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 int COME_NO_CLOSER_PARTICLE_INTERVAL = 4; private static final int COME_NO_CLOSER_PARTICLE_COUNT = 40; private static final double CLOSE_QUARTERS_RADIUS = 16.0D; private static final int CLOSE_QUARTERS_APPROACH_TICKS = 120; 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_PER_HIT = 10; 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 COMBAT_WAYPOINT_MIN_TICKS = 45; 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 private Vec3 patrolCenter; @@ -127,11 +147,16 @@ public class KingEntity extends RoyalBossEntity { private int treePatrolFailures; private int decreeCooldownTicks; private int activeDecreeTicks; + private int decreeResponseGraceTicks; private int stompCooldownTicks; private int wingGustCooldownTicks; private int fireballCooldownTicks = 80; private int iceballCooldownTicks = 120; private int iceSpikeCooldownTicks = 160; + private int royalChargeOpeningDelayTicks = 35; + private boolean royalChargeActive; + private Vec3 royalChargeDirection = Vec3.ZERO; + private final Set royalChargeHitTargets = new HashSet<>(); private boolean decreeRetreatPressure; @Nullable private String lastDecreeKey; @@ -150,6 +175,9 @@ public class KingEntity extends RoyalBossEntity { private Vec3 kingCombatProgressPosition; private int kingCombatWaypointTicks; private int kingCombatProgressTicks; + private int kingGroundCombatWaypointTicks; + @Nullable + private Vec3 kingGroundCombatWaypoint; private int kingStrafeDirection = 1; private int royalBoundaryGraceTicks; @Nullable @@ -194,10 +222,16 @@ public class KingEntity extends RoyalBossEntity { } 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); } + @Override + public float scaleRoyalDamage(double damage) { + return super.scaleRoyalDamage(damage) * (float) KING_DAMAGE_MULTIPLIER; + } + @Override protected int maxConcurrentHeadAttacks(Phase phase) { return phase.maxConcurrentHeadAttacks(); @@ -205,14 +239,34 @@ public class KingEntity extends RoyalBossEntity { @Override protected boolean blocksRoyalAttacksForMovement() { - return this.kingLandingStage == KingLandingStage.TELEGRAPH + return this.royalChargeActive + || this.kingLandingStage == KingLandingStage.TELEGRAPH || this.kingLandingStage == KingLandingStage.DESCENT || this.kingLandingStage == KingLandingStage.IMPACT || 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 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) { this.endDecree(null); } @@ -226,7 +280,7 @@ public class KingEntity extends RoyalBossEntity { Component title = Component.translatable(nextPhase == Phase.TWO ? "boss.antarchy.king.phase_two" : "boss.antarchy.king.phase_three") .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)) { player.connection.send(new ClientboundSetTitlesAnimationPacket(5, 45, 10)); player.connection.send(new ClientboundSetTitleTextPacket(title)); @@ -239,7 +293,7 @@ public class KingEntity extends RoyalBossEntity { this.getNavigation().stop(); this.setDeltaMovement(this.getDeltaMovement().scale(0.7D)); 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(), 18, this.getBbWidth() * 0.25D, this.getBbHeight() * 0.25D, this.getBbWidth() * 0.25D, 0.08D); @@ -337,6 +391,12 @@ public class KingEntity extends RoyalBossEntity { : AntarchySoundEvents.KING_BEAM_SHOOT.get(); } + @Override + protected float royalBeamStartSoundVolume(RoyalHead head) { + return head.slot() == RoyalHead.Slot.LEFT + ? 0.18F : (float) AntarchySettings.royalBossSoundVolume(); + } + @Override 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) { java.util.Set struck = new java.util.HashSet<>(); 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++) { LivingEntity next = null; double bestScore = Double.MAX_VALUE; @@ -501,6 +561,9 @@ public class KingEntity extends RoyalBossEntity { if (this.tickCount % 20 == 0 && this.getArrowCount() > 6) { this.setArrowCount(6); } + if (this.royalChargeOpeningDelayTicks > 0) { + this.royalChargeOpeningDelayTicks--; + } if (this.isRoyalPhaseTransitionActive()) { return; } @@ -554,7 +617,7 @@ public class KingEntity extends RoyalBossEntity { double angle = (Math.PI * 2.0D * index) / COME_NO_CLOSER_PARTICLE_COUNT; double x = this.getX() + Math.cos(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, 1, 0.0D, 0.0D, 0.0D, 0.0D); } @@ -700,6 +763,10 @@ public class KingEntity extends RoyalBossEntity { } private void tickKingCombatMovement(LivingEntity target) { + if (this.royalChargeActive) { + this.getNavigation().stop(); + return; + } boolean closeQuarters = this.activeDecree instanceof CloseQuartersDecree; if (closeQuarters && this.isRoyalFlying() && this.kingLandingStage == KingLandingStage.AERIAL) { this.beginKingLanding(target); @@ -728,9 +795,13 @@ public class KingEntity extends RoyalBossEntity { this.setRoyalFlying(true); this.getMoveControl().setWantedPosition(landing.x, landing.y, landing.z, 1.35D); 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() || this.position().distanceToSqr(landing) <= 2.25D - || this.getY() <= landing.y + 1.25D; + || Math.abs(localLandingDelta.y) <= 1.25D && horizontalDistanceSquared <= 4.0D; if (reachedLanding) { this.setRoyalFlying(false); this.setPos(this.getX(), landing.y, this.getZ()); @@ -750,8 +821,8 @@ public class KingEntity extends RoyalBossEntity { if (--this.kingLandingStageTicks <= 0) { this.kingLandingStage = KingLandingStage.GROUND_COMBAT; this.kingLandingStageTicks = closeQuarters - ? Math.max(GROUND_ASSAULT_TICKS, this.activeDecreeTicks + 20) - : GROUND_ASSAULT_TICKS; + ? Math.max(this.groundAssaultTicksForPhase(), this.activeDecreeTicks + 20) + : this.groundAssaultTicksForPhase(); this.kingLandingWaypoint = null; } return; @@ -760,7 +831,14 @@ public class KingEntity extends RoyalBossEntity { if (closeQuarters) { 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) { this.beginKingTakeoff(target); } @@ -808,6 +886,9 @@ public class KingEntity extends RoyalBossEntity { this.kingCombatWaypoint = null; this.playRoyalSound(AntarchySoundEvents.KING_ROAR.get(), 1.12F); } 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; } } @@ -821,7 +902,8 @@ public class KingEntity extends RoyalBossEntity { this.performWingGust(level, target); } 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; } @@ -835,9 +917,11 @@ public class KingEntity extends RoyalBossEntity { double radius = Mth.lerp(progress, 4.0D, STOMP_RADIUS); for (int index = 0; index < 36; index++) { 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.y + 0.2D, + indicator.y, this.kingLandingWaypoint.z + Math.sin(angle) * radius, 1, 0.0D, 0.02D, 0.0D, 0.0D); } @@ -847,13 +931,15 @@ public class KingEntity extends RoyalBossEntity { private Vec3 findKingLandingPoint(LivingEntity target) { 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[] radii = {9.0D, 13.0D, 17.0D, 6.0D}; - for (int angleIndex = 0; angleIndex < 12; angleIndex++) { - double angle = baseAngle + angleIndex * Mth.TWO_PI / 12.0D; + double[] radii = {9.0D, 13.0D, 17.0D, 6.0D, 4.0D, 2.0D, 0.0D}; + for (int angleIndex = 0; angleIndex < 16; angleIndex++) { + double angle = baseAngle + angleIndex * Mth.TWO_PI / 16.0D; for (double radius : radii) { double x = target.getX() + Math.cos(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); AABB box = this.getBoundingBox().move( 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) { @@ -871,22 +975,71 @@ public class KingEntity extends RoyalBossEntity { 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 range = this.activeDecree instanceof ComeNoCloserDecree || this.projectilePressure >= 30 - ? 40.0D : 34.0D; - Vec3 horizontal = target.position().add(radial.scale(range)).add(tangent.scale(12.0D)); - double groundY = this.groundYBelow(horizontal.x, horizontal.z); - double y = Mth.clamp(target.getY() + 8.0D, groundY + FLYING_MIN_HOVER, - groundY + FLYING_MAX_HOVER_ABOVE_GROUND); - this.kingCombatWaypoint = new Vec3(horizontal.x, y, horizontal.z); + ? 40.0D : this.aerialCombatRangeForPhase(); + double currentAngle = Math.atan2(radial.z, radial.x); + Vec3 orbitWaypoint = null; + boolean treeBoundCombat = this.treePatrolBound && this.treePatrolCenter != null; + double boundaryRadius = treeBoundCombat + ? 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.random.nextInt(COMBAT_WAYPOINT_VARIANCE + 1); this.kingCombatProgressPosition = this.position(); this.kingCombatProgressTicks = 0; - if (this.random.nextInt(4) == 0) { - this.kingStrafeDirection *= -1; - } + // Keep a consistent orbit direction for several passes so movement reads as circling. } private void stabilizeKingRotation() { @@ -908,11 +1061,38 @@ public class KingEntity extends RoyalBossEntity { this.kingCombatProgressPosition = null; this.kingCombatWaypointTicks = 0; this.kingCombatProgressTicks = 0; + this.kingGroundCombatWaypoint = null; + this.kingGroundCombatWaypointTicks = 0; if (this.groundAssaultCooldownTicks <= 0) { 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) { if (target != null && target.isAlive() && this.isRoyalFlying() && this.kingLandingStage == KingLandingStage.AERIAL) { @@ -937,6 +1117,13 @@ public class KingEntity extends RoyalBossEntity { ? 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() { if (this.exileTarget == null && this.titheTarget == null && this.targetShotPlayer == null && this.sealedTarget == null) { 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.hasImpulse = true; this.playRoyalSound(AntarchySoundEvents.KING_WING_FLAP.get(), 0.82F); + if (this.level() instanceof ServerLevel level) { + this.spawnKingWingPressure(level, away); + } this.exileWingAnimationPending = true; } } @@ -1147,11 +1337,12 @@ public class KingEntity extends RoyalBossEntity { this.endDecree(this.getTarget()); this.activeDecree = decree; this.activeDecreeTicks = 300; + this.decreeResponseGraceTicks = DECREE_RESPONSE_GRACE_TICKS; this.decreeCooldownTicks = 0; this.closeQuartersEntered.clear(); this.closeQuartersOutsideTicks.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()); return true; } @@ -1189,7 +1380,7 @@ public class KingEntity extends RoyalBossEntity { case KINGS_SEAL -> { this.sealUntil = level.getGameTime() + 100L; 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); this.startSeal(target); return true; @@ -1249,12 +1440,16 @@ public class KingEntity extends RoyalBossEntity { || this.activeDecree instanceof ComeNoCloserDecree ; + if (!forceFar && this.activeDecree == null && this.tryStartRoyalCharge(level, target)) { + return; + } + { double distance = target.distanceTo(this); if (this.attackScheduler.ready("stomp", RoyalAttackLane.BODY) && (distance <= STOMP_RADIUS || skyIsMine) && 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() { @Override public void onStart() { 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); } } else if (this.attackScheduler.ready("wing_gust", RoyalAttackLane.BODY) - && distance <= WING_GUST_RADIUS && (forceFar || this.random.nextInt(3) == 0) - && this.beginRoyalAttack("wing_gust", RoyalAttackLane.BODY, WING_GUST_COOLDOWN_TICKS, + && distance <= WING_GUST_RADIUS && (forceFar || this.random.nextInt(this.phase() == Phase.ONE ? 3 : 2) == 0) + && 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() { @Override public void onStart() { 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) && !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, AntarchySettings.kingRoyalMusterCooldownTicks(), AntarchySettings.kingRoyalMusterWindupTicks(), 1, @@ -1289,8 +1484,8 @@ public class KingEntity extends RoyalBossEntity { new com.craisinlord.antarchy.content.entity.royal.attack.RoyalAttackScheduler.Action() { @Override public void onStart() { KingEntity.this.triggerAnim("body_action", "minion_spawn"); - KingEntity.this.playRoyalSound(AntarchySoundEvents.KING_DECREE_CAST.get(), 0.72F); - level.sendParticles(ParticleTypes.ENCHANT, + KingEntity.this.playRoyalSound(AntarchySoundEvents.KING_DECREE_CAST.get(), 0.34F, 0.72F); + level.sendParticles(KING_GOLD_DUST, KingEntity.this.getX(), KingEntity.this.getY() + 2.0D, KingEntity.this.getZ(), 36, 5.0D, 2.0D, 5.0D, 0.12D); } @@ -1315,6 +1510,151 @@ public class KingEntity extends RoyalBossEntity { 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 findRoyalMusterTargets(ServerLevel level) { double radius = AntarchySettings.kingRoyalMusterRadius(); return level.getEntitiesOfClass(Mob.class, this.getBoundingBox().inflate(radius), mob -> @@ -1342,7 +1682,7 @@ public class KingEntity extends RoyalBossEntity { } } 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, RoyalBeamElement.FIRE, origin, direction); 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; this.fireballCooldownTicks = AntarchySettings.kingFireballCooldownTicks(); @@ -1414,7 +1756,7 @@ public class KingEntity extends RoyalBossEntity { } @Override public void onActive(int elapsedTicks) { 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; 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)); living.hurt(source, base * 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; } RoyalBlockDestruction.destroySphere(level, this, @@ -1475,9 +1819,25 @@ public class KingEntity extends RoyalBossEntity { living.hasImpulse = true; 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); } + 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) { if (this.isRoyalRecoveryActive() && this.activeDecree == null) { return; @@ -1502,31 +1862,41 @@ public class KingEntity extends RoyalBossEntity { return; } this.activeDecreeTicks = 300; + this.decreeResponseGraceTicks = DECREE_RESPONSE_GRACE_TICKS; this.activeDecree = this.pickDecree(target); this.decreeRetreatPressure = false; this.closeQuartersEntered.clear(); this.closeQuartersOutsideTicks.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.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); boolean hasCompletableParticipants = false; boolean everyParticipantComplete = true; for (ServerPlayer participant : participants) { - int countdown = this.activeDecree.countdownTicks(participant); - if (countdown > 0 && countdown <= 100 && this.tickCount % 20 == 0) { - level.playSound(null, participant.blockPosition(), SoundEvents.BELL_BLOCK, - SoundSource.HOSTILE, 0.8F, 1.0F + countdown / 500.0F); + if (this.activeDecreeTicks % 20 == 0) { + int deadlineTicks = this.activeDecree.countdownTicks(this, participant); + int countdownTicks = deadlineTicks >= 0 ? deadlineTicks : this.activeDecreeTicks; + 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); if (this.activeDecree == null) { return; } 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); return; } @@ -1541,6 +1911,9 @@ public class KingEntity extends RoyalBossEntity { this.endDecree(target); return; } + if (this.decreeResponseGraceTicks > 0) { + this.decreeResponseGraceTicks--; + } if (this.activeDecree != null && --this.activeDecreeTicks <= 0) { this.projectRoyalSound(AntarchySoundEvents.KING_SUCCESS.get(), 4.0F, 1.0F, target); this.endDecree(target); @@ -1588,6 +1961,9 @@ public class KingEntity extends RoyalBossEntity { } for (ServerPlayer participant : this.royalEncounterPlayers(level)) { if (this.decreeRecipients.add(participant.getUUID())) { + if (this.activeDecree instanceof ShowNoMercyDecree) { + this.lastPlayerDamageTime.put(participant.getUUID(), level.getGameTime()); + } this.sendDecreeTitle(participant); } } @@ -1608,6 +1984,7 @@ public class KingEntity extends RoyalBossEntity { Player recipientPlayer = level.getPlayerByUUID(recipientId); if (recipientPlayer instanceof ServerPlayer recipient) { recipient.connection.send(new ClientboundClearTitlesPacket(false)); + recipient.displayClientMessage(Component.empty(), true); } } } @@ -1617,6 +1994,7 @@ public class KingEntity extends RoyalBossEntity { } this.activeDecree = null; this.activeDecreeTicks = 0; + this.decreeResponseGraceTicks = 0; this.decreeRetreatPressure = false; this.closeQuartersEntered.clear(); this.closeQuartersOutsideTicks.clear(); @@ -1639,8 +2017,13 @@ public class KingEntity extends RoyalBossEntity { } this.attackScheduler.cancel(RoyalAttackLane.DECREE); 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.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) { this.sendDecreeViolationTitle(player); } @@ -1650,9 +2033,9 @@ public class KingEntity extends RoyalBossEntity { private void sendDecreeViolationTitle(ServerPlayer player) { player.connection.send(new ClientboundSetTitlesAnimationPacket(5, 45, 10)); 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") - .withStyle(style -> style.withColor(ChatFormatting.LIGHT_PURPLE)))); + .withStyle(style -> style.withColor(ChatFormatting.YELLOW)))); } private void startRoyalJudgmentBarrage(LivingEntity target) { @@ -1666,18 +2049,71 @@ public class KingEntity extends RoyalBossEntity { for (RoyalHead.Slot slot : RoyalHead.Slot.values()) { 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.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 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) { KingEntity.this.fireRoyalJudgmentWave(level, target); } } - }); + }); + } + + 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) { @@ -1688,7 +2124,7 @@ public class KingEntity extends RoyalBossEntity { Vec3 direction = predicted.subtract(origin).normalize(); RoyalBoltEntity bolt = new RoyalBoltEntity(AntarchyObjects.ROYAL_BOLT.get(), level); bolt.setOwner(this); - bolt.setElement(RoyalBeamElement.QUEEN_PURPLE); + bolt.setElement(RoyalBeamElement.KING_GOLD); bolt.setPos(origin.x, origin.y, origin.z); bolt.shoot(direction.x, direction.y, direction.z, 1.55F, 1.0F); level.addFreshEntity(bolt); @@ -1710,7 +2146,7 @@ public class KingEntity extends RoyalBossEntity { long now = this.level().getGameTime(); if (this.judgmentCooldowns.getOrDefault(target.getUUID(), 0L) <= now) { 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)); } } @@ -1734,7 +2170,7 @@ public class KingEntity extends RoyalBossEntity { } boolean decreeApplies = attacker instanceof ServerPlayer && this.decreeRecipients.contains(attacker.getUUID()); - if (decreeApplies) { + if (decreeApplies && this.decreeResponseGraceTicks <= 0) { if (this.activeDecree instanceof CloseQuartersDecree && projectile) { this.failActiveDecree(attacker); } else if (this.activeDecree instanceof ComeNoCloserDecree && !projectile) { diff --git a/common-1.21.1/src/main/java/com/craisinlord/antarchy/content/entity/royal/QueenEntity.java b/common-1.21.1/src/main/java/com/craisinlord/antarchy/content/entity/royal/QueenEntity.java index 3dbee60a..9952333f 100644 --- a/common-1.21.1/src/main/java/com/craisinlord/antarchy/content/entity/royal/QueenEntity.java +++ b/common-1.21.1/src/main/java/com/craisinlord/antarchy/content/entity/royal/QueenEntity.java @@ -5,8 +5,10 @@ import com.craisinlord.antarchy.content.AntarchySoundEvents; import com.craisinlord.antarchy.content.AntarchyObjects; import com.craisinlord.antarchy.content.AntarchyTags; 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.AntarchyGravityDirection; +import com.craisinlord.antarchy.content.gravity.AntarchyGravityRotationUtil; import com.craisinlord.antarchy.content.gravity.AntarchyGravityTransition; import com.craisinlord.antarchy.content.worldgen.thoraxis.ThoraxisUndersideManager; 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_DURATION = 90; 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_DURATION = 120; 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 BLACK_HOLE_COOLDOWN = 340; 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 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_IMPACT_TICKS = 18; private static final int GROUND_COMBAT_TICKS = 140; private static final int TAKEOFF_TICKS = 30; private static final int LANDING_COOLDOWN_MIN = 360; 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 gravityStompCooldownTicks; @@ -89,7 +100,13 @@ public class QueenEntity extends RoyalBossEntity { private int crushingGravityCooldownTicks; private int accelerationCooldownTicks; private int blackHoleCooldownTicks; + private int gravityStompTelegraphTicks; + private int blackHoleTelegraphTicks; private int momentumLockTelegraphTicks; + private int crushingGravityTelegraphTicks; + private int accelerationTelegraphTicks; + @Nullable + private Vec3 pendingBlackHoleAnchor; private int queenLandingCooldownTicks = 300; private int queenLandingStageTicks; private QueenLandingStage queenLandingStage = QueenLandingStage.AERIAL; @@ -97,6 +114,12 @@ public class QueenEntity extends RoyalBossEntity { private Vec3 queenLandingWaypoint; @Nullable 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 int idleWanderCooldownTicks; private boolean manticoreAnimationPending; @@ -115,6 +138,13 @@ public class QueenEntity extends RoyalBossEntity { TAKEOFF } + private enum QueenFollowUp { + NONE, + FIREBALL_VOLLEY, + LANDING, + ACCELERATION_VOLLEY + } + public QueenEntity(EntityType entityType, Level level) { super(entityType, level); } @@ -148,13 +178,20 @@ public class QueenEntity extends RoyalBossEntity { return this.queenLandingStage == QueenLandingStage.TELEGRAPH || this.queenLandingStage == QueenLandingStage.DESCENT || this.queenLandingStage == QueenLandingStage.IMPACT - || this.queenLandingStage == QueenLandingStage.TAKEOFF; + || this.queenLandingStage == QueenLandingStage.TAKEOFF + || this.isQueenAbilityTelegraphActive(); + } + + @Override + protected boolean blocksRoyalBeamScheduling() { + return this.directedFireballVolleyTicks > 0; } @Override protected void onRoyalPhaseTransitionStarted(Phase previousPhase, Phase nextPhase) { this.resetQueenLandingSequence(); - this.momentumLockTelegraphTicks = 0; + this.clearQueenAbilityTelegraphs(); + this.clearQueenAttackDirector(); this.pendingPhaseSignature = null; this.getNavigation().stop(); this.setDeltaMovement(this.getDeltaMovement().scale(0.35D)); @@ -191,7 +228,7 @@ public class QueenEntity extends RoyalBossEntity { this.queenLandingCooldownTicks = Math.min(this.queenLandingCooldownTicks, 100); } else if (nextPhase == Phase.THREE) { this.accelerationCooldownTicks = 0; - this.startRoyalAcceleration(); + this.pendingPhaseSignature = Phase.THREE; this.queenLandingCooldownTicks = 0; } } @@ -334,12 +371,14 @@ public class QueenEntity extends RoyalBossEntity { } LivingEntity target = this.getTarget(); if (target == null || !target.isAlive() || !this.canAttack(target)) { + this.clearQueenAttackDirector(); this.resetQueenLandingSequence(); this.tickIdleWander(); } else { + this.tickQueenAttackDirector(target); this.tickQueenLandingSequence(target); } - this.tickMomentumLockTelegraph(); + this.tickQueenAbilityTelegraphs(); if (this.blocksRoyalAttacksForMovement()) { this.royalEffects.tick(); return; @@ -347,13 +386,19 @@ public class QueenEntity extends RoyalBossEntity { this.tickManticoreSummon(); this.tickPendingManticoreAnimation(); this.tickQueenAbilities(); + if (this.isQueenAbilityTelegraphActive()) { + this.royalEffects.tick(); + return; + } this.tickDreamFireballs(); + this.tickDirectedFireballVolley(); this.tickFinalAcceleration(); this.royalEffects.tick(); } private void tickQueenLandingSequence(LivingEntity target) { if (this.queenLandingStage == QueenLandingStage.AERIAL && this.isRoyalFlying() + && !this.isQueenAbilityTelegraphActive() && this.queenLandingCooldownTicks-- <= 0 && target.onGround()) { this.beginQueenLanding(target); } @@ -435,7 +480,7 @@ public class QueenEntity extends RoyalBossEntity { double x = target.getX() + Math.cos(angle) * radius; double z = target.getZ() + Math.sin(angle) * radius; double y = inverted - ? target.getY() + target.getBbHeight() - this.getBbHeight() + ? target.position().y - 0.1D : this.groundYBelow(x, z) + 0.1D; Vec3 candidate = new Vec3(x, y, z); 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; } @@ -456,13 +510,13 @@ public class QueenEntity extends RoyalBossEntity { double progress = this.queenLandingStage == QueenLandingStage.TELEGRAPH ? 1.0D - this.queenLandingStageTicks / (double) LANDING_TELEGRAPH_TICKS : 1.0D; double radius = Mth.lerp(progress, 4.0D, 18.0D); - double indicatorY = this.queenLandingWaypoint.y - + (AntarchyGravityApi.isGravityInverted(this) ? this.getBbHeight() : 0.2D); + Vec3 indicator = this.queenLandingWaypoint.add(AntarchyGravityRotationUtil.vecPlayerToWorld( + 0.0D, 0.2D, 0.0D, AntarchyGravityApi.getGravityDirection(this))); for (int index = 0; index < 40; index++) { double angle = Mth.TWO_PI * index / 40.0D; level.sendParticles(ParticleTypes.REVERSE_PORTAL, this.queenLandingWaypoint.x + Math.cos(angle) * radius, - indicatorY, + indicator.y, this.queenLandingWaypoint.z + Math.sin(angle) * radius, 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.playRoyalSound(AntarchySoundEvents.QUEEN_FLY_LOOP.get(), 0.9F); if (this.level() instanceof ServerLevel level) { + this.spawnQueenWingPressure(level); level.sendParticles(ParticleTypes.REVERSE_PORTAL, this.getX(), this.getY() + this.getBbHeight() * 0.4D, this.getZ(), 60, 7.0D, 1.0D, 7.0D, 0.12D); } this.setRoyalFlying(true); - double lift = AntarchyGravityApi.isGravityInverted(this) ? -0.42D : 0.42D; - this.setDeltaMovement(this.getDeltaMovement().add(0.0D, lift, 0.0D)); + this.addGravityAwareImpulse(this, AntarchyGravityRotationUtil.vecPlayerToWorld( + 0.0D, 0.42D, 0.0D, AntarchyGravityApi.getGravityDirection(this))); this.hasImpulse = true; } @@ -507,9 +562,20 @@ public class QueenEntity extends RoyalBossEntity { this.dreamFireballCooldownTicks[index]--; 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); 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, new com.craisinlord.antarchy.content.entity.royal.attack.RoyalAttackScheduler.Action() { @Override public void onStart() { @@ -518,7 +584,7 @@ public class QueenEntity extends RoyalBossEntity { } @Override public void onActive(int elapsedTicks) { Vec3 origin = QueenEntity.this.headAnchor(head); - Vec3 direction = target.getEyePosition().subtract(origin).normalize(); + Vec3 direction = committedTarget.getEyePosition().subtract(origin).normalize(); RoyalElementalProjectileEntity projectile = RoyalElementalProjectileEntity.create( level, QueenEntity.this, RoyalBeamElement.DREAM_FIRE, origin, direction); 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() { if (this.idleWanderCooldownTicks-- > 0) { return; @@ -716,23 +860,27 @@ public class QueenEntity extends RoyalBossEntity { if (target == null || !target.isAlive()) { return; } + if (this.queuedQueenFollowUp != QueenFollowUp.NONE || this.directedFireballVolleyTicks > 0) { + return; + } Phase phase = this.phase(); - boolean majorBusy = this.royalEffects.active("momentum_lock") || this.royalEffects.active("crushing_gravity"); - boolean allowOverlap = phase != Phase.ONE; - String[] candidates = phase == Phase.ONE && !this.isMomentumLockAvailableEarly() - ? new String[] {"gravity_stomp", "black_hole"} - : new String[] {"gravity_stomp", "black_hole", "momentum_lock", "crushing_gravity", "acceleration"}; - String selected = this.pendingPhaseSignature == Phase.TWO - && this.queenAttackReady("momentum_lock", phase, majorBusy, allowOverlap) - ? "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; - }; + boolean majorBusy = this.royalEffects.active("momentum_lock") + || this.royalEffects.active("crushing_gravity") + || this.royalEffects.active("acceleration"); + String[] candidates = switch (phase) { + case ONE -> new String[] {"gravity_stomp", "black_hole"}; + case TWO -> new String[] {"gravity_stomp", "black_hole", "momentum_lock", "crushing_gravity"}; + case THREE -> new String[] {"gravity_stomp", "black_hole", "momentum_lock", "crushing_gravity", "acceleration"}; + }; + 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); if (selected == null) return; int cooldown = switch (selected) { @@ -743,27 +891,30 @@ public class QueenEntity extends RoyalBossEntity { default -> ACCELERATION_COOLDOWN; }; RoyalAttackLane lane = "black_hole".equals(selected) ? RoyalAttackLane.CENTER_HEAD : RoyalAttackLane.HAZARD; - int actionWindup = "momentum_lock".equals(selected) ? MOMENTUM_LOCK_TELEGRAPH_TICKS : 10; - int actionRecovery = "momentum_lock".equals(selected) - ? this.animationRecovery(55, actionWindup, 1, 14) - : this.animationRecovery(25, actionWindup, 1, 14); + int actionWindup = this.queenAbilityWindup(selected); + int actionAnimationTicks = "momentum_lock".equals(selected) + || "crushing_gravity".equals(selected) || "acceleration".equals(selected) ? 55 : 25; + int actionRecovery = this.animationRecovery(actionAnimationTicks, actionWindup, 1, 14); if (!this.beginRoyalAttack(selected, lane, this.cooldown(cooldown), actionWindup, 1, actionRecovery, new com.craisinlord.antarchy.content.entity.royal.attack.RoyalAttackScheduler.Action() { @Override public void onStart() { if ("gravity_stomp".equals(selected)) { 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.momentumLockTelegraphTicks = MOMENTUM_LOCK_TELEGRAPH_TICKS; - QueenEntity.this.playRoyalSound(AntarchySoundEvents.QUEEN_ROAR.get(), 0.58F); } else if ("black_hole".equals(selected)) { QueenEntity.this.royalHead(RoyalHead.Slot.CENTER).startShoot(); QueenEntity.this.triggerAnim(RoyalHead.Slot.CENTER.controllerName(), "shoot"); } + QueenEntity.this.startQueenAbilityTelegraph(selected, target); } @Override public void onActive(int elapsedTicks) { - if ("momentum_lock".equals(selected)) { - QueenEntity.this.momentumLockTelegraphTicks = 0; + QueenEntity.this.finishQueenAbilityTelegraph(selected); + if ("momentum_lock".equals(selected) || "crushing_gravity".equals(selected) + || "acceleration".equals(selected)) { + QueenEntity.this.spawnQueenWingPressure(level); } QueenEntity.this.performSelectedQueenAttack(selected, cooldown, level, target); } @@ -773,7 +924,8 @@ public class QueenEntity extends RoyalBossEntity { } } })) return; - if ("momentum_lock".equals(selected)) { + this.lastQueenMajorAttack = selected; + if (selected.equals(forcedAttack)) { this.pendingPhaseSignature = null; } } @@ -781,23 +933,65 @@ public class QueenEntity extends RoyalBossEntity { private void performSelectedQueenAttack(String selected, int cooldown, ServerLevel level, LivingEntity target) { switch (selected) { - case "gravity_stomp" -> { this.gravityStompCooldownTicks = cooldown; this.performGravityStomp(level, target); } - case "black_hole" -> { this.blackHoleCooldownTicks = cooldown; this.castBlackHole(level, target); } - case "momentum_lock" -> { this.momentumLockCooldownTicks = cooldown; this.startMomentumLock(level); } + case "gravity_stomp" -> { + this.gravityStompCooldownTicks = cooldown; + 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" -> { this.crushingGravityCooldownTicks = cooldown; this.royalEffects.start("crushing_gravity", CRUSHING_GRAVITY_DURATION, () -> {}, this::tickCrushingGravity, () -> this.startRoyalRecovery(35)); 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) { - if (!allowOverlap && majorBusy) return false; + private void spawnQueenWingPressure(ServerLevel level) { + 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) { - 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 && this.royalHead(RoyalHead.Slot.CENTER).readyToAttack() && !this.attackScheduler.laneBusy(RoyalAttackLane.CENTER_HEAD); @@ -808,9 +1002,23 @@ public class QueenEntity extends RoyalBossEntity { }; } - private boolean isMomentumLockAvailableEarly() { - return this.getMaxHealth() > 0.0F - && this.getHealth() / this.getMaxHealth() <= MOMENTUM_LOCK_EARLY_HEALTH_THRESHOLD; + private int queenAttackWeight(String id, Phase phase) { + return switch (phase) { + 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() { @@ -818,12 +1026,18 @@ public class QueenEntity extends RoyalBossEntity { return; } if (!this.royalEffects.active("acceleration") && this.accelerationCooldownTicks <= 0) { - this.startRoyalAcceleration(); + this.pendingPhaseSignature = Phase.THREE; } } private void performGravityStomp(ServerLevel level, LivingEntity target) { - this.triggerAnim("body_action", "stomp"); + this.performGravityStomp(level, target, true); + } + + private void performGravityStomp(ServerLevel level, LivingEntity target, boolean triggerAnimation) { + if (triggerAnimation) { + this.triggerAnim("body_action", "stomp"); + } this.playRoyalSound(AntarchySoundEvents.QUEEN_ROAR.get(), 0.7F); DamageSource source = this.damageSources().mobAttack(this); 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)); 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); - 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; } 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) { - double vertical = 2.0D + this.random.nextDouble() * 2.0D; - if (AntarchyGravityApi.isGravityInverted(this)) { - vertical = -vertical; - } - Vec3 anchor = target.position().add( - (this.random.nextDouble() - 0.5D) * 4.0D, - vertical, - (this.random.nextDouble() - 0.5D) * 4.0D); + Vec3 anchor = this.pendingBlackHoleAnchor != null + ? this.pendingBlackHoleAnchor : this.selectBlackHoleAnchor(target); + this.pendingBlackHoleAnchor = null; Vec3 mouth = this.beamAnchor(this.royalHead(RoyalHead.Slot.CENTER)); RoyalBlackHoleEntity hole = RoyalBlackHoleEntity.createProjectile(level, mouth, anchor, this.getUUID()); level.addFreshEntity(hole); @@ -855,40 +1066,136 @@ public class QueenEntity extends RoyalBossEntity { 30, 0.6D, 0.6D, 0.6D, 0.25D); } - private void tickMomentumLockTelegraph() { - if (this.momentumLockTelegraphTicks <= 0 || !(this.level() instanceof ServerLevel level)) { + private Vec3 selectBlackHoleAnchor(LivingEntity target) { + 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; } this.getNavigation().stop(); 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; if (this.tickCount % 2 == 0) { - double inwardRadius = Mth.lerp(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); - } - } + this.spawnMomentumLockCage(level, elapsed / (double) MOMENTUM_LOCK_TELEGRAPH_TICKS); } - if (this.momentumLockTelegraphTicks <= 10) { + if (this.momentumLockTelegraphTicks <= 14) { for (LivingEntity living : level.getEntitiesOfClass(LivingEntity.class, this.getBoundingBox().inflate(MOMENTUM_LOCK_RADIUS), this::canDamageWithRoyalAttack)) { - level.sendParticles(ParticleTypes.END_ROD, - living.getX(), living.getY() + living.getBbHeight() * 0.5D, living.getZ(), - 2, 0.25D, 0.35D, 0.25D, 0.0D); + this.spawnMomentumTargetCage(level, living); + if (this.momentumLockTelegraphTicks == 12) { + NightmareBiteEntity.spawnTelegraphAt(level, living, true); + } } } if (this.momentumLockTelegraphTicks == 40 || this.momentumLockTelegraphTicks == 20 @@ -900,6 +1207,164 @@ public class QueenEntity extends RoyalBossEntity { 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 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) { this.momentumLockCooldownTicks = MOMENTUM_LOCK_COOLDOWN; this.royalEffects.start("momentum_lock", MOMENTUM_LOCK_DURATION, () -> { @@ -1061,6 +1526,13 @@ public class QueenEntity extends RoyalBossEntity { tag.putInt("AccelerationCooldownTicks", this.accelerationCooldownTicks); tag.putInt("BlackHoleCooldownTicks", this.blackHoleCooldownTicks); 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) { tag.putLong("NaturalTrailSiteId", this.naturalTrailSiteId); tag.putLong("NaturalTrailHome", this.naturalTrailHome.asLong()); @@ -1078,6 +1550,21 @@ public class QueenEntity extends RoyalBossEntity { this.blackHoleCooldownTicks = Math.max(0, tag.getInt("BlackHoleCooldownTicks")); this.queenLandingCooldownTicks = tag.contains("QueenLandingCooldownTicks") ? 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")) { this.naturalTrailSiteId = tag.getLong("NaturalTrailSiteId"); this.naturalTrailHome = BlockPos.of(tag.getLong("NaturalTrailHome")); diff --git a/common-1.21.1/src/main/java/com/craisinlord/antarchy/content/entity/royal/RoyalBoltEntity.java b/common-1.21.1/src/main/java/com/craisinlord/antarchy/content/entity/royal/RoyalBoltEntity.java index 2cc46c0c..eaa815da 100644 --- a/common-1.21.1/src/main/java/com/craisinlord/antarchy/content/entity/royal/RoyalBoltEntity.java +++ b/common-1.21.1/src/main/java/com/craisinlord/antarchy/content/entity/royal/RoyalBoltEntity.java @@ -4,6 +4,7 @@ import com.craisinlord.antarchy.config.AntarchySettings; import com.craisinlord.antarchy.content.entity.royal.beam.RoyalBeamElement; import net.minecraft.core.particles.ParticleOptions; import net.minecraft.core.particles.ParticleTypes; +import net.minecraft.core.particles.DustParticleOptions; import net.minecraft.nbt.CompoundTag; import net.minecraft.network.syncher.EntityDataAccessor; 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.phys.EntityHitResult; import net.minecraft.world.phys.HitResult; +import org.joml.Vector3f; 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 ELEMENT = SynchedEntityData.defineId(RoyalBoltEntity.class, EntityDataSerializers.INT); @@ -78,6 +82,7 @@ public class RoyalBoltEntity extends AbstractHurtingProjectile { case FIRE -> ParticleTypes.FLAME; case LIGHTNING -> ParticleTypes.ELECTRIC_SPARK; case ICE -> ParticleTypes.SNOWFLAKE; + case KING_GOLD -> KING_GOLD_TRAIL; default -> ParticleTypes.WITCH; }; } diff --git a/common-1.21.1/src/main/java/com/craisinlord/antarchy/content/entity/royal/RoyalBossEntity.java b/common-1.21.1/src/main/java/com/craisinlord/antarchy/content/entity/royal/RoyalBossEntity.java index 75cd9140..fea0d22d 100644 --- a/common-1.21.1/src/main/java/com/craisinlord/antarchy/content/entity/royal/RoyalBossEntity.java +++ b/common-1.21.1/src/main/java/com/craisinlord/antarchy/content/entity/royal/RoyalBossEntity.java @@ -258,6 +258,10 @@ public abstract class RoyalBossEntity extends Monster implements GeoEntity, Mult return this.royalBeamStartSound(); } + protected float royalBeamStartSoundVolume(RoyalHead head) { + return (float) AntarchySettings.royalBossSoundVolume(); + } + protected void tickRoyalBeamEffects(RoyalHead head, Vec3 start, Vec3 end) { } @@ -307,6 +311,11 @@ public abstract class RoyalBossEntity extends Monster implements GeoEntity, Mult return false; } + /** Prevents new beam volleys while a bespoke attack has reserved the heads. */ + protected boolean blocksRoyalBeamScheduling() { + return false; + } + protected int royalPhaseTransitionDuration(Phase nextPhase) { return 50; } @@ -428,6 +437,10 @@ public abstract class RoyalBossEntity extends Monster implements GeoEntity, Mult 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) { if (this.level().isClientSide) { return; @@ -540,7 +553,9 @@ public abstract class RoyalBossEntity extends Monster implements GeoEntity, Mult this.getLookControl().setLookAt(primaryTarget, 30.0F, 30.0F); } this.assignHeadTargets(primaryTarget); - this.tickRoyalBeam(primaryTarget); + if (!this.blocksRoyalBeamScheduling()) { + this.tickRoyalBeam(primaryTarget); + } this.tickBodyCrush(); 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_ACTIVE[index], true); 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); } @@ -686,6 +702,11 @@ public abstract class RoyalBossEntity extends Monster implements GeoEntity, Mult 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() { return this.phaseTransitionTicks > 0; } @@ -726,9 +747,10 @@ public abstract class RoyalBossEntity extends Monster implements GeoEntity, Mult return; } int maxPlayers = Math.max(1, AntarchySettings.royalBossScalingMaxPlayers()); + double encounterRange = Math.max(32.0D, this.getAttributeValue(Attributes.FOLLOW_RANGE)); List players = level.getPlayers(player -> player.isAlive() && !player.isSpectator() - && player.distanceToSqr(this) <= AntarchySettings.royalBossFollowRange() * AntarchySettings.royalBossFollowRange()); + && player.distanceToSqr(this) <= encounterRange * encounterRange); if (target instanceof ServerPlayer player && player.isAlive() && !players.contains(player)) { players.add(player); } @@ -761,6 +783,14 @@ public abstract class RoyalBossEntity extends Monster implements GeoEntity, Mult 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() { float fraction = this.getMaxHealth() <= 0.0F ? 1.0F : this.getHealth() / this.getMaxHealth(); 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, (this.random.nextDouble() - 0.5D) * 0.12D); itemEntity.setDefaultPickUpDelay(); + RoyalLootDropGlow.start(itemEntity); this.level().addFreshEntity(itemEntity); return itemEntity; } diff --git a/common-1.21.1/src/main/java/com/craisinlord/antarchy/content/entity/royal/RoyalElementalProjectileEntity.java b/common-1.21.1/src/main/java/com/craisinlord/antarchy/content/entity/royal/RoyalElementalProjectileEntity.java index efaf689a..c73b07e6 100644 --- a/common-1.21.1/src/main/java/com/craisinlord/antarchy/content/entity/royal/RoyalElementalProjectileEntity.java +++ b/common-1.21.1/src/main/java/com/craisinlord/antarchy/content/entity/royal/RoyalElementalProjectileEntity.java @@ -11,6 +11,8 @@ import net.minecraft.network.syncher.EntityDataAccessor; import net.minecraft.network.syncher.EntityDataSerializers; import net.minecraft.network.syncher.SynchedEntityData; 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.entity.Entity; import net.minecraft.world.entity.EntityType; @@ -143,6 +145,9 @@ public class RoyalElementalProjectileEntity extends ThrowableProjectile implemen } 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.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) { diff --git a/common-1.21.1/src/main/java/com/craisinlord/antarchy/content/entity/royal/RoyalIceSpikeEntity.java b/common-1.21.1/src/main/java/com/craisinlord/antarchy/content/entity/royal/RoyalIceSpikeEntity.java index 8b0f58ba..bf952c16 100644 --- a/common-1.21.1/src/main/java/com/craisinlord/antarchy/content/entity/royal/RoyalIceSpikeEntity.java +++ b/common-1.21.1/src/main/java/com/craisinlord/antarchy/content/entity/royal/RoyalIceSpikeEntity.java @@ -30,6 +30,12 @@ public class RoyalIceSpikeEntity extends Entity implements GeoEntity { private boolean impacted; @Nullable 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 type, Level level) { super(type, level); @@ -49,6 +55,18 @@ public class RoyalIceSpikeEntity extends Entity implements GeoEntity { 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 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) { DamageSource source = this.damageSources().magic(); + Entity owner = this.ownerId == null ? null : level.getEntity(this.ownerId); + if (this.ownerId != null && owner == null) return; 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); - living.hurt(source, owner instanceof RoyalBossEntity royalBoss - ? royalBoss.scaleRoyalDamage(AntarchySettings.kingIceSpikeDamage()) - : (float) AntarchySettings.kingIceSpikeDamage()); - living.setTicksFrozen(Math.min(living.getTicksRequiredToFreeze() + 60, living.getTicksFrozen() + 100)); - living.setDeltaMovement(living.getDeltaMovement().add(0.0D, 0.35D, 0.0D)); + 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()) + : (float) AntarchySettings.kingIceSpikeDamage(); + boolean hit = owner instanceof net.minecraft.world.entity.player.Player player && this.customImpactDamage >= 0.0F + ? 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; } 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 protected void readAdditionalSaveData(CompoundTag tag) { this.impacted = tag.getBoolean("Impacted"); if (tag.hasUUID("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 @@ -100,6 +148,14 @@ public class RoyalIceSpikeEntity extends Entity implements GeoEntity { if (this.ownerId != null) { 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 diff --git a/common-1.21.1/src/main/java/com/craisinlord/antarchy/content/entity/royal/RoyalLootDropGlow.java b/common-1.21.1/src/main/java/com/craisinlord/antarchy/content/entity/royal/RoyalLootDropGlow.java new file mode 100644 index 00000000..a1a04227 --- /dev/null +++ b/common-1.21.1/src/main/java/com/craisinlord/antarchy/content/entity/royal/RoyalLootDropGlow.java @@ -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); + } + } +} diff --git a/common-1.21.1/src/main/java/com/craisinlord/antarchy/content/entity/royal/attack/RoyalAttackScheduler.java b/common-1.21.1/src/main/java/com/craisinlord/antarchy/content/entity/royal/attack/RoyalAttackScheduler.java index d3c3338e..b885a307 100644 --- a/common-1.21.1/src/main/java/com/craisinlord/antarchy/content/entity/royal/attack/RoyalAttackScheduler.java +++ b/common-1.21.1/src/main/java/com/craisinlord/antarchy/content/entity/royal/attack/RoyalAttackScheduler.java @@ -11,6 +11,7 @@ import net.minecraft.util.RandomSource; public final class RoyalAttackScheduler { public interface Action { default void onStart() {} + default void onWindup(int elapsedTicks, int remainingTicks) {} default void onActive(int elapsedTicks) {} default void onComplete() {} } @@ -44,6 +45,9 @@ public final class RoyalAttackScheduler { running.elapsedTicks++; int activeStart = running.windupTicks; int activeEnd = activeStart + running.activeTicks; + if (running.elapsedTicks < activeStart) { + running.action.onWindup(running.elapsedTicks, activeStart - running.elapsedTicks); + } if (running.elapsedTicks >= activeStart && running.elapsedTicks < activeEnd) { running.action.onActive(running.elapsedTicks - activeStart); } @@ -116,6 +120,10 @@ public final class RoyalAttackScheduler { return cooldowns.getOrDefault(id, 0); } + public void resetCooldown(String id) { + cooldowns.remove(id); + } + public void cancel(RoyalAttackLane lane) { active.remove(lane); } diff --git a/common-1.21.1/src/main/java/com/craisinlord/antarchy/content/entity/royal/beam/RoyalBeamElement.java b/common-1.21.1/src/main/java/com/craisinlord/antarchy/content/entity/royal/beam/RoyalBeamElement.java index f98e9967..57d4e0ac 100644 --- a/common-1.21.1/src/main/java/com/craisinlord/antarchy/content/entity/royal/beam/RoyalBeamElement.java +++ b/common-1.21.1/src/main/java/com/craisinlord/antarchy/content/entity/royal/beam/RoyalBeamElement.java @@ -8,5 +8,6 @@ public enum RoyalBeamElement { QUEEN_PURPLE, QUEEN_RED, QUEEN_BLACK, - DREAM_FIRE + DREAM_FIRE, + KING_GOLD } diff --git a/common-1.21.1/src/main/java/com/craisinlord/antarchy/content/entity/royal/decree/CloseQuartersDecree.java b/common-1.21.1/src/main/java/com/craisinlord/antarchy/content/entity/royal/decree/CloseQuartersDecree.java index 5474f2a8..a1d671a3 100644 --- a/common-1.21.1/src/main/java/com/craisinlord/antarchy/content/entity/royal/decree/CloseQuartersDecree.java +++ b/common-1.21.1/src/main/java/com/craisinlord/antarchy/content/entity/royal/decree/CloseQuartersDecree.java @@ -12,6 +12,10 @@ public final class CloseQuartersDecree implements RoyalDecree { return king.evaluateCloseQuarters(target); } @Override + public int countdownTicks(KingEntity king, LivingEntity target) { + return king.closeQuartersCountdownTicks(target); + } + @Override public int contextWeight(KingEntity king, LivingEntity target) { return 4 + king.behaviorScore(KingEntity.Behavior.RANGED); } diff --git a/common-1.21.1/src/main/java/com/craisinlord/antarchy/content/entity/royal/decree/FightMeCowardDecree.java b/common-1.21.1/src/main/java/com/craisinlord/antarchy/content/entity/royal/decree/FightMeCowardDecree.java index efeab5e4..9af0aef8 100644 --- a/common-1.21.1/src/main/java/com/craisinlord/antarchy/content/entity/royal/decree/FightMeCowardDecree.java +++ b/common-1.21.1/src/main/java/com/craisinlord/antarchy/content/entity/royal/decree/FightMeCowardDecree.java @@ -1,12 +1,18 @@ package com.craisinlord.antarchy.content.entity.royal.decree; 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.world.entity.LivingEntity; import net.minecraft.world.phys.Vec3; public final class FightMeCowardDecree implements RoyalDecree { + private static final int APPROACH_TICKS = 80; + private final Map deadlines = new HashMap<>(); public String name() { return "FIGHT ME, COWARD!"; } public String translationKey() { return "decree.antarchy.fight_me_coward"; } 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) { Vec3 pull = king.position().subtract(target.position()).normalize().scale(0.35D); target.setDeltaMovement(target.getDeltaMovement().add(pull.x, 0.05D, pull.z)); @@ -15,6 +21,21 @@ public final class FightMeCowardDecree implements RoyalDecree { } @Override 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(); } } diff --git a/common-1.21.1/src/main/java/com/craisinlord/antarchy/content/entity/royal/decree/KneelDecree.java b/common-1.21.1/src/main/java/com/craisinlord/antarchy/content/entity/royal/decree/KneelDecree.java index aded72fe..bb8566ff 100644 --- a/common-1.21.1/src/main/java/com/craisinlord/antarchy/content/entity/royal/decree/KneelDecree.java +++ b/common-1.21.1/src/main/java/com/craisinlord/antarchy/content/entity/royal/decree/KneelDecree.java @@ -23,7 +23,7 @@ public final class KneelDecree implements RoyalDecree { } @Override - public int countdownTicks(LivingEntity target) { + public int countdownTicks(KingEntity king, LivingEntity target) { return this.deadlines.getOrDefault(target.getUUID(), target.tickCount + 100) - target.tickCount; } diff --git a/common-1.21.1/src/main/java/com/craisinlord/antarchy/content/entity/royal/decree/RoyalDecree.java b/common-1.21.1/src/main/java/com/craisinlord/antarchy/content/entity/royal/decree/RoyalDecree.java index b75a43c1..8bf8a971 100644 --- a/common-1.21.1/src/main/java/com/craisinlord/antarchy/content/entity/royal/decree/RoyalDecree.java +++ b/common-1.21.1/src/main/java/com/craisinlord/antarchy/content/entity/royal/decree/RoyalDecree.java @@ -18,7 +18,7 @@ public interface RoyalDecree { return Evaluation.COMPLIANT; } - default int countdownTicks(LivingEntity target) { + default int countdownTicks(KingEntity king, LivingEntity target) { return -1; } diff --git a/common-1.21.1/src/main/java/com/craisinlord/antarchy/content/entity/trades/ComputerScientistTradeManager.java b/common-1.21.1/src/main/java/com/craisinlord/antarchy/content/entity/trades/ComputerScientistTradeManager.java index 406c16df..e67bd15b 100644 --- a/common-1.21.1/src/main/java/com/craisinlord/antarchy/content/entity/trades/ComputerScientistTradeManager.java +++ b/common-1.21.1/src/main/java/com/craisinlord/antarchy/content/entity/trades/ComputerScientistTradeManager.java @@ -35,7 +35,7 @@ public final class ComputerScientistTradeManager extends SimpleJsonResourceReloa } 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) { diff --git a/common-1.21.1/src/main/java/com/craisinlord/antarchy/content/gravity/ProjectileLaunchGravity.java b/common-1.21.1/src/main/java/com/craisinlord/antarchy/content/gravity/ProjectileLaunchGravity.java new file mode 100644 index 00000000..9ed220b4 --- /dev/null +++ b/common-1.21.1/src/main/java/com/craisinlord/antarchy/content/gravity/ProjectileLaunchGravity.java @@ -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); + } +} diff --git a/common-1.21.1/src/main/java/com/craisinlord/antarchy/content/guide/ComputerGuideData.java b/common-1.21.1/src/main/java/com/craisinlord/antarchy/content/guide/ComputerGuideData.java index e3189a8b..53b72158 100644 --- a/common-1.21.1/src/main/java/com/craisinlord/antarchy/content/guide/ComputerGuideData.java +++ b/common-1.21.1/src/main/java/com/craisinlord/antarchy/content/guide/ComputerGuideData.java @@ -1,5 +1,6 @@ package com.craisinlord.antarchy.content.guide; +import com.craisinlord.antarchy.Antarchy; import com.craisinlord.antarchy.config.AntarchySettings; import com.google.gson.JsonArray; import com.google.gson.JsonElement; @@ -41,14 +42,14 @@ public final class ComputerGuideData extends SimplePreparableReloadListener entriesFor(List diskIds) { if (AntarchySettings.unlockAllArchives()) { Map 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); return result.values().stream().sorted(Comparator.comparing(entry -> entry.titleKey().toString())).toList(); } Map result = new LinkedHashMap<>(); for (ResourceLocation diskId : diskIds) { 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; } Disk disk = disks.get(diskId); @@ -65,6 +66,87 @@ public final class ComputerGuideData extends SimplePreparableReloadListener 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 loadedEntries = new HashMap<>(); + Map 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 protected LoadedData prepare(ResourceManager resourceManager, ProfilerFiller profiler) { Map loadedEntries = new HashMap<>(); @@ -74,7 +156,8 @@ public final class ComputerGuideData extends SimplePreparableReloadListener resource : resourceManager.listResources("computer/disk", path -> path.getPath().endsWith(".json")).entrySet()) { @@ -82,7 +165,8 @@ public final class ComputerGuideData extends SimplePreparableReloadListener description = strings(object.get("description")); String item = string(object, "item", ""); String entity = string(object, "entity", ""); + String enchantment = string(object, "enchantment", ""); String recipe = string(object, "recipe", ""); String structure = string(object, "structure", ""); + String structureTag = string(object, "structure_tag", ""); 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 target) { @@ -168,9 +265,11 @@ public final class ComputerGuideData extends SimplePreparableReloadListener descriptionKeys, String itemId, String entityId, String recipeId, String structureId, String dimensionId) { + List 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 entries, String wallpaper) { diff --git a/common-1.21.1/src/main/java/com/craisinlord/antarchy/content/item/BrutalflyElytraItem.java b/common-1.21.1/src/main/java/com/craisinlord/antarchy/content/item/BrutalflyElytraItem.java index 3ba3dcce..1dbfe747 100644 --- a/common-1.21.1/src/main/java/com/craisinlord/antarchy/content/item/BrutalflyElytraItem.java +++ b/common-1.21.1/src/main/java/com/craisinlord/antarchy/content/item/BrutalflyElytraItem.java @@ -8,13 +8,19 @@ import net.minecraft.core.registries.BuiltInRegistries; import net.minecraft.resources.ResourceLocation; import net.minecraft.network.chat.Component; import net.minecraft.world.entity.EquipmentSlot; +import net.minecraft.world.entity.EquipmentSlotGroup; 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.Item; import net.minecraft.world.item.ItemStack; +import net.minecraft.world.item.component.ItemAttributeModifiers; public final class BrutalflyElytraItem extends ElytraItem { 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 public static Supplier 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; } + @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 public boolean isValidRepairItem(ItemStack toRepair, ItemStack repair) { Optional repairMaterial = BuiltInRegistries.ITEM.getOptional(REPAIR_MATERIAL_ID); diff --git a/common-1.21.1/src/main/java/com/craisinlord/antarchy/content/item/ManticoreWingsItem.java b/common-1.21.1/src/main/java/com/craisinlord/antarchy/content/item/ManticoreWingsItem.java index edd1f17e..dcf5466e 100644 --- a/common-1.21.1/src/main/java/com/craisinlord/antarchy/content/item/ManticoreWingsItem.java +++ b/common-1.21.1/src/main/java/com/craisinlord/antarchy/content/item/ManticoreWingsItem.java @@ -10,16 +10,22 @@ import net.minecraft.core.registries.BuiltInRegistries; import net.minecraft.network.chat.Component; import net.minecraft.resources.ResourceLocation; import net.minecraft.world.entity.EquipmentSlot; +import net.minecraft.world.entity.EquipmentSlotGroup; 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.item.ElytraItem; import net.minecraft.world.item.Item; import net.minecraft.world.item.ItemStack; import net.minecraft.world.item.TooltipFlag; +import net.minecraft.world.item.component.ItemAttributeModifiers; import net.minecraft.world.level.Level; public final class ManticoreWingsItem extends ElytraItem { 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 double MIN_COLLISION_SPEED = 0.2D; 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; } + @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 public void appendHoverText(ItemStack stack, Item.TooltipContext context, List tooltipComponents, TooltipFlag tooltipFlag) { tooltipComponents.add(Component.translatable("tooltip.antarchy.manticore_wings").withStyle(ChatFormatting.GRAY)); diff --git a/common-1.21.1/src/main/java/com/craisinlord/antarchy/content/item/RainbowSugarItem.java b/common-1.21.1/src/main/java/com/craisinlord/antarchy/content/item/RainbowSugarItem.java index 8ebc470a..ed2d0d8d 100644 --- a/common-1.21.1/src/main/java/com/craisinlord/antarchy/content/item/RainbowSugarItem.java +++ b/common-1.21.1/src/main/java/com/craisinlord/antarchy/content/item/RainbowSugarItem.java @@ -34,8 +34,12 @@ public class RainbowSugarItem extends Item { if (!level.isClientSide && livingEntity instanceof ServerPlayer serverPlayer && isInInfinityDimension(serverPlayer.serverLevel())) { ServerLevel returnLevel = AntTeleportHelper.resolveReturnDestinationLevel(serverPlayer); var returnPos = AntTeleportHelper.getDestinationPosition(serverPlayer, returnLevel); - serverPlayer.teleportTo(returnLevel, returnPos.x, returnPos.y, returnPos.z, serverPlayer.getYRot(), serverPlayer.getXRot()); - serverPlayer.playSound(SoundEvents.ENDERMAN_TELEPORT, 1.0F, 1.0F); + 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.playSound(SoundEvents.ENDERMAN_TELEPORT, 1.0F, 1.0F); + } } return result; diff --git a/common-1.21.1/src/main/java/com/craisinlord/antarchy/content/item/RoyalGuardianArmorItem.java b/common-1.21.1/src/main/java/com/craisinlord/antarchy/content/item/RoyalGuardianArmorItem.java index 73b234ef..cda042d7 100644 --- a/common-1.21.1/src/main/java/com/craisinlord/antarchy/content/item/RoyalGuardianArmorItem.java +++ b/common-1.21.1/src/main/java/com/craisinlord/antarchy/content/item/RoyalGuardianArmorItem.java @@ -2,16 +2,21 @@ package com.craisinlord.antarchy.content.item; import com.craisinlord.antarchy.config.AntarchySettings; import com.craisinlord.antarchy.content.item.royal.RoyalGearHelper; +import java.util.List; import net.minecraft.core.Holder; +import net.minecraft.network.chat.Component; import net.minecraft.resources.ResourceLocation; import net.minecraft.world.entity.Entity; +import net.minecraft.world.entity.EquipmentSlot; import net.minecraft.world.entity.EquipmentSlotGroup; 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.item.ArmorItem; import net.minecraft.world.item.ArmorMaterial; +import net.minecraft.world.item.Item; import net.minecraft.world.item.ItemStack; +import net.minecraft.world.item.TooltipFlag; import net.minecraft.world.item.component.ItemAttributeModifiers; import net.minecraft.world.level.Level; @@ -42,6 +47,40 @@ public class RoyalGuardianArmorItem extends ArmorItem { 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 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 public ItemAttributeModifiers getDefaultAttributeModifiers() { String prefix = "royal_guardian_" + this.armorType.getName(); diff --git a/common-1.21.1/src/main/java/com/craisinlord/antarchy/content/item/RoyalGuardianShieldItem.java b/common-1.21.1/src/main/java/com/craisinlord/antarchy/content/item/RoyalGuardianShieldItem.java index 878c90fc..86fd2dbb 100644 --- a/common-1.21.1/src/main/java/com/craisinlord/antarchy/content/item/RoyalGuardianShieldItem.java +++ b/common-1.21.1/src/main/java/com/craisinlord/antarchy/content/item/RoyalGuardianShieldItem.java @@ -2,9 +2,15 @@ package com.craisinlord.antarchy.content.item; import com.craisinlord.antarchy.content.effect.RoyalEffectEligibility; 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 java.util.List; +import java.util.Set; +import java.util.HashSet; import java.util.Comparator; +import java.util.UUID; +import java.util.stream.StreamSupport; import java.util.function.Supplier; import net.minecraft.core.Holder; 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.MobEffectInstance; 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.item.Item; import net.minecraft.world.item.ItemStack; @@ -24,9 +34,6 @@ import net.minecraft.world.level.Level; import net.minecraft.world.phys.AABB; 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 repairIngredient; public RoyalGuardianShieldItem(Item.Properties properties, Supplier repairIngredient) { @@ -54,6 +61,9 @@ public class RoyalGuardianShieldItem extends ShieldItem { @Override public InteractionResultHolder use(Level level, Player player, InteractionHand hand) { ItemStack stack = player.getItemInHand(hand); + if (!player.isShiftKeyDown()) { + return super.use(level, player, hand); + } if (player.getCooldowns().isOnCooldown(this)) { return InteractionResultHolder.fail(stack); } @@ -64,16 +74,54 @@ public class RoyalGuardianShieldItem extends ShieldItem { if (commanded == null) { return InteractionResultHolder.fail(stack); } - Mob target = level.getEntitiesOfClass(Mob.class, - 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)) { + if (!(level instanceof ServerLevel serverLevel)) { 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 rosterIds = new HashSet<>(); + List 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 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); } + + 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 + } } diff --git a/common-1.21.1/src/main/java/com/craisinlord/antarchy/content/item/RoyalGuardianSwordAbilities.java b/common-1.21.1/src/main/java/com/craisinlord/antarchy/content/item/RoyalGuardianSwordAbilities.java new file mode 100644 index 00000000..16615bd9 --- /dev/null +++ b/common-1.21.1/src/main/java/com/craisinlord/antarchy/content/item/RoyalGuardianSwordAbilities.java @@ -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 SECONDARY_DAMAGE_DEPTH = ThreadLocal.withInitial(() -> 0); + private static final ThreadLocal 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 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) { + } +} diff --git a/common-1.21.1/src/main/java/com/craisinlord/antarchy/content/item/RoyalGuardianSwordItem.java b/common-1.21.1/src/main/java/com/craisinlord/antarchy/content/item/RoyalGuardianSwordItem.java index fba30b17..d56f9d1a 100644 --- a/common-1.21.1/src/main/java/com/craisinlord/antarchy/content/item/RoyalGuardianSwordItem.java +++ b/common-1.21.1/src/main/java/com/craisinlord/antarchy/content/item/RoyalGuardianSwordItem.java @@ -4,14 +4,25 @@ import com.craisinlord.antarchy.config.AntarchySettings; import com.craisinlord.antarchy.content.client.model.ResourceBackedGeoItemModel; import com.craisinlord.antarchy.content.client.renderer.AnimatedHeldItemRenderer; 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.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.ai.attributes.AttributeModifier; import net.minecraft.world.entity.ai.attributes.Attributes; import net.minecraft.world.item.Item; import net.minecraft.world.item.ItemStack; +import net.minecraft.world.item.TooltipFlag; import net.minecraft.world.item.SwordItem; 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 software.bernie.geckolib.animatable.GeoItem; 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 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 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 AnimatableInstanceCache geoCache = GeckoLibUtil.createInstanceCache(this); @@ -60,6 +73,79 @@ public class RoyalGuardianSwordItem extends SwordItem implements GeoItem { return AntarchySettings.royalWeaponEnchantability(); } + @Override + public InteractionResultHolder 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 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 public void registerControllers(AnimatableManager.ControllerRegistrar controllers) { } diff --git a/common-1.21.1/src/main/java/com/craisinlord/antarchy/content/network/ActivateRoyalBoundaryPayload.java b/common-1.21.1/src/main/java/com/craisinlord/antarchy/content/network/ActivateRoyalBoundaryPayload.java new file mode 100644 index 00000000..176aaef6 --- /dev/null +++ b/common-1.21.1/src/main/java/com/craisinlord/antarchy/content/network/ActivateRoyalBoundaryPayload.java @@ -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 TYPE = new Type<>( + ResourceLocation.fromNamespaceAndPath(Antarchy.MODID, "activate_royal_boundary")); + public static final StreamCodec STREAM_CODEC = StreamCodec.unit(new ActivateRoyalBoundaryPayload()); + + @Override + public Type type() { + return TYPE; + } +} diff --git a/common-1.21.1/src/main/java/com/craisinlord/antarchy/content/network/AntmailNetworking.java b/common-1.21.1/src/main/java/com/craisinlord/antarchy/content/network/AntmailNetworking.java index a4d0409d..ff9a0cc6 100644 --- a/common-1.21.1/src/main/java/com/craisinlord/antarchy/content/network/AntmailNetworking.java +++ b/common-1.21.1/src/main/java/com/craisinlord/antarchy/content/network/AntmailNetworking.java @@ -1,26 +1,32 @@ package com.craisinlord.antarchy.content.network; 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.AntmailWire; import net.minecraft.core.BlockPos; import java.util.List; import java.util.UUID; +import java.util.concurrent.atomic.AtomicBoolean; import java.util.function.Consumer; public final class AntmailNetworking { private static Consumer sender = payload -> { }; + private static volatile boolean senderConfigured; + private static final AtomicBoolean senderErrorLogged = new AtomicBoolean(); private AntmailNetworking() { } public static void setSender(Consumer sender) { AntmailNetworking.sender = sender; + senderConfigured = sender != null; } 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) { @@ -32,38 +38,46 @@ public final class AntmailNetworking { } 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) { - 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 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) { - sender.accept(new AntmailReadPayload(pos, messageId.toString(), true)); + sendPayload(new AntmailReadPayload(pos, messageId.toString(), true)); } 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) { - sender.accept(new AntmailDeletePayload(pos, messageId.toString(), sent)); + sendPayload(new AntmailDeletePayload(pos, messageId.toString(), sent)); } 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) { - 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) { - 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); } } diff --git a/common-1.21.1/src/main/java/com/craisinlord/antarchy/content/network/AntmailServerHandler.java b/common-1.21.1/src/main/java/com/craisinlord/antarchy/content/network/AntmailServerHandler.java index 9ccc6416..5230e6c6 100644 --- a/common-1.21.1/src/main/java/com/craisinlord/antarchy/content/network/AntmailServerHandler.java +++ b/common-1.21.1/src/main/java/com/craisinlord/antarchy/content/network/AntmailServerHandler.java @@ -1,6 +1,7 @@ package com.craisinlord.antarchy.content.network; 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.AntmailDeliveryResult; import com.craisinlord.antarchy.content.antmail.AntmailDraft; @@ -20,17 +21,22 @@ import java.util.function.BiConsumer; public final class AntmailServerHandler { private static BiConsumer resultSender = (player, payload) -> { }; + private static volatile boolean resultSenderConfigured; private AntmailServerHandler() { } public static void setResultSender(BiConsumer sender) { resultSender = sender; + resultSenderConfigured = sender != null; } 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()); 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", ""); return; } @@ -43,8 +49,11 @@ public final class AntmailServerHandler { } 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()); 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", ""); return; } @@ -63,7 +72,13 @@ public final class AntmailServerHandler { int folder = payload.folder() < AntmailStateRequestPayload.INBOX || payload.folder() > AntmailStateRequestPayload.DRAFTS ? AntmailStateRequestPayload.INBOX : payload.folder(); 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) { @@ -240,6 +255,12 @@ public final class AntmailServerHandler { if (!address.isBlank()) version = AntmailServerData.access(player.server).mailboxVersion(AntmailAddress.parse(address)); } 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)); } } diff --git a/common-1.21.1/src/main/java/com/craisinlord/antarchy/content/network/ComputerAccessHandler.java b/common-1.21.1/src/main/java/com/craisinlord/antarchy/content/network/ComputerAccessHandler.java index 0f7bdeaf..7aaa3c8b 100644 --- a/common-1.21.1/src/main/java/com/craisinlord/antarchy/content/network/ComputerAccessHandler.java +++ b/common-1.21.1/src/main/java/com/craisinlord/antarchy/content/network/ComputerAccessHandler.java @@ -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.TerminalFileSystem; 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.computer.blockle.BlockleAnswers; 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.world.level.Level; 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 java.util.function.BiConsumer; @@ -68,6 +72,7 @@ public final class ComputerAccessHandler { case ComputerAccessPayload.ANTMAN_STATE -> antmanState(player, computer, payload); case ComputerAccessPayload.BLOCKLE_STATE -> blockleState(player, computer, payload, false); case ComputerAccessPayload.BLOCKLE_GUESS -> blockleState(player, computer, payload, true); + case ComputerAccessPayload.LOCATE_STRUCTURE -> locateStructure(player, computer, payload); default -> send(player, payload, ComputerAccessResultPayload.INVALID); } } @@ -75,6 +80,7 @@ public final class ComputerAccessHandler { private static void open(ServerPlayer player, ComputerBlockEntity computer, ComputerAccessPayload payload) { if (computer.hasActiveUser() && !computer.isAuthenticatedBy(player)) { send(player, payload, ComputerAccessResultPayload.BUSY); + sendArchive(player, computer, payload); return; } if (!computer.hasPassword()) { @@ -86,6 +92,13 @@ public final class ComputerAccessHandler { } else { 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) { @@ -204,10 +217,6 @@ public final class ComputerAccessHandler { } 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()); if (request == null) { sendTerminal(player, payload, TerminalResult.error("/", "ERROR: INVALID REQUEST")); @@ -320,7 +329,7 @@ public final class ComputerAccessHandler { return; } 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"); return; } @@ -332,6 +341,10 @@ public final class ComputerAccessHandler { long day = overworld.getDayTime() / 24000L; computer.resetBlockle(day); BlockleAnswers.Answer answer = BlockleAnswers.answerForDay(day); + if (answer == null) { + sendFile(player, payload, false, "", "answers_unavailable"); + return; + } if (submit) { String guess = payload.value() == null ? "" : payload.value().toLowerCase(java.util.Locale.ROOT); 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()), ""); } + 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 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 guesses) { StringBuilder result = new StringBuilder(Long.toString(day)); boolean solved = false; diff --git a/common-1.21.1/src/main/java/com/craisinlord/antarchy/content/network/ComputerAccessPayload.java b/common-1.21.1/src/main/java/com/craisinlord/antarchy/content/network/ComputerAccessPayload.java index 8391f52e..8f310b86 100644 --- a/common-1.21.1/src/main/java/com/craisinlord/antarchy/content/network/ComputerAccessPayload.java +++ b/common-1.21.1/src/main/java/com/craisinlord/antarchy/content/network/ComputerAccessPayload.java @@ -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 BLOCKLE_STATE = 18; 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 TYPE = new Type<>(ResourceLocation.fromNamespaceAndPath(Antarchy.MODID, "computer_access")); public static final StreamCodec STREAM_CODEC = StreamCodec.composite( BlockPos.STREAM_CODEC, ComputerAccessPayload::pos, diff --git a/common-1.21.1/src/main/java/com/craisinlord/antarchy/content/network/ComputerNetworking.java b/common-1.21.1/src/main/java/com/craisinlord/antarchy/content/network/ComputerNetworking.java index 3393d055..05159994 100644 --- a/common-1.21.1/src/main/java/com/craisinlord/antarchy/content/network/ComputerNetworking.java +++ b/common-1.21.1/src/main/java/com/craisinlord/antarchy/content/network/ComputerNetworking.java @@ -103,4 +103,8 @@ public final class ComputerNetworking { public static void submitBlockleGuess(BlockPos pos, String 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())); + } } diff --git a/common-1.21.1/src/main/java/com/craisinlord/antarchy/content/network/KingJudgmentFlashPayload.java b/common-1.21.1/src/main/java/com/craisinlord/antarchy/content/network/KingJudgmentFlashPayload.java new file mode 100644 index 00000000..90627fd4 --- /dev/null +++ b/common-1.21.1/src/main/java/com/craisinlord/antarchy/content/network/KingJudgmentFlashPayload.java @@ -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 TYPE = + new Type<>(ResourceLocation.fromNamespaceAndPath(Antarchy.MODID, "king_judgment_flash")); + public static final StreamCodec STREAM_CODEC = + ByteBufCodecs.VAR_INT.map(KingJudgmentFlashPayload::new, KingJudgmentFlashPayload::durationTicks); + + @Override + public Type type() { + return TYPE; + } +} diff --git a/common-1.21.1/src/main/java/com/craisinlord/antarchy/content/network/KingJudgmentFlashSync.java b/common-1.21.1/src/main/java/com/craisinlord/antarchy/content/network/KingJudgmentFlashSync.java new file mode 100644 index 00000000..3eaea965 --- /dev/null +++ b/common-1.21.1/src/main/java/com/craisinlord/antarchy/content/network/KingJudgmentFlashSync.java @@ -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 NOOP = (player, ticks) -> { + }; + + private static volatile BiConsumer sink = NOOP; + + private KingJudgmentFlashSync() { + } + + public static void setSink(BiConsumer sink) { + KingJudgmentFlashSync.sink = sink == null ? NOOP : sink; + } + + public static void send(ServerPlayer player, int ticks) { + sink.accept(player, ticks); + } +} diff --git a/common-1.21.1/src/main/java/com/craisinlord/antarchy/content/portal/PermanentPortalTeleporter.java b/common-1.21.1/src/main/java/com/craisinlord/antarchy/content/portal/PermanentPortalTeleporter.java index 22ecc618..10ff1077 100644 --- a/common-1.21.1/src/main/java/com/craisinlord/antarchy/content/portal/PermanentPortalTeleporter.java +++ b/common-1.21.1/src/main/java/com/craisinlord/antarchy/content/portal/PermanentPortalTeleporter.java @@ -5,6 +5,7 @@ import java.util.LinkedHashSet; import java.util.Set; import net.minecraft.core.BlockPos; import net.minecraft.core.Direction; +import net.minecraft.tags.BlockTags; import net.minecraft.resources.ResourceKey; import net.minecraft.server.level.ServerLevel; import net.minecraft.server.level.ServerPlayer; @@ -46,6 +47,12 @@ public final class PermanentPortalTeleporter { } 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); } @@ -57,17 +64,24 @@ public final class PermanentPortalTeleporter { return sourceLevel.getServer().getLevel(type.primaryDimension()); } + @Nullable private static Vec3 findArrivalPosition(Entity entity, ServerLevel destination, BlockPos preferredPos, PermanentPortalType type) { PermanentPortalShape active = PermanentPortalShape.findActiveNear(destination, preferredPos, type); 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); if (safe != null) { PermanentPortalShape nearbyActive = findActiveNearby(destination, BlockPos.containing(safe), type); 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); @@ -77,11 +91,7 @@ public final class PermanentPortalTeleporter { return createFallbackPortal(destination, BlockPos.containing(safe), type); } - BlockPos fallbackPos = preferredPos; - if (type == PermanentPortalType.ELYTHIA && destination.hasChunkAt(preferredPos)) { - fallbackPos = destination.getHeightmapPos(Heightmap.Types.MOTION_BLOCKING_NO_LEAVES, preferredPos); - } - return createFallbackPortal(destination, fallbackPos, type); + return null; } @Nullable @@ -119,7 +129,8 @@ public final class PermanentPortalTeleporter { } Set candidates = new LinkedHashSet<>(); - if (type != PermanentPortalType.ELYTHIA) { + boolean elythia = destination.dimension() == PermanentPortalType.ELYTHIA.primaryDimension(); + if (!elythia) { addCandidate(candidates, preferredPos); } @@ -132,20 +143,28 @@ public final class PermanentPortalTeleporter { BlockPos horizontalPos = preferredPos.offset(xOff, 0, zOff); if (destination.hasChunkAt(horizontalPos)) { - addCandidate(candidates, destination.getHeightmapPos(Heightmap.Types.MOTION_BLOCKING_NO_LEAVES, horizontalPos)); - addCandidate(candidates, destination.getHeightmapPos(Heightmap.Types.WORLD_SURFACE, 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)); + } } - for (int yOff = 0; yOff <= VERTICAL_SEARCH; yOff++) { - addCandidate(candidates, horizontalPos.above(yOff)); - if (yOff > 0) { - addCandidate(candidates, horizontalPos.below(yOff)); + if (!elythia) { + for (int yOff = 0; yOff <= VERTICAL_SEARCH; yOff++) { + addCandidate(candidates, horizontalPos.above(yOff)); + if (yOff > 0) { + addCandidate(candidates, horizontalPos.below(yOff)); + } } } } } } - if (type == PermanentPortalType.ELYTHIA) { + if (elythia) { addCandidate(candidates, preferredPos); } @@ -300,6 +319,36 @@ public final class PermanentPortalTeleporter { private static boolean isValidArrivalPosition(ServerLevel destination, Vec3 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() || destination.dimension() == AntarchySettings.redAntDestinationDimension()) { if (!destination.hasChunkAt(standPos) || !destination.hasChunkAt(standPos.below())) { diff --git a/common-1.21.1/src/main/java/com/craisinlord/antarchy/content/worldgen/thoraxis/ThoraxisTransitionThroatFeature.java b/common-1.21.1/src/main/java/com/craisinlord/antarchy/content/worldgen/thoraxis/ThoraxisTransitionThroatFeature.java index 038fb6bc..615bef9e 100644 --- a/common-1.21.1/src/main/java/com/craisinlord/antarchy/content/worldgen/thoraxis/ThoraxisTransitionThroatFeature.java +++ b/common-1.21.1/src/main/java/com/craisinlord/antarchy/content/worldgen/thoraxis/ThoraxisTransitionThroatFeature.java @@ -28,11 +28,11 @@ public final class ThoraxisTransitionThroatFeature extends Feature STATES = new WeakHashMap<>(); @@ -231,9 +231,7 @@ public final class ThoraxisUndersideManager { if (!crossing.toUnderside) { refreshInvertedEffect(player); } - if (crossing.ticks > CROSSING_TIMEOUT_TICKS - || (crossing.toUnderside && velocity.y > 0.08D) - || (!crossing.toUnderside && velocity.y < -0.08D)) { + if (crossing.ticks > CROSSING_TIMEOUT_TICKS) { tracking.playerCrossings.remove(player.getUUID()); return; } @@ -248,6 +246,11 @@ public final class ThoraxisUndersideManager { ? AntarchyGravityDirection.UP : AntarchyGravityDirection.DOWN; 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(); crossing.flipped = true; crossing.ticks = 0; diff --git a/common-1.21.1/src/main/java/com/craisinlord/antarchy/mixins/AntarchyMixinPlugin.java b/common-1.21.1/src/main/java/com/craisinlord/antarchy/mixins/AntarchyMixinPlugin.java index e6202b3a..da4f8bf8 100644 --- a/common-1.21.1/src/main/java/com/craisinlord/antarchy/mixins/AntarchyMixinPlugin.java +++ b/common-1.21.1/src/main/java/com/craisinlord/antarchy/mixins/AntarchyMixinPlugin.java @@ -25,10 +25,23 @@ public final class AntarchyMixinPlugin implements IMixinConfigPlugin { @Override public boolean shouldApplyMixin(String targetClassName, String mixinClassName) { - String resourcePath = targetClassName.replace('.', '/') + ".class"; - if (this.getClass().getClassLoader().getResource(resourcePath) == null) { + if (!classPresent(targetClassName)) { 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")) { // Trade registration is platform-driven on every supported loader now. Keeping the // static VillagerTrades class-init mixin around risks touching AntarchyObjects before @@ -38,6 +51,16 @@ public final class AntarchyMixinPlugin implements IMixinConfigPlugin { 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 public void acceptTargets(Set myTargets, Set otherTargets) { } diff --git a/common-1.21.1/src/main/java/com/craisinlord/antarchy/mixins/CommandedMobMixin.java b/common-1.21.1/src/main/java/com/craisinlord/antarchy/mixins/CommandedMobMixin.java index 0dd0ff3f..b3ad46fc 100644 --- a/common-1.21.1/src/main/java/com/craisinlord/antarchy/mixins/CommandedMobMixin.java +++ b/common-1.21.1/src/main/java/com/craisinlord/antarchy/mixins/CommandedMobMixin.java @@ -13,6 +13,8 @@ public abstract class CommandedMobMixin implements CommandedEntityAccess { private UUID antarchy$commanderUuid; @Unique private boolean antarchy$commandedTargetOwned; + @Unique + private boolean antarchy$royalInvested; @Override public UUID antarchy$getCommanderUuid() { @@ -34,11 +36,22 @@ public abstract class CommandedMobMixin implements CommandedEntityAccess { 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")) private void antarchy$saveCommandedData(CompoundTag tag, org.spongepowered.asm.mixin.injection.callback.CallbackInfo ci) { if (antarchy$commanderUuid != null) { tag.putUUID("AntarchyCommander", antarchy$commanderUuid); tag.putBoolean("AntarchyCommandedTargetOwned", antarchy$commandedTargetOwned); + tag.putBoolean("AntarchyRoyalInvested", antarchy$royalInvested); } } @@ -47,6 +60,7 @@ public abstract class CommandedMobMixin implements CommandedEntityAccess { if (tag.hasUUID("AntarchyCommander")) { antarchy$commanderUuid = tag.getUUID("AntarchyCommander"); antarchy$commandedTargetOwned = tag.getBoolean("AntarchyCommandedTargetOwned"); + antarchy$royalInvested = tag.getBoolean("AntarchyRoyalInvested"); } } } diff --git a/common-1.21.1/src/main/java/com/craisinlord/antarchy/mixins/JudgmentMarkEntityMixin.java b/common-1.21.1/src/main/java/com/craisinlord/antarchy/mixins/JudgmentMarkEntityMixin.java new file mode 100644 index 00000000..436a0bb7 --- /dev/null +++ b/common-1.21.1/src/main/java/com/craisinlord/antarchy/mixins/JudgmentMarkEntityMixin.java @@ -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 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); + } + } +} diff --git a/common-1.21.1/src/main/java/com/craisinlord/antarchy/mixins/ProjectileRoyalBoundaryMixin.java b/common-1.21.1/src/main/java/com/craisinlord/antarchy/mixins/ProjectileRoyalBoundaryMixin.java new file mode 100644 index 00000000..4e11d521 --- /dev/null +++ b/common-1.21.1/src/main/java/com/craisinlord/antarchy/mixins/ProjectileRoyalBoundaryMixin.java @@ -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); + } +} diff --git a/common-1.21.1/src/main/java/com/craisinlord/antarchy/mixins/RoyalBoundaryOwnerCleanupMixin.java b/common-1.21.1/src/main/java/com/craisinlord/antarchy/mixins/RoyalBoundaryOwnerCleanupMixin.java new file mode 100644 index 00000000..31ce2c37 --- /dev/null +++ b/common-1.21.1/src/main/java/com/craisinlord/antarchy/mixins/RoyalBoundaryOwnerCleanupMixin.java @@ -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); + } +} diff --git a/common-1.21.1/src/main/java/com/craisinlord/antarchy/mixins/RoyalGuardianPlayerAttackMixin.java b/common-1.21.1/src/main/java/com/craisinlord/antarchy/mixins/RoyalGuardianPlayerAttackMixin.java new file mode 100644 index 00000000..de94f6fe --- /dev/null +++ b/common-1.21.1/src/main/java/com/craisinlord/antarchy/mixins/RoyalGuardianPlayerAttackMixin.java @@ -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); + } +} diff --git a/common-1.21.1/src/main/java/com/craisinlord/antarchy/mixins/RoyalGuardianSwordCombatMixin.java b/common-1.21.1/src/main/java/com/craisinlord/antarchy/mixins/RoyalGuardianSwordCombatMixin.java new file mode 100644 index 00000000..0edd0160 --- /dev/null +++ b/common-1.21.1/src/main/java/com/craisinlord/antarchy/mixins/RoyalGuardianSwordCombatMixin.java @@ -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 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()); + } +} diff --git a/common-1.21.1/src/main/java/com/craisinlord/antarchy/mixins/RoyalJudgmentCombatMixin.java b/common-1.21.1/src/main/java/com/craisinlord/antarchy/mixins/RoyalJudgmentCombatMixin.java new file mode 100644 index 00000000..f193ee91 --- /dev/null +++ b/common-1.21.1/src/main/java/com/craisinlord/antarchy/mixins/RoyalJudgmentCombatMixin.java @@ -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 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; + } +} diff --git a/common-1.21.1/src/main/java/com/craisinlord/antarchy/mixins/RoyalLootItemGlowMixin.java b/common-1.21.1/src/main/java/com/craisinlord/antarchy/mixins/RoyalLootItemGlowMixin.java new file mode 100644 index 00000000..8ce132a4 --- /dev/null +++ b/common-1.21.1/src/main/java/com/craisinlord/antarchy/mixins/RoyalLootItemGlowMixin.java @@ -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); + } +} diff --git a/common-1.21.1/src/main/java/com/craisinlord/antarchy/mixins/ServerPlayerRoyalBoundaryMixin.java b/common-1.21.1/src/main/java/com/craisinlord/antarchy/mixins/ServerPlayerRoyalBoundaryMixin.java new file mode 100644 index 00000000..25978774 --- /dev/null +++ b/common-1.21.1/src/main/java/com/craisinlord/antarchy/mixins/ServerPlayerRoyalBoundaryMixin.java @@ -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); + } +} diff --git a/common-1.21.1/src/main/java/com/craisinlord/antarchy/mixins/client/HoverboardRiderPoseMixin.java b/common-1.21.1/src/main/java/com/craisinlord/antarchy/mixins/client/HoverboardRiderPoseMixin.java index edc34b4a..0281f995 100644 --- a/common-1.21.1/src/main/java/com/craisinlord/antarchy/mixins/client/HoverboardRiderPoseMixin.java +++ b/common-1.21.1/src/main/java/com/craisinlord/antarchy/mixins/client/HoverboardRiderPoseMixin.java @@ -64,4 +64,17 @@ public abstract class HoverboardRiderPoseMixin 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); + } + } } diff --git a/common-1.21.1/src/main/java/com/craisinlord/antarchy/mixins/compat/alexscaves/AlexsCavesCandyCaneHookMixin.java b/common-1.21.1/src/main/java/com/craisinlord/antarchy/mixins/compat/alexscaves/AlexsCavesCandyCaneHookMixin.java new file mode 100644 index 00000000..e809f64e --- /dev/null +++ b/common-1.21.1/src/main/java/com/craisinlord/antarchy/mixins/compat/alexscaves/AlexsCavesCandyCaneHookMixin.java @@ -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; + } +} diff --git a/common-1.21.1/src/main/java/com/craisinlord/antarchy/mixins/compat/alexscaves/AlexsCavesDepthChargeMixin.java b/common-1.21.1/src/main/java/com/craisinlord/antarchy/mixins/compat/alexscaves/AlexsCavesDepthChargeMixin.java new file mode 100644 index 00000000..410b00fd --- /dev/null +++ b/common-1.21.1/src/main/java/com/craisinlord/antarchy/mixins/compat/alexscaves/AlexsCavesDepthChargeMixin.java @@ -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; + } +} diff --git a/common-1.21.1/src/main/java/com/craisinlord/antarchy/mixins/compat/alexscaves/AlexsCavesTephraMixin.java b/common-1.21.1/src/main/java/com/craisinlord/antarchy/mixins/compat/alexscaves/AlexsCavesTephraMixin.java new file mode 100644 index 00000000..374bb783 --- /dev/null +++ b/common-1.21.1/src/main/java/com/craisinlord/antarchy/mixins/compat/alexscaves/AlexsCavesTephraMixin.java @@ -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; + } +} diff --git a/common-1.21.1/src/main/java/com/craisinlord/antarchy/mixins/compat/alexscaves/AlexsCavesWaterBoltMixin.java b/common-1.21.1/src/main/java/com/craisinlord/antarchy/mixins/compat/alexscaves/AlexsCavesWaterBoltMixin.java new file mode 100644 index 00000000..329169ae --- /dev/null +++ b/common-1.21.1/src/main/java/com/craisinlord/antarchy/mixins/compat/alexscaves/AlexsCavesWaterBoltMixin.java @@ -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; + } +} diff --git a/common-1.21.1/src/main/java/com/craisinlord/antarchy/mixins/compat/ars_nouveau/ArsNouveauProjectileGravityMixin.java b/common-1.21.1/src/main/java/com/craisinlord/antarchy/mixins/compat/ars_nouveau/ArsNouveauProjectileGravityMixin.java new file mode 100644 index 00000000..d938dd91 --- /dev/null +++ b/common-1.21.1/src/main/java/com/craisinlord/antarchy/mixins/compat/ars_nouveau/ArsNouveauProjectileGravityMixin.java @@ -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)); + } +} diff --git a/common-1.21.1/src/main/java/com/craisinlord/antarchy/mixins/compat/irons_spellbooks/IronsSpellbooksProjectileGravityMixin.java b/common-1.21.1/src/main/java/com/craisinlord/antarchy/mixins/compat/irons_spellbooks/IronsSpellbooksProjectileGravityMixin.java new file mode 100644 index 00000000..2abf065f --- /dev/null +++ b/common-1.21.1/src/main/java/com/craisinlord/antarchy/mixins/compat/irons_spellbooks/IronsSpellbooksProjectileGravityMixin.java @@ -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)); + } +} diff --git a/common-1.21.1/src/main/java/com/craisinlord/antarchy/mixins/compat/scguns/ScgunsArrowGravityMixin.java b/common-1.21.1/src/main/java/com/craisinlord/antarchy/mixins/compat/scguns/ScgunsArrowGravityMixin.java new file mode 100644 index 00000000..fbe65aef --- /dev/null +++ b/common-1.21.1/src/main/java/com/craisinlord/antarchy/mixins/compat/scguns/ScgunsArrowGravityMixin.java @@ -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)); + } +} diff --git a/common-1.21.1/src/main/java/com/craisinlord/antarchy/mixins/compat/scguns/ScgunsProjectileEntityGravityMixin.java b/common-1.21.1/src/main/java/com/craisinlord/antarchy/mixins/compat/scguns/ScgunsProjectileEntityGravityMixin.java new file mode 100644 index 00000000..f76ed2a2 --- /dev/null +++ b/common-1.21.1/src/main/java/com/craisinlord/antarchy/mixins/compat/scguns/ScgunsProjectileEntityGravityMixin.java @@ -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)); + } +} diff --git a/common-1.21.1/src/main/java/com/craisinlord/antarchy/mixins/gravity/ProjectileGravityMixin.java b/common-1.21.1/src/main/java/com/craisinlord/antarchy/mixins/gravity/ProjectileGravityMixin.java index 16758cf2..eea80ce1 100644 --- a/common-1.21.1/src/main/java/com/craisinlord/antarchy/mixins/gravity/ProjectileGravityMixin.java +++ b/common-1.21.1/src/main/java/com/craisinlord/antarchy/mixins/gravity/ProjectileGravityMixin.java @@ -1,7 +1,7 @@ package com.craisinlord.antarchy.mixins.gravity; 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.llamalad7.mixinextras.injector.wrapoperation.Operation; import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation; @@ -58,7 +58,7 @@ public abstract class ProjectileGravityMixin { ) { Vec3 direction = new Vec3(x, y, z); if (AntarchyGravityApi.isGravityInverted(shooter)) { - direction = AntarchyGravityRotationUtil.vecPlayerToWorld( + direction = ProjectileLaunchGravity.toWorld( direction, AntarchyGravityApi.getGravityDirection(shooter) ); diff --git a/common-1.21.1/src/main/resources/antarchy-common.mixins.json b/common-1.21.1/src/main/resources/antarchy-common.mixins.json index 0d686526..0bcd2a72 100644 --- a/common-1.21.1/src/main/resources/antarchy-common.mixins.json +++ b/common-1.21.1/src/main/resources/antarchy-common.mixins.json @@ -31,8 +31,13 @@ "LivingEntityJumpingAccessor", "CommandedMobMixin", "LivingEntityRoyalEffectsMixin", + "JudgmentMarkEntityMixin", + "RoyalJudgmentCombatMixin", + "RoyalGuardianPlayerAttackMixin", + "RoyalGuardianSwordCombatMixin", "MobCommandedTargetMixin", "RoyalNoRespiteMixin", + "RoyalLootItemGlowMixin", "MantisClawWallClimbMixin", "MilkBucketSizeResetMixin", "MinecartItemMixin", @@ -49,6 +54,9 @@ "VillagerTradesMixin", "WaterlilyLumenMixin", "ServerPlayerGameModeMixin", + "ServerPlayerRoyalBoundaryMixin", + "ProjectileRoyalBoundaryMixin", + "RoyalBoundaryOwnerCleanupMixin", "PlayerAdvancementsMixin", "SpongeAntiwaterMixin", "SquidzookaEnchantMixin", @@ -87,6 +95,14 @@ "gravity.ServerPlayerGravityMixin", "gravity.ThrowableProjectileGravityMixin", "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", "time.EntityDeltaMovementTimeDilationMixin", "time.EntityMoveTimeDilationMixin", diff --git a/common-1.21.1/src/main/resources/assets/antarchy/lang/en_us.json b/common-1.21.1/src/main/resources/assets/antarchy/lang/en_us.json index 8be5aa82..903dc8d7 100644 --- a/common-1.21.1/src/main/resources/assets/antarchy/lang/en_us.json +++ b/common-1.21.1/src/main/resources/assets/antarchy/lang/en_us.json @@ -543,6 +543,15 @@ "item.antarchy.royal_guardian_boots": "Royal Guardian Boots", "item.antarchy.royal_guardian_sword": "Royal Guardian Sword", "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_chestplate": "Royal Assailant Chestplate", "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_battleaxe.black_hole": "Use to create a Black Hole", "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_boots.fall": "Cancels fall damage while falling upward.", "item.antarchy.mantis_claw": "Mantis Claw", @@ -562,6 +576,9 @@ "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.", "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", "item.antarchy.springbug_leg": "Springbug Leg", "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_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.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_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.", @@ -1425,7 +1443,8 @@ ,"decree.antarchy.close_quarters": "CLOSE QUARTERS" ,"decree.antarchy.close_quarters.subtitle": "Enter the royal circle. Projectiles are forbidden." ,"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.subtitle": "Keep out of the King's inner radius." ,"decree.antarchy.fight_me_coward": "FIGHT ME, COWARD!" @@ -1475,9 +1494,9 @@ ,"guide.antarchy.entry.chronosphere.title": "Chronosphere" ,"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.toreterror_arena.title": "Toreterror Arena" - ,"guide.antarchy.entry.toreterror_arena.subtitle": "Structure location fragment" - ,"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.title": "Toreterror" + ,"guide.antarchy.entry.toreterror_arena.subtitle": "Elythia Ocean boss" + ,"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.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." @@ -1505,6 +1524,27 @@ ,"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.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.prompt": "CREATE A PASSWORD TO CONTINUE" ,"screen.antarchy.computer.first_use.confirm": "CONFIRM PASSWORD" diff --git a/common-1.21.1/src/main/resources/assets/antarchy/models/item/computer.json b/common-1.21.1/src/main/resources/assets/antarchy/models/item/computer.json index b119ecb7..7b1a8ed5 100644 --- a/common-1.21.1/src/main/resources/assets/antarchy/models/item/computer.json +++ b/common-1.21.1/src/main/resources/assets/antarchy/models/item/computer.json @@ -3,8 +3,8 @@ "credit": "Made with Blockbench", "texture_size": [128, 128], "textures": { - "0": "antarchy/item/computer", - "particle": "antarchy/item/computer" + "0": "antarchy:item/computer", + "particle": "antarchy:item/computer" }, "elements": [ { diff --git a/common-1.21.1/src/main/resources/assets/antarchy/textures/gui/antpaint/ATTRIBUTION.txt b/common-1.21.1/src/main/resources/assets/antarchy/textures/gui/antpaint/ATTRIBUTION.txt new file mode 100644 index 00000000..eba7c488 --- /dev/null +++ b/common-1.21.1/src/main/resources/assets/antarchy/textures/gui/antpaint/ATTRIBUTION.txt @@ -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. diff --git a/common-1.21.1/src/main/resources/assets/antarchy/textures/gui/antpaint/LICENSE-pixelarticons.txt b/common-1.21.1/src/main/resources/assets/antarchy/textures/gui/antpaint/LICENSE-pixelarticons.txt new file mode 100644 index 00000000..4be35e8f --- /dev/null +++ b/common-1.21.1/src/main/resources/assets/antarchy/textures/gui/antpaint/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. diff --git a/common-1.21.1/src/main/resources/assets/antarchy/textures/gui/antpaint/eraser.png b/common-1.21.1/src/main/resources/assets/antarchy/textures/gui/antpaint/eraser.png new file mode 100644 index 00000000..c08255a2 Binary files /dev/null and b/common-1.21.1/src/main/resources/assets/antarchy/textures/gui/antpaint/eraser.png differ diff --git a/common-1.21.1/src/main/resources/assets/antarchy/textures/gui/antpaint/eraser.svg b/common-1.21.1/src/main/resources/assets/antarchy/textures/gui/antpaint/eraser.svg new file mode 100644 index 00000000..00156f00 --- /dev/null +++ b/common-1.21.1/src/main/resources/assets/antarchy/textures/gui/antpaint/eraser.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/common-1.21.1/src/main/resources/assets/antarchy/textures/gui/antpaint/pencil.png b/common-1.21.1/src/main/resources/assets/antarchy/textures/gui/antpaint/pencil.png new file mode 100644 index 00000000..561b0b85 Binary files /dev/null and b/common-1.21.1/src/main/resources/assets/antarchy/textures/gui/antpaint/pencil.png differ diff --git a/common-1.21.1/src/main/resources/assets/antarchy/textures/gui/antpaint/pencil.svg b/common-1.21.1/src/main/resources/assets/antarchy/textures/gui/antpaint/pencil.svg new file mode 100644 index 00000000..e89ccbd6 --- /dev/null +++ b/common-1.21.1/src/main/resources/assets/antarchy/textures/gui/antpaint/pencil.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/common-1.21.1/src/main/resources/assets/antarchy/textures/gui/antpaint/redo.png b/common-1.21.1/src/main/resources/assets/antarchy/textures/gui/antpaint/redo.png new file mode 100644 index 00000000..295aefb5 Binary files /dev/null and b/common-1.21.1/src/main/resources/assets/antarchy/textures/gui/antpaint/redo.png differ diff --git a/common-1.21.1/src/main/resources/assets/antarchy/textures/gui/antpaint/redo.svg b/common-1.21.1/src/main/resources/assets/antarchy/textures/gui/antpaint/redo.svg new file mode 100644 index 00000000..e3d850e2 --- /dev/null +++ b/common-1.21.1/src/main/resources/assets/antarchy/textures/gui/antpaint/redo.svg @@ -0,0 +1,4 @@ + + + + diff --git a/common-1.21.1/src/main/resources/assets/antarchy/textures/gui/antpaint/trash.png b/common-1.21.1/src/main/resources/assets/antarchy/textures/gui/antpaint/trash.png new file mode 100644 index 00000000..b8ba1b21 Binary files /dev/null and b/common-1.21.1/src/main/resources/assets/antarchy/textures/gui/antpaint/trash.png differ diff --git a/common-1.21.1/src/main/resources/assets/antarchy/textures/gui/antpaint/trash.svg b/common-1.21.1/src/main/resources/assets/antarchy/textures/gui/antpaint/trash.svg new file mode 100644 index 00000000..f67fe4ec --- /dev/null +++ b/common-1.21.1/src/main/resources/assets/antarchy/textures/gui/antpaint/trash.svg @@ -0,0 +1,3 @@ + + + diff --git a/common-1.21.1/src/main/resources/assets/antarchy/textures/gui/antpaint/undo.png b/common-1.21.1/src/main/resources/assets/antarchy/textures/gui/antpaint/undo.png new file mode 100644 index 00000000..6ad846e0 Binary files /dev/null and b/common-1.21.1/src/main/resources/assets/antarchy/textures/gui/antpaint/undo.png differ diff --git a/common-1.21.1/src/main/resources/assets/antarchy/textures/gui/antpaint/undo.svg b/common-1.21.1/src/main/resources/assets/antarchy/textures/gui/antpaint/undo.svg new file mode 100644 index 00000000..f9fbac20 --- /dev/null +++ b/common-1.21.1/src/main/resources/assets/antarchy/textures/gui/antpaint/undo.svg @@ -0,0 +1,3 @@ + + + diff --git a/common-1.21.1/src/main/resources/assets/antarchy/textures/item/glimmer_bottle.png b/common-1.21.1/src/main/resources/assets/antarchy/textures/item/glimmer_bottle.png index f9c4da5c..f783957b 100644 Binary files a/common-1.21.1/src/main/resources/assets/antarchy/textures/item/glimmer_bottle.png and b/common-1.21.1/src/main/resources/assets/antarchy/textures/item/glimmer_bottle.png differ diff --git a/common-1.21.1/src/main/resources/assets/antarchy/textures/item/glimmer_bottle.png.mcmeta b/common-1.21.1/src/main/resources/assets/antarchy/textures/item/glimmer_bottle.png.mcmeta new file mode 100644 index 00000000..1e3d6590 --- /dev/null +++ b/common-1.21.1/src/main/resources/assets/antarchy/textures/item/glimmer_bottle.png.mcmeta @@ -0,0 +1,6 @@ +{ + "animation": { + "frametime": 3, + "interpolate": true + } +} \ No newline at end of file diff --git a/common-1.21.1/src/main/resources/data/antarchy/blockle/answers.json b/common-1.21.1/src/main/resources/data/antarchy/blockle/answers.json new file mode 100644 index 00000000..28dcd606 --- /dev/null +++ b/common-1.21.1/src/main/resources/data/antarchy/blockle/answers.json @@ -0,0 +1,46 @@ +[ + "anvil", + "apple", + "arrow", + "bread", + "brick", + "chain", + "chest", + "clock", + "cocoa", + "flint", + "glass", + "melon", + "paper", + "poppy", + "sculk", + "stone", + "stick", + "sugar", + "torch", + "vault", + "wheat", + "allay", + "blaze", + "camel", + "drown", + "ghast", + "horse", + "llama", + "panda", + "sheep", + "slime", + "squid", + "stray", + "witch", + "lever", + "sword", + "cheep", + "jerry", + "lucid", + "queen", + "ichor", + "lotus", + "mucus", + "peach" +] diff --git a/common-1.21.1/src/main/resources/data/antarchy/computer/disk/king.json b/common-1.21.1/src/main/resources/data/antarchy/computer/disk/king.json new file mode 100644 index 00000000..4966df7f --- /dev/null +++ b/common-1.21.1/src/main/resources/data/antarchy/computer/disk/king.json @@ -0,0 +1,5 @@ +{ + "category": "elythia", + "title": "guide.antarchy.disk.king.title", + "entries": ["antarchy:king"] +} diff --git a/common-1.21.1/src/main/resources/data/antarchy/computer/disk/kraken.json b/common-1.21.1/src/main/resources/data/antarchy/computer/disk/kraken.json new file mode 100644 index 00000000..2b2d6f7c --- /dev/null +++ b/common-1.21.1/src/main/resources/data/antarchy/computer/disk/kraken.json @@ -0,0 +1,5 @@ +{ + "category": "general", + "title": "guide.antarchy.disk.kraken.title", + "entries": ["antarchy:kraken"] +} diff --git a/common-1.21.1/src/main/resources/data/antarchy/computer/disk/queen.json b/common-1.21.1/src/main/resources/data/antarchy/computer/disk/queen.json new file mode 100644 index 00000000..39e9f6ed --- /dev/null +++ b/common-1.21.1/src/main/resources/data/antarchy/computer/disk/queen.json @@ -0,0 +1,5 @@ +{ + "category": "thoraxis", + "title": "guide.antarchy.disk.queen.title", + "entries": ["antarchy:queen"] +} diff --git a/common-1.21.1/src/main/resources/data/antarchy/computer/entry/bloodglass.json b/common-1.21.1/src/main/resources/data/antarchy/computer/entry/bloodglass.json index bed99ccf..f1ce876b 100644 --- a/common-1.21.1/src/main/resources/data/antarchy/computer/entry/bloodglass.json +++ b/common-1.21.1/src/main/resources/data/antarchy/computer/entry/bloodglass.json @@ -3,5 +3,6 @@ "category": "thoraxis", "title": "guide.antarchy.entry.bloodglass.title", "subtitle": "guide.antarchy.entry.bloodglass.subtitle", - "description": ["guide.antarchy.entry.bloodglass.description"] + "description": ["guide.antarchy.entry.bloodglass.description"], + "cover_item": "antarchy:blood_crystal_shard" } diff --git a/common-1.21.1/src/main/resources/data/antarchy/computer/entry/cavaryn.json b/common-1.21.1/src/main/resources/data/antarchy/computer/entry/cavaryn.json index 7f03da60..91bffaf3 100644 --- a/common-1.21.1/src/main/resources/data/antarchy/computer/entry/cavaryn.json +++ b/common-1.21.1/src/main/resources/data/antarchy/computer/entry/cavaryn.json @@ -4,5 +4,6 @@ "title": "guide.antarchy.entry.cavaryn.title", "subtitle": "guide.antarchy.entry.cavaryn.subtitle", "description": ["guide.antarchy.entry.cavaryn.description"], + "cover_entity": "antarchy:termite", "dimension": "antarchy:cavaryn" } diff --git a/common-1.21.1/src/main/resources/data/antarchy/computer/entry/chronosphere.json b/common-1.21.1/src/main/resources/data/antarchy/computer/entry/chronosphere.json index 75fc77bd..861bbf08 100644 --- a/common-1.21.1/src/main/resources/data/antarchy/computer/entry/chronosphere.json +++ b/common-1.21.1/src/main/resources/data/antarchy/computer/entry/chronosphere.json @@ -1,8 +1,8 @@ { - "type": "item", + "type": "enchantment", "category": "thoraxis", "title": "guide.antarchy.entry.chronosphere.title", "subtitle": "guide.antarchy.entry.chronosphere.subtitle", "description": ["guide.antarchy.entry.chronosphere.description"], - "item": "antarchy:chronosphere" + "enchantment": "antarchy:chronosphere" } diff --git a/common-1.21.1/src/main/resources/data/antarchy/computer/entry/duplicator_tree.json b/common-1.21.1/src/main/resources/data/antarchy/computer/entry/duplicator_tree.json new file mode 100644 index 00000000..8ff71cf7 --- /dev/null +++ b/common-1.21.1/src/main/resources/data/antarchy/computer/entry/duplicator_tree.json @@ -0,0 +1,8 @@ +{ + "type": "article", + "category": "general", + "title": "guide.antarchy.entry.duplicator_tree.title", + "subtitle": "guide.antarchy.entry.duplicator_tree.subtitle", + "description": ["guide.antarchy.entry.duplicator_tree.description"], + "cover_item": "antarchy:duplicator_log" +} diff --git a/common-1.21.1/src/main/resources/data/antarchy/computer/entry/easter_bunny.json b/common-1.21.1/src/main/resources/data/antarchy/computer/entry/easter_bunny.json new file mode 100644 index 00000000..b8386b7b --- /dev/null +++ b/common-1.21.1/src/main/resources/data/antarchy/computer/entry/easter_bunny.json @@ -0,0 +1,9 @@ +{ + "type": "entity", + "category": "general", + "title": "guide.antarchy.entry.easter_bunny.title", + "subtitle": "guide.antarchy.entry.easter_bunny.subtitle", + "description": ["guide.antarchy.entry.easter_bunny.description"], + "entity": "antarchy:easter_bunny", + "cover_entity": "antarchy:easter_bunny" +} diff --git a/common-1.21.1/src/main/resources/data/antarchy/computer/entry/elythia.json b/common-1.21.1/src/main/resources/data/antarchy/computer/entry/elythia.json index 47a52165..bf0f06ec 100644 --- a/common-1.21.1/src/main/resources/data/antarchy/computer/entry/elythia.json +++ b/common-1.21.1/src/main/resources/data/antarchy/computer/entry/elythia.json @@ -4,5 +4,6 @@ "title": "guide.antarchy.entry.elythia.title", "subtitle": "guide.antarchy.entry.elythia.subtitle", "description": ["guide.antarchy.entry.elythia.description"], + "cover_entity": "antarchy:brown_ant", "dimension": "antarchy:elythia" } diff --git a/common-1.21.1/src/main/resources/data/antarchy/computer/entry/flying_squirrel.json b/common-1.21.1/src/main/resources/data/antarchy/computer/entry/flying_squirrel.json index 34a61e11..d19d5ff8 100644 --- a/common-1.21.1/src/main/resources/data/antarchy/computer/entry/flying_squirrel.json +++ b/common-1.21.1/src/main/resources/data/antarchy/computer/entry/flying_squirrel.json @@ -4,5 +4,7 @@ "title": "guide.antarchy.entry.flying_squirrel.title", "subtitle": "guide.antarchy.entry.flying_squirrel.subtitle", "description": ["guide.antarchy.entry.flying_squirrel.description"], - "entity": "antarchy:flying_squirrel" + "entity": "antarchy:flying_squirrel", + "rotation": 45, + "render_scale": 0.8 } diff --git a/common-1.21.1/src/main/resources/data/antarchy/computer/entry/high_fructose_corn_syrup.json b/common-1.21.1/src/main/resources/data/antarchy/computer/entry/high_fructose_corn_syrup.json index 27c0c65e..0a055889 100644 --- a/common-1.21.1/src/main/resources/data/antarchy/computer/entry/high_fructose_corn_syrup.json +++ b/common-1.21.1/src/main/resources/data/antarchy/computer/entry/high_fructose_corn_syrup.json @@ -3,5 +3,6 @@ "category": "general", "title": "guide.antarchy.entry.high_fructose_corn_syrup.title", "subtitle": "guide.antarchy.entry.high_fructose_corn_syrup.subtitle", - "description": ["guide.antarchy.entry.high_fructose_corn_syrup.description"] + "description": ["guide.antarchy.entry.high_fructose_corn_syrup.description"], + "cover_item": "antarchy:high_fructose_corn_syrup" } diff --git a/common-1.21.1/src/main/resources/data/antarchy/computer/entry/introduction.json b/common-1.21.1/src/main/resources/data/antarchy/computer/entry/introduction.json index c240a231..cbd6611e 100644 --- a/common-1.21.1/src/main/resources/data/antarchy/computer/entry/introduction.json +++ b/common-1.21.1/src/main/resources/data/antarchy/computer/entry/introduction.json @@ -3,5 +3,6 @@ "category": "general", "title": "guide.antarchy.entry.introduction.title", "subtitle": "guide.antarchy.entry.introduction.subtitle", - "description": ["guide.antarchy.entry.introduction.description"] + "description": ["guide.antarchy.entry.introduction.description"], + "cover_item": "antarchy:floppy_disk" } diff --git a/common-1.21.1/src/main/resources/data/antarchy/computer/entry/king.json b/common-1.21.1/src/main/resources/data/antarchy/computer/entry/king.json new file mode 100644 index 00000000..aa9ac91b --- /dev/null +++ b/common-1.21.1/src/main/resources/data/antarchy/computer/entry/king.json @@ -0,0 +1,9 @@ +{ + "type": "entity", + "category": "elythia", + "title": "guide.antarchy.entry.king.title", + "subtitle": "guide.antarchy.entry.king.subtitle", + "description": ["guide.antarchy.entry.king.description"], + "entity": "antarchy:king", + "cover_entity": "antarchy:king" +} diff --git a/common-1.21.1/src/main/resources/data/antarchy/computer/entry/kraken.json b/common-1.21.1/src/main/resources/data/antarchy/computer/entry/kraken.json new file mode 100644 index 00000000..296f3734 --- /dev/null +++ b/common-1.21.1/src/main/resources/data/antarchy/computer/entry/kraken.json @@ -0,0 +1,9 @@ +{ + "type": "entity", + "category": "general", + "title": "guide.antarchy.entry.kraken.title", + "subtitle": "guide.antarchy.entry.kraken.subtitle", + "description": ["guide.antarchy.entry.kraken.description"], + "entity": "antarchy:kraken", + "cover_entity": "antarchy:kraken" +} diff --git a/common-1.21.1/src/main/resources/data/antarchy/computer/entry/nightmare.json b/common-1.21.1/src/main/resources/data/antarchy/computer/entry/nightmare.json index 6fda6806..209a95cf 100644 --- a/common-1.21.1/src/main/resources/data/antarchy/computer/entry/nightmare.json +++ b/common-1.21.1/src/main/resources/data/antarchy/computer/entry/nightmare.json @@ -4,5 +4,6 @@ "title": "guide.antarchy.entry.nightmare.title", "subtitle": "guide.antarchy.entry.nightmare.subtitle", "description": ["guide.antarchy.entry.nightmare.description"], - "entity": "antarchy:nightmare" + "entity": "antarchy:nightmare", + "recipe": "antarchy:nightmare/nightmare_sword" } diff --git a/common-1.21.1/src/main/resources/data/antarchy/computer/entry/queen.json b/common-1.21.1/src/main/resources/data/antarchy/computer/entry/queen.json new file mode 100644 index 00000000..3a944ab6 --- /dev/null +++ b/common-1.21.1/src/main/resources/data/antarchy/computer/entry/queen.json @@ -0,0 +1,9 @@ +{ + "type": "entity", + "category": "thoraxis", + "title": "guide.antarchy.entry.queen.title", + "subtitle": "guide.antarchy.entry.queen.subtitle", + "description": ["guide.antarchy.entry.queen.description"], + "entity": "antarchy:queen", + "cover_entity": "antarchy:queen" +} diff --git a/common-1.21.1/src/main/resources/data/antarchy/computer/entry/rainbow_ant.json b/common-1.21.1/src/main/resources/data/antarchy/computer/entry/rainbow_ant.json new file mode 100644 index 00000000..21074d3b --- /dev/null +++ b/common-1.21.1/src/main/resources/data/antarchy/computer/entry/rainbow_ant.json @@ -0,0 +1,9 @@ +{ + "type": "entity", + "category": "general", + "title": "guide.antarchy.entry.rainbow_ant.title", + "subtitle": "guide.antarchy.entry.rainbow_ant.subtitle", + "description": ["guide.antarchy.entry.rainbow_ant.description"], + "entity": "antarchy:rainbow_ant", + "cover_entity": "antarchy:rainbow_ant" +} diff --git a/common-1.21.1/src/main/resources/data/antarchy/computer/entry/temporal_fields.json b/common-1.21.1/src/main/resources/data/antarchy/computer/entry/temporal_fields.json index 5144dd2e..1c6b0c59 100644 --- a/common-1.21.1/src/main/resources/data/antarchy/computer/entry/temporal_fields.json +++ b/common-1.21.1/src/main/resources/data/antarchy/computer/entry/temporal_fields.json @@ -3,5 +3,6 @@ "category": "thoraxis", "title": "guide.antarchy.entry.temporal_fields.title", "subtitle": "guide.antarchy.entry.temporal_fields.subtitle", - "description": ["guide.antarchy.entry.temporal_fields.description"] + "description": ["guide.antarchy.entry.temporal_fields.description"], + "cover_potion": "antarchy:time_dilation" } diff --git a/common-1.21.1/src/main/resources/data/antarchy/computer/entry/thoraxis.json b/common-1.21.1/src/main/resources/data/antarchy/computer/entry/thoraxis.json index 8579f75c..8acfbcaf 100644 --- a/common-1.21.1/src/main/resources/data/antarchy/computer/entry/thoraxis.json +++ b/common-1.21.1/src/main/resources/data/antarchy/computer/entry/thoraxis.json @@ -4,5 +4,6 @@ "title": "guide.antarchy.entry.thoraxis.title", "subtitle": "guide.antarchy.entry.thoraxis.subtitle", "description": ["guide.antarchy.entry.thoraxis.description"], + "cover_entity": "antarchy:red_ant", "dimension": "antarchy:thoraxis" } diff --git a/common-1.21.1/src/main/resources/data/antarchy/computer/entry/toreterror_arena.json b/common-1.21.1/src/main/resources/data/antarchy/computer/entry/toreterror_arena.json index fdc89d70..fd35c8c5 100644 --- a/common-1.21.1/src/main/resources/data/antarchy/computer/entry/toreterror_arena.json +++ b/common-1.21.1/src/main/resources/data/antarchy/computer/entry/toreterror_arena.json @@ -1,9 +1,14 @@ { "type": "structure", "category": "elythia", - "title": "guide.antarchy.entry.toreterror_arena.title", + "title": "guide.antarchy.entry.toreterror.title", "subtitle": "guide.antarchy.entry.toreterror_arena.subtitle", "description": ["guide.antarchy.entry.toreterror_arena.description"], + "entity": "antarchy:toreterror", "structure": "antarchy:toreterror_arena", - "dimension": "antarchy:elythia" + "structure_tag": "antarchy:toreterror_arena_locator", + "dimension": "antarchy:elythia", + "search_radius": 100, + "cover_entity": "antarchy:toreterror", + "render_scale": 0.8 } diff --git a/common-1.21.1/src/main/resources/data/antarchy/computer/entry/vortex.json b/common-1.21.1/src/main/resources/data/antarchy/computer/entry/vortex.json index 915f2f9c..170fd71a 100644 --- a/common-1.21.1/src/main/resources/data/antarchy/computer/entry/vortex.json +++ b/common-1.21.1/src/main/resources/data/antarchy/computer/entry/vortex.json @@ -3,5 +3,7 @@ "category": "thoraxis", "title": "guide.antarchy.entry.vortex.title", "subtitle": "guide.antarchy.entry.vortex.subtitle", - "description": ["guide.antarchy.entry.vortex.description"] + "description": ["guide.antarchy.entry.vortex.description"], + "cover_entity": "antarchy:vortex", + "render_scale": 0.8 } diff --git a/common-1.21.1/src/main/resources/data/antarchy/recipe/computer.json b/common-1.21.1/src/main/resources/data/antarchy/recipe/computer.json index aae9c142..6def057f 100644 --- a/common-1.21.1/src/main/resources/data/antarchy/recipe/computer.json +++ b/common-1.21.1/src/main/resources/data/antarchy/recipe/computer.json @@ -8,18 +8,18 @@ "item": "minecraft:copper_ingot" }, "C": { - "item": "minecraft:tinted_glass" + "tag": "c:glass_blocks" }, "D": { - "item": "minecraft:comparator" + "item": "minecraft:repeater" }, "E": { - "item": "antarchy:bluestone_comparator" + "item": "minecraft:redstone_torch" } }, "pattern": [ "AAC", - "DBE", + "BDE", "AAA" ], "result": { diff --git a/common-1.21.1/src/main/resources/data/antarchy/structure/village/computer_scientist_basement.nbt b/common-1.21.1/src/main/resources/data/antarchy/structure/village/computer_scientist_basement.nbt new file mode 100644 index 00000000..4ec1b08a Binary files /dev/null and b/common-1.21.1/src/main/resources/data/antarchy/structure/village/computer_scientist_basement.nbt differ diff --git a/common-1.21.1/src/main/resources/data/antarchy/structure/village/computer_scientist_house.nbt b/common-1.21.1/src/main/resources/data/antarchy/structure/village/computer_scientist_house.nbt index 7a2a9f41..1eb3c103 100644 Binary files a/common-1.21.1/src/main/resources/data/antarchy/structure/village/computer_scientist_house.nbt and b/common-1.21.1/src/main/resources/data/antarchy/structure/village/computer_scientist_house.nbt differ diff --git a/common-1.21.1/src/main/resources/data/antarchy/tags/worldgen/structure/toreterror_arena_locator.json b/common-1.21.1/src/main/resources/data/antarchy/tags/worldgen/structure/toreterror_arena_locator.json new file mode 100644 index 00000000..967cd68f --- /dev/null +++ b/common-1.21.1/src/main/resources/data/antarchy/tags/worldgen/structure/toreterror_arena_locator.json @@ -0,0 +1,6 @@ +{ + "replace": false, + "values": [ + "antarchy:toreterror_arena" + ] +} diff --git a/common-1.21.1/src/main/resources/data/antarchy/worldgen/biome/dream_dunes.json b/common-1.21.1/src/main/resources/data/antarchy/worldgen/biome/dream_dunes.json index 4539f8a0..2af66886 100644 --- a/common-1.21.1/src/main/resources/data/antarchy/worldgen/biome/dream_dunes.json +++ b/common-1.21.1/src/main/resources/data/antarchy/worldgen/biome/dream_dunes.json @@ -41,9 +41,7 @@ "liquid": [] }, "features": [ - [ - "antarchy:thoraxis_transition_throat" - ], + [], [], [], [], @@ -52,6 +50,7 @@ [ "antarchy:bluestone_ore", "antarchy:thoraxis_ichor_lake", + "antarchy:thoraxis_transition_throat", "antarchy:dimensional_tears" ], [], diff --git a/common-1.21.1/src/main/resources/data/antarchy/worldgen/template_pool/village/computer_scientist_basement.json b/common-1.21.1/src/main/resources/data/antarchy/worldgen/template_pool/village/computer_scientist_basement.json new file mode 100644 index 00000000..8610967e --- /dev/null +++ b/common-1.21.1/src/main/resources/data/antarchy/worldgen/template_pool/village/computer_scientist_basement.json @@ -0,0 +1,15 @@ +{ + "name": "antarchy:village/computer_scientist_basement", + "fallback": "minecraft:empty", + "elements": [ + { + "weight": 1, + "element": { + "location": "antarchy:village/computer_scientist_basement", + "processors": "antarchy:generic_processor", + "projection": "rigid", + "element_type": "integrated_api:integrated_api_single_pool_element" + } + } + ] +} \ No newline at end of file diff --git a/fabric-1.21.1/src/main/java/com/craisinlord/antarchy/fabric/AntarchyConfigModuleFabric.java b/fabric-1.21.1/src/main/java/com/craisinlord/antarchy/fabric/AntarchyConfigModuleFabric.java index 4bf84945..5fe59470 100644 --- a/fabric-1.21.1/src/main/java/com/craisinlord/antarchy/fabric/AntarchyConfigModuleFabric.java +++ b/fabric-1.21.1/src/main/java/com/craisinlord/antarchy/fabric/AntarchyConfigModuleFabric.java @@ -56,6 +56,14 @@ public final class AntarchyConfigModuleFabric { SettingBinding binding = entry.getValue(); JsonObject loadedSection = loaded.get(binding.section); JsonElement element = loadedSection != null ? loadedSection.get(key) : null; + if (element == null) { + for (JsonObject otherSection : loaded.values()) { + if (otherSection != loadedSection && otherSection.has(key)) { + element = otherSection.get(key); + break; + } + } + } if (element == null && legacy != null) { element = legacy.get(key); } diff --git a/fabric-1.21.1/src/main/java/com/craisinlord/antarchy/fabric/AntarchyFabric.java b/fabric-1.21.1/src/main/java/com/craisinlord/antarchy/fabric/AntarchyFabric.java index 9e2356c6..c0b568a8 100644 --- a/fabric-1.21.1/src/main/java/com/craisinlord/antarchy/fabric/AntarchyFabric.java +++ b/fabric-1.21.1/src/main/java/com/craisinlord/antarchy/fabric/AntarchyFabric.java @@ -188,6 +188,20 @@ public final class AntarchyFabric implements ModInitializer { barrier, manager, prepareProfiler, applyProfiler, prepareExecutor, applyExecutor); } }); + ResourceManagerHelper.get(PackType.SERVER_DATA).registerReloadListener(new IdentifiableResourceReloadListener() { + @Override + public ResourceLocation getFabricId() { + return ResourceLocation.fromNamespaceAndPath(Antarchy.MODID, "blockle_answers"); + } + + @Override + public CompletableFuture reload(PreparableReloadListener.PreparationBarrier barrier, ResourceManager manager, + ProfilerFiller prepareProfiler, ProfilerFiller applyProfiler, + Executor prepareExecutor, Executor applyExecutor) { + return com.craisinlord.antarchy.content.computer.blockle.BlockleAnswers.instance().reload( + barrier, manager, prepareProfiler, applyProfiler, prepareExecutor, applyExecutor); + } + }); } private static void registerTradeReloadListener() { diff --git a/fabric-1.21.1/src/main/java/com/craisinlord/antarchy/fabric/AntarchyFabricContent.java b/fabric-1.21.1/src/main/java/com/craisinlord/antarchy/fabric/AntarchyFabricContent.java index c9644656..44df8be9 100644 --- a/fabric-1.21.1/src/main/java/com/craisinlord/antarchy/fabric/AntarchyFabricContent.java +++ b/fabric-1.21.1/src/main/java/com/craisinlord/antarchy/fabric/AntarchyFabricContent.java @@ -390,6 +390,7 @@ public final class AntarchyFabricContent { AntarchyObjects.setGlimmer(AntarchyFabricEntities.GLIMMER); AntarchyObjects.setSpiritApple(() -> AntarchyFabricItems.SPIRIT_APPLE.get()); AntarchyObjects.setGlimmeringEffect(() -> AntarchyFabricMisc.mobEffectHolder(AntarchyFabricMisc.GLIMMERING)); + AntarchyObjects.setRoyalVitalityPenaltyEffect(() -> AntarchyFabricMisc.mobEffectHolder(AntarchyFabricMisc.ROYAL_VITALITY_PENALTY)); AntarchyObjects.setElka(AntarchyFabricEntities.ELKA); AntarchyObjects.setPeach(() -> AntarchyFabricItems.PEACH.get()); AntarchyObjects.setCorn(() -> AntarchyFabricItems.CORN.get()); @@ -546,6 +547,7 @@ public final class AntarchyFabricContent { ScorpionWhipTetherSync.setSink(AntarchyFabricNetworking::syncScorpionWhipTether); WormHookTetherSync.setSink(AntarchyFabricNetworking::syncWormHookTether); HerculesBeetleImpactShakeSync.setSink((player, ticks) -> net.fabricmc.fabric.api.networking.v1.ServerPlayNetworking.send(player, new com.craisinlord.antarchy.content.network.HerculesBeetleImpactShakePayload(ticks))); + com.craisinlord.antarchy.content.network.KingJudgmentFlashSync.setSink((player, ticks) -> net.fabricmc.fabric.api.networking.v1.ServerPlayNetworking.send(player, new com.craisinlord.antarchy.content.network.KingJudgmentFlashPayload(ticks))); ImpactShakeSync.setSink((player, payload) -> net.fabricmc.fabric.api.networking.v1.ServerPlayNetworking.send(player, payload)); HordeIntensitySync.setSink((player, payload) -> net.fabricmc.fabric.api.networking.v1.ServerPlayNetworking.send(player, payload)); TigerEyeCamouflageSync.setSendToPlayer((player, payload) -> net.fabricmc.fabric.api.networking.v1.ServerPlayNetworking.send(player, payload)); diff --git a/fabric-1.21.1/src/main/java/com/craisinlord/antarchy/fabric/client/AntarchyFabricClient.java b/fabric-1.21.1/src/main/java/com/craisinlord/antarchy/fabric/client/AntarchyFabricClient.java index c6a6d189..a1f3f6b7 100644 --- a/fabric-1.21.1/src/main/java/com/craisinlord/antarchy/fabric/client/AntarchyFabricClient.java +++ b/fabric-1.21.1/src/main/java/com/craisinlord/antarchy/fabric/client/AntarchyFabricClient.java @@ -24,20 +24,6 @@ public final class AntarchyFabricClient implements ClientModInitializer { AntarchyFabricClientNetworking.bootstrapMultipartClient(); AntarchyFabricClientBootstrap.register(); AntarchyClientHooks.setComputerOpener(pos -> Minecraft.getInstance().setScreen(new ComputerScreen(pos))); - ResourceManagerHelper.get(PackType.CLIENT_RESOURCES).registerReloadListener(new IdentifiableResourceReloadListener() { - @Override - public ResourceLocation getFabricId() { - return ResourceLocation.fromNamespaceAndPath(Antarchy.MODID, "computer_guide_client"); - } - - @Override - public CompletableFuture reload(PreparableReloadListener.PreparationBarrier barrier, ResourceManager manager, - ProfilerFiller prepareProfiler, ProfilerFiller applyProfiler, - Executor prepareExecutor, Executor applyExecutor) { - return com.craisinlord.antarchy.content.guide.ComputerGuideData.instance().reload( - barrier, manager, prepareProfiler, applyProfiler, prepareExecutor, applyExecutor); - } - }); ResourceManagerHelper.get(PackType.CLIENT_RESOURCES).registerReloadListener(new IdentifiableResourceReloadListener() { @Override public ResourceLocation getFabricId() { diff --git a/fabric-1.21.1/src/main/java/com/craisinlord/antarchy/fabric/client/AntarchyFabricClientBootstrap.java b/fabric-1.21.1/src/main/java/com/craisinlord/antarchy/fabric/client/AntarchyFabricClientBootstrap.java index b57c5c5e..cdba72f8 100644 --- a/fabric-1.21.1/src/main/java/com/craisinlord/antarchy/fabric/client/AntarchyFabricClientBootstrap.java +++ b/fabric-1.21.1/src/main/java/com/craisinlord/antarchy/fabric/client/AntarchyFabricClientBootstrap.java @@ -8,6 +8,7 @@ import com.craisinlord.antarchy.Antarchy; import com.craisinlord.antarchy.content.client.CameraShakeClientState; import com.craisinlord.antarchy.content.client.HerculesBeetleImpactShakeClientState; import com.craisinlord.antarchy.content.client.HordeClientState; +import com.craisinlord.antarchy.content.client.KingJudgmentFlashClientState; import com.craisinlord.antarchy.content.client.PortalGunRollClientState; import com.craisinlord.antarchy.content.client.SizeRaySoundHandler; import com.craisinlord.antarchy.content.client.TigerEyeClientHooks; @@ -531,6 +532,7 @@ public final class AntarchyFabricClientBootstrap { registrationHelper.register(new TigerEyeCamouflagePlayerLayer(playerRenderer)); registrationHelper.register(new GoopedLivingLayer(playerRenderer)); registrationHelper.register(new GlimmeringLivingLayer(playerRenderer)); + registrationHelper.register(new com.craisinlord.antarchy.content.client.renderer.RoyalIndicatorLivingLayer<>(playerRenderer)); registrationHelper.register(new BrutalflyElytraLayer(playerRenderer)); registrationHelper.register(new com.craisinlord.antarchy.content.client.renderer.ManticoreWingsLayer(playerRenderer)); registrationHelper.register(new FallenKingCrownLayer(playerRenderer)); @@ -546,11 +548,13 @@ public final class AntarchyFabricClientBootstrap { (software.bernie.geckolib.renderer.GeoEntityRenderer) (Object) renderer; geoRenderer.addRenderLayer(new ParalyzedStoneGeoLayer(geoRenderer)); geoRenderer.addRenderLayer(new GoopedGeoLayer(geoRenderer)); + geoRenderer.addRenderLayer(new com.craisinlord.antarchy.content.client.renderer.RoyalIndicatorGeoLayer<>(geoRenderer)); return; } registrationHelper.register(new ParalyzedStoneLivingLayer(renderer)); registrationHelper.register(new GoopedLivingLayer(renderer)); registrationHelper.register(new GlimmeringLivingLayer(renderer)); + registrationHelper.register(new com.craisinlord.antarchy.content.client.renderer.RoyalIndicatorLivingLayer<>(renderer)); }); } @@ -559,6 +563,7 @@ public final class AntarchyFabricClientBootstrap { ClientTickEvents.END_CLIENT_TICK.register(client -> { CameraShakeClientState.tick(); HerculesBeetleImpactShakeClientState.tick(); + KingJudgmentFlashClientState.tick(); HordeClientState.tick(); PortalGunRollClientState.tick(client); if (client.player != null) { @@ -571,6 +576,7 @@ public final class AntarchyFabricClientBootstrap { DreadClientHandler.tick(); TigerEyeCamouflageClientHandler.tick(); RoyalInversionClientHandler.tick(); + RoyalBoundaryClientHandler.tick(); BloodCrystalKatanaTrailHandler.tick(); ScorpionWhipTetherRenderHandler.tick(); WormHookTetherRenderHandler.tick(); @@ -605,6 +611,7 @@ public final class AntarchyFabricClientBootstrap { FlytrapGooHudRenderer.render(guiGraphics); DorrieJumpHudRenderer.render(guiGraphics); HerculesBeetleChargeHudRenderer.render(guiGraphics); + com.craisinlord.antarchy.content.client.KingJudgmentFlashHudRenderer.render(guiGraphics); }); WorldRenderEvents.AFTER_TRANSLUCENT.register(context -> { diff --git a/fabric-1.21.1/src/main/java/com/craisinlord/antarchy/fabric/client/AntarchyKeyBindings.java b/fabric-1.21.1/src/main/java/com/craisinlord/antarchy/fabric/client/AntarchyKeyBindings.java index 4d8f581a..a986d1b0 100644 --- a/fabric-1.21.1/src/main/java/com/craisinlord/antarchy/fabric/client/AntarchyKeyBindings.java +++ b/fabric-1.21.1/src/main/java/com/craisinlord/antarchy/fabric/client/AntarchyKeyBindings.java @@ -43,6 +43,12 @@ public final class AntarchyKeyBindings { GLFW.GLFW_KEY_V, CATEGORY ); + public static final KeyMapping ROYAL_BOUNDARY = new KeyMapping( + "key.antarchy.royal_boundary", + InputConstants.Type.KEYSYM, + GLFW.GLFW_KEY_B, + CATEGORY + ); private AntarchyKeyBindings() {} @@ -52,6 +58,7 @@ public final class AntarchyKeyBindings { KeyBindingHelper.registerKeyBinding(MOUNT_FLIGHT_TOGGLE); KeyBindingHelper.registerKeyBinding(TIGERS_EYE_CAMOUFLAGE); KeyBindingHelper.registerKeyBinding(ROYAL_INVERSION_TOGGLE); + KeyBindingHelper.registerKeyBinding(ROYAL_BOUNDARY); } public static boolean isBrutalflyFlapPressed() { @@ -73,4 +80,8 @@ public final class AntarchyKeyBindings { public static boolean consumeRoyalInversionTogglePressed() { return Minecraft.getInstance().screen == null && ROYAL_INVERSION_TOGGLE.consumeClick(); } + + public static boolean consumeRoyalBoundaryPressed() { + return Minecraft.getInstance().screen == null && ROYAL_BOUNDARY.consumeClick(); + } } diff --git a/fabric-1.21.1/src/main/java/com/craisinlord/antarchy/fabric/client/RoyalBoundaryClientHandler.java b/fabric-1.21.1/src/main/java/com/craisinlord/antarchy/fabric/client/RoyalBoundaryClientHandler.java new file mode 100644 index 00000000..7b0cde75 --- /dev/null +++ b/fabric-1.21.1/src/main/java/com/craisinlord/antarchy/fabric/client/RoyalBoundaryClientHandler.java @@ -0,0 +1,18 @@ +package com.craisinlord.antarchy.fabric.client; + +import com.craisinlord.antarchy.content.network.ActivateRoyalBoundaryPayload; +import net.fabricmc.fabric.api.client.networking.v1.ClientPlayNetworking; +import net.minecraft.client.Minecraft; + +public final class RoyalBoundaryClientHandler { + private RoyalBoundaryClientHandler() { + } + + public static void tick() { + Minecraft minecraft = Minecraft.getInstance(); + if (minecraft.player == null || minecraft.level == null) return; + while (AntarchyKeyBindings.consumeRoyalBoundaryPressed()) { + ClientPlayNetworking.send(new ActivateRoyalBoundaryPayload()); + } + } +} diff --git a/fabric-1.21.1/src/main/java/com/craisinlord/antarchy/fabric/network/AntarchyFabricClientNetworking.java b/fabric-1.21.1/src/main/java/com/craisinlord/antarchy/fabric/network/AntarchyFabricClientNetworking.java index 3045beea..656e5566 100644 --- a/fabric-1.21.1/src/main/java/com/craisinlord/antarchy/fabric/network/AntarchyFabricClientNetworking.java +++ b/fabric-1.21.1/src/main/java/com/craisinlord/antarchy/fabric/network/AntarchyFabricClientNetworking.java @@ -58,6 +58,8 @@ public final class AntarchyFabricClientNetworking { context.client().execute(() -> BrutalflyElytraClientState.trigger(payload.entityId(), payload.durationTicks(), payload.strength()))); ClientPlayNetworking.registerGlobalReceiver(HerculesBeetleImpactShakePayload.TYPE, (payload, context) -> context.client().execute(() -> AntarchyFabricNetworking.triggerHerculesBeetleImpactShake(payload.durationTicks()))); + ClientPlayNetworking.registerGlobalReceiver(KingJudgmentFlashPayload.TYPE, (payload, context) -> + context.client().execute(() -> com.craisinlord.antarchy.content.client.KingJudgmentFlashClientState.trigger(payload.durationTicks()))); ClientPlayNetworking.registerGlobalReceiver(ImpactShakePayload.TYPE, (payload, context) -> context.client().execute(() -> AntarchyFabricNetworking.triggerImpactShake(payload))); ClientPlayNetworking.registerGlobalReceiver(HordeIntensityPayload.TYPE, (payload, context) -> diff --git a/fabric-1.21.1/src/main/java/com/craisinlord/antarchy/fabric/network/AntarchyFabricNetworking.java b/fabric-1.21.1/src/main/java/com/craisinlord/antarchy/fabric/network/AntarchyFabricNetworking.java index a0327818..ded60b8c 100644 --- a/fabric-1.21.1/src/main/java/com/craisinlord/antarchy/fabric/network/AntarchyFabricNetworking.java +++ b/fabric-1.21.1/src/main/java/com/craisinlord/antarchy/fabric/network/AntarchyFabricNetworking.java @@ -85,6 +85,7 @@ public final class AntarchyFabricNetworking { PayloadTypeRegistry.playS2C().register(WormHookTetherPayload.TYPE, WormHookTetherPayload.STREAM_CODEC); PayloadTypeRegistry.playS2C().register(BrutalflyElytraAnimationPayload.TYPE, BrutalflyElytraAnimationPayload.STREAM_CODEC); PayloadTypeRegistry.playS2C().register(HerculesBeetleImpactShakePayload.TYPE, HerculesBeetleImpactShakePayload.STREAM_CODEC); + PayloadTypeRegistry.playS2C().register(KingJudgmentFlashPayload.TYPE, KingJudgmentFlashPayload.STREAM_CODEC); PayloadTypeRegistry.playS2C().register(ImpactShakePayload.TYPE, ImpactShakePayload.STREAM_CODEC); PayloadTypeRegistry.playS2C().register(HordeIntensityPayload.TYPE, HordeIntensityPayload.STREAM_CODEC); AntarchyFabricTimeDilationNetworking.registerPayloadTypes(); @@ -98,6 +99,7 @@ public final class AntarchyFabricNetworking { PayloadTypeRegistry.playC2S().register(BigBerthaModeCyclePayload.TYPE, BigBerthaModeCyclePayload.STREAM_CODEC); PayloadTypeRegistry.playC2S().register(ToggleTigerEyeCamouflagePayload.TYPE, ToggleTigerEyeCamouflagePayload.STREAM_CODEC); PayloadTypeRegistry.playC2S().register(ToggleRoyalInversionPayload.TYPE, ToggleRoyalInversionPayload.STREAM_CODEC); + PayloadTypeRegistry.playC2S().register(com.craisinlord.antarchy.content.network.ActivateRoyalBoundaryPayload.TYPE, com.craisinlord.antarchy.content.network.ActivateRoyalBoundaryPayload.STREAM_CODEC); PayloadTypeRegistry.playC2S().register(DiamondMinecartInputPayload.TYPE, DiamondMinecartInputPayload.STREAM_CODEC); PayloadTypeRegistry.playC2S().register(BrutalflyElytraFlapPayload.TYPE, BrutalflyElytraFlapPayload.STREAM_CODEC); PayloadTypeRegistry.playC2S().register(SpringyBootsLaunchPayload.TYPE, SpringyBootsLaunchPayload.STREAM_CODEC); @@ -157,6 +159,8 @@ public final class AntarchyFabricNetworking { context.server().execute(() -> handleTigerEyeCamouflageToggle(context.player()))); ServerPlayNetworking.registerGlobalReceiver(ToggleRoyalInversionPayload.TYPE, (payload, context) -> context.server().execute(() -> com.craisinlord.antarchy.content.item.RoyalAssailantArmorItem.toggleInversion(context.player()))); + ServerPlayNetworking.registerGlobalReceiver(com.craisinlord.antarchy.content.network.ActivateRoyalBoundaryPayload.TYPE, (payload, context) -> + context.server().execute(() -> com.craisinlord.antarchy.content.effect.RoyalBoundaryManager.activate(context.player()))); ServerPlayNetworking.registerGlobalReceiver(DiamondMinecartInputPayload.TYPE, (payload, context) -> context.server().execute(() -> handleDiamondMinecartInput(context.player(), payload))); ServerPlayNetworking.registerGlobalReceiver(BrutalflyElytraFlapPayload.TYPE, (payload, context) -> diff --git a/fabric-1.21.1/src/main/java/com/craisinlord/antarchy/fabric/registry/AntarchyFabricMisc.java b/fabric-1.21.1/src/main/java/com/craisinlord/antarchy/fabric/registry/AntarchyFabricMisc.java index 13d73c90..9066e779 100644 --- a/fabric-1.21.1/src/main/java/com/craisinlord/antarchy/fabric/registry/AntarchyFabricMisc.java +++ b/fabric-1.21.1/src/main/java/com/craisinlord/antarchy/fabric/registry/AntarchyFabricMisc.java @@ -448,6 +448,7 @@ public final class AntarchyFabricMisc { public static final DeferredHolder GLIMMERING = MOB_EFFECTS.register("glimmering", com.craisinlord.antarchy.content.effect.GlimmeringMobEffect::new); + public static final DeferredHolder ROYAL_VITALITY_PENALTY = MOB_EFFECTS.register("royal_vitality_penalty", com.craisinlord.antarchy.content.effect.RoyalVitalityPenaltyEffect::new); public static final DeferredHolder BLOODGLASS_MAX_HEARTS = ATTRIBUTES.register( diff --git a/neoforge-1.21.1/src/main/java/com/craisinlord/antarchy/neoforge/AntarchyConfigModuleNeoforge.java b/neoforge-1.21.1/src/main/java/com/craisinlord/antarchy/neoforge/AntarchyConfigModuleNeoforge.java index 1fa32aef..cdb4885a 100644 --- a/neoforge-1.21.1/src/main/java/com/craisinlord/antarchy/neoforge/AntarchyConfigModuleNeoforge.java +++ b/neoforge-1.21.1/src/main/java/com/craisinlord/antarchy/neoforge/AntarchyConfigModuleNeoforge.java @@ -5,6 +5,8 @@ import com.craisinlord.antarchy.content.network.ImpactShakeSync; import com.craisinlord.antarchy.config.ConfigResetGuard; import com.craisinlord.antarchy.content.network.HerculesBeetleImpactShakePayload; import com.craisinlord.antarchy.content.network.HerculesBeetleImpactShakeSync; +import com.craisinlord.antarchy.content.network.KingJudgmentFlashPayload; +import com.craisinlord.antarchy.content.network.KingJudgmentFlashSync; import com.craisinlord.antarchy.content.network.HordeIntensitySync; import java.nio.file.Path; import net.neoforged.fml.loading.FMLPaths; @@ -28,6 +30,7 @@ public final class AntarchyConfigModuleNeoforge { modContainer.registerConfig(ModConfig.Type.COMMON, AntarchyToolsConfig.SPEC, "antarchy/antarchy_tools.toml"); modContainer.registerConfig(ModConfig.Type.COMMON, AntarchyMiscConfig.SPEC, "antarchy/antarchy_misc.toml"); HerculesBeetleImpactShakeSync.setSink((player, ticks) -> PacketDistributor.sendToPlayer(player, new HerculesBeetleImpactShakePayload(ticks))); + KingJudgmentFlashSync.setSink((player, ticks) -> PacketDistributor.sendToPlayer(player, new KingJudgmentFlashPayload(ticks))); ImpactShakeSync.setSink(PacketDistributor::sendToPlayer); HordeIntensitySync.setSink(PacketDistributor::sendToPlayer); AntarchyNeoforge.modEventBusTempHolder.addListener(AntarchyConfigModuleNeoforge::onConfigChange); @@ -475,6 +478,11 @@ public final class AntarchyConfigModuleNeoforge { AntarchySettings.setRoyalGuardianBootsArmorValue(AntarchyToolsConfig.royalGuardianBootsArmorValue()); AntarchySettings.setRoyalGuardianArmorToughness(AntarchyToolsConfig.royalGuardianArmorToughness()); AntarchySettings.setRoyalGuardianArmorKnockbackResistance(AntarchyToolsConfig.royalGuardianArmorKnockbackResistance()); + AntarchySettings.setRoyalGuardianHelmetJudgmentBonus(AntarchyToolsConfig.royalGuardianHelmetJudgmentBonus()); + AntarchySettings.setRoyalGuardianChestplateJudgmentBonus(AntarchyToolsConfig.royalGuardianChestplateJudgmentBonus()); + AntarchySettings.setRoyalGuardianLeggingsJudgmentBonus(AntarchyToolsConfig.royalGuardianLeggingsJudgmentBonus()); + AntarchySettings.setRoyalGuardianBootsJudgmentBonus(AntarchyToolsConfig.royalGuardianBootsJudgmentBonus()); + AntarchySettings.setRoyalGuardianJudgmentDurationTicks(AntarchyToolsConfig.royalGuardianJudgmentDurationTicks()); AntarchySettings.setRoyalAssailantHelmetArmorValue(AntarchyToolsConfig.royalAssailantHelmetArmorValue()); AntarchySettings.setRoyalAssailantChestplateArmorValue(AntarchyToolsConfig.royalAssailantChestplateArmorValue()); AntarchySettings.setRoyalAssailantLeggingsArmorValue(AntarchyToolsConfig.royalAssailantLeggingsArmorValue()); @@ -487,6 +495,14 @@ public final class AntarchyConfigModuleNeoforge { AntarchySettings.setRoyalAssailantBootsDoubleDamageChance(AntarchyToolsConfig.royalAssailantBootsDoubleDamageChance()); AntarchySettings.setRoyalGuardianSwordAttackDamage(AntarchyToolsConfig.royalGuardianSwordAttackDamage()); AntarchySettings.setRoyalGuardianSwordAttackSpeed(AntarchyToolsConfig.royalGuardianSwordAttackSpeed()); + AntarchySettings.setRoyalGuardianSwordElementalCooldownTicks(AntarchyToolsConfig.royalGuardianSwordElementalCooldownTicks()); + AntarchySettings.setRoyalGuardianSwordFirePrimaryDamage(AntarchyToolsConfig.royalGuardianSwordFirePrimaryDamage()); + AntarchySettings.setRoyalGuardianSwordFireSplashDamage(AntarchyToolsConfig.royalGuardianSwordFireSplashDamage()); + AntarchySettings.setRoyalGuardianSwordFrostSpikeDamage(AntarchyToolsConfig.royalGuardianSwordFrostSpikeDamage()); + AntarchySettings.setRoyalGuardianSwordStormPrimaryDamage(AntarchyToolsConfig.royalGuardianSwordStormPrimaryDamage()); + AntarchySettings.setRoyalGuardianSwordStormJumpDamage(AntarchyToolsConfig.royalGuardianSwordStormJumpDamage()); + AntarchySettings.setRoyalGuardianSwordStormMaxJumps(AntarchyToolsConfig.royalGuardianSwordStormMaxJumps()); + AntarchySettings.setRoyalGuardianSwordStormJumpRange(AntarchyToolsConfig.royalGuardianSwordStormJumpRange()); AntarchySettings.setRoyalAssailantBattleAxeAttackDamage(AntarchyToolsConfig.royalAssailantBattleAxeAttackDamage()); AntarchySettings.setRoyalAssailantBattleAxeAttackSpeed(AntarchyToolsConfig.royalAssailantBattleAxeAttackSpeed()); AntarchySettings.setRoyalWeaponAttackReachBonus(AntarchyToolsConfig.royalWeaponAttackReachBonus()); @@ -494,6 +510,21 @@ public final class AntarchyConfigModuleNeoforge { AntarchySettings.setRoyalWeaponDurability(AntarchyToolsConfig.royalWeaponDurability()); AntarchySettings.setRoyalWeaponEnchantability(AntarchyToolsConfig.royalWeaponEnchantability()); AntarchySettings.setRoyalGuardianShieldDurability(AntarchyToolsConfig.royalGuardianShieldDurability()); + AntarchySettings.setRoyalGuardianMusterRange(AntarchyToolsConfig.royalGuardianMusterRange()); + AntarchySettings.setRoyalGuardianMusterDurationTicks(AntarchyToolsConfig.royalGuardianMusterDurationTicks()); + AntarchySettings.setRoyalGuardianMusterCooldownTicks(AntarchyToolsConfig.royalGuardianMusterCooldownTicks()); + AntarchySettings.setRoyalGuardianMusterMobCap(AntarchyToolsConfig.royalGuardianMusterMobCap()); + AntarchySettings.setRoyalGuardianMusterDamageReduction(AntarchyToolsConfig.royalGuardianMusterDamageReduction()); + AntarchySettings.setRoyalGuardianMusterAttackBonus(AntarchyToolsConfig.royalGuardianMusterAttackBonus()); + AntarchySettings.setRoyalGuardianMusterSpeedBonus(AntarchyToolsConfig.royalGuardianMusterSpeedBonus()); + AntarchySettings.setRoyalGuardianMusterPassiveDamageFloor(AntarchyToolsConfig.royalGuardianMusterPassiveDamageFloor()); + AntarchySettings.setRoyalGuardianBoundaryRadius(AntarchyToolsConfig.royalGuardianBoundaryRadius()); + AntarchySettings.setRoyalGuardianBoundaryDurationTicks(AntarchyToolsConfig.royalGuardianBoundaryDurationTicks()); + AntarchySettings.setRoyalGuardianBoundaryCooldownTicks(AntarchyToolsConfig.royalGuardianBoundaryCooldownTicks()); + AntarchySettings.setRoyalGuardianBoundaryStrengthAmplifier(AntarchyToolsConfig.royalGuardianBoundaryStrengthAmplifier()); + AntarchySettings.setRoyalGuardianBoundaryArrestTicks(AntarchyToolsConfig.royalGuardianBoundaryArrestTicks()); + AntarchySettings.setRoyalGuardianBoundaryDropTicks(AntarchyToolsConfig.royalGuardianBoundaryDropTicks()); + AntarchySettings.setRoyalGuardianBoundaryProjectileCap(AntarchyToolsConfig.royalGuardianBoundaryProjectileCap()); AntarchySettings.setBattleAxeAttackDamage(AntarchyToolsConfig.battleAxeAttackDamage()); AntarchySettings.setBattleAxeAttackSpeed(AntarchyToolsConfig.battleAxeAttackSpeed()); diff --git a/neoforge-1.21.1/src/main/java/com/craisinlord/antarchy/neoforge/AntarchyNeoforge.java b/neoforge-1.21.1/src/main/java/com/craisinlord/antarchy/neoforge/AntarchyNeoforge.java index b3f95d04..3882d215 100644 --- a/neoforge-1.21.1/src/main/java/com/craisinlord/antarchy/neoforge/AntarchyNeoforge.java +++ b/neoforge-1.21.1/src/main/java/com/craisinlord/antarchy/neoforge/AntarchyNeoforge.java @@ -472,6 +472,7 @@ public class AntarchyNeoforge { AntarchyObjects.setGlimmer(AntarchyNeoforgeEntites.GLIMMER); AntarchyObjects.setSpiritApple(() -> AntarchyNeoforgeItems.SPIRIT_APPLE.get()); AntarchyObjects.setGlimmeringEffect(() -> AntarchyNeoforgeMisc.GLIMMERING); + AntarchyObjects.setRoyalVitalityPenaltyEffect(() -> AntarchyNeoforgeMisc.ROYAL_VITALITY_PENALTY); AntarchyObjects.setElka(AntarchyNeoforgeEntites.ELKA); AntarchyObjects.setPeach(() -> AntarchyNeoforgeItems.PEACH.get()); AntarchyObjects.setCorn(() -> AntarchyNeoforgeItems.CORN.get()); diff --git a/neoforge-1.21.1/src/main/java/com/craisinlord/antarchy/neoforge/AntarchyToolsConfig.java b/neoforge-1.21.1/src/main/java/com/craisinlord/antarchy/neoforge/AntarchyToolsConfig.java index 601cd2e8..02bbb003 100644 --- a/neoforge-1.21.1/src/main/java/com/craisinlord/antarchy/neoforge/AntarchyToolsConfig.java +++ b/neoforge-1.21.1/src/main/java/com/craisinlord/antarchy/neoforge/AntarchyToolsConfig.java @@ -77,6 +77,9 @@ public final class AntarchyToolsConfig { private static final ModConfigSpec.IntValue ROYAL_GUARDIAN_BOOTS_ARMOR_VALUE; private static final ModConfigSpec.DoubleValue ROYAL_GUARDIAN_ARMOR_TOUGHNESS; private static final ModConfigSpec.DoubleValue ROYAL_GUARDIAN_ARMOR_KNOCKBACK_RESISTANCE; + private static final ModConfigSpec.DoubleValue ROYAL_GUARDIAN_HELMET_JUDGMENT_BONUS, ROYAL_GUARDIAN_CHESTPLATE_JUDGMENT_BONUS, + ROYAL_GUARDIAN_LEGGINGS_JUDGMENT_BONUS, ROYAL_GUARDIAN_BOOTS_JUDGMENT_BONUS; + private static final ModConfigSpec.IntValue ROYAL_GUARDIAN_JUDGMENT_DURATION_TICKS; private static final ModConfigSpec.IntValue ROYAL_ASSAILANT_HELMET_ARMOR_VALUE; private static final ModConfigSpec.IntValue ROYAL_ASSAILANT_CHESTPLATE_ARMOR_VALUE; private static final ModConfigSpec.IntValue ROYAL_ASSAILANT_LEGGINGS_ARMOR_VALUE; @@ -89,6 +92,10 @@ public final class AntarchyToolsConfig { private static final ModConfigSpec.DoubleValue ROYAL_ASSAILANT_BOOTS_DOUBLE_DAMAGE_CHANCE; private static final ModConfigSpec.DoubleValue ROYAL_GUARDIAN_SWORD_ATTACK_DAMAGE; private static final ModConfigSpec.DoubleValue ROYAL_GUARDIAN_SWORD_ATTACK_SPEED; + private static final ModConfigSpec.IntValue ROYAL_GUARDIAN_SWORD_ELEMENTAL_COOLDOWN_TICKS, ROYAL_GUARDIAN_SWORD_STORM_MAX_JUMPS; + private static final ModConfigSpec.DoubleValue ROYAL_GUARDIAN_SWORD_FIRE_PRIMARY_DAMAGE, ROYAL_GUARDIAN_SWORD_FIRE_SPLASH_DAMAGE, + ROYAL_GUARDIAN_SWORD_FROST_SPIKE_DAMAGE, ROYAL_GUARDIAN_SWORD_STORM_PRIMARY_DAMAGE, + ROYAL_GUARDIAN_SWORD_STORM_JUMP_DAMAGE, ROYAL_GUARDIAN_SWORD_STORM_JUMP_RANGE; private static final ModConfigSpec.DoubleValue ROYAL_ASSAILANT_BATTLE_AXE_ATTACK_DAMAGE; private static final ModConfigSpec.DoubleValue ROYAL_ASSAILANT_BATTLE_AXE_ATTACK_SPEED; private static final ModConfigSpec.DoubleValue ROYAL_WEAPON_ATTACK_REACH_BONUS; @@ -96,6 +103,13 @@ public final class AntarchyToolsConfig { private static final ModConfigSpec.IntValue ROYAL_WEAPON_DURABILITY; private static final ModConfigSpec.IntValue ROYAL_WEAPON_ENCHANTABILITY; private static final ModConfigSpec.IntValue ROYAL_GUARDIAN_SHIELD_DURABILITY; + private static final ModConfigSpec.DoubleValue ROYAL_GUARDIAN_MUSTER_RANGE, ROYAL_GUARDIAN_MUSTER_DAMAGE_REDUCTION, + ROYAL_GUARDIAN_MUSTER_ATTACK_BONUS, ROYAL_GUARDIAN_MUSTER_SPEED_BONUS, ROYAL_GUARDIAN_MUSTER_PASSIVE_DAMAGE_FLOOR, + ROYAL_GUARDIAN_BOUNDARY_RADIUS; + private static final ModConfigSpec.IntValue ROYAL_GUARDIAN_MUSTER_DURATION_TICKS, ROYAL_GUARDIAN_MUSTER_COOLDOWN_TICKS, + ROYAL_GUARDIAN_MUSTER_MOB_CAP, ROYAL_GUARDIAN_BOUNDARY_DURATION_TICKS, ROYAL_GUARDIAN_BOUNDARY_COOLDOWN_TICKS, + ROYAL_GUARDIAN_BOUNDARY_STRENGTH_AMPLIFIER, ROYAL_GUARDIAN_BOUNDARY_ARREST_TICKS, + ROYAL_GUARDIAN_BOUNDARY_DROP_TICKS, ROYAL_GUARDIAN_BOUNDARY_PROJECTILE_CAP; // Battle Axe @@ -360,6 +374,11 @@ public final class AntarchyToolsConfig { ROYAL_GUARDIAN_BOOTS_ARMOR_VALUE = b.defineInRange("guardianBootsArmor", 5, 0, 100); ROYAL_GUARDIAN_ARMOR_TOUGHNESS = b.defineInRange("guardianArmorToughness", 4.0D, 0.0D, 100.0D); ROYAL_GUARDIAN_ARMOR_KNOCKBACK_RESISTANCE = b.defineInRange("guardianArmorKnockbackResistance", 0.15D, 0.0D, 1.0D); + ROYAL_GUARDIAN_HELMET_JUDGMENT_BONUS = b.defineInRange("guardianHelmetJudgmentBonus", 0.06D, 0.0D, 10.0D); + ROYAL_GUARDIAN_CHESTPLATE_JUDGMENT_BONUS = b.defineInRange("guardianChestplateJudgmentBonus", 0.08D, 0.0D, 10.0D); + ROYAL_GUARDIAN_LEGGINGS_JUDGMENT_BONUS = b.defineInRange("guardianLeggingsJudgmentBonus", 0.07D, 0.0D, 10.0D); + ROYAL_GUARDIAN_BOOTS_JUDGMENT_BONUS = b.defineInRange("guardianBootsJudgmentBonus", 0.05D, 0.0D, 10.0D); + ROYAL_GUARDIAN_JUDGMENT_DURATION_TICKS = b.defineInRange("guardianJudgmentDurationTicks", 160, 1, 72000); ROYAL_ASSAILANT_HELMET_ARMOR_VALUE = b.defineInRange("assailantHelmetArmor", 5, 0, 100); ROYAL_ASSAILANT_CHESTPLATE_ARMOR_VALUE = b.defineInRange("assailantChestplateArmor", 10, 0, 100); ROYAL_ASSAILANT_LEGGINGS_ARMOR_VALUE = b.defineInRange("assailantLeggingsArmor", 8, 0, 100); @@ -372,6 +391,14 @@ public final class AntarchyToolsConfig { ROYAL_ASSAILANT_BOOTS_DOUBLE_DAMAGE_CHANCE = b.defineInRange("assailantBootsDoubleDamageChance", 0.05D, 0.0D, 1.0D); ROYAL_GUARDIAN_SWORD_ATTACK_DAMAGE = b.defineInRange("guardianSwordAttackDamage", 111.0D, 0.0D, 4096.0D); ROYAL_GUARDIAN_SWORD_ATTACK_SPEED = b.defineInRange("guardianSwordAttackSpeed", -2.5D, -10.0D, 10.0D); + ROYAL_GUARDIAN_SWORD_ELEMENTAL_COOLDOWN_TICKS = b.defineInRange("guardianSwordElementalCooldownTicks", 120, 0, 72000); + ROYAL_GUARDIAN_SWORD_FIRE_PRIMARY_DAMAGE = b.defineInRange("guardianSwordFirePrimaryDamage", 22.0D, 0.0D, 4096.0D); + ROYAL_GUARDIAN_SWORD_FIRE_SPLASH_DAMAGE = b.defineInRange("guardianSwordFireSplashDamage", 14.0D, 0.0D, 4096.0D); + ROYAL_GUARDIAN_SWORD_FROST_SPIKE_DAMAGE = b.defineInRange("guardianSwordFrostSpikeDamage", 9.0D, 0.0D, 4096.0D); + ROYAL_GUARDIAN_SWORD_STORM_PRIMARY_DAMAGE = b.defineInRange("guardianSwordStormPrimaryDamage", 16.0D, 0.0D, 4096.0D); + ROYAL_GUARDIAN_SWORD_STORM_JUMP_DAMAGE = b.defineInRange("guardianSwordStormJumpDamage", 14.0D, 0.0D, 4096.0D); + ROYAL_GUARDIAN_SWORD_STORM_MAX_JUMPS = b.defineInRange("guardianSwordStormMaxJumps", 4, 0, 64); + ROYAL_GUARDIAN_SWORD_STORM_JUMP_RANGE = b.defineInRange("guardianSwordStormJumpRange", 9.0D, 0.0D, 128.0D); ROYAL_ASSAILANT_BATTLE_AXE_ATTACK_DAMAGE = b.defineInRange("assailantBattleAxeAttackDamage", 151.0D, 0.0D, 4096.0D); ROYAL_ASSAILANT_BATTLE_AXE_ATTACK_SPEED = b.defineInRange("assailantBattleAxeAttackSpeed", -2.9D, -10.0D, 10.0D); ROYAL_WEAPON_ATTACK_REACH_BONUS = b.defineInRange("weaponAttackReachBonus", 1.0D, 0.0D, 32.0D); @@ -379,6 +406,21 @@ public final class AntarchyToolsConfig { ROYAL_WEAPON_DURABILITY = b.defineInRange("weaponDurability", 4608, 1, 100000); ROYAL_WEAPON_ENCHANTABILITY = b.defineInRange("weaponEnchantability", 30, 0, 100); ROYAL_GUARDIAN_SHIELD_DURABILITY = b.defineInRange("guardianShieldDurability", 1008, 1, 100000); + ROYAL_GUARDIAN_MUSTER_RANGE = b.defineInRange("guardianMusterRange", 16.0D, 0.0D, 128.0D); + ROYAL_GUARDIAN_MUSTER_DURATION_TICKS = b.defineInRange("guardianMusterDurationTicks", 1200, 1, 72000); + ROYAL_GUARDIAN_MUSTER_COOLDOWN_TICKS = b.defineInRange("guardianMusterCooldownTicks", 500, 0, 72000); + ROYAL_GUARDIAN_MUSTER_MOB_CAP = b.defineInRange("guardianMusterMobCap", 3, 0, 64); + ROYAL_GUARDIAN_MUSTER_DAMAGE_REDUCTION = b.defineInRange("guardianMusterDamageReduction", 0.40D, 0.0D, 1.0D); + ROYAL_GUARDIAN_MUSTER_ATTACK_BONUS = b.defineInRange("guardianMusterAttackBonus", 0.50D, 0.0D, 10.0D); + ROYAL_GUARDIAN_MUSTER_SPEED_BONUS = b.defineInRange("guardianMusterSpeedBonus", 0.25D, 0.0D, 10.0D); + ROYAL_GUARDIAN_MUSTER_PASSIVE_DAMAGE_FLOOR = b.defineInRange("guardianMusterPassiveDamageFloor", 3.0D, 0.0D, 1024.0D); + ROYAL_GUARDIAN_BOUNDARY_RADIUS = b.defineInRange("guardianBoundaryRadius", 9.0D, 0.5D, 128.0D); + ROYAL_GUARDIAN_BOUNDARY_DURATION_TICKS = b.defineInRange("guardianBoundaryDurationTicks", 120, 1, 72000); + ROYAL_GUARDIAN_BOUNDARY_COOLDOWN_TICKS = b.defineInRange("guardianBoundaryCooldownTicks", 700, 0, 72000); + ROYAL_GUARDIAN_BOUNDARY_STRENGTH_AMPLIFIER = b.defineInRange("guardianBoundaryStrengthAmplifier", 1, 0, 255); + ROYAL_GUARDIAN_BOUNDARY_ARREST_TICKS = b.defineInRange("guardianBoundaryArrestTicks", 20, 0, 1200); + ROYAL_GUARDIAN_BOUNDARY_DROP_TICKS = b.defineInRange("guardianBoundaryDropTicks", 8, 0, 1200); + ROYAL_GUARDIAN_BOUNDARY_PROJECTILE_CAP = b.defineInRange("guardianBoundaryProjectileCap", 64, 0, 4096); b.pop(); @@ -828,6 +870,11 @@ public final class AntarchyToolsConfig { static int royalGuardianBootsArmorValue() { return ROYAL_GUARDIAN_BOOTS_ARMOR_VALUE.get(); } static double royalGuardianArmorToughness() { return ROYAL_GUARDIAN_ARMOR_TOUGHNESS.get(); } static double royalGuardianArmorKnockbackResistance() { return ROYAL_GUARDIAN_ARMOR_KNOCKBACK_RESISTANCE.get(); } + static double royalGuardianHelmetJudgmentBonus() { return ROYAL_GUARDIAN_HELMET_JUDGMENT_BONUS.get(); } + static double royalGuardianChestplateJudgmentBonus() { return ROYAL_GUARDIAN_CHESTPLATE_JUDGMENT_BONUS.get(); } + static double royalGuardianLeggingsJudgmentBonus() { return ROYAL_GUARDIAN_LEGGINGS_JUDGMENT_BONUS.get(); } + static double royalGuardianBootsJudgmentBonus() { return ROYAL_GUARDIAN_BOOTS_JUDGMENT_BONUS.get(); } + static int royalGuardianJudgmentDurationTicks() { return ROYAL_GUARDIAN_JUDGMENT_DURATION_TICKS.get(); } static int royalAssailantHelmetArmorValue() { return ROYAL_ASSAILANT_HELMET_ARMOR_VALUE.get(); } static int royalAssailantChestplateArmorValue() { return ROYAL_ASSAILANT_CHESTPLATE_ARMOR_VALUE.get(); } static int royalAssailantLeggingsArmorValue() { return ROYAL_ASSAILANT_LEGGINGS_ARMOR_VALUE.get(); } @@ -840,6 +887,14 @@ public final class AntarchyToolsConfig { static double royalAssailantBootsDoubleDamageChance() { return ROYAL_ASSAILANT_BOOTS_DOUBLE_DAMAGE_CHANCE.get(); } static double royalGuardianSwordAttackDamage() { return ROYAL_GUARDIAN_SWORD_ATTACK_DAMAGE.get(); } static double royalGuardianSwordAttackSpeed() { return ROYAL_GUARDIAN_SWORD_ATTACK_SPEED.get(); } + static int royalGuardianSwordElementalCooldownTicks() { return ROYAL_GUARDIAN_SWORD_ELEMENTAL_COOLDOWN_TICKS.get(); } + static double royalGuardianSwordFirePrimaryDamage() { return ROYAL_GUARDIAN_SWORD_FIRE_PRIMARY_DAMAGE.get(); } + static double royalGuardianSwordFireSplashDamage() { return ROYAL_GUARDIAN_SWORD_FIRE_SPLASH_DAMAGE.get(); } + static double royalGuardianSwordFrostSpikeDamage() { return ROYAL_GUARDIAN_SWORD_FROST_SPIKE_DAMAGE.get(); } + static double royalGuardianSwordStormPrimaryDamage() { return ROYAL_GUARDIAN_SWORD_STORM_PRIMARY_DAMAGE.get(); } + static double royalGuardianSwordStormJumpDamage() { return ROYAL_GUARDIAN_SWORD_STORM_JUMP_DAMAGE.get(); } + static int royalGuardianSwordStormMaxJumps() { return ROYAL_GUARDIAN_SWORD_STORM_MAX_JUMPS.get(); } + static double royalGuardianSwordStormJumpRange() { return ROYAL_GUARDIAN_SWORD_STORM_JUMP_RANGE.get(); } static double royalAssailantBattleAxeAttackDamage() { return ROYAL_ASSAILANT_BATTLE_AXE_ATTACK_DAMAGE.get(); } static double royalAssailantBattleAxeAttackSpeed() { return ROYAL_ASSAILANT_BATTLE_AXE_ATTACK_SPEED.get(); } static double royalWeaponAttackReachBonus() { return ROYAL_WEAPON_ATTACK_REACH_BONUS.get(); } @@ -847,6 +902,21 @@ public final class AntarchyToolsConfig { static int royalWeaponDurability() { return ROYAL_WEAPON_DURABILITY.get(); } static int royalWeaponEnchantability() { return ROYAL_WEAPON_ENCHANTABILITY.get(); } static int royalGuardianShieldDurability() { return ROYAL_GUARDIAN_SHIELD_DURABILITY.get(); } + static double royalGuardianMusterRange() { return ROYAL_GUARDIAN_MUSTER_RANGE.get(); } + static int royalGuardianMusterDurationTicks() { return ROYAL_GUARDIAN_MUSTER_DURATION_TICKS.get(); } + static int royalGuardianMusterCooldownTicks() { return ROYAL_GUARDIAN_MUSTER_COOLDOWN_TICKS.get(); } + static int royalGuardianMusterMobCap() { return ROYAL_GUARDIAN_MUSTER_MOB_CAP.get(); } + static double royalGuardianMusterDamageReduction() { return ROYAL_GUARDIAN_MUSTER_DAMAGE_REDUCTION.get(); } + static double royalGuardianMusterAttackBonus() { return ROYAL_GUARDIAN_MUSTER_ATTACK_BONUS.get(); } + static double royalGuardianMusterSpeedBonus() { return ROYAL_GUARDIAN_MUSTER_SPEED_BONUS.get(); } + static double royalGuardianMusterPassiveDamageFloor() { return ROYAL_GUARDIAN_MUSTER_PASSIVE_DAMAGE_FLOOR.get(); } + static double royalGuardianBoundaryRadius() { return ROYAL_GUARDIAN_BOUNDARY_RADIUS.get(); } + static int royalGuardianBoundaryDurationTicks() { return ROYAL_GUARDIAN_BOUNDARY_DURATION_TICKS.get(); } + static int royalGuardianBoundaryCooldownTicks() { return ROYAL_GUARDIAN_BOUNDARY_COOLDOWN_TICKS.get(); } + static int royalGuardianBoundaryStrengthAmplifier() { return ROYAL_GUARDIAN_BOUNDARY_STRENGTH_AMPLIFIER.get(); } + static int royalGuardianBoundaryArrestTicks() { return ROYAL_GUARDIAN_BOUNDARY_ARREST_TICKS.get(); } + static int royalGuardianBoundaryDropTicks() { return ROYAL_GUARDIAN_BOUNDARY_DROP_TICKS.get(); } + static int royalGuardianBoundaryProjectileCap() { return ROYAL_GUARDIAN_BOUNDARY_PROJECTILE_CAP.get(); } } diff --git a/neoforge-1.21.1/src/main/java/com/craisinlord/antarchy/neoforge/client/AntarchyKeyBindings.java b/neoforge-1.21.1/src/main/java/com/craisinlord/antarchy/neoforge/client/AntarchyKeyBindings.java index cda2d19f..2a2757bb 100644 --- a/neoforge-1.21.1/src/main/java/com/craisinlord/antarchy/neoforge/client/AntarchyKeyBindings.java +++ b/neoforge-1.21.1/src/main/java/com/craisinlord/antarchy/neoforge/client/AntarchyKeyBindings.java @@ -51,6 +51,13 @@ public final class AntarchyKeyBindings { InputConstants.KEY_V, CATEGORY ); + public static final KeyMapping ROYAL_BOUNDARY = new KeyMapping( + "key.antarchy.royal_boundary", + KeyConflictContext.IN_GAME, + InputConstants.Type.KEYSYM, + InputConstants.KEY_B, + CATEGORY + ); private AntarchyKeyBindings() {} @@ -61,6 +68,7 @@ public final class AntarchyKeyBindings { event.register(MOUNT_FLIGHT_TOGGLE); event.register(TIGERS_EYE_CAMOUFLAGE); event.register(ROYAL_INVERSION_TOGGLE); + event.register(ROYAL_BOUNDARY); } public static boolean isMountFlightTogglePressed() { @@ -78,4 +86,8 @@ public final class AntarchyKeyBindings { public static boolean consumeRoyalInversionTogglePressed() { return ROYAL_INVERSION_TOGGLE.consumeClick(); } + + public static boolean consumeRoyalBoundaryPressed() { + return ROYAL_BOUNDARY.consumeClick(); + } } diff --git a/neoforge-1.21.1/src/main/java/com/craisinlord/antarchy/neoforge/client/AntarchyNeoForgeClient.java b/neoforge-1.21.1/src/main/java/com/craisinlord/antarchy/neoforge/client/AntarchyNeoForgeClient.java index c1ee80d2..37a79002 100644 --- a/neoforge-1.21.1/src/main/java/com/craisinlord/antarchy/neoforge/client/AntarchyNeoForgeClient.java +++ b/neoforge-1.21.1/src/main/java/com/craisinlord/antarchy/neoforge/client/AntarchyNeoForgeClient.java @@ -204,6 +204,7 @@ public final class AntarchyNeoForgeClient { renderer.addLayer(new TigerEyeCamouflagePlayerLayer(renderer)); renderer.addLayer(new GoopedLivingLayer(renderer)); renderer.addLayer(new GlimmeringLivingLayer(renderer)); + renderer.addLayer(new com.craisinlord.antarchy.content.client.renderer.RoyalIndicatorLivingLayer<>(renderer)); renderer.addLayer(new RoyalAssailantAxeAfterimageLayer(renderer)); renderer.addLayer(new BrutalflyElytraLayer(renderer)); renderer.addLayer(new com.craisinlord.antarchy.content.client.renderer.ManticoreWingsLayer(renderer)); @@ -244,11 +245,13 @@ public final class AntarchyNeoForgeClient { ((LivingEntityRenderer) livingRenderer).addLayer(new ParalyzedStoneLivingLayer<>(livingRenderer)); ((LivingEntityRenderer) livingRenderer).addLayer(new GoopedLivingLayer<>(livingRenderer)); ((LivingEntityRenderer) livingRenderer).addLayer(new GlimmeringLivingLayer<>(livingRenderer)); + ((LivingEntityRenderer) livingRenderer).addLayer(new com.craisinlord.antarchy.content.client.renderer.RoyalIndicatorLivingLayer<>(livingRenderer)); } private static GeoEntityRenderer withParalyzedGeoLayer(GeoEntityRenderer renderer) { renderer.addRenderLayer(new ParalyzedStoneGeoLayer<>(renderer)); renderer.addRenderLayer(new GoopedGeoLayer<>(renderer)); + renderer.addRenderLayer(new com.craisinlord.antarchy.content.client.renderer.RoyalIndicatorGeoLayer<>(renderer)); renderer.addRenderLayer(new TemporalContractionGeoLayer<>(renderer)); return renderer; } @@ -394,6 +397,10 @@ public final class AntarchyNeoForgeClient { ResourceLocation.fromNamespaceAndPath(Antarchy.MODID, "hercules_beetle_charge"), (guiGraphics, partialTick) -> HerculesBeetleChargeHudRenderer.render(guiGraphics) ); + event.registerAboveAll( + ResourceLocation.fromNamespaceAndPath(Antarchy.MODID, "king_judgment_flash"), + (guiGraphics, partialTick) -> com.craisinlord.antarchy.content.client.KingJudgmentFlashHudRenderer.render(guiGraphics) + ); } @SubscribeEvent @@ -714,7 +721,6 @@ public final class AntarchyNeoForgeClient { @SubscribeEvent public static void registerClientReloadListeners(RegisterClientReloadListenersEvent event) { - event.registerReloadListener(com.craisinlord.antarchy.content.guide.ComputerGuideData.instance()); event.registerReloadListener(new net.minecraft.server.packs.resources.SimplePreparableReloadListener() { @Override protected Void prepare(net.minecraft.server.packs.resources.ResourceManager resourceManager, net.minecraft.util.profiling.ProfilerFiller profiler) { diff --git a/neoforge-1.21.1/src/main/java/com/craisinlord/antarchy/neoforge/client/HerculesBeetleClientHandler.java b/neoforge-1.21.1/src/main/java/com/craisinlord/antarchy/neoforge/client/HerculesBeetleClientHandler.java index 1909161a..71e2f07a 100644 --- a/neoforge-1.21.1/src/main/java/com/craisinlord/antarchy/neoforge/client/HerculesBeetleClientHandler.java +++ b/neoforge-1.21.1/src/main/java/com/craisinlord/antarchy/neoforge/client/HerculesBeetleClientHandler.java @@ -4,6 +4,7 @@ import com.craisinlord.antarchy.Antarchy; import com.craisinlord.antarchy.content.client.CameraShakeClientState; import com.craisinlord.antarchy.content.client.HerculesBeetleImpactShakeClientState; import com.craisinlord.antarchy.content.client.HordeClientState; +import com.craisinlord.antarchy.content.client.KingJudgmentFlashClientState; import com.craisinlord.antarchy.content.entity.HerculesBeetleEntity; import com.craisinlord.antarchy.content.network.HerculesBeetleFlightTogglePayload; import com.craisinlord.antarchy.content.network.HerculesBeetleJumpInputPayload; @@ -31,6 +32,7 @@ public final class HerculesBeetleClientHandler { public static void onClientTick(ClientTickEvent.Post event) { CameraShakeClientState.tick(); HerculesBeetleImpactShakeClientState.tick(); + KingJudgmentFlashClientState.tick(); HordeClientState.tick(); Minecraft mc = Minecraft.getInstance(); diff --git a/neoforge-1.21.1/src/main/java/com/craisinlord/antarchy/neoforge/client/RoyalBoundaryClientHandler.java b/neoforge-1.21.1/src/main/java/com/craisinlord/antarchy/neoforge/client/RoyalBoundaryClientHandler.java new file mode 100644 index 00000000..6ef63b2a --- /dev/null +++ b/neoforge-1.21.1/src/main/java/com/craisinlord/antarchy/neoforge/client/RoyalBoundaryClientHandler.java @@ -0,0 +1,25 @@ +package com.craisinlord.antarchy.neoforge.client; + +import com.craisinlord.antarchy.Antarchy; +import com.craisinlord.antarchy.content.network.ActivateRoyalBoundaryPayload; +import net.minecraft.client.Minecraft; +import net.neoforged.api.distmarker.Dist; +import net.neoforged.bus.api.SubscribeEvent; +import net.neoforged.fml.common.EventBusSubscriber; +import net.neoforged.neoforge.client.event.ClientTickEvent; +import net.neoforged.neoforge.network.PacketDistributor; + +@EventBusSubscriber(modid = Antarchy.MODID, value = Dist.CLIENT) +public final class RoyalBoundaryClientHandler { + private RoyalBoundaryClientHandler() { + } + + @SubscribeEvent + public static void onClientTick(ClientTickEvent.Post event) { + Minecraft minecraft = Minecraft.getInstance(); + if (minecraft.player == null || minecraft.level == null) return; + while (AntarchyKeyBindings.consumeRoyalBoundaryPressed()) { + PacketDistributor.sendToServer(new ActivateRoyalBoundaryPayload()); + } + } +} diff --git a/neoforge-1.21.1/src/main/java/com/craisinlord/antarchy/neoforge/registry/AntarchyNeoForgeEvents.java b/neoforge-1.21.1/src/main/java/com/craisinlord/antarchy/neoforge/registry/AntarchyNeoForgeEvents.java index a7770bbd..a58be766 100644 --- a/neoforge-1.21.1/src/main/java/com/craisinlord/antarchy/neoforge/registry/AntarchyNeoForgeEvents.java +++ b/neoforge-1.21.1/src/main/java/com/craisinlord/antarchy/neoforge/registry/AntarchyNeoForgeEvents.java @@ -396,6 +396,7 @@ public final class AntarchyNeoForgeEvents { event.addListener(new ComputerScientistTradeManager()); event.addListener(com.craisinlord.antarchy.content.guide.ComputerGuideData.instance()); event.addListener(com.craisinlord.antarchy.content.antmail.AntmailEventData.instance()); + event.addListener(com.craisinlord.antarchy.content.computer.blockle.BlockleAnswers.instance()); } static void handleStartTracking(PlayerEvent.StartTracking event) { if (event.getTarget().level().isClientSide()) { @@ -985,6 +986,7 @@ public final class AntarchyNeoForgeEvents { static void handleNightmareArmorDoubleDamage(LivingIncomingDamageEvent event) { + if (com.craisinlord.antarchy.content.item.RoyalGuardianSwordAbilities.isApplyingSecondaryDamage()) return; net.minecraft.world.damagesource.DamageSource source = event.getSource(); if (!(source.getEntity() instanceof net.minecraft.world.entity.LivingEntity attacker)) return; if (!attacker.getAttributes().hasAttribute(AntarchyNeoforgeMisc.DOUBLE_DAMAGE_CHANCE)) return; diff --git a/neoforge-1.21.1/src/main/java/com/craisinlord/antarchy/neoforge/registry/AntarchyNeoforgeMisc.java b/neoforge-1.21.1/src/main/java/com/craisinlord/antarchy/neoforge/registry/AntarchyNeoforgeMisc.java index 1d167f45..ebcf9680 100644 --- a/neoforge-1.21.1/src/main/java/com/craisinlord/antarchy/neoforge/registry/AntarchyNeoforgeMisc.java +++ b/neoforge-1.21.1/src/main/java/com/craisinlord/antarchy/neoforge/registry/AntarchyNeoforgeMisc.java @@ -167,6 +167,7 @@ public final class AntarchyNeoforgeMisc { public static final DeferredHolder BLOODGLASS_WARD = MOB_EFFECTS.register("bloodglass_ward", com.craisinlord.antarchy.content.effect.BloodglassWardEffect::new); public static final DeferredHolder GLIMMERING = MOB_EFFECTS.register("glimmering", com.craisinlord.antarchy.content.effect.GlimmeringMobEffect::new); + public static final DeferredHolder ROYAL_VITALITY_PENALTY = MOB_EFFECTS.register("royal_vitality_penalty", com.craisinlord.antarchy.content.effect.RoyalVitalityPenaltyEffect::new); public static final DeferredHolder SHRINKING_EFFECT = MOB_EFFECTS.register("shrinking", ShrinkMobEffect::new); public static final DeferredHolder GROWTH_EFFECT = MOB_EFFECTS.register("growth", GrowthMobEffect::new); diff --git a/neoforge-1.21.1/src/main/java/com/craisinlord/antarchy/neoforge/registry/AntarchyNeoforgePayloadHandlers.java b/neoforge-1.21.1/src/main/java/com/craisinlord/antarchy/neoforge/registry/AntarchyNeoforgePayloadHandlers.java index e6aefb62..746159c8 100644 --- a/neoforge-1.21.1/src/main/java/com/craisinlord/antarchy/neoforge/registry/AntarchyNeoforgePayloadHandlers.java +++ b/neoforge-1.21.1/src/main/java/com/craisinlord/antarchy/neoforge/registry/AntarchyNeoforgePayloadHandlers.java @@ -91,6 +91,12 @@ public class AntarchyNeoforgePayloadHandlers { SpringyBootsNetworking.register(registrar); DorrieJumpNetworking.register(registrar); HerculesBeetleNetworking.register(registrar); + registrar.playToClient( + com.craisinlord.antarchy.content.network.KingJudgmentFlashPayload.TYPE, + com.craisinlord.antarchy.content.network.KingJudgmentFlashPayload.STREAM_CODEC, + (payload, ctx) -> ctx.enqueueWork(() -> + com.craisinlord.antarchy.content.client.KingJudgmentFlashClientState.trigger(payload.durationTicks())) + ); registrar.playToClient( com.craisinlord.antarchy.content.network.BloodglassStatePayload.TYPE, com.craisinlord.antarchy.content.network.BloodglassStatePayload.STREAM_CODEC, @@ -105,6 +111,15 @@ public class AntarchyNeoforgePayloadHandlers { com.craisinlord.antarchy.content.client.TigerEyeCamouflageClientState.update(payload.entityId(), payload.active(), payload.blockStateId()) ) ); + registrar.playToServer( + com.craisinlord.antarchy.content.network.ActivateRoyalBoundaryPayload.TYPE, + com.craisinlord.antarchy.content.network.ActivateRoyalBoundaryPayload.STREAM_CODEC, + (payload, ctx) -> ctx.enqueueWork(() -> { + if (ctx.player() instanceof net.minecraft.server.level.ServerPlayer player) { + com.craisinlord.antarchy.content.effect.RoyalBoundaryManager.activate(player); + } + }) + ); registrar.playToServer( com.craisinlord.antarchy.content.network.ToggleRoyalInversionPayload.TYPE, com.craisinlord.antarchy.content.network.ToggleRoyalInversionPayload.STREAM_CODEC, diff --git a/tools/rasterize_pixelarticons.py b/tools/rasterize_pixelarticons.py new file mode 100644 index 00000000..3b92ffc5 --- /dev/null +++ b/tools/rasterize_pixelarticons.py @@ -0,0 +1,125 @@ +import re +from pathlib import Path + +from PIL import Image + + +SOURCE_DIR = ( + Path(__file__).resolve().parents[1] + / "common-1.21.1/src/main/resources/assets/antarchy/textures/gui/antpaint" +) +ICON_NAMES = ("undo", "redo", "pencil", "eraser", "trash") +TOKEN = re.compile(r"[MmLlHhVvZz]|-?(?:\d+(?:\.\d*)?|\.\d+)") + + +def polygons_from_path(data): + tokens = TOKEN.findall(data) + polygons = [] + points = [] + x = y = start_x = start_y = 0.0 + command = None + i = 0 + + while i < len(tokens): + if tokens[i].isalpha(): + command = tokens[i] + i += 1 + if command in "Zz": + if len(points) > 2: + polygons.append(points) + points = [] + x, y = start_x, start_y + command = None + continue + + if command is None: + raise ValueError(f"Unexpected SVG path data near token {i}: {data}") + + relative = command.islower() + op = command.upper() + if op in ("M", "L"): + px = float(tokens[i]) + py = float(tokens[i + 1]) + i += 2 + if relative: + px += x + py += y + x, y = px, py + if op == "M": + if len(points) > 2: + polygons.append(points) + points = [(x, y)] + start_x, start_y = x, y + command = "l" if relative else "L" + else: + points.append((x, y)) + elif op == "H": + px = float(tokens[i]) + i += 1 + x = x + px if relative else px + points.append((x, y)) + elif op == "V": + py = float(tokens[i]) + i += 1 + y = y + py if relative else py + points.append((x, y)) + else: + raise ValueError(f"Unsupported SVG command {command!r}: {data}") + + if len(points) > 2: + polygons.append(points) + return polygons + + +def contains_point(polygon, px, py): + inside = False + previous = polygon[-1] + for current in polygon: + x1, y1 = previous + x2, y2 = current + if (y1 > py) != (y2 > py): + crossing_x = (x2 - x1) * (py - y1) / (y2 - y1) + x1 + if px < crossing_x: + inside = not inside + previous = current + return inside + + +def main(): + for icon_name in ICON_NAMES: + source = (SOURCE_DIR / f"{icon_name}.svg").read_text(encoding="utf-8") + paths = re.findall(r']*\bd="([^"]+)"', source) + if not paths: + raise ValueError(f"No SVG path found in {icon_name}.svg") + polygons = [polygon for path in paths for polygon in polygons_from_path(path)] + + mask = [ + [ + any(contains_point(poly, x + 0.5, y + 0.5) for poly in polygons) + for x in range(24) + ] + for y in range(24) + ] + image = Image.new("RGBA", (24, 24), (0, 0, 0, 0)) + pixels = image.load() + for y in range(24): + for x in range(24): + if mask[y][x]: + pixels[x, y] = (101, 255, 101, 255) + continue + outlined = any( + 0 <= nx < 24 and 0 <= ny < 24 and mask[ny][nx] + for nx, ny in ( + (x - 1, y), + (x + 1, y), + (x, y - 1), + (x, y + 1), + ) + ) + if outlined: + pixels[x, y] = (3, 6, 3, 255) + image.save(SOURCE_DIR / f"{icon_name}.png") + + +if __name__ == "__main__": + main()