From c77d1da53b4d2580f0a6dd99311d55e993197ce2 Mon Sep 17 00:00:00 2001 From: craisinlord Date: Wed, 16 Sep 2026 23:12:47 -0500 Subject: [PATCH] new icons royal guardian first pass --- .../caches/9.4.1/fileContent/fileContent.lock | Bin 0 -> 17 bytes .../caches/9.4.1/fileHashes/fileHashes.bin | Bin 0 -> 85215 bytes .../caches/9.4.1/fileHashes/fileHashes.lock | Bin 0 -> 17 bytes .../9.4.1/fileHashes/resourceHashesCache.bin | Bin 0 -> 22475 bytes .gradle-codex/caches/9.4.1/gc.properties | 0 .../9.4.1/transforms/.internal/gc.properties | 0 .../gradle-9.4.1-bin.zip.lck | 0 .../gradle-9.4.1-bin.zip.part | 0 .../config/AntarchyConfigCatalog.java | 6 +- .../antarchy/config/AntarchySettings.java | 87 ++ .../antarchy/content/AntarchyObjects.java | 5 + .../content/antmail/AntmailDebug.java | 19 + .../content/antmail/AntmailServerData.java | 11 +- .../antarchy/content/antmail/AntmailWire.java | 29 +- .../QueenTrailSpawnMarkerBlockEntity.java | 20 + .../content/client/AntmailClientState.java | 13 +- .../client/ComputerAccessClientState.java | 7 + .../client/ComputerFileSystemClientState.java | 10 + .../ComputerStructureLocatorClientState.java | 43 + .../client/KingJudgmentFlashClientState.java | 36 + .../client/KingJudgmentFlashHudRenderer.java | 17 + .../renderer/RoyalIndicatorGeoLayer.java | 32 + .../renderer/RoyalIndicatorLivingLayer.java | 24 + .../renderer/RoyalIndicatorRenderer.java | 79 + .../content/client/screen/ComputerScreen.java | 1318 ++++++++++++----- .../content/command/QueenLocateCommand.java | 7 +- .../computer/blockle/BlockleAnswers.java | 122 +- .../content/effect/CommandedBehavior.java | 37 +- .../content/effect/CommandedEntityAccess.java | 2 + .../content/effect/JudgmentMarkAccess.java | 8 + .../content/effect/JudgmentMarkManager.java | 140 ++ .../content/effect/RoyalBoundaryManager.java | 269 ++++ .../effect/RoyalVitalityPenaltyEffect.java | 34 + .../content/entity/ant/AntTeleportHelper.java | 168 +-- .../content/entity/ant/BaseAntEntity.java | 1 + .../content/entity/ant/RainbowAntEntity.java | 4 + .../entity/nightmare/NightmareBiteEntity.java | 51 +- .../entity/nightmare/NightmareEntity.java | 5 + .../content/entity/royal/KingEntity.java | 550 ++++++- .../content/entity/royal/QueenEntity.java | 655 ++++++-- .../content/entity/royal/RoyalBoltEntity.java | 5 + .../content/entity/royal/RoyalBossEntity.java | 37 +- .../royal/RoyalElementalProjectileEntity.java | 5 + .../entity/royal/RoyalIceSpikeEntity.java | 70 +- .../entity/royal/RoyalLootDropGlow.java | 37 + .../royal/attack/RoyalAttackScheduler.java | 8 + .../entity/royal/beam/RoyalBeamElement.java | 3 +- .../royal/decree/CloseQuartersDecree.java | 4 + .../royal/decree/FightMeCowardDecree.java | 23 +- .../entity/royal/decree/KneelDecree.java | 2 +- .../entity/royal/decree/RoyalDecree.java | 2 +- .../trades/ComputerScientistTradeManager.java | 2 +- .../gravity/ProjectileLaunchGravity.java | 13 + .../content/guide/ComputerGuideData.java | 113 +- .../content/item/BrutalflyElytraItem.java | 15 + .../content/item/ManticoreWingsItem.java | 18 + .../content/item/RainbowSugarItem.java | 8 +- .../content/item/RoyalGuardianArmorItem.java | 39 + .../content/item/RoyalGuardianShieldItem.java | 70 +- .../item/RoyalGuardianSwordAbilities.java | 211 +++ .../content/item/RoyalGuardianSwordItem.java | 86 ++ .../network/ActivateRoyalBoundaryPayload.java | 18 + .../content/network/AntmailNetworking.java | 34 +- .../content/network/AntmailServerHandler.java | 23 +- .../network/ComputerAccessHandler.java | 66 +- .../network/ComputerAccessPayload.java | 2 + .../content/network/ComputerNetworking.java | 4 + .../network/KingJudgmentFlashPayload.java | 20 + .../network/KingJudgmentFlashSync.java | 22 + .../portal/PermanentPortalTeleporter.java | 79 +- .../ThoraxisTransitionThroatFeature.java | 8 +- .../thoraxis/ThoraxisUndersideManager.java | 13 +- .../antarchy/mixins/AntarchyMixinPlugin.java | 27 +- .../antarchy/mixins/CommandedMobMixin.java | 14 + .../mixins/JudgmentMarkEntityMixin.java | 42 + .../mixins/ProjectileRoyalBoundaryMixin.java | 21 + .../RoyalBoundaryOwnerCleanupMixin.java | 17 + .../RoyalGuardianPlayerAttackMixin.java | 23 + .../mixins/RoyalGuardianSwordCombatMixin.java | 27 + .../mixins/RoyalJudgmentCombatMixin.java | 59 + .../mixins/RoyalLootItemGlowMixin.java | 16 + .../ServerPlayerRoyalBoundaryMixin.java | 16 + .../client/HoverboardRiderPoseMixin.java | 13 + .../AlexsCavesCandyCaneHookMixin.java | 52 + .../AlexsCavesDepthChargeMixin.java | 25 + .../alexscaves/AlexsCavesTephraMixin.java | 25 + .../alexscaves/AlexsCavesWaterBoltMixin.java | 25 + .../ArsNouveauProjectileGravityMixin.java | 43 + ...IronsSpellbooksProjectileGravityMixin.java | 65 + .../scguns/ScgunsArrowGravityMixin.java | 26 + .../ScgunsProjectileEntityGravityMixin.java | 82 + .../gravity/ProjectileGravityMixin.java | 4 +- .../resources/antarchy-common.mixins.json | 16 + .../resources/assets/antarchy/lang/en_us.json | 50 +- .../assets/antarchy/models/item/computer.json | 4 +- .../textures/gui/antpaint/ATTRIBUTION.txt | 7 + .../gui/antpaint/LICENSE-pixelarticons.txt | 21 + .../antarchy/textures/gui/antpaint/eraser.png | Bin 0 -> 233 bytes .../antarchy/textures/gui/antpaint/eraser.svg | 1 + .../antarchy/textures/gui/antpaint/pencil.png | Bin 0 -> 252 bytes .../antarchy/textures/gui/antpaint/pencil.svg | 1 + .../antarchy/textures/gui/antpaint/redo.png | Bin 0 -> 218 bytes .../antarchy/textures/gui/antpaint/redo.svg | 4 + .../antarchy/textures/gui/antpaint/trash.png | Bin 0 -> 209 bytes .../antarchy/textures/gui/antpaint/trash.svg | 3 + .../antarchy/textures/gui/antpaint/undo.png | Bin 0 -> 218 bytes .../antarchy/textures/gui/antpaint/undo.svg | 3 + .../antarchy/textures/item/glimmer_bottle.png | Bin 414 -> 334 bytes .../textures/item/glimmer_bottle.png.mcmeta | 6 + .../data/antarchy/blockle/answers.json | 46 + .../data/antarchy/computer/disk/king.json | 5 + .../data/antarchy/computer/disk/kraken.json | 5 + .../data/antarchy/computer/disk/queen.json | 5 + .../antarchy/computer/entry/bloodglass.json | 3 +- .../data/antarchy/computer/entry/cavaryn.json | 1 + .../antarchy/computer/entry/chronosphere.json | 4 +- .../computer/entry/duplicator_tree.json | 8 + .../antarchy/computer/entry/easter_bunny.json | 9 + .../data/antarchy/computer/entry/elythia.json | 1 + .../computer/entry/flying_squirrel.json | 4 +- .../entry/high_fructose_corn_syrup.json | 3 +- .../antarchy/computer/entry/introduction.json | 3 +- .../data/antarchy/computer/entry/king.json | 9 + .../data/antarchy/computer/entry/kraken.json | 9 + .../antarchy/computer/entry/nightmare.json | 3 +- .../data/antarchy/computer/entry/queen.json | 9 + .../antarchy/computer/entry/rainbow_ant.json | 9 + .../computer/entry/temporal_fields.json | 3 +- .../antarchy/computer/entry/thoraxis.json | 1 + .../computer/entry/toreterror_arena.json | 9 +- .../data/antarchy/computer/entry/vortex.json | 4 +- .../data/antarchy/recipe/computer.json | 8 +- .../village/computer_scientist_basement.nbt | Bin 0 -> 1451 bytes .../village/computer_scientist_house.nbt | Bin 1807 -> 1909 bytes .../structure/toreterror_arena_locator.json | 6 + .../antarchy/worldgen/biome/dream_dunes.json | 5 +- .../village/computer_scientist_basement.json | 15 + .../fabric/AntarchyConfigModuleFabric.java | 8 + .../antarchy/fabric/AntarchyFabric.java | 14 + .../fabric/AntarchyFabricContent.java | 2 + .../fabric/client/AntarchyFabricClient.java | 14 - .../client/AntarchyFabricClientBootstrap.java | 7 + .../fabric/client/AntarchyKeyBindings.java | 11 + .../client/RoyalBoundaryClientHandler.java | 18 + .../AntarchyFabricClientNetworking.java | 2 + .../network/AntarchyFabricNetworking.java | 4 + .../fabric/registry/AntarchyFabricMisc.java | 1 + .../AntarchyConfigModuleNeoforge.java | 31 + .../antarchy/neoforge/AntarchyNeoforge.java | 1 + .../neoforge/AntarchyToolsConfig.java | 70 + .../neoforge/client/AntarchyKeyBindings.java | 12 + .../client/AntarchyNeoForgeClient.java | 8 +- .../client/HerculesBeetleClientHandler.java | 2 + .../client/RoyalBoundaryClientHandler.java | 25 + .../registry/AntarchyNeoForgeEvents.java | 2 + .../registry/AntarchyNeoforgeMisc.java | 1 + .../AntarchyNeoforgePayloadHandlers.java | 15 + tools/rasterize_pixelarticons.py | 125 ++ 158 files changed, 5342 insertions(+), 825 deletions(-) create mode 100644 .gradle-codex/caches/9.4.1/fileContent/fileContent.lock create mode 100644 .gradle-codex/caches/9.4.1/fileHashes/fileHashes.bin create mode 100644 .gradle-codex/caches/9.4.1/fileHashes/fileHashes.lock create mode 100644 .gradle-codex/caches/9.4.1/fileHashes/resourceHashesCache.bin create mode 100644 .gradle-codex/caches/9.4.1/gc.properties create mode 100644 .gradle-codex/caches/9.4.1/transforms/.internal/gc.properties create mode 100644 .gradle-codex/wrapper/dists/gradle-9.4.1-bin/arn2x92ynaizyzdaamcbpbhtj/gradle-9.4.1-bin.zip.lck create mode 100644 .gradle-codex/wrapper/dists/gradle-9.4.1-bin/arn2x92ynaizyzdaamcbpbhtj/gradle-9.4.1-bin.zip.part create mode 100644 common-1.21.1/src/main/java/com/craisinlord/antarchy/content/antmail/AntmailDebug.java create mode 100644 common-1.21.1/src/main/java/com/craisinlord/antarchy/content/client/ComputerStructureLocatorClientState.java create mode 100644 common-1.21.1/src/main/java/com/craisinlord/antarchy/content/client/KingJudgmentFlashClientState.java create mode 100644 common-1.21.1/src/main/java/com/craisinlord/antarchy/content/client/KingJudgmentFlashHudRenderer.java create mode 100644 common-1.21.1/src/main/java/com/craisinlord/antarchy/content/client/renderer/RoyalIndicatorGeoLayer.java create mode 100644 common-1.21.1/src/main/java/com/craisinlord/antarchy/content/client/renderer/RoyalIndicatorLivingLayer.java create mode 100644 common-1.21.1/src/main/java/com/craisinlord/antarchy/content/client/renderer/RoyalIndicatorRenderer.java create mode 100644 common-1.21.1/src/main/java/com/craisinlord/antarchy/content/effect/JudgmentMarkAccess.java create mode 100644 common-1.21.1/src/main/java/com/craisinlord/antarchy/content/effect/JudgmentMarkManager.java create mode 100644 common-1.21.1/src/main/java/com/craisinlord/antarchy/content/effect/RoyalBoundaryManager.java create mode 100644 common-1.21.1/src/main/java/com/craisinlord/antarchy/content/effect/RoyalVitalityPenaltyEffect.java create mode 100644 common-1.21.1/src/main/java/com/craisinlord/antarchy/content/entity/royal/RoyalLootDropGlow.java create mode 100644 common-1.21.1/src/main/java/com/craisinlord/antarchy/content/gravity/ProjectileLaunchGravity.java create mode 100644 common-1.21.1/src/main/java/com/craisinlord/antarchy/content/item/RoyalGuardianSwordAbilities.java create mode 100644 common-1.21.1/src/main/java/com/craisinlord/antarchy/content/network/ActivateRoyalBoundaryPayload.java create mode 100644 common-1.21.1/src/main/java/com/craisinlord/antarchy/content/network/KingJudgmentFlashPayload.java create mode 100644 common-1.21.1/src/main/java/com/craisinlord/antarchy/content/network/KingJudgmentFlashSync.java create mode 100644 common-1.21.1/src/main/java/com/craisinlord/antarchy/mixins/JudgmentMarkEntityMixin.java create mode 100644 common-1.21.1/src/main/java/com/craisinlord/antarchy/mixins/ProjectileRoyalBoundaryMixin.java create mode 100644 common-1.21.1/src/main/java/com/craisinlord/antarchy/mixins/RoyalBoundaryOwnerCleanupMixin.java create mode 100644 common-1.21.1/src/main/java/com/craisinlord/antarchy/mixins/RoyalGuardianPlayerAttackMixin.java create mode 100644 common-1.21.1/src/main/java/com/craisinlord/antarchy/mixins/RoyalGuardianSwordCombatMixin.java create mode 100644 common-1.21.1/src/main/java/com/craisinlord/antarchy/mixins/RoyalJudgmentCombatMixin.java create mode 100644 common-1.21.1/src/main/java/com/craisinlord/antarchy/mixins/RoyalLootItemGlowMixin.java create mode 100644 common-1.21.1/src/main/java/com/craisinlord/antarchy/mixins/ServerPlayerRoyalBoundaryMixin.java create mode 100644 common-1.21.1/src/main/java/com/craisinlord/antarchy/mixins/compat/alexscaves/AlexsCavesCandyCaneHookMixin.java create mode 100644 common-1.21.1/src/main/java/com/craisinlord/antarchy/mixins/compat/alexscaves/AlexsCavesDepthChargeMixin.java create mode 100644 common-1.21.1/src/main/java/com/craisinlord/antarchy/mixins/compat/alexscaves/AlexsCavesTephraMixin.java create mode 100644 common-1.21.1/src/main/java/com/craisinlord/antarchy/mixins/compat/alexscaves/AlexsCavesWaterBoltMixin.java create mode 100644 common-1.21.1/src/main/java/com/craisinlord/antarchy/mixins/compat/ars_nouveau/ArsNouveauProjectileGravityMixin.java create mode 100644 common-1.21.1/src/main/java/com/craisinlord/antarchy/mixins/compat/irons_spellbooks/IronsSpellbooksProjectileGravityMixin.java create mode 100644 common-1.21.1/src/main/java/com/craisinlord/antarchy/mixins/compat/scguns/ScgunsArrowGravityMixin.java create mode 100644 common-1.21.1/src/main/java/com/craisinlord/antarchy/mixins/compat/scguns/ScgunsProjectileEntityGravityMixin.java create mode 100644 common-1.21.1/src/main/resources/assets/antarchy/textures/gui/antpaint/ATTRIBUTION.txt create mode 100644 common-1.21.1/src/main/resources/assets/antarchy/textures/gui/antpaint/LICENSE-pixelarticons.txt create mode 100644 common-1.21.1/src/main/resources/assets/antarchy/textures/gui/antpaint/eraser.png create mode 100644 common-1.21.1/src/main/resources/assets/antarchy/textures/gui/antpaint/eraser.svg create mode 100644 common-1.21.1/src/main/resources/assets/antarchy/textures/gui/antpaint/pencil.png create mode 100644 common-1.21.1/src/main/resources/assets/antarchy/textures/gui/antpaint/pencil.svg create mode 100644 common-1.21.1/src/main/resources/assets/antarchy/textures/gui/antpaint/redo.png create mode 100644 common-1.21.1/src/main/resources/assets/antarchy/textures/gui/antpaint/redo.svg create mode 100644 common-1.21.1/src/main/resources/assets/antarchy/textures/gui/antpaint/trash.png create mode 100644 common-1.21.1/src/main/resources/assets/antarchy/textures/gui/antpaint/trash.svg create mode 100644 common-1.21.1/src/main/resources/assets/antarchy/textures/gui/antpaint/undo.png create mode 100644 common-1.21.1/src/main/resources/assets/antarchy/textures/gui/antpaint/undo.svg create mode 100644 common-1.21.1/src/main/resources/assets/antarchy/textures/item/glimmer_bottle.png.mcmeta create mode 100644 common-1.21.1/src/main/resources/data/antarchy/blockle/answers.json create mode 100644 common-1.21.1/src/main/resources/data/antarchy/computer/disk/king.json create mode 100644 common-1.21.1/src/main/resources/data/antarchy/computer/disk/kraken.json create mode 100644 common-1.21.1/src/main/resources/data/antarchy/computer/disk/queen.json create mode 100644 common-1.21.1/src/main/resources/data/antarchy/computer/entry/duplicator_tree.json create mode 100644 common-1.21.1/src/main/resources/data/antarchy/computer/entry/easter_bunny.json create mode 100644 common-1.21.1/src/main/resources/data/antarchy/computer/entry/king.json create mode 100644 common-1.21.1/src/main/resources/data/antarchy/computer/entry/kraken.json create mode 100644 common-1.21.1/src/main/resources/data/antarchy/computer/entry/queen.json create mode 100644 common-1.21.1/src/main/resources/data/antarchy/computer/entry/rainbow_ant.json create mode 100644 common-1.21.1/src/main/resources/data/antarchy/structure/village/computer_scientist_basement.nbt create mode 100644 common-1.21.1/src/main/resources/data/antarchy/tags/worldgen/structure/toreterror_arena_locator.json create mode 100644 common-1.21.1/src/main/resources/data/antarchy/worldgen/template_pool/village/computer_scientist_basement.json create mode 100644 fabric-1.21.1/src/main/java/com/craisinlord/antarchy/fabric/client/RoyalBoundaryClientHandler.java create mode 100644 neoforge-1.21.1/src/main/java/com/craisinlord/antarchy/neoforge/client/RoyalBoundaryClientHandler.java create mode 100644 tools/rasterize_pixelarticons.py 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 0000000000000000000000000000000000000000..5179b48089af9c487a5adecc808ef5e5dfa6c6ce GIT binary patch literal 17 TcmZQx!0dD6{T{wF1}FdkFZTo1 literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..21dae325630937c7e1dddb027b06cda9b436140c GIT binary patch literal 85215 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 literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..7feee32230d49859b75221ae56d920cd9bd946c2 GIT binary patch literal 17 VcmZQxBymhrTZ`*00~j!$1^^}z1IGXW literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..22063717572e5cd35ee86883e2338339e2dd8fec GIT binary patch literal 22475 zcmeI(i8oa5;|FjNSt_Kl6_K^Fi?T(CVJu^pB-tV%L`Xz@Y{^z3M3#giOO|Y@l9&G^pL`9%he_^AAxJ=K@WR{7YKjm8%p%M(8Eo`mL;fHzj?Qc7nVs6TIx&s~z>UJ7KNaTdizPqS30(IO^kN&%VpXng zJ>dE&&`ajbQ>WG}sfm6KdReN#;oB664Zsa3pjUOu$Q{s0y$f7t40<(}otKnPg9328 z4D^~n*MD8MS>T9%X49)eX_`_?>49skLa&R+8>BH4&twK+*C=pZ9Q3z^ zIQP4~VJCoVcR_EE!zZ4&VWR=uI1&1LsqZY07Bz@H;On5b9r-=!dnR!L) zfpLuHH$54f(zZ@P+^!MNrY|lYzP(^x1M=E5(7*j)lhEkJ6F+x)9nj~8N8N>vN+*K6 z<~!)$D;?Vddy|`i>&QW0%E<1*WCeq*~MvPk<7&cxCuA(mHkv>G9`6xz%|^V zuPK+ML>GCF12;5*zVVqop+Kp&2RJqxI=SqyFZb>UMc`T!(5ask=C@TFd;qTP0-YvD zWr-^B4e>l0_d%!C;dKn~F#iSeI{wh<7Lt)yI-^s$aKtL5DsZiO=zAO(sQUIi z0Tr8KeF2MVmOE z8%janAAo;lv9DMg6tx9ZW@xYB4 zq4VnB(w`i0;{vY3w&_!rOHKOI>%fgJKLZgf7a>aMSOs?l5q}B#+jYUWP8!rA_&vUC9-=o+I>QqHWkYM$=QoajK!qXpTg`n>oWvbZ_W#WteO%iVQGKpyA1>GyU!cTZXq=M7^c=(zN-8dpn|8z7Hofv%;t6n4Qn z&J4Kz=}nJ#E72{P8xLGt8@i614=#j>vluvz4!XXJ?CDwyaUWv-2y}y0k3GdlhSY%T z{)BGkDs9MG&-(|si7j;Vkiv(3y=lbj&A1Z!`IJxA0tr{sLEb12x|O)Ido}MLOW+0( zn{JA`s#rSt5;#s6x;?|P!%g{0;_(_XZTivGzISD3PJ_H2CG?B@{POo#I*2{!4?({) zQ^Xjr!(9vVMyH@Vc`M)BlQZK9Tq^*&bF{+gei{klJb+Jy?qb^9!-Q$ICT@2Lx+`Bq z@gM)H7r?Q>&^>vX9mqMgh}VOb0(7qf*bZsox27PE>)v#;<8^8WUJ~zfOjMxz7I67m zCTSAC#|`+PUvDWL-l=k3k+}aE(BWS)GCCUY9{MlZ0NMcB0NMcB0NMcB0NMcB0NMcB z0NMcB0NMcB0NMcB0NMcB0NMcB0NMcB0NMcB0NMcB0NMcB0NMcB0NMcB0NMcB!2jI_ zB>(O@+uvVC!Y{+%t%g|h^fKW$6$71qfi%;9k@J5i43+IIH>5;aRHE|&sT#*Nkcnqp zLQ$*~+*a2n!x^=YYO5d<5sy_lx}4+a-{6zG|4b@fMJ70gY=-zkT@M>ke;2hXj@Lvc zrVPyW0#68=jYq`=bscAahfL7Xls-=Jd(DiQ>Zq14XCOR{y5(3pJ~+D3e~7kwhOw=_k@`d{`Xr9lc^G^eUO~T=$kScA2YVsy%dbD}}wW#q_R(C#Sb0 z5{&jMzBHZB7^NQ-j*2HYMkZ*Ntl6JE&6vz_84KRa@@W>C_^Ei$TXHstGFnD1fkBPK z9GUP;PNDgC_w2n$_N?D-|MCkV6U!zEMn2*4&IxR zFD*Vk-hK?3sNt*Ue%O$xu%ffWFzv)i5{+FU3%ax_GMZmZ^?=Uh0s}X4jEHtR2i_n# zN$D5YpYWZfA*_4ZvL6A|uD96kKN(XM=Q-)p(yWV2j94-m+CO)zD38zPd+d?jflPEI zwFV70_0bji=aUPv9I8Mj9GJ}H{3|@zS!}X*=LDQ1JlnfvKQ@%>mO__p69sdm5*&UC znIIEt|Gc<%`q4Jp%%wv?W)o?I=bpEWkx$M#^EuL~^OL>k>>;^cOJsuHG@3d$LiGe5 zb5~g9`^<4<)oees>$0FbSWNT&0!}>(M9f!S%r}wB4VI9Vnqm^hjIBCi@DCk;I-g$D!mBjp^+2~h& z{Abb45YONF=A9Y&$T3tJMcR+W-!ObhwEE}HNZ|CX)rmnfw?wHnmW%ASeySd6wr zCU&xjR&vhJ>GFt14KmV(gdr2dA317yz1mDDPlU4B(+edc6NT-bIVazfm(r|{D8wpN zWFQmMAL6E8WA>y9r0_j6;BChs6Ib@xCiT1Y*ax$;-pJ@WOkyr1U&|g*o)bu<`4z~b zJd(DA97Ekk*;>-y!!hhdTtm(pMKm(;i{hy9Gnojwd;bjcZ1mFjA`{y!Q!8gmo@d+W zRH%vvEju6+{ufdi@p(<#Bbc03F5Z>Bi%jH2W@+y1Bh!(+c*=F3f7DrILRMW`Pn7&{ zk!W{;rT;*j95T^okKH|!tT)yg<;`R)WESDGDaWj}^UKG`F_MPZ z(r1P6@$=IWXIq%n32O(oT!oTmuRE|&RRuO>ub3^4gcDXxY)K??^qF1d(hFVS_z_a^ zXPrbsF|wTF)%veF6L}+lp?|5HkYfm6$~_sv{+{_F)<&nb!HdNF5jt^1d>i}6`+^Q@ z;#%ETNZdKPR9>QgxIAlFRd7>XWc4W}ax2QQ6qDz>!}{M=an?zCej7t3mdM=6wtKy= zkID!#Vc;LRk4yw#v3|+z()UGb-F9Ul^<^kB(W3X-Rg7%8wEm0VY6n6RUUO_WJ8gf(AVS~r$`WLWy;?!x`aQDsJ&ToU)% zxz84Mz0ml$gsoVX&|2&xv8U<>2RCS=Bvq_+UXEXwQ0hl+h0J39&~b^`beiegmQixv ztjNUo_sMkhQza?uhsx>vs5p-!6Q8HP1-Aa9)yMbEv8pBP2Z`B1Ix)*lp=UIf)$cOj zLSeNtatyvXja(0=A)es|hrRunv`S>6OTSsyEnm4=or7g0sG!CVnMlgyQt!8D3eRuz z>Z@vm6$YiJ`Ri7UsQ6zF(PanmsaV2&=xfTtV+VhNWwQ!%82-&Gciez6B!_ z7CV;`%`rO?PA549Nk@v4m_Gt8;0?Z-@`x|VW>xv_|3P9;e+#xmd`wdJZd~Cv3C|~W z2fM> 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 0000000000000000000000000000000000000000..c08255a27d0c022316da735104a1d9a719400a48 GIT binary patch literal 233 zcmV \ 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 0000000000000000000000000000000000000000..561b0b85e334c3b456eaa9ac97fbc5cb595c2c5a GIT binary patch literal 252 zcmVuEJeA+zy!;wjIcZ;gB \ 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 0000000000000000000000000000000000000000..295aefb5ea5d0e3f6ba216080da5eca18e55e763 GIT binary patch literal 218 zcmeAS@N?(olHy`uVBq!ia0vp^5+KaM1|%Pp+x`Gj3q4&NLn`LHoqUn2L4n70d)F_2 z`<(xg20Zdj0_!hl=F5IieYUrJv*6#Fqr27{%C64)*wtZ(7(&mKX7qiGTmGuWTD!qb~-+x$n-ks(Cf{>ij$W`m5wlqxgzst<} z=pEmeplQyFRNt9p2+p%oDraB1f2rwDX63*wCnx1*eeb9ej&j&IFZI_#!MsU1{_Fb| SEer=bmci52&t;ucLK6UYZCC#Q literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..b8ba1b211ca96167bdca225aa05cd60c287bfa96 GIT binary patch literal 209 zcmeAS@N?(olHy`uVBq!ia0vp^5+KaM1|%Pp+x`GjGdx`!Ln`LHo$AfkpuppzDX>$W z{qek60=(QkE0?ZS%KM_L`Iq-(!kGkz;*-94OV53tbhPGzNPn?PvZl($&Z%9}M;T&d zOeQa%aKid{MYeE#Y82m`c<{$`c)FopHT + + 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 0000000000000000000000000000000000000000..6ad846e0bb33d681c44f0b7a94506b3c02c74826 GIT binary patch literal 218 zcmeAS@N?(olHy`uVBq!ia0vp^5+KaM1|%Pp+x`Gj3q4&NLn`LH4T_?3u0Y zjPG|GWa>B$-`th;J>5Y-KKD`tqghxZ`!l1o_qPK-6tHO|n{m!eX#Fb8C-F?GK8Ba-cd^T-i7&tWdUR{js%aXfe}tqv4yoN~5X?LD#@G9C SRI4J;u?(KBelF{r5}E)i=~-9+ literal 0 HcmV?d00001 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 f9c4da5c9d8121c6e9743f4a531906a7aec1fce3..f783957b1c5fc5c4811baa0d5b2298bfd672c098 100644 GIT binary patch delta 320 zcmV-G0l)s91I_}F7k^L)1^@s65UceK0003MNklYyMMyn(aelG)9zf`i}~c_~jqDI^~RDfN5s@%1O~C10AnmS$O_VI~8M z*!8=%dM^lbf7?sG{l@89$V5Ou&xlp8BNqTj8ZXasGC+aF(SOJVFbBv}^OTE#4C3te za2eN-qe@}Vn^)lkM(cycF@=I6?488DEnJ2e?gvZ3#Gx?)WClfo0&D<+KLXB4umpf2 zAo#Ny7`)DIG(C4PV`dj39=_yKpJNJPI6)DM~k^#lEXpnmW}KllJkKs*$w SrmFq`0000~41^@s6AM^iV0003~NklixK#*|^h>?@V5L-9TggHntvI4*&AmRNh0m0o#hLBNv#pv|~U?X*~&Jf5z vjdgD{00030|Gc+iA^-pY21!IgR09BsD2XAqMYh`j015yANkvXXu0mjfoqV=W 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 0000000000000000000000000000000000000000..4ec1b08ac5bf238900de824746339b6773474a28 GIT binary patch literal 1451 zcmV;c1yuSUiwFP!00000|Fu}lZrer_y?jZO?AT4v0BO-py9-dj`vOJkM0V;$9l^2l zLIGh!j-`o74ly&5qAuEQ3v|~X=(=B#4=B)oD9}}x-E^1U5lP9C#^eA+Xb6<3Gv}VU z_spGVCHszg0H9xOeEIs`fz~kL(Ut7tr)M4nQV~V*J&y2lE~^0!6qf_nFpe4H zT3|d2j1SFvf*Kyzq}erNTnmh6f$=T$)M@rCG*_59%@w9jbA_qXTw&@o`xcrjOr7Qm zQ>VGY)M>6TYnnIw6PPqRW=x%*D@>iAD@>iAD@>hcXZfmMJ5Gb<3e&U(od(SnrfCf} zX?87mJPRJrg2y-UcxFtK$F^b_YTBf^!ZdlTsa}Ce^9_2MJmy+>%(d{CYvD21!eg$5 z$C@;|7CfE>k7vQ-Tk!ZM9z(sFcmimyMTKedPg5-d3(d~*USD_5!atsce>@BScozO? z((GFBcosaK1&?pRcH)j47DT)jr)R+1WBte zZkvPEz<5}?#CEk8WBukjQ`kIZ#%TOVNeIn(Ii&%efSq!&Hrih5f^DiQv<`YGQ z2e+!h%YiGiR6*R_)`;y|lyaMR3Owo~GYX0EIA_D&Ad#Rn~fsYYhRe|xbe&2RrUyk`~ z$R}CMveA&gagbj>B1}LrNRM}lw=ar1`58$m++P-F%94bP=s{Jafs7a?lu0!h^NFNb zS;29x=z(GmNRjfGg8%gJ*-=sIk5ZP=NRUhQg;Zj)NcLdy)3ns9W4DnhrHaHv5iO)e zCTfg{aUM9Z$3__@_Xy)EwlMj%4DXnCh1N2|IFk`ou)mzskSoR05Q5hnlL~E;AU};g zxRWO&qS`QnTrjSaU1lZcJc0eyJZPG$c?dg~EF;OFkaEv7%_(PbmDNoJ7*NuLMml*L z0d?9wSn<7PBS~i3`0bn|R4J;hzl+4Dn)u_j$SlukJmBPNknm9#y0~s}J?q3Fp1ffa zocUr>i?MfCV3!Szy0~$c=Fr#hrj$Z`{VgvzyI*J z1G^{LbdqFL5NrL@GQ_8VMBRoanmQ;?^JEkb#zd)9MH0O>+Ji{~7Ldd>^{{W~6k`|T~ F004^p-L(J! literal 0 HcmV?d00001 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 7a2a9f4133c4a5b1ee76ac8e527f498ba911d5ca..1eb3c103c764e862947946c7e3ac38b44e2eca40 100644 GIT binary patch literal 1909 zcmZ8f3pCr;AMb1Ftf4eTtf5-1S1A$1D^!$tg>|8(UX>y-!&3cwHqzE(q$ZvXQ9=== z9i7m6L>W=jHIlMY&-|rDh*FP6E7X4N{^x&ocF#TcobUbK@AA&%+$*pLy(pZwKwe ztk4k4yn%uG#ahK0^vHbYdAVa_BXS)cjy8O1Z%v~9*>!t49Ma{|q~`>r&fxn*%|q=C z&McWM)WWZWE*$6)z}`ft z2gdb_<^SGQ7!wiE3HIjZtJ-7KOZ(~%!igO@WyBp6M}_)p;8*KVI1#IS37Z?%pHT@V z9oEVY<34|EQT7f^%CPUaKHU8!N99S4ufmAih8fMc!g?2+`Wq1h88lF1oYcz3bI)a2 zFqAL|5!4BWM@QJZ6AiPvB7&ie>T=7p;v9`uzG~os0yp?43OvF6V#cdv*o#nwprTA@ zR){g`Fhe= z#LcqcrY05^4%l^ev;hkRP>GfNe94yhnhTUg)jV*@x6jdwM0_~TA27b7?obp_cuiOhm_5qpr({> z$Qkx)`x8`e3|Kz9!ePEwGo^$GWzH~D3Wynu0Um&+bdo{5qXll8mwz@Ue$+&9fl<>_ z%vqcrWK5Mh?FPT9$81nSRZMAucvek;jA=K-)o|bj#Ot{}Tg#N2hiCc2D{Z;u#hKqj z5vGuoFzp3-yx^Hvmo;2Gz`7QP7nEXmF9ZGBYa7lNmzA`#L%6wnMgVpP&yi0!u<^w6 z;LmDIPo;+e7LysCTVTc|XLcCT9L!5s(h1`p1Mzg5Sln{8v;_w+|L0W2Acp%#=()k#>l%;c^`+yXU&@<7BK*3rjr`m7m z?&V-?m+8scrp!z^9s!SDD=y03h_QpLdVt$x@|>iw<*s&u#&J|$1qO(TS(a;|U+xS4 zzeE&UVIIiqNzELjE>T;cq?uacaOh z5CHe{*hUU8Oib5W&YLa;2)WrotU5F1 ze?JHg8k>`RBJv~NDq?exUoOL? zbnEMQirMWozd2mRY63!ul<#$FJR>N2XySC;YgPhMST$Hkaq;%s7|DVCJv-H<;R*zWAob^+-VcN$%& ze6KJdPO*+^jiw+{XPv6c z(#PiSC<%}9KfWvEjA6>rr>WzYU%~hbjtgb3k}z}6g~-%E@1UbLE%- z!RenpGQFi+84#Q?Xo^BR@XW>s(nG3MJ9`ZGF#tz9{

1Y&niAf<{>HcNt$-qQu zjVk_I1CqzXZPA7t;)r=MMjih{r?o% NrG+bi^-#NZ{RKmuyovw- literal 1807 zcmYLI4Oo&_8_sHekd~$<`dRs@?ah^+Vx>0!_>`%`@X}RgHqjUvWrnF}YKk*ao0_bN z`IrB?2`pTiT9VMPwEW@4}&v;YC6kWMU@Cb`p z6gU0G=_<(sqzf*?{T;Zehcl)&fR%GGtO4F@To|N&zPUPM55Bj$Z6h&ECE|})yGQ~| zDTb0hV+s{BREPWwTlJKfh->_<}<1 zUU$aNy+Y_rEwUjyID`D!i8AB%tTDd6XWp2XU5wqv>`|FAw@2;2ANggUvHR^ZYrL?c ze&wDXs;*u5B<Sa!^oyE({aitr4! zNYq=smKahtQhpFC9oz*Z&2A%@V*OJDusuPqH?~lVkc@U?pxvC`IL0@rZnMjKd*2)? zdeUb2^v7a7t$tfFdwqxNUs!}Rz5W~qiSudM#p}1gAV!3#?|wV-A>RPge;TVJz^gv1 zv%@kWc}8i-inGUE04%eYiize8Y;85jUM|WsqB%3<{Mxlw(>(;ki70yxk^lnyTCW?u zCV)#`_~YP0%n;r`WsFJ;QV&zDgVfGcYqZ)~59P|Q)nxy5+Lsvk8kYURh-r-v(fY_f zHI$<4IqqY&Qj~_+YjIfMeG}QNRo5IOxHoIh{fCoI30Uh>rSH^{32&GHzr)PEd>(b1URaByiT6`Ec!+S1TgUSLhKfbqn)9&{DK2M1pFfL==PK%=VoF( z*9OY{`y4`FiQNwMl=h>(D96X^M7DfUL1-r0Ruu|&>MPMlQ2biT4bMybHsBwy@wDQ% z!BdvOXmpAzm3SY_~lfpB~hi2@Xu{+fCEW$h+dPy zqpP8{TD6FG14*bNW>tSnruumG*VI^Kry+;3}-NvvhAUsF$6=ckeJj$=^WTrVVT9boO z67jtvbT=Zpbh@Om)!;Bj-s14b{*j(rkVTMBI)xxnMaI?JA<%sh6H4r3Z~#^;CGZ^htOp@R0qf!UJCmpxDyAxmU)Z)vEJo^{)zapVwD@IV`JZ#!su1>&x0bf0HVNhb zbL4>r#Zn2x=H-7R8+;^>M4AKbr8$`SXx&9VbBKONLw8_UH9TRcma6xs2XR(>c{^PHgoTisUcBa|@)VM;Lc*!W>im*c1raTct1 zSh{(w^Wai_%(QaL(ed`f(Wnu>QAWWd2iKCXhfbdQd8A+|(Kd zx*os6dOV|vOW@5~yJhw9-jo;MBD>g$OB!WoqKmePag}YU3C5tlUWxK!0hugKiz3f* z?X9P86znN!?o~6yvXc38h;u2L`L$N8b&-NO4kd!bg?aK1L1Lrt55f57`lV zw>TpP5QA2p=9*uc*7SmDQ(X%UovGoDU0va%hwurN>^lP5J%w)6`Y*(g2g> z?_~73qS~lwlAbDdOiqnL|C;3oxGWC;9{RbwxK4CeR29%mm!)Fel?>>#+(NOx#^m%cmZYVv*w@t#S_ z*Sw`)^}{g=Br8uTSAR&k;)n6}7AwqE;|TA# 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()