From c7cb48edfbf6f0c47f4be6677e40aeef585e769a Mon Sep 17 00:00:00 2001 From: craisinlord Date: Tue, 29 Sep 2026 23:37:11 -0500 Subject: [PATCH] moved miners dream to seperate mod new portal gun draft fixed scorched guns mixin sodium compat layer --- README.md | 2 +- .../config/AntarchyConfigCatalog.java | 2 +- .../antarchy/config/AntarchySettings.java | 45 - .../antarchy/content/AntarchyTags.java | 1 - .../block/entity/UpperBlockEntity.java | 5 + .../item/MinersDreamCaveGenerator.java | 173 --- .../content/item/MinersDreamExcavation.java | 152 --- .../item/MinersDreamExcavationManager.java | 81 -- .../content/item/MinersDreamItem.java | 79 -- .../content/item/MinersDreamTorchPlacer.java | 89 -- .../resources/assets/antarchy/lang/de_de.json | 5 - .../resources/assets/antarchy/lang/en_us.json | 5 - .../resources/assets/antarchy/lang/es_es.json | 5 - .../resources/assets/antarchy/lang/fr_fr.json | 5 - .../resources/assets/antarchy/lang/it_it.json | 5 - .../resources/assets/antarchy/lang/ja_jp.json | 5 - .../resources/assets/antarchy/lang/ko_kr.json | 5 - .../resources/assets/antarchy/lang/pt_br.json | 5 - .../resources/assets/antarchy/lang/ru_ru.json | 5 - .../resources/assets/antarchy/lang/zh_cn.json | 5 - .../resources/assets/antarchy/lang/zh_tw.json | 5 - .../antarchy/models/item/miners_dream.json | 6 - .../antarchy/textures/item/miners_dream.png | Bin 425 -> 0 bytes .../recipe/miners_dream.json.disabled | 19 - .../recipes/miners_dream.json.disabled | 19 - .../tags/block/miners_dream_carvable.json | 36 - .../tags/blocks/miners_dream_carvable.json | 36 - .../config/AntarchyConfigCatalog.java | 9 +- .../antarchy/config/AntarchySettings.java | 99 +- .../antarchy/content/AntarchyTags.java | 1 - .../PortalGunPortalBaseBlockEntity.java | 49 +- .../entity/PortalGunPortalCellAccess.java | 118 ++ .../entity/PortalGunPortalFaceRecord.java | 151 +++ .../PortalGunPortalMasterBlockEntity.java | 193 ++- .../block/entity/UpperBlockEntity.java | 5 + .../client/PortalGunCrosshairRenderer.java | 116 ++ .../PortalGunEntityTransformationStack.java | 39 +- .../client/PortalGunGrabClientState.java | 20 + .../client/PortalGunIndicatorClientState.java | 59 + .../client/PortalGunPortalRenderState.java | 4 +- .../client/PortalGunResetClientState.java | 51 + .../client/PortalGunRollClientState.java | 57 +- .../client/PortalGunZoomClientState.java | 72 ++ .../client/game/AntarchyComputerGames.java | 30 +- .../renderer/PortalGunPortalRenderer.java | 70 +- .../renderer/PortalGunPortalRendererPool.java | 148 +++ .../PortalGunPortalViewAreaManager.java | 403 ++++++ .../renderer/PortalGunPortalViewRenderer.java | 1096 ++++++++++++----- .../renderer/PortalGunProxySceneRenderer.java | 193 +++ .../renderer/PortalSceneRenderTrace.java | 82 ++ .../client/renderer/PortalStencilTarget.java | 196 +++ .../renderer/PortalViewSectionMarker.java | 7 + .../content/client/renderer/SodiumCompat.java | 117 ++ .../computer/AntarchyArchiveLocators.java | 10 + .../content/creative/CreativeTabContents.java | 19 +- .../gravity/ScgunsProjectileAccess.java | 10 + .../antarchy/content/item/BigBerthaItem.java | 14 +- .../item/MinersDreamCaveGenerator.java | 173 --- .../content/item/MinersDreamExcavation.java | 152 --- .../item/MinersDreamExcavationManager.java | 81 -- .../content/item/MinersDreamItem.java | 79 -- .../content/item/MinersDreamTorchPlacer.java | 89 -- .../antarchy/content/item/PortalGunItem.java | 601 ++++++--- .../content/network/PortalGunGrabPayload.java | 17 + .../network/PortalGunGrabStatePayload.java | 20 + .../network/PortalGunIndicatorPayload.java | 26 + .../PortalGunIndicatorRequestPayload.java | 24 + .../network/PortalGunPrimaryPayload.java | 8 +- .../network/PortalGunResetPayload.java | 20 + .../portalgun/PortalGunBlackHoleEntity.java | 3 +- .../portalgun/PortalGunCollisionHelper.java | 108 +- .../portalgun/PortalGunGrabManager.java | 280 +++++ .../portalgun/PortalGunIndicatorSync.java | 49 + .../content/portalgun/PortalGunPlacement.java | 63 +- .../portalgun/PortalGunPortalEntity.java | 767 +++++++----- .../portalgun/PortalGunProjectileEntity.java | 271 +++- .../portalgun/PortalGunResetManager.java | 86 ++ .../content/portalgun/PortalGunSavedData.java | 387 +++++- .../portalgun/PortalGunTransformUtil.java | 28 +- .../portalgun/PortalGunWorldPortalShape.java | 17 +- .../content/time/TimeDilationFieldEntity.java | 6 + .../content/time/TimeDilationManager.java | 3 +- .../thoraxis/ThoraxisBiomeSource.java | 52 +- .../antarchy/mixins/AntarchyMixinPlugin.java | 12 + .../mixins/FallingBlockEntityAccessor.java | 11 + .../antarchy/mixins/GrindstoneMenuMixin.java | 6 + .../mixins/PortalGunEntityCollisionMixin.java | 31 +- .../antarchy/mixins/PortalGunZombieMixin.java | 29 + .../mixins/client/CameraBasisAccessor.java | 18 + .../LevelRendererPortalChunkBudgetMixin.java | 65 + .../LevelRendererPortalLifecycleMixin.java | 55 + .../LevelRendererPortalSceneInvoker.java | 34 + .../LevelRendererPortalTargetsAccessor.java | 15 + .../LevelRendererPortalViewAreaAccessor.java | 160 +++ .../LevelRendererPortalViewAreaMixin.java | 63 + .../LivingEntityPortalLerpAccessor.java | 17 + .../MinecraftMainRenderTargetAccessor.java | 14 + .../PortalGunEntityTeleportPacketMixin.java | 68 + .../mixins/client/PortalGunZoomFovMixin.java | 18 + .../client/PortalViewChunkCacheMixin.java | 17 + .../client/PortalViewSectionSortingMixin.java | 42 + .../client/PostChainTargetAccessor.java | 17 + .../client/SodiumWorldRendererMixin.java | 19 + .../ScgunsProjectileEntityAccessMixin.java | 29 + .../ScgunsProjectileEntityGravityMixin.java | 20 +- .../gravity/EntityProjectileGravityMixin.java | 3 + .../resources/antarchy-common.mixins.json | 18 + .../resources/antarchy-sodium.mixins.json | 13 + .../resources/assets/antarchy/lang/de_de.json | 7 +- .../resources/assets/antarchy/lang/en_us.json | 15 +- .../resources/assets/antarchy/lang/es_es.json | 7 +- .../resources/assets/antarchy/lang/fr_fr.json | 7 +- .../resources/assets/antarchy/lang/it_it.json | 7 +- .../resources/assets/antarchy/lang/ja_jp.json | 7 +- .../resources/assets/antarchy/lang/ko_kr.json | 7 +- .../resources/assets/antarchy/lang/pt_br.json | 7 +- .../resources/assets/antarchy/lang/ru_ru.json | 7 +- .../resources/assets/antarchy/lang/zh_cn.json | 7 +- .../resources/assets/antarchy/lang/zh_tw.json | 7 +- .../licenses/aperture_innovations_mit.txt | 25 + .../antarchy/models/item/miners_dream.json | 6 - .../antarchy/shaders/core/portal_view.fsh | 12 + .../antarchy/shaders/core/portal_view.json | 14 + .../antarchy/shaders/core/portal_view.vsh | 11 + .../resources/assets/antarchy/sounds.json | 49 +- .../sounds/portal_aperture/portal_ambient.ogg | Bin 0 -> 37697 bytes .../sounds/portal_aperture/portal_enter_1.ogg | Bin 0 -> 14251 bytes .../sounds/portal_aperture/portal_enter_2.ogg | Bin 0 -> 15455 bytes .../sounds/portal_aperture/portal_enter_3.ogg | Bin 0 -> 17341 bytes .../portal_aperture/portal_fizzle_1.ogg | Bin 0 -> 23488 bytes .../portal_aperture/portal_fizzle_2.ogg | Bin 0 -> 18241 bytes .../portal_gun_fire_primary_1.ogg | Bin 0 -> 24949 bytes .../portal_gun_fire_primary_2.ogg | Bin 0 -> 29578 bytes .../portal_gun_fire_primary_3.ogg | Bin 0 -> 28222 bytes .../portal_gun_fire_secondary_1.ogg | Bin 0 -> 39870 bytes .../portal_gun_fire_secondary_2.ogg | Bin 0 -> 31672 bytes .../portal_gun_fire_secondary_3.ogg | Bin 0 -> 26615 bytes .../portal_invalid_surface_1.ogg | Bin 0 -> 23665 bytes .../portal_invalid_surface_2.ogg | Bin 0 -> 21731 bytes .../portal_invalid_surface_3.ogg | Bin 0 -> 12802 bytes .../portal_invalid_surface_4.ogg | Bin 0 -> 13370 bytes .../portal_aperture/portal_open_primary_1.ogg | Bin 0 -> 27822 bytes .../portal_open_secondary_1.ogg | Bin 0 -> 24406 bytes .../portal_open_secondary_2.ogg | Bin 0 -> 26902 bytes .../entity/portal_gun/portal_blue_vortex.png | Bin 0 -> 1192 bytes .../portal_gun/portal_orange_vortex.png | Bin 0 -> 1199 bytes .../textures/gui/portal_gun_crosshair.png | Bin 0 -> 341 bytes .../antarchy/textures/item/miners_dream.png | Bin 425 -> 0 bytes .../antarchy/computer/entry/brutalfly.json | 4 +- .../temple_of_technoblade/treasure.json | 2 +- .../antarchy/recipe/big_bertha_blade.json | 2 +- .../antarchy/recipe/big_bertha_handle.json | 2 +- .../data/antarchy/recipe/big_bertha_hilt.json | 2 +- .../recipe/miners_dream.json.disabled | 19 - .../tags/block/miners_dream_carvable.json | 36 - .../tags/item/big_bertha_ingredient.json | 2 +- .../weapon_attributes/blood_gods_blade.json | 39 + .../fabric/AntarchyConfigModuleFabric.java | 1 - .../antarchy/fabric/AntarchyFabricEvents.java | 2 - .../fabric/registry/AntarchyFabricItems.java | 3 - .../src/main/resources/fabric.mod.json | 3 + fabric-1.21.1/build.gradle | 18 +- .../antarchy/fabric/AntarchyFabricEvents.java | 14 +- .../client/AntarchyFabricClientBootstrap.java | 20 +- .../fabric/client/AntarchyKeyBindings.java | 37 + .../fabric/client/PortalGunClientHandler.java | 28 +- .../AntarchyFabricClientNetworking.java | 5 + .../network/AntarchyFabricNetworking.java | 18 +- .../fabric/registry/AntarchyFabricSounds.java | 3 + .../AntarchyFabricSpawnPlacements.java | 2 +- .../src/main/resources/fabric.mod.json | 5 +- .../forge/AntarchyConfigModuleForge.java | 5 - .../antarchy/forge/AntarchyToolsConfig.java | 20 - .../forge/registry/AntarchyForgeEvents.java | 6 +- .../forge/registry/AntarchyForgeItems.java | 3 - .../src/main/resources/META-INF/mods.toml | 9 + gradle.properties | 6 +- neoforge-1.21.1/build.gradle | 3 + .../AntarchyConfigModuleNeoforge.java | 14 +- .../antarchy/neoforge/AntarchyMiscConfig.java | 30 + .../neoforge/AntarchyNeoforgeEvents.java | 6 + .../neoforge/AntarchyToolsConfig.java | 20 - .../neoforge/client/AntarchyKeyBindings.java | 40 + .../client/AntarchyNeoForgeClient.java | 22 + .../client/PortalGunClientHandler.java | 29 +- .../PortalGunPortalViewRenderHandler.java | 16 +- .../network/AntarchyGravityNetworking.java | 48 +- .../registry/AntarchyNeoForgeEvents.java | 110 +- .../registry/AntarchyNeoforgeSounds.java | 3 + .../AntarchyNeoforgeSpawnPlacements.java | 2 +- .../registry/PortalGunGrabEvents.java | 25 + .../resources/META-INF/neoforge.mods.toml | 19 + settings.gradle | 2 +- 193 files changed, 6528 insertions(+), 2821 deletions(-) delete mode 100644 common-1.20.1/src/main/java/com/craisinlord/antarchy/content/item/MinersDreamCaveGenerator.java delete mode 100644 common-1.20.1/src/main/java/com/craisinlord/antarchy/content/item/MinersDreamExcavation.java delete mode 100644 common-1.20.1/src/main/java/com/craisinlord/antarchy/content/item/MinersDreamExcavationManager.java delete mode 100644 common-1.20.1/src/main/java/com/craisinlord/antarchy/content/item/MinersDreamItem.java delete mode 100644 common-1.20.1/src/main/java/com/craisinlord/antarchy/content/item/MinersDreamTorchPlacer.java delete mode 100644 common-1.20.1/src/main/resources/assets/antarchy/models/item/miners_dream.json delete mode 100644 common-1.20.1/src/main/resources/assets/antarchy/textures/item/miners_dream.png delete mode 100644 common-1.20.1/src/main/resources/data/antarchy/recipe/miners_dream.json.disabled delete mode 100644 common-1.20.1/src/main/resources/data/antarchy/recipes/miners_dream.json.disabled delete mode 100644 common-1.20.1/src/main/resources/data/antarchy/tags/block/miners_dream_carvable.json delete mode 100644 common-1.20.1/src/main/resources/data/antarchy/tags/blocks/miners_dream_carvable.json create mode 100644 common-1.21.1/src/main/java/com/craisinlord/antarchy/content/block/entity/PortalGunPortalCellAccess.java create mode 100644 common-1.21.1/src/main/java/com/craisinlord/antarchy/content/block/entity/PortalGunPortalFaceRecord.java create mode 100644 common-1.21.1/src/main/java/com/craisinlord/antarchy/content/client/PortalGunCrosshairRenderer.java create mode 100644 common-1.21.1/src/main/java/com/craisinlord/antarchy/content/client/PortalGunGrabClientState.java create mode 100644 common-1.21.1/src/main/java/com/craisinlord/antarchy/content/client/PortalGunIndicatorClientState.java create mode 100644 common-1.21.1/src/main/java/com/craisinlord/antarchy/content/client/PortalGunResetClientState.java create mode 100644 common-1.21.1/src/main/java/com/craisinlord/antarchy/content/client/PortalGunZoomClientState.java create mode 100644 common-1.21.1/src/main/java/com/craisinlord/antarchy/content/client/renderer/PortalGunPortalRendererPool.java create mode 100644 common-1.21.1/src/main/java/com/craisinlord/antarchy/content/client/renderer/PortalGunPortalViewAreaManager.java create mode 100644 common-1.21.1/src/main/java/com/craisinlord/antarchy/content/client/renderer/PortalGunProxySceneRenderer.java create mode 100644 common-1.21.1/src/main/java/com/craisinlord/antarchy/content/client/renderer/PortalSceneRenderTrace.java create mode 100644 common-1.21.1/src/main/java/com/craisinlord/antarchy/content/client/renderer/PortalStencilTarget.java create mode 100644 common-1.21.1/src/main/java/com/craisinlord/antarchy/content/client/renderer/PortalViewSectionMarker.java create mode 100644 common-1.21.1/src/main/java/com/craisinlord/antarchy/content/client/renderer/SodiumCompat.java create mode 100644 common-1.21.1/src/main/java/com/craisinlord/antarchy/content/gravity/ScgunsProjectileAccess.java delete mode 100644 common-1.21.1/src/main/java/com/craisinlord/antarchy/content/item/MinersDreamCaveGenerator.java delete mode 100644 common-1.21.1/src/main/java/com/craisinlord/antarchy/content/item/MinersDreamExcavation.java delete mode 100644 common-1.21.1/src/main/java/com/craisinlord/antarchy/content/item/MinersDreamExcavationManager.java delete mode 100644 common-1.21.1/src/main/java/com/craisinlord/antarchy/content/item/MinersDreamItem.java delete mode 100644 common-1.21.1/src/main/java/com/craisinlord/antarchy/content/item/MinersDreamTorchPlacer.java create mode 100644 common-1.21.1/src/main/java/com/craisinlord/antarchy/content/network/PortalGunGrabPayload.java create mode 100644 common-1.21.1/src/main/java/com/craisinlord/antarchy/content/network/PortalGunGrabStatePayload.java create mode 100644 common-1.21.1/src/main/java/com/craisinlord/antarchy/content/network/PortalGunIndicatorPayload.java create mode 100644 common-1.21.1/src/main/java/com/craisinlord/antarchy/content/network/PortalGunIndicatorRequestPayload.java create mode 100644 common-1.21.1/src/main/java/com/craisinlord/antarchy/content/network/PortalGunResetPayload.java create mode 100644 common-1.21.1/src/main/java/com/craisinlord/antarchy/content/portalgun/PortalGunGrabManager.java create mode 100644 common-1.21.1/src/main/java/com/craisinlord/antarchy/content/portalgun/PortalGunIndicatorSync.java create mode 100644 common-1.21.1/src/main/java/com/craisinlord/antarchy/content/portalgun/PortalGunResetManager.java create mode 100644 common-1.21.1/src/main/java/com/craisinlord/antarchy/mixins/FallingBlockEntityAccessor.java create mode 100644 common-1.21.1/src/main/java/com/craisinlord/antarchy/mixins/PortalGunZombieMixin.java create mode 100644 common-1.21.1/src/main/java/com/craisinlord/antarchy/mixins/client/CameraBasisAccessor.java create mode 100644 common-1.21.1/src/main/java/com/craisinlord/antarchy/mixins/client/LevelRendererPortalChunkBudgetMixin.java create mode 100644 common-1.21.1/src/main/java/com/craisinlord/antarchy/mixins/client/LevelRendererPortalLifecycleMixin.java create mode 100644 common-1.21.1/src/main/java/com/craisinlord/antarchy/mixins/client/LevelRendererPortalSceneInvoker.java create mode 100644 common-1.21.1/src/main/java/com/craisinlord/antarchy/mixins/client/LevelRendererPortalTargetsAccessor.java create mode 100644 common-1.21.1/src/main/java/com/craisinlord/antarchy/mixins/client/LevelRendererPortalViewAreaAccessor.java create mode 100644 common-1.21.1/src/main/java/com/craisinlord/antarchy/mixins/client/LevelRendererPortalViewAreaMixin.java create mode 100644 common-1.21.1/src/main/java/com/craisinlord/antarchy/mixins/client/LivingEntityPortalLerpAccessor.java create mode 100644 common-1.21.1/src/main/java/com/craisinlord/antarchy/mixins/client/MinecraftMainRenderTargetAccessor.java create mode 100644 common-1.21.1/src/main/java/com/craisinlord/antarchy/mixins/client/PortalGunEntityTeleportPacketMixin.java create mode 100644 common-1.21.1/src/main/java/com/craisinlord/antarchy/mixins/client/PortalGunZoomFovMixin.java create mode 100644 common-1.21.1/src/main/java/com/craisinlord/antarchy/mixins/client/PortalViewChunkCacheMixin.java create mode 100644 common-1.21.1/src/main/java/com/craisinlord/antarchy/mixins/client/PortalViewSectionSortingMixin.java create mode 100644 common-1.21.1/src/main/java/com/craisinlord/antarchy/mixins/client/PostChainTargetAccessor.java create mode 100644 common-1.21.1/src/main/java/com/craisinlord/antarchy/mixins/client/SodiumWorldRendererMixin.java create mode 100644 common-1.21.1/src/main/java/com/craisinlord/antarchy/mixins/compat/scguns/ScgunsProjectileEntityAccessMixin.java create mode 100644 common-1.21.1/src/main/resources/antarchy-sodium.mixins.json create mode 100644 common-1.21.1/src/main/resources/assets/antarchy/licenses/aperture_innovations_mit.txt delete mode 100644 common-1.21.1/src/main/resources/assets/antarchy/models/item/miners_dream.json create mode 100644 common-1.21.1/src/main/resources/assets/antarchy/shaders/core/portal_view.fsh create mode 100644 common-1.21.1/src/main/resources/assets/antarchy/shaders/core/portal_view.json create mode 100644 common-1.21.1/src/main/resources/assets/antarchy/shaders/core/portal_view.vsh create mode 100644 common-1.21.1/src/main/resources/assets/antarchy/sounds/portal_aperture/portal_ambient.ogg create mode 100644 common-1.21.1/src/main/resources/assets/antarchy/sounds/portal_aperture/portal_enter_1.ogg create mode 100644 common-1.21.1/src/main/resources/assets/antarchy/sounds/portal_aperture/portal_enter_2.ogg create mode 100644 common-1.21.1/src/main/resources/assets/antarchy/sounds/portal_aperture/portal_enter_3.ogg create mode 100644 common-1.21.1/src/main/resources/assets/antarchy/sounds/portal_aperture/portal_fizzle_1.ogg create mode 100644 common-1.21.1/src/main/resources/assets/antarchy/sounds/portal_aperture/portal_fizzle_2.ogg create mode 100644 common-1.21.1/src/main/resources/assets/antarchy/sounds/portal_aperture/portal_gun_fire_primary_1.ogg create mode 100644 common-1.21.1/src/main/resources/assets/antarchy/sounds/portal_aperture/portal_gun_fire_primary_2.ogg create mode 100644 common-1.21.1/src/main/resources/assets/antarchy/sounds/portal_aperture/portal_gun_fire_primary_3.ogg create mode 100644 common-1.21.1/src/main/resources/assets/antarchy/sounds/portal_aperture/portal_gun_fire_secondary_1.ogg create mode 100644 common-1.21.1/src/main/resources/assets/antarchy/sounds/portal_aperture/portal_gun_fire_secondary_2.ogg create mode 100644 common-1.21.1/src/main/resources/assets/antarchy/sounds/portal_aperture/portal_gun_fire_secondary_3.ogg create mode 100644 common-1.21.1/src/main/resources/assets/antarchy/sounds/portal_aperture/portal_invalid_surface_1.ogg create mode 100644 common-1.21.1/src/main/resources/assets/antarchy/sounds/portal_aperture/portal_invalid_surface_2.ogg create mode 100644 common-1.21.1/src/main/resources/assets/antarchy/sounds/portal_aperture/portal_invalid_surface_3.ogg create mode 100644 common-1.21.1/src/main/resources/assets/antarchy/sounds/portal_aperture/portal_invalid_surface_4.ogg create mode 100644 common-1.21.1/src/main/resources/assets/antarchy/sounds/portal_aperture/portal_open_primary_1.ogg create mode 100644 common-1.21.1/src/main/resources/assets/antarchy/sounds/portal_aperture/portal_open_secondary_1.ogg create mode 100644 common-1.21.1/src/main/resources/assets/antarchy/sounds/portal_aperture/portal_open_secondary_2.ogg create mode 100644 common-1.21.1/src/main/resources/assets/antarchy/textures/entity/portal_gun/portal_blue_vortex.png create mode 100644 common-1.21.1/src/main/resources/assets/antarchy/textures/entity/portal_gun/portal_orange_vortex.png create mode 100644 common-1.21.1/src/main/resources/assets/antarchy/textures/gui/portal_gun_crosshair.png delete mode 100644 common-1.21.1/src/main/resources/assets/antarchy/textures/item/miners_dream.png delete mode 100644 common-1.21.1/src/main/resources/data/antarchy/recipe/miners_dream.json.disabled delete mode 100644 common-1.21.1/src/main/resources/data/antarchy/tags/block/miners_dream_carvable.json create mode 100644 common-1.21.1/src/main/resources/data/antarchy/weapon_attributes/blood_gods_blade.json create mode 100644 neoforge-1.21.1/src/main/java/com/craisinlord/antarchy/neoforge/registry/PortalGunGrabEvents.java diff --git a/README.md b/README.md index 1ce5e6ee..4fc4c909 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ https://www.curseforge.com/minecraft/mc-mods/antarchy ANTS! ANTS! ANTS! -Antarchy requires Architectury API, AntOS, Integrated API, GeckoLib, and Sizeable Folia. +Antarchy requires Architectury API, AntOS, Integrated API, GeckoLib, and Sizeable Foliage. ## Credits diff --git a/common-1.20.1/src/main/java/com/craisinlord/antarchy/config/AntarchyConfigCatalog.java b/common-1.20.1/src/main/java/com/craisinlord/antarchy/config/AntarchyConfigCatalog.java index 1efd5ec0..cda374f1 100644 --- a/common-1.20.1/src/main/java/com/craisinlord/antarchy/config/AntarchyConfigCatalog.java +++ b/common-1.20.1/src/main/java/com/craisinlord/antarchy/config/AntarchyConfigCatalog.java @@ -110,7 +110,7 @@ public final class AntarchyConfigCatalog { || name.startsWith("nightmareSword") || name.startsWith("basiliskDagger") || name.startsWith("squidzooka") || name.equals("invertProjectilesFromInvertedPlayers") || name.startsWith("gravityGun") || name.startsWith("critterCage") - || name.startsWith("minersDream") || name.equals("potentNyxiteInvertedDurationSeconds") + || name.equals("potentNyxiteInvertedDurationSeconds") || name.equals("corneaEarNightVisionSeconds") || name.startsWith("american") || name.startsWith("mogglesVision") || name.equals("ductTapeRepairPercentPerUse") || name.startsWith("fallenKingCrown"); diff --git a/common-1.20.1/src/main/java/com/craisinlord/antarchy/config/AntarchySettings.java b/common-1.20.1/src/main/java/com/craisinlord/antarchy/config/AntarchySettings.java index d4449786..df4311b1 100644 --- a/common-1.20.1/src/main/java/com/craisinlord/antarchy/config/AntarchySettings.java +++ b/common-1.20.1/src/main/java/com/craisinlord/antarchy/config/AntarchySettings.java @@ -163,11 +163,6 @@ public final class AntarchySettings { private static double gravityGunMaxHoldDistance = 10.0D; private static double critterCageMaxCapturableWidth = 1.65D; private static double critterCageMaxCapturableHeight = 1.65D; - private static boolean minersDreamEnabled = true; - private static int minersDreamMinimumRange = 48; - private static int minersDreamMaximumRange = 96; - private static int minersDreamTorchSpacing = 10; - private static int minersDreamBlocksPerTick = 150; private static double lucidAttackRange = 9.0D; private static double lucidPearlInvertedDurationSeconds = 6.0D; private static int corneaEarNightVisionSeconds = 15; @@ -1189,26 +1184,6 @@ public final class AntarchySettings { return critterCageMaxCapturableHeight; } - public static boolean minersDreamEnabled() { - return minersDreamEnabled; - } - - public static int minersDreamMinimumRange() { - return minersDreamMinimumRange; - } - - public static int minersDreamMaximumRange() { - return minersDreamMaximumRange; - } - - public static int minersDreamTorchSpacing() { - return minersDreamTorchSpacing; - } - - public static int minersDreamBlocksPerTick() { - return minersDreamBlocksPerTick; - } - public static void setDisableInfinityBookPortalCreation(boolean value) { disableInfinityBookPortalCreation = value; } @@ -1731,26 +1706,6 @@ public final class AntarchySettings { critterCageMaxCapturableHeight = value; } - public static void setMinersDreamEnabled(boolean value) { - minersDreamEnabled = value; - } - - public static void setMinersDreamMinimumRange(int value) { - minersDreamMinimumRange = value; - } - - public static void setMinersDreamMaximumRange(int value) { - minersDreamMaximumRange = value; - } - - public static void setMinersDreamTorchSpacing(int value) { - minersDreamTorchSpacing = value; - } - - public static void setMinersDreamBlocksPerTick(int value) { - minersDreamBlocksPerTick = value; - } - public static double lucidAttackRange() { return lucidAttackRange; } diff --git a/common-1.20.1/src/main/java/com/craisinlord/antarchy/content/AntarchyTags.java b/common-1.20.1/src/main/java/com/craisinlord/antarchy/content/AntarchyTags.java index fb50d407..07474827 100644 --- a/common-1.20.1/src/main/java/com/craisinlord/antarchy/content/AntarchyTags.java +++ b/common-1.20.1/src/main/java/com/craisinlord/antarchy/content/AntarchyTags.java @@ -40,7 +40,6 @@ public final class AntarchyTags { public static final TagKey CAVARYN_PORTAL_FRAMES = TagKey.create(Registries.BLOCK, id("cavaryn_portal_frames")); public static final TagKey RAINBOW_PORTAL_FRAMES = TagKey.create(Registries.BLOCK, id("rainbow_portal_frames")); public static final TagKey ROLLY_CAVERN_CARVEABLE = TagKey.create(Registries.BLOCK, id("rolly_cavern_carveable")); - public static final TagKey MINERS_DREAM_CARVABLE = TagKey.create(Registries.BLOCK, id("miners_dream_carvable")); public static final TagKey BLUESTONE_COMPONENTS = TagKey.create(Registries.BLOCK, id("bluestone_components")); public static final TagKey BLUESTONE_CONNECTABLE = TagKey.create(Registries.BLOCK, id("bluestone_connectable")); public static final TagKey ANTIMETAL_INVERSION_BLOCKS = TagKey.create(Registries.BLOCK, id("antimetal_inversion_blocks")); diff --git a/common-1.20.1/src/main/java/com/craisinlord/antarchy/content/block/entity/UpperBlockEntity.java b/common-1.20.1/src/main/java/com/craisinlord/antarchy/content/block/entity/UpperBlockEntity.java index 1e5f62e0..6fb68c2f 100644 --- a/common-1.20.1/src/main/java/com/craisinlord/antarchy/content/block/entity/UpperBlockEntity.java +++ b/common-1.20.1/src/main/java/com/craisinlord/antarchy/content/block/entity/UpperBlockEntity.java @@ -19,6 +19,7 @@ import net.minecraft.world.entity.player.Inventory; import net.minecraft.world.inventory.AbstractContainerMenu; import net.minecraft.world.inventory.HopperMenu; import net.minecraft.world.item.ItemStack; +import net.minecraft.world.level.block.ChestBlock; import net.minecraft.world.level.block.entity.BlockEntity; import net.minecraft.world.level.block.entity.BlockEntityType; import net.minecraft.world.level.block.entity.RandomizableContainerBlockEntity; @@ -118,6 +119,10 @@ public class UpperBlockEntity extends RandomizableContainerBlockEntity { } private static Container getContainerAt(ServerLevel level, BlockPos pos) { + BlockState state = level.getBlockState(pos); + if (state.getBlock() instanceof ChestBlock chestBlock) { + return ChestBlock.getContainer(chestBlock, state, level, pos, false); + } BlockEntity blockEntity = level.getBlockEntity(pos); return blockEntity instanceof Container container ? container : null; } diff --git a/common-1.20.1/src/main/java/com/craisinlord/antarchy/content/item/MinersDreamCaveGenerator.java b/common-1.20.1/src/main/java/com/craisinlord/antarchy/content/item/MinersDreamCaveGenerator.java deleted file mode 100644 index 4aa42bac..00000000 --- a/common-1.20.1/src/main/java/com/craisinlord/antarchy/content/item/MinersDreamCaveGenerator.java +++ /dev/null @@ -1,173 +0,0 @@ -package com.craisinlord.antarchy.content.item; - -import com.craisinlord.antarchy.content.AntarchyTags; -import it.unimi.dsi.fastutil.longs.LongOpenHashSet; -import java.util.ArrayList; -import java.util.HashSet; -import java.util.List; -import java.util.Set; -import net.minecraft.core.BlockPos; -import net.minecraft.core.Direction; -import net.minecraft.server.level.ServerLevel; -import net.minecraft.util.Mth; -import net.minecraft.util.RandomSource; -import net.minecraft.world.level.block.state.BlockState; - -public final class MinersDreamCaveGenerator { - private static final double MIN_HORIZONTAL_RADIUS = 2.0D; - private static final double MAX_HORIZONTAL_RADIUS = 5.0D; - private static final double MIN_VERTICAL_RADIUS = 1.5D; - private static final double MAX_VERTICAL_RADIUS = 4.0D; - private static final double MAX_VERTICAL_DEVIATION = 8.0D; - private static final double SOFT_VERTICAL_DEVIATION = 4.0D; - private static final int WORLD_MARGIN = 3; - - private MinersDreamCaveGenerator() { - } - - public record CaveNode(BlockPos center, double horizontalRadius, double verticalRadius) { - } - - public record CaveGenerationResult(List nodes, Set torchCheckpoints) { - } - - public static CaveGenerationResult generate(BlockPos start, Direction initialDirection, int length, - int torchSpacing, int minBuildHeight, int maxBuildHeight, RandomSource random) { - List nodes = new ArrayList<>(length); - Set torchCheckpoints = new HashSet<>(); - - double x = start.getX() + 0.5D; - double y = start.getY() + 0.5D; - double z = start.getZ() + 0.5D; - double startY = y; - - double headingX = initialDirection.getStepX(); - double headingZ = initialDirection.getStepZ(); - if (headingX == 0.0D && headingZ == 0.0D) { - headingX = 1.0D; - } - - double verticalVelocity = 0.0D; - double horizontalRadius = 3.0D; - double verticalRadius = 2.0D; - int minY = minBuildHeight + WORLD_MARGIN; - int maxY = maxBuildHeight - WORLD_MARGIN; - - int spacing = Math.max(4, torchSpacing); - int traveledSinceTorch = 0; - int nextTorchDistance = spacing + random.nextInt(5) - 2; - - for (int i = 0; i < length; i++) { - BlockPos center = BlockPos.containing(x, Mth.clamp(y, minY, maxY), z); - nodes.add(new CaveNode(center, horizontalRadius, verticalRadius)); - - traveledSinceTorch++; - if (traveledSinceTorch >= Math.max(1, nextTorchDistance)) { - torchCheckpoints.add(i); - traveledSinceTorch = 0; - nextTorchDistance = spacing + random.nextInt(5) - 2; - } - - double turnDegrees = random.nextGaussian() * 8.0D; - if (random.nextFloat() < 0.1F) { - turnDegrees = random.nextGaussian() * 15.0D; - } - turnDegrees = Mth.clamp(turnDegrees, -25.0D, 25.0D); - double turnRadians = Math.toRadians(turnDegrees); - double cos = Math.cos(turnRadians); - double sin = Math.sin(turnRadians); - double newHeadingX = headingX * cos - headingZ * sin; - double newHeadingZ = headingX * sin + headingZ * cos; - double headingLength = Math.sqrt(newHeadingX * newHeadingX + newHeadingZ * newHeadingZ); - headingX = newHeadingX / headingLength; - headingZ = newHeadingZ / headingLength; - - verticalVelocity += random.nextGaussian() * 0.15D; - double deviation = y - startY; - verticalVelocity -= deviation * 0.06D; - if (deviation > SOFT_VERTICAL_DEVIATION) { - verticalVelocity -= 0.08D; - } else if (deviation < -SOFT_VERTICAL_DEVIATION) { - verticalVelocity += 0.08D; - } - verticalVelocity = Mth.clamp(verticalVelocity, -0.6D, 0.6D); - - x += headingX; - z += headingZ; - y = Mth.clamp(y + verticalVelocity, startY - MAX_VERTICAL_DEVIATION, startY + MAX_VERTICAL_DEVIATION); - - horizontalRadius = Mth.clamp(horizontalRadius + random.nextGaussian() * 0.15D, MIN_HORIZONTAL_RADIUS, MAX_HORIZONTAL_RADIUS); - verticalRadius = Mth.clamp(verticalRadius + random.nextGaussian() * 0.12D, MIN_VERTICAL_RADIUS, MAX_VERTICAL_RADIUS); - if (random.nextFloat() < 0.05F) { - horizontalRadius = Mth.clamp(horizontalRadius + 0.6D, MIN_HORIZONTAL_RADIUS, MAX_HORIZONTAL_RADIUS); - verticalRadius = Mth.clamp(verticalRadius + 0.5D, MIN_VERTICAL_RADIUS, MAX_VERTICAL_RADIUS); - } - } - - return new CaveGenerationResult(nodes, torchCheckpoints); - } - - public static void collectNodeCandidates(ServerLevel level, CaveNode node, LongOpenHashSet visited, RandomSource random, List out) { - BlockPos center = node.center(); - int hr = Mth.ceil(node.horizontalRadius()); - double upExtent = node.verticalRadius() * 1.25D; - double downExtent = node.verticalRadius() * 0.65D; - int upR = Mth.ceil(upExtent); - int downR = Mth.ceil(downExtent); - int minBuildHeight = level.getMinBuildHeight(); - int maxBuildHeight = level.getMaxBuildHeight(); - - for (int dy = -downR; dy <= upR; dy++) { - double vExtent = dy >= 0 ? upExtent : downExtent; - if (vExtent <= 0.0D) { - continue; - } - double vTerm = (dy * dy) / (vExtent * vExtent); - if (vTerm > 1.05D) { - continue; - } - - for (int dx = -hr; dx <= hr; dx++) { - double hxTerm = (dx * dx) / (node.horizontalRadius() * node.horizontalRadius()); - for (int dz = -hr; dz <= hr; dz++) { - double hzTerm = (dz * dz) / (node.horizontalRadius() * node.horizontalRadius()); - double distance = hxTerm + vTerm + hzTerm; - if (distance > 1.0D) { - continue; - } - if (distance > 0.7D) { - double edgeChance = (1.0D - distance) / 0.3D * 0.85D; - if (random.nextFloat() > edgeChance) { - continue; - } - } - - BlockPos pos = center.offset(dx, dy, dz); - if (pos.getY() < minBuildHeight || pos.getY() >= maxBuildHeight) { - continue; - } - - long packed = pos.asLong(); - if (visited.contains(packed)) { - continue; - } - - BlockState state = level.getBlockState(pos); - if (!state.is(AntarchyTags.Blocks.MINERS_DREAM_CARVABLE)) { - continue; - } - if (level.getBlockEntity(pos) != null) { - continue; - } - - visited.add(packed); - out.add(pos); - } - } - } - } - - public static boolean isChunkLoaded(ServerLevel level, BlockPos pos) { - return level.isLoaded(pos); - } -} diff --git a/common-1.20.1/src/main/java/com/craisinlord/antarchy/content/item/MinersDreamExcavation.java b/common-1.20.1/src/main/java/com/craisinlord/antarchy/content/item/MinersDreamExcavation.java deleted file mode 100644 index 6d7e01a0..00000000 --- a/common-1.20.1/src/main/java/com/craisinlord/antarchy/content/item/MinersDreamExcavation.java +++ /dev/null @@ -1,152 +0,0 @@ -package com.craisinlord.antarchy.content.item; - -import it.unimi.dsi.fastutil.longs.LongOpenHashSet; -import java.util.ArrayList; -import java.util.List; -import java.util.Set; -import java.util.UUID; -import net.minecraft.core.BlockPos; -import net.minecraft.core.particles.BlockParticleOption; -import net.minecraft.core.particles.DustParticleOptions; -import net.minecraft.core.particles.ParticleTypes; -import net.minecraft.resources.ResourceKey; -import net.minecraft.server.level.ServerLevel; -import net.minecraft.sounds.SoundEvents; -import net.minecraft.sounds.SoundSource; -import net.minecraft.util.RandomSource; -import net.minecraft.world.level.Level; -import net.minecraft.world.level.block.Blocks; -import net.minecraft.world.level.block.state.BlockState; -import org.joml.Vector3f; - -public final class MinersDreamExcavation { - private static final Vector3f TRIFFID_GOO_COLOR = new Vector3f(0.35F, 0.85F, 0.4F); - private static final int SLICES_BETWEEN_RUMBLE = 4; - - private final UUID owner; - private final ResourceKey dimension; - private final List nodes; - private final Set torchCheckpoints; - private final LongOpenHashSet visited = new LongOpenHashSet(); - - private int nodeIndex = 0; - private List currentNodePositions = null; - private int positionCursor = 0; - private int slicesSinceRumble = 0; - private boolean finished = false; - - public MinersDreamExcavation(UUID owner, ResourceKey dimension, List nodes, Set torchCheckpoints) { - this.owner = owner; - this.dimension = dimension; - this.nodes = nodes; - this.torchCheckpoints = torchCheckpoints; - } - - public UUID owner() { - return owner; - } - - public ResourceKey dimension() { - return dimension; - } - - public boolean isFinished() { - return finished; - } - - public void tick(ServerLevel level, int blockBudget) { - RandomSource random = level.getRandom(); - int remaining = blockBudget; - - while (remaining > 0 && !finished) { - if (currentNodePositions == null) { - if (nodeIndex >= nodes.size()) { - finished = true; - playCompletionSound(level); - return; - } - - MinersDreamCaveGenerator.CaveNode node = nodes.get(nodeIndex); - if (!MinersDreamCaveGenerator.isChunkLoaded(level, node.center())) { - advanceNode(level, node); - continue; - } - - currentNodePositions = new ArrayList<>(); - MinersDreamCaveGenerator.collectNodeCandidates(level, node, visited, random, currentNodePositions); - positionCursor = 0; - } - - if (currentNodePositions.isEmpty()) { - MinersDreamCaveGenerator.CaveNode node = nodes.get(nodeIndex); - advanceNode(level, node); - continue; - } - - int endIndex = Math.min(currentNodePositions.size(), positionCursor + remaining); - for (; positionCursor < endIndex; positionCursor++) { - BlockPos pos = currentNodePositions.get(positionCursor); - BlockState removedState = level.getBlockState(pos); - if (removedState.isAir()) { - continue; - } - level.setBlock(pos, Blocks.AIR.defaultBlockState(), 3); - remaining--; - if (random.nextFloat() < 0.12F) { - spawnBreakParticles(level, pos, removedState, random); - } - } - - if (positionCursor >= currentNodePositions.size()) { - MinersDreamCaveGenerator.CaveNode node = nodes.get(nodeIndex); - advanceNode(level, node); - } - } - } - - private void advanceNode(ServerLevel level, MinersDreamCaveGenerator.CaveNode node) { - RandomSource random = level.getRandom(); - spawnFrontDust(level, node, random); - slicesSinceRumble++; - if (slicesSinceRumble >= SLICES_BETWEEN_RUMBLE) { - slicesSinceRumble = 0; - playRumble(level, node); - } - - if (torchCheckpoints.contains(nodeIndex)) { - MinersDreamTorchPlacer.tryPlaceTorch(level, node.center(), random); - } - - currentNodePositions = null; - positionCursor = 0; - nodeIndex++; - } - - private void spawnBreakParticles(ServerLevel level, BlockPos pos, BlockState state, RandomSource random) { - level.sendParticles(new BlockParticleOption(ParticleTypes.BLOCK, state), - pos.getX() + 0.5D, pos.getY() + 0.5D, pos.getZ() + 0.5D, 2, 0.25D, 0.25D, 0.25D, 0.02D); - if (random.nextFloat() < 0.2F) { - level.sendParticles(new DustParticleOptions(TRIFFID_GOO_COLOR, 1.0F), - pos.getX() + 0.5D, pos.getY() + 0.5D, pos.getZ() + 0.5D, 1, 0.2D, 0.2D, 0.2D, 0.0D); - } - } - - private void spawnFrontDust(ServerLevel level, MinersDreamCaveGenerator.CaveNode node, RandomSource random) { - BlockPos center = node.center(); - level.sendParticles(ParticleTypes.CLOUD, center.getX() + 0.5D, center.getY() + 0.5D, center.getZ() + 0.5D, - 3, node.horizontalRadius() * 0.4D, node.verticalRadius() * 0.3D, node.horizontalRadius() * 0.4D, 0.01D); - } - - private void playRumble(ServerLevel level, MinersDreamCaveGenerator.CaveNode node) { - BlockPos center = node.center(); - level.playSound(null, center, SoundEvents.STONE_BREAK, SoundSource.BLOCKS, 0.5F, 0.7F); - } - - private void playCompletionSound(ServerLevel level) { - if (nodes.isEmpty()) { - return; - } - BlockPos center = nodes.get(nodes.size() - 1).center(); - level.playSound(null, center, SoundEvents.BEACON_ACTIVATE, SoundSource.PLAYERS, 0.4F, 1.4F); - } -} diff --git a/common-1.20.1/src/main/java/com/craisinlord/antarchy/content/item/MinersDreamExcavationManager.java b/common-1.20.1/src/main/java/com/craisinlord/antarchy/content/item/MinersDreamExcavationManager.java deleted file mode 100644 index 4effa987..00000000 --- a/common-1.20.1/src/main/java/com/craisinlord/antarchy/content/item/MinersDreamExcavationManager.java +++ /dev/null @@ -1,81 +0,0 @@ -package com.craisinlord.antarchy.content.item; - -import com.craisinlord.antarchy.config.AntarchySettings; -import java.util.Iterator; -import java.util.Map; -import java.util.UUID; -import java.util.concurrent.ConcurrentHashMap; -import net.minecraft.core.BlockPos; -import net.minecraft.core.Direction; -import net.minecraft.server.level.ServerLevel; -import net.minecraft.util.Mth; -import net.minecraft.util.RandomSource; - -public final class MinersDreamExcavationManager { - private static final int MAX_ACTIVE_TASKS = 8; - private static final int SAFE_MIN_RANGE = 8; - private static final int SAFE_MAX_RANGE = 256; - private static final Map ACTIVE = new ConcurrentHashMap<>(); - - private MinersDreamExcavationManager() { - } - - public static boolean hasActiveExcavation(UUID owner) { - return ACTIVE.containsKey(owner); - } - - public static MinersDreamExcavation tryStart(ServerLevel level, UUID owner, BlockPos startPos, Direction direction) { - if (ACTIVE.containsKey(owner) || ACTIVE.size() >= MAX_ACTIVE_TASKS) { - return null; - } - - int minRange = Mth.clamp(AntarchySettings.minersDreamMinimumRange(), SAFE_MIN_RANGE, SAFE_MAX_RANGE); - int maxRange = Mth.clamp(AntarchySettings.minersDreamMaximumRange(), SAFE_MIN_RANGE, SAFE_MAX_RANGE); - if (minRange > maxRange) { - int swap = minRange; - minRange = maxRange; - maxRange = swap; - } - - RandomSource random = level.getRandom(); - int length = minRange == maxRange ? minRange : minRange + random.nextInt(maxRange - minRange + 1); - - MinersDreamCaveGenerator.CaveGenerationResult result = MinersDreamCaveGenerator.generate( - startPos, - direction, - length, - AntarchySettings.minersDreamTorchSpacing(), - level.getMinBuildHeight(), - level.getMaxBuildHeight(), - random - ); - - if (result.nodes().isEmpty()) { - return null; - } - - MinersDreamExcavation excavation = new MinersDreamExcavation(owner, level.dimension(), result.nodes(), result.torchCheckpoints()); - ACTIVE.put(owner, excavation); - return excavation; - } - - public static void tick(ServerLevel level) { - if (ACTIVE.isEmpty()) { - return; - } - - int budget = Math.max(1, AntarchySettings.minersDreamBlocksPerTick()); - Iterator> iterator = ACTIVE.entrySet().iterator(); - while (iterator.hasNext()) { - MinersDreamExcavation excavation = iterator.next().getValue(); - if (!excavation.dimension().equals(level.dimension())) { - continue; - } - - excavation.tick(level, budget); - if (excavation.isFinished()) { - iterator.remove(); - } - } - } -} diff --git a/common-1.20.1/src/main/java/com/craisinlord/antarchy/content/item/MinersDreamItem.java b/common-1.20.1/src/main/java/com/craisinlord/antarchy/content/item/MinersDreamItem.java deleted file mode 100644 index d736aeea..00000000 --- a/common-1.20.1/src/main/java/com/craisinlord/antarchy/content/item/MinersDreamItem.java +++ /dev/null @@ -1,79 +0,0 @@ -package com.craisinlord.antarchy.content.item; - -import com.craisinlord.antarchy.config.AntarchySettings; -import java.util.List; -import net.minecraft.ChatFormatting; -import net.minecraft.core.BlockPos; -import net.minecraft.core.Direction; -import net.minecraft.network.chat.Component; -import net.minecraft.server.level.ServerLevel; -import net.minecraft.sounds.SoundEvents; -import net.minecraft.sounds.SoundSource; -import net.minecraft.world.InteractionResult; -import net.minecraft.world.entity.player.Player; -import net.minecraft.world.item.Item; -import net.minecraft.world.item.ItemStack; -import net.minecraft.world.item.TooltipFlag; -import net.minecraft.world.item.context.UseOnContext; -import net.minecraft.world.level.Level; - -public final class MinersDreamItem extends Item { - public MinersDreamItem(Properties properties) { - super(properties); - } - - @Override - public InteractionResult useOn(UseOnContext context) { - Level level = context.getLevel(); - Player player = context.getPlayer(); - if (player == null) { - return InteractionResult.PASS; - } - - if (!AntarchySettings.minersDreamEnabled()) { - if (!level.isClientSide()) { - player.displayClientMessage(Component.translatable("message.antarchy.miners_dream_disabled"), true); - } - return InteractionResult.FAIL; - } - - if (level.isClientSide()) { - return InteractionResult.SUCCESS; - } - - ServerLevel serverLevel = (ServerLevel) level; - BlockPos clickedPos = context.getClickedPos(); - Direction clickedFace = context.getClickedFace(); - Direction travelDirection = clickedFace.getAxis().isHorizontal() ? clickedFace : player.getDirection(); - BlockPos startPos = clickedPos.relative(travelDirection); - - if (!serverLevel.isInWorldBounds(startPos) || !MinersDreamCaveGenerator.isChunkLoaded(serverLevel, startPos)) { - return InteractionResult.FAIL; - } - - if (MinersDreamExcavationManager.hasActiveExcavation(player.getUUID())) { - player.displayClientMessage(Component.translatable("message.antarchy.miners_dream_busy"), true); - return InteractionResult.FAIL; - } - - MinersDreamExcavation excavation = MinersDreamExcavationManager.tryStart(serverLevel, player.getUUID(), startPos, travelDirection); - if (excavation == null) { - return InteractionResult.FAIL; - } - - serverLevel.playSound(null, clickedPos, SoundEvents.TNT_PRIMED, SoundSource.PLAYERS, 0.8F, 1.1F); - - if (!player.getAbilities().instabuild) { - context.getItemInHand().shrink(1); - } - - return InteractionResult.CONSUME; - } - - @Override - public void appendHoverText(ItemStack stack, Level level, List tooltipComponents, TooltipFlag tooltipFlag) { - tooltipComponents.add(Component.translatable("tooltip.antarchy.miners_dream.line1").withStyle(ChatFormatting.GRAY)); - tooltipComponents.add(Component.translatable("tooltip.antarchy.miners_dream.line2").withStyle(ChatFormatting.GRAY)); - super.appendHoverText(stack, level, tooltipComponents, tooltipFlag); - } -} diff --git a/common-1.20.1/src/main/java/com/craisinlord/antarchy/content/item/MinersDreamTorchPlacer.java b/common-1.20.1/src/main/java/com/craisinlord/antarchy/content/item/MinersDreamTorchPlacer.java deleted file mode 100644 index 08e8f78b..00000000 --- a/common-1.20.1/src/main/java/com/craisinlord/antarchy/content/item/MinersDreamTorchPlacer.java +++ /dev/null @@ -1,89 +0,0 @@ -package com.craisinlord.antarchy.content.item; - -import java.util.List; -import net.minecraft.core.BlockPos; -import net.minecraft.core.Direction; -import net.minecraft.server.level.ServerLevel; -import net.minecraft.sounds.SoundSource; -import net.minecraft.util.RandomSource; -import net.minecraft.world.level.block.Blocks; -import net.minecraft.world.level.block.state.BlockState; -import net.minecraft.world.level.block.state.properties.BlockStateProperties; - -public final class MinersDreamTorchPlacer { - private MinersDreamTorchPlacer() { - } - - public static void tryPlaceTorch(ServerLevel level, BlockPos center, RandomSource random) { - BlockPos floorSpot = findFloorSpot(level, center); - boolean floorFirst = random.nextBoolean(); - - if (floorFirst) { - if (floorSpot != null && placeFloorTorch(level, floorSpot)) { - return; - } - placeWallTorch(level, center, random); - return; - } - - if (placeWallTorch(level, center, random)) { - return; - } - if (floorSpot != null) { - placeFloorTorch(level, floorSpot); - } - } - - private static BlockPos findFloorSpot(ServerLevel level, BlockPos center) { - for (int dy = -2; dy <= 1; dy++) { - BlockPos candidate = center.offset(0, dy, 0); - if (!isOpenForTorch(level, candidate)) { - continue; - } - BlockState below = level.getBlockState(candidate.below()); - if (below.isFaceSturdy(level, candidate.below(), Direction.UP)) { - return candidate; - } - } - return null; - } - - private static boolean placeFloorTorch(ServerLevel level, BlockPos pos) { - if (!isOpenForTorch(level, pos)) { - return false; - } - BlockState state = Blocks.TORCH.defaultBlockState(); - if (!state.canSurvive(level, pos)) { - return false; - } - level.setBlock(pos, state, 3); - level.playSound(null, pos, state.getSoundType().getPlaceSound(), SoundSource.BLOCKS, 1.0F, 1.0F); - return true; - } - - private static boolean placeWallTorch(ServerLevel level, BlockPos center, RandomSource random) { - List order = Direction.Plane.HORIZONTAL.shuffledCopy(random); - for (int dy = -1; dy <= 1; dy++) { - BlockPos candidate = center.offset(0, dy, 0); - if (!isOpenForTorch(level, candidate)) { - continue; - } - for (Direction wallSide : order) { - Direction facing = wallSide.getOpposite(); - BlockState state = Blocks.WALL_TORCH.defaultBlockState().setValue(BlockStateProperties.HORIZONTAL_FACING, facing); - if (!state.canSurvive(level, candidate)) { - continue; - } - level.setBlock(candidate, state, 3); - level.playSound(null, candidate, state.getSoundType().getPlaceSound(), SoundSource.BLOCKS, 1.0F, 1.0F); - return true; - } - } - return false; - } - - private static boolean isOpenForTorch(ServerLevel level, BlockPos pos) { - BlockState state = level.getBlockState(pos); - return (state.isAir() || state.canBeReplaced()) && level.getFluidState(pos).isEmpty(); - } -} diff --git a/common-1.20.1/src/main/resources/assets/antarchy/lang/de_de.json b/common-1.20.1/src/main/resources/assets/antarchy/lang/de_de.json index 9eecf3e6..737410dc 100644 --- a/common-1.20.1/src/main/resources/assets/antarchy/lang/de_de.json +++ b/common-1.20.1/src/main/resources/assets/antarchy/lang/de_de.json @@ -411,11 +411,6 @@ "tooltip.antarchy.reverie_bottle.capture": "Rechtsklicke mit einer Glasflasche auf eine Reverie, um sie einzufangen.", "tooltip.antarchy.reverie_bottle.release": "Rechtsklick, um die Reverie freizulassen.", "item.antarchy.gravity_gun": "Gravitationskanone", - "item.antarchy.miners_dream": "Traum des Bergmanns", - "tooltip.antarchy.miners_dream.line1": "Rechtsklick, um eine gewundene Höhle zu graben.", - "tooltip.antarchy.miners_dream.line2": "Lässt wertvolle Blöcke unberührt.", - "message.antarchy.miners_dream_disabled": "Der Traum des Bergmanns ist deaktiviert.", - "message.antarchy.miners_dream_busy": "Dein vorheriger Traum entfaltet sich noch.", "item.antarchy.diamond_minecart": "Die Diamantlore", "item.antarchy.cogwheel_pet": "Zahnrad-Haustier", "item.antarchy.fire_dragon_pet": "Feuerdrachen-Haustier", diff --git a/common-1.20.1/src/main/resources/assets/antarchy/lang/en_us.json b/common-1.20.1/src/main/resources/assets/antarchy/lang/en_us.json index 0ba1bb83..7b1c83d4 100644 --- a/common-1.20.1/src/main/resources/assets/antarchy/lang/en_us.json +++ b/common-1.20.1/src/main/resources/assets/antarchy/lang/en_us.json @@ -632,11 +632,6 @@ "tooltip.antarchy.reverie_bottle.release": "Right click to release the Reverie.", "item.antarchy.gravity_gun": "Gravity Gun", "item.antarchy.portal_gun": "Portal Gun", - "item.antarchy.miners_dream": "Miner's Dream", - "tooltip.antarchy.miners_dream.line1": "Use to carve a winding cave.", - "tooltip.antarchy.miners_dream.line2": "Leaves valuable blocks untouched.", - "message.antarchy.miners_dream_disabled": "The Miner's Dream is disabled.", - "message.antarchy.miners_dream_busy": "Your previous dream is still unfolding.", "item.antarchy.diamond_minecart": "The Diamond Minecart", "item.antarchy.cogwheel_pet": "Cogwheel Pet", "item.antarchy.fire_dragon_pet": "Fire Dragon Pet", diff --git a/common-1.20.1/src/main/resources/assets/antarchy/lang/es_es.json b/common-1.20.1/src/main/resources/assets/antarchy/lang/es_es.json index dd76c4b6..34305034 100644 --- a/common-1.20.1/src/main/resources/assets/antarchy/lang/es_es.json +++ b/common-1.20.1/src/main/resources/assets/antarchy/lang/es_es.json @@ -411,11 +411,6 @@ "tooltip.antarchy.reverie_bottle.capture": "Haz clic derecho en una Reverie con una botella de cristal para guardarla.", "tooltip.antarchy.reverie_bottle.release": "Clic derecho para liberar a la Reverie.", "item.antarchy.gravity_gun": "Pistola gravitatoria", - "item.antarchy.miners_dream": "Sueño del minero", - "tooltip.antarchy.miners_dream.line1": "Clic derecho para excavar una cueva sinuosa.", - "tooltip.antarchy.miners_dream.line2": "Deja intactos los bloques valiosos.", - "message.antarchy.miners_dream_disabled": "El Sueño del minero está desactivado.", - "message.antarchy.miners_dream_busy": "Tu sueño anterior todavía se está desarrollando.", "item.antarchy.diamond_minecart": "La vagoneta de diamante", "item.antarchy.cogwheel_pet": "Mascota engranaje", "item.antarchy.fire_dragon_pet": "Mascota de dragón de fuego", diff --git a/common-1.20.1/src/main/resources/assets/antarchy/lang/fr_fr.json b/common-1.20.1/src/main/resources/assets/antarchy/lang/fr_fr.json index 1bb5b8ef..0a7b39e0 100644 --- a/common-1.20.1/src/main/resources/assets/antarchy/lang/fr_fr.json +++ b/common-1.20.1/src/main/resources/assets/antarchy/lang/fr_fr.json @@ -411,11 +411,6 @@ "tooltip.antarchy.reverie_bottle.capture": "Faites un clic droit sur une Reverie avec une fiole en verre pour la capturer.", "tooltip.antarchy.reverie_bottle.release": "Clic droit pour libérer la Reverie.", "item.antarchy.gravity_gun": "Canon à gravité", - "item.antarchy.miners_dream": "Rêve du mineur", - "tooltip.antarchy.miners_dream.line1": "Clic droit pour creuser une grotte sinueuse.", - "tooltip.antarchy.miners_dream.line2": "Laisse les blocs précieux intacts.", - "message.antarchy.miners_dream_disabled": "Le Rêve du mineur est désactivé.", - "message.antarchy.miners_dream_busy": "Votre rêve précédent est encore en train de se déployer.", "item.antarchy.diamond_minecart": "Le wagonnet de diamant", "item.antarchy.cogwheel_pet": "Familier engrenage", "item.antarchy.fire_dragon_pet": "Familier dragon de feu", diff --git a/common-1.20.1/src/main/resources/assets/antarchy/lang/it_it.json b/common-1.20.1/src/main/resources/assets/antarchy/lang/it_it.json index e5ceab2e..69fa31ce 100644 --- a/common-1.20.1/src/main/resources/assets/antarchy/lang/it_it.json +++ b/common-1.20.1/src/main/resources/assets/antarchy/lang/it_it.json @@ -411,11 +411,6 @@ "tooltip.antarchy.reverie_bottle.capture": "Fai clic destro su una Reverie con una bottiglia di vetro per catturarla.", "tooltip.antarchy.reverie_bottle.release": "Fai clic destro per liberare la Reverie.", "item.antarchy.gravity_gun": "Cannone gravitazionale", - "item.antarchy.miners_dream": "Sogno del minatore", - "tooltip.antarchy.miners_dream.line1": "Fai clic destro per scavare una grotta tortuosa.", - "tooltip.antarchy.miners_dream.line2": "Lascia intatti i blocchi preziosi.", - "message.antarchy.miners_dream_disabled": "Il Sogno del minatore è disattivato.", - "message.antarchy.miners_dream_busy": "Il tuo sogno precedente si sta ancora dispiegando.", "item.antarchy.diamond_minecart": "Il carrello di diamante", "item.antarchy.cogwheel_pet": "Famiglio ingranaggio", "item.antarchy.fire_dragon_pet": "Famiglio drago di fuoco", diff --git a/common-1.20.1/src/main/resources/assets/antarchy/lang/ja_jp.json b/common-1.20.1/src/main/resources/assets/antarchy/lang/ja_jp.json index 664b7767..1d17dbc3 100644 --- a/common-1.20.1/src/main/resources/assets/antarchy/lang/ja_jp.json +++ b/common-1.20.1/src/main/resources/assets/antarchy/lang/ja_jp.json @@ -411,11 +411,6 @@ "tooltip.antarchy.reverie_bottle.capture": "ガラス瓶を持ってレヴェリーを右クリックすると収納できます。", "tooltip.antarchy.reverie_bottle.release": "右クリックでレヴェリーを解放します。", "item.antarchy.gravity_gun": "重力銃", - "item.antarchy.miners_dream": "採掘者の夢", - "tooltip.antarchy.miners_dream.line1": "右クリックで曲がりくねった洞窟を掘ります。", - "tooltip.antarchy.miners_dream.line2": "貴重なブロックはそのまま残します。", - "message.antarchy.miners_dream_disabled": "採掘者の夢は無効になっています。", - "message.antarchy.miners_dream_busy": "前の夢はまだ展開中です。", "item.antarchy.diamond_minecart": "ダイヤモンドのトロッコ", "item.antarchy.cogwheel_pet": "歯車ペット", "item.antarchy.fire_dragon_pet": "ファイアドラゴンのペット", diff --git a/common-1.20.1/src/main/resources/assets/antarchy/lang/ko_kr.json b/common-1.20.1/src/main/resources/assets/antarchy/lang/ko_kr.json index 4d74cf03..7fc4ee1e 100644 --- a/common-1.20.1/src/main/resources/assets/antarchy/lang/ko_kr.json +++ b/common-1.20.1/src/main/resources/assets/antarchy/lang/ko_kr.json @@ -411,11 +411,6 @@ "tooltip.antarchy.reverie_bottle.capture": "유리병으로 레버리를 우클릭하여 담습니다.", "tooltip.antarchy.reverie_bottle.release": "우클릭하여 레버리를 풀어줍니다.", "item.antarchy.gravity_gun": "중력총", - "item.antarchy.miners_dream": "광부의 꿈", - "tooltip.antarchy.miners_dream.line1": "우클릭하여 구불구불한 동굴을 뚫습니다.", - "tooltip.antarchy.miners_dream.line2": "귀중한 블록은 건드리지 않습니다.", - "message.antarchy.miners_dream_disabled": "광부의 꿈이 비활성화되어 있습니다.", - "message.antarchy.miners_dream_busy": "이전 꿈이 아직 펼쳐지고 있습니다.", "item.antarchy.diamond_minecart": "다이아몬드 광산 수레", "item.antarchy.cogwheel_pet": "톱니바퀴 펫", "item.antarchy.fire_dragon_pet": "화염 드래곤 펫", diff --git a/common-1.20.1/src/main/resources/assets/antarchy/lang/pt_br.json b/common-1.20.1/src/main/resources/assets/antarchy/lang/pt_br.json index 55cf70c9..3ae07a99 100644 --- a/common-1.20.1/src/main/resources/assets/antarchy/lang/pt_br.json +++ b/common-1.20.1/src/main/resources/assets/antarchy/lang/pt_br.json @@ -411,11 +411,6 @@ "tooltip.antarchy.reverie_bottle.capture": "Clique com o botão direito em uma Reverie com um frasco de vidro para guardá-la.", "tooltip.antarchy.reverie_bottle.release": "Clique com o botão direito para soltar a Reverie.", "item.antarchy.gravity_gun": "Arma Gravitacional", - "item.antarchy.miners_dream": "Sonho do Minerador", - "tooltip.antarchy.miners_dream.line1": "Clique com o botão direito para escavar uma caverna sinuosa.", - "tooltip.antarchy.miners_dream.line2": "Deixa blocos valiosos intactos.", - "message.antarchy.miners_dream_disabled": "O Sonho do Minerador está desativado.", - "message.antarchy.miners_dream_busy": "Seu sonho anterior ainda está se desenrolando.", "item.antarchy.diamond_minecart": "O Carrinho de Diamante", "item.antarchy.cogwheel_pet": "Mascote de Engrenagem", "item.antarchy.fire_dragon_pet": "Mascote de Dragão de Fogo", diff --git a/common-1.20.1/src/main/resources/assets/antarchy/lang/ru_ru.json b/common-1.20.1/src/main/resources/assets/antarchy/lang/ru_ru.json index 1bc3167c..fb7a6071 100644 --- a/common-1.20.1/src/main/resources/assets/antarchy/lang/ru_ru.json +++ b/common-1.20.1/src/main/resources/assets/antarchy/lang/ru_ru.json @@ -387,11 +387,6 @@ "tooltip.antarchy.reverie_bottle.capture": "Нажмите ПКМ стеклянной бутылочкой по грёзе, чтобы поймать её.", "tooltip.antarchy.reverie_bottle.release": "Нажмите ПКМ, чтобы выпустить грёзу.", "item.antarchy.gravity_gun": "Гравитационная пушка", - "item.antarchy.miners_dream": "Мечта шахтёра", - "tooltip.antarchy.miners_dream.line1": "Нажмите ПКМ, чтобы вырезать извилистую пещеру.", - "tooltip.antarchy.miners_dream.line2": "Оставляет ценные блоки нетронутыми.", - "message.antarchy.miners_dream_disabled": "Мечта шахтёра отключена.", - "message.antarchy.miners_dream_busy": "Ваша предыдущая мечта всё ещё воплощается.", "item.antarchy.diamond_minecart": "Алмазная вагонетка", "item.antarchy.cogwheel_pet": "Шестерёнка-питомец", "item.antarchy.fire_dragon_pet": "Огненный дракон-питомец", diff --git a/common-1.20.1/src/main/resources/assets/antarchy/lang/zh_cn.json b/common-1.20.1/src/main/resources/assets/antarchy/lang/zh_cn.json index bde52c13..e3acfc40 100644 --- a/common-1.20.1/src/main/resources/assets/antarchy/lang/zh_cn.json +++ b/common-1.20.1/src/main/resources/assets/antarchy/lang/zh_cn.json @@ -411,11 +411,6 @@ "tooltip.antarchy.reverie_bottle.capture": "手持玻璃瓶右键雷弗里即可将其收纳。", "tooltip.antarchy.reverie_bottle.release": "右键释放雷弗里。", "item.antarchy.gravity_gun": "重力枪", - "item.antarchy.miners_dream": "矿工之梦", - "tooltip.antarchy.miners_dream.line1": "右键挖出一条蜿蜒洞穴。", - "tooltip.antarchy.miners_dream.line2": "不会破坏贵重方块。", - "message.antarchy.miners_dream_disabled": "矿工之梦已禁用。", - "message.antarchy.miners_dream_busy": "你之前的梦仍在展开。", "item.antarchy.diamond_minecart": "钻石矿车", "item.antarchy.cogwheel_pet": "齿轮宠物", "item.antarchy.fire_dragon_pet": "火龙宠物", diff --git a/common-1.20.1/src/main/resources/assets/antarchy/lang/zh_tw.json b/common-1.20.1/src/main/resources/assets/antarchy/lang/zh_tw.json index 2c3a6435..971be7e9 100644 --- a/common-1.20.1/src/main/resources/assets/antarchy/lang/zh_tw.json +++ b/common-1.20.1/src/main/resources/assets/antarchy/lang/zh_tw.json @@ -411,11 +411,6 @@ "tooltip.antarchy.reverie_bottle.capture": "手持玻璃瓶右鍵雷弗裡即可將其收納。", "tooltip.antarchy.reverie_bottle.release": "右鍵釋放雷弗裡。", "item.antarchy.gravity_gun": "重力槍", - "item.antarchy.miners_dream": "礦工之夢", - "tooltip.antarchy.miners_dream.line1": "右鍵挖出一條蜿蜒洞穴。", - "tooltip.antarchy.miners_dream.line2": "不會破壞貴重方塊。", - "message.antarchy.miners_dream_disabled": "礦工之夢已禁用。", - "message.antarchy.miners_dream_busy": "你之前的夢仍在展開。", "item.antarchy.diamond_minecart": "鑽石礦車", "item.antarchy.cogwheel_pet": "齒輪寵物", "item.antarchy.fire_dragon_pet": "火龍寵物", diff --git a/common-1.20.1/src/main/resources/assets/antarchy/models/item/miners_dream.json b/common-1.20.1/src/main/resources/assets/antarchy/models/item/miners_dream.json deleted file mode 100644 index c7a0f166..00000000 --- a/common-1.20.1/src/main/resources/assets/antarchy/models/item/miners_dream.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "parent": "minecraft:item/generated", - "textures": { - "layer0": "antarchy:item/miners_dream" - } -} diff --git a/common-1.20.1/src/main/resources/assets/antarchy/textures/item/miners_dream.png b/common-1.20.1/src/main/resources/assets/antarchy/textures/item/miners_dream.png deleted file mode 100644 index d8371b7bf30b5fda9ce81b5fc1864e5bbbbc19f8..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 425 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!63?wyl`GbKJV{wqX6T`Z5GB1G~mUKs7M+SzC z{oH>NS%Li60G|+7qdIyEN zISXjRt$ER%KuW$O$S?RmUchixvJB*U&H|6fVg?3oVGw3ym^DWNC@4|l8c`CQpH@G`{S%?doM4-T<(-_TT0n*8x! zf94Ov$Z2cE&syFO<^GxdK%ZgD4YT9}KNqKI^zu4!Xs&ihQ`RzkIdfj9!sb***DbHq zSbL5-icZ|qyzzxuRQjWR&4twgOY$vxZZm1kl6qpX%T}VhD~`8_=b=G*RrUMdOF#N( i9{W-F>uz0bHDhmx$h0#rOFsc!#Ng@b=d#Wzp$P!~ait~z diff --git a/common-1.20.1/src/main/resources/data/antarchy/recipe/miners_dream.json.disabled b/common-1.20.1/src/main/resources/data/antarchy/recipe/miners_dream.json.disabled deleted file mode 100644 index b35b1c80..00000000 --- a/common-1.20.1/src/main/resources/data/antarchy/recipe/miners_dream.json.disabled +++ /dev/null @@ -1,19 +0,0 @@ -{ - "type": "minecraft:crafting_shaped", - "category": "misc", - "pattern": [ - "GGG", - "RDR", - "TTT" - ], - "key": { - "G": { "item": "antarchy:triffid_goo" }, - "R": { "item": "minecraft:redstone_block" }, - "D": { "item": "minecraft:diamond_pickaxe" }, - "T": { "item": "minecraft:tnt" } - }, - "result": { - "id": "antarchy:miners_dream", - "count": 1 - } -} diff --git a/common-1.20.1/src/main/resources/data/antarchy/recipes/miners_dream.json.disabled b/common-1.20.1/src/main/resources/data/antarchy/recipes/miners_dream.json.disabled deleted file mode 100644 index 0fba3562..00000000 --- a/common-1.20.1/src/main/resources/data/antarchy/recipes/miners_dream.json.disabled +++ /dev/null @@ -1,19 +0,0 @@ -{ - "type": "minecraft:crafting_shaped", - "category": "misc", - "pattern": [ - "GGG", - "RDR", - "TTT" - ], - "key": { - "G": { "item": "antarchy:triffid_goo" }, - "R": { "item": "minecraft:redstone_block" }, - "D": { "item": "minecraft:diamond_pickaxe" }, - "T": { "item": "minecraft:tnt" } - }, - "result": { - "item": "antarchy:miners_dream", - "count": 1 - } -} diff --git a/common-1.20.1/src/main/resources/data/antarchy/tags/block/miners_dream_carvable.json b/common-1.20.1/src/main/resources/data/antarchy/tags/block/miners_dream_carvable.json deleted file mode 100644 index 6f7d26ee..00000000 --- a/common-1.20.1/src/main/resources/data/antarchy/tags/block/miners_dream_carvable.json +++ /dev/null @@ -1,36 +0,0 @@ -{ - "replace": false, - "values": [ - "#minecraft:base_stone_overworld", - "#minecraft:base_stone_nether", - "minecraft:stone", - "minecraft:deepslate", - "minecraft:granite", - "minecraft:diorite", - "minecraft:andesite", - "minecraft:tuff", - "minecraft:calcite", - "minecraft:netherrack", - "minecraft:blackstone", - "minecraft:basalt", - "minecraft:smooth_basalt", - "minecraft:end_stone", - "minecraft:dirt", - "minecraft:coarse_dirt", - "minecraft:rooted_dirt", - "minecraft:podzol", - "minecraft:mycelium", - "minecraft:gravel", - "minecraft:sand", - "minecraft:red_sand", - "minecraft:sandstone", - "minecraft:red_sandstone", - "minecraft:clay", - "minecraft:soul_sand", - "minecraft:soul_soil", - "antarchy:nyxite", - "antarchy:pale_nyxite", - "antarchy:dream_sand", - "antarchy:dream_sandstone" - ] -} diff --git a/common-1.20.1/src/main/resources/data/antarchy/tags/blocks/miners_dream_carvable.json b/common-1.20.1/src/main/resources/data/antarchy/tags/blocks/miners_dream_carvable.json deleted file mode 100644 index 6f7d26ee..00000000 --- a/common-1.20.1/src/main/resources/data/antarchy/tags/blocks/miners_dream_carvable.json +++ /dev/null @@ -1,36 +0,0 @@ -{ - "replace": false, - "values": [ - "#minecraft:base_stone_overworld", - "#minecraft:base_stone_nether", - "minecraft:stone", - "minecraft:deepslate", - "minecraft:granite", - "minecraft:diorite", - "minecraft:andesite", - "minecraft:tuff", - "minecraft:calcite", - "minecraft:netherrack", - "minecraft:blackstone", - "minecraft:basalt", - "minecraft:smooth_basalt", - "minecraft:end_stone", - "minecraft:dirt", - "minecraft:coarse_dirt", - "minecraft:rooted_dirt", - "minecraft:podzol", - "minecraft:mycelium", - "minecraft:gravel", - "minecraft:sand", - "minecraft:red_sand", - "minecraft:sandstone", - "minecraft:red_sandstone", - "minecraft:clay", - "minecraft:soul_sand", - "minecraft:soul_soil", - "antarchy:nyxite", - "antarchy:pale_nyxite", - "antarchy:dream_sand", - "antarchy:dream_sandstone" - ] -} diff --git a/common-1.21.1/src/main/java/com/craisinlord/antarchy/config/AntarchyConfigCatalog.java b/common-1.21.1/src/main/java/com/craisinlord/antarchy/config/AntarchyConfigCatalog.java index 123e04b1..45def0a3 100644 --- a/common-1.21.1/src/main/java/com/craisinlord/antarchy/config/AntarchyConfigCatalog.java +++ b/common-1.21.1/src/main/java/com/craisinlord/antarchy/config/AntarchyConfigCatalog.java @@ -137,6 +137,14 @@ public final class AntarchyConfigCatalog { || name.equals("elythiaPortalEnabled") || name.equals("thoraxisPortalEnabled") || name.equals("cavarynPortalEnabled") + || name.startsWith("portalGunCanFireThrough") + || name.startsWith("portalGunSeeThrough") + || name.startsWith("portalGunFancy") + || name.equals("portalGunCanPortalsResizeWhenCreated") + || name.equals("portalGunCanPortalProjectilesChunkload") + || name.equals("portalGunMaxShootDistance") + || name.equals("portalGunEntityGrabWeightBase") + || name.equals("portalGunIndicatorSize") || name.equals("hushweedSporeLifetimeSeconds") || name.equals("elythiaFireflyParticlesEnabled") || name.equals("duplicatorTreeEnabled") @@ -177,7 +185,6 @@ public final class AntarchyConfigCatalog { || name.equals("invertProjectilesFromInvertedPlayers") || name.startsWith("gravityGun") || name.startsWith("critterCage") - || name.startsWith("minersDream") || name.equals("potentNyxiteInvertedDurationSeconds") || name.equals("corneaEarNightVisionSeconds") || name.startsWith("american") diff --git a/common-1.21.1/src/main/java/com/craisinlord/antarchy/config/AntarchySettings.java b/common-1.21.1/src/main/java/com/craisinlord/antarchy/config/AntarchySettings.java index 6ac2de62..b8207c6d 100644 --- a/common-1.21.1/src/main/java/com/craisinlord/antarchy/config/AntarchySettings.java +++ b/common-1.21.1/src/main/java/com/craisinlord/antarchy/config/AntarchySettings.java @@ -167,11 +167,6 @@ public final class AntarchySettings { private static double gravityGunMaxHoldDistance = 10.0D; private static double critterCageMaxCapturableWidth = 1.65D; private static double critterCageMaxCapturableHeight = 1.65D; - private static boolean minersDreamEnabled = true; - private static int minersDreamMinimumRange = 48; - private static int minersDreamMaximumRange = 96; - private static int minersDreamTorchSpacing = 10; - private static int minersDreamBlocksPerTick = 150; private static double lucidAttackRange = 9.0D; private static double lucidPearlInvertedDurationSeconds = 6.0D; private static int corneaEarNightVisionSeconds = 15; @@ -554,6 +549,15 @@ public final class AntarchySettings { private static boolean elythiaPortalEnabled = true; private static boolean thoraxisPortalEnabled = true; private static boolean cavarynPortalEnabled = true; + private static boolean portalGunCanFireThroughGlass = false; + private static boolean portalGunCanFireThroughLiquid = false; + private static boolean portalGunCanPortalsResizeWhenCreated = true; + private static boolean portalGunCanPortalProjectilesChunkload = true; + private static boolean portalGunSeeThroughPortals = true; + private static boolean portalGunFancyPortals = true; + private static int portalGunIndicatorSize = 30; + private static int portalGunMaxShootDistance = 10000; + private static int portalGunEntityGrabWeightBase = 100; private static boolean elythiaFireflyParticlesEnabled = true; private static int bloodCrystalArmorShieldRechargeTicks = 600; private static int bloodCrystalAppleShieldCount = 2; @@ -1251,26 +1255,6 @@ public final class AntarchySettings { return critterCageMaxCapturableHeight; } - public static boolean minersDreamEnabled() { - return minersDreamEnabled; - } - - public static int minersDreamMinimumRange() { - return minersDreamMinimumRange; - } - - public static int minersDreamMaximumRange() { - return minersDreamMaximumRange; - } - - public static int minersDreamTorchSpacing() { - return minersDreamTorchSpacing; - } - - public static int minersDreamBlocksPerTick() { - return minersDreamBlocksPerTick; - } - public static void setDisableInfinityBookPortalCreation(boolean value) { disableInfinityBookPortalCreation = value; } @@ -1327,11 +1311,56 @@ public final class AntarchySettings { cavarynPortalEnabled = value; } + public static void setPortalGunCanFireThroughGlass(boolean value) { + portalGunCanFireThroughGlass = value; + } + + public static void setPortalGunCanFireThroughLiquid(boolean value) { + portalGunCanFireThroughLiquid = value; + } + + public static void setPortalGunCanPortalsResizeWhenCreated(boolean value) { + portalGunCanPortalsResizeWhenCreated = value; + } + + public static void setPortalGunCanPortalProjectilesChunkload(boolean value) { + portalGunCanPortalProjectilesChunkload = value; + } + + public static void setPortalGunSeeThroughPortals(boolean value) { + portalGunSeeThroughPortals = value; + } + + public static void setPortalGunFancyPortals(boolean value) { + portalGunFancyPortals = value; + } + + public static void setPortalGunIndicatorSize(int value) { + portalGunIndicatorSize = Math.max(0, Math.min(100, value)); + } + + public static void setPortalGunMaxShootDistance(int value) { + portalGunMaxShootDistance = Math.max(1, value); + } + + public static void setPortalGunEntityGrabWeightBase(int value) { + portalGunEntityGrabWeightBase = Math.max(0, value); + } + public static boolean permanentPortalsEnabled() { return permanentPortalsEnabled; } public static boolean permanentPortalsFlintAndSteelEnabled() { return permanentPortalsFlintAndSteelEnabled; } public static boolean elythiaPortalEnabled() { return elythiaPortalEnabled; } public static boolean thoraxisPortalEnabled() { return thoraxisPortalEnabled; } public static boolean cavarynPortalEnabled() { return cavarynPortalEnabled; } + public static boolean portalGunCanFireThroughGlass() { return portalGunCanFireThroughGlass; } + public static boolean portalGunCanFireThroughLiquid() { return portalGunCanFireThroughLiquid; } + public static boolean portalGunCanPortalsResizeWhenCreated() { return portalGunCanPortalsResizeWhenCreated; } + public static boolean portalGunCanPortalProjectilesChunkload() { return portalGunCanPortalProjectilesChunkload; } + public static boolean portalGunSeeThroughPortals() { return portalGunSeeThroughPortals; } + public static boolean portalGunFancyPortals() { return portalGunFancyPortals; } + public static int portalGunIndicatorSize() { return portalGunIndicatorSize; } + public static int portalGunMaxShootDistance() { return portalGunMaxShootDistance; } + public static int portalGunEntityGrabWeightBase() { return portalGunEntityGrabWeightBase; } public static void setBrownAntRequiresReagent(boolean value) { brownAntRequiresReagent = value; @@ -1809,26 +1838,6 @@ public final class AntarchySettings { critterCageMaxCapturableHeight = value; } - public static void setMinersDreamEnabled(boolean value) { - minersDreamEnabled = value; - } - - public static void setMinersDreamMinimumRange(int value) { - minersDreamMinimumRange = value; - } - - public static void setMinersDreamMaximumRange(int value) { - minersDreamMaximumRange = value; - } - - public static void setMinersDreamTorchSpacing(int value) { - minersDreamTorchSpacing = value; - } - - public static void setMinersDreamBlocksPerTick(int value) { - minersDreamBlocksPerTick = value; - } - public static double lucidAttackRange() { return lucidAttackRange; } diff --git a/common-1.21.1/src/main/java/com/craisinlord/antarchy/content/AntarchyTags.java b/common-1.21.1/src/main/java/com/craisinlord/antarchy/content/AntarchyTags.java index 42b4b92e..63c0a129 100644 --- a/common-1.21.1/src/main/java/com/craisinlord/antarchy/content/AntarchyTags.java +++ b/common-1.21.1/src/main/java/com/craisinlord/antarchy/content/AntarchyTags.java @@ -41,7 +41,6 @@ public final class AntarchyTags { public static final TagKey CAVARYN_PORTAL_FRAMES = TagKey.create(Registries.BLOCK, id("cavaryn_portal_frames")); public static final TagKey RAINBOW_PORTAL_FRAMES = TagKey.create(Registries.BLOCK, id("rainbow_portal_frames")); public static final TagKey ROLLY_CAVERN_CARVEABLE = TagKey.create(Registries.BLOCK, id("rolly_cavern_carveable")); - public static final TagKey MINERS_DREAM_CARVABLE = TagKey.create(Registries.BLOCK, id("miners_dream_carvable")); public static final TagKey BLUESTONE_COMPONENTS = TagKey.create(Registries.BLOCK, id("bluestone_components")); public static final TagKey BLUESTONE_CONNECTABLE = TagKey.create(Registries.BLOCK, id("bluestone_connectable")); public static final TagKey ANTIMETAL_INVERSION_BLOCKS = TagKey.create(Registries.BLOCK, id("antimetal_inversion_blocks")); diff --git a/common-1.21.1/src/main/java/com/craisinlord/antarchy/content/block/entity/PortalGunPortalBaseBlockEntity.java b/common-1.21.1/src/main/java/com/craisinlord/antarchy/content/block/entity/PortalGunPortalBaseBlockEntity.java index be1b9709..c3a3483b 100644 --- a/common-1.21.1/src/main/java/com/craisinlord/antarchy/content/block/entity/PortalGunPortalBaseBlockEntity.java +++ b/common-1.21.1/src/main/java/com/craisinlord/antarchy/content/block/entity/PortalGunPortalBaseBlockEntity.java @@ -1,6 +1,8 @@ package com.craisinlord.antarchy.content.block.entity; import com.craisinlord.antarchy.content.portalgun.PortalGunPortalEntity; +import java.util.EnumMap; +import java.util.Map; import java.util.UUID; import java.util.function.Supplier; import net.minecraft.core.BlockPos; @@ -14,7 +16,8 @@ import net.minecraft.world.level.block.entity.BlockEntity; import net.minecraft.world.level.block.entity.BlockEntityType; import net.minecraft.world.level.block.state.BlockState; -public class PortalGunPortalBaseBlockEntity extends BlockEntity { +public class PortalGunPortalBaseBlockEntity extends BlockEntity implements PortalGunPortalCellAccess { + private final Map faceRecords = new EnumMap<>(net.minecraft.core.Direction.class); private UUID ownerId; private UUID portalId; private PortalGunPortalEntity.PortalSide side = PortalGunPortalEntity.PortalSide.BLUE; @@ -26,6 +29,11 @@ public class PortalGunPortalBaseBlockEntity extends BlockEntity { super(typeSupplier.get(), pos, state); } + @Override + public Map portalGun$getFaceRecords() { + return this.faceRecords; + } + public void configure(UUID ownerId, UUID portalId, PortalGunPortalEntity.PortalSide side, BlockPos masterPos) { this.ownerId = ownerId; this.portalId = portalId; @@ -61,6 +69,11 @@ public class PortalGunPortalBaseBlockEntity extends BlockEntity { public void updatePair(UUID linkedPortalId, int pairTime) { this.linkedPortalId = linkedPortalId; this.pairTime = pairTime; + for (PortalGunPortalFaceRecord record : java.util.List.copyOf(this.faceRecords.values())) { + if (record.portalId().equals(this.portalId)) { + this.portalGun$putFaceRecord(record.withPair(linkedPortalId, pairTime)); + } + } this.setChanged(); } @@ -69,11 +82,20 @@ public class PortalGunPortalBaseBlockEntity extends BlockEntity { } public void onBroken() { - if (!(this.level instanceof ServerLevel serverLevel) || this.portalId == null) { + if (!(this.level instanceof ServerLevel serverLevel)) { return; } - if (serverLevel.getEntity(this.portalId) instanceof PortalGunPortalEntity portal && !portal.isRemoved()) { - portal.discard(); + java.util.Set portalIds = new java.util.HashSet<>(); + if (this.portalId != null) { + portalIds.add(this.portalId); + } + for (PortalGunPortalFaceRecord record : this.faceRecords.values()) { + portalIds.add(record.portalId()); + } + for (UUID id : portalIds) { + if (serverLevel.getEntity(id) instanceof PortalGunPortalEntity portal && !portal.isRemoved()) { + portal.discard(); + } } } @@ -81,15 +103,28 @@ public class PortalGunPortalBaseBlockEntity extends BlockEntity { if (((level.getGameTime() + pos.asLong()) & 15L) != 0L) { return; } + if (level.getBlockEntity(blockEntity.masterPos) instanceof PortalGunPortalMasterBlockEntity master + && master.matches(blockEntity.ownerId, blockEntity.portalId, blockEntity.side)) { + PortalGunPortalFaceRecord masterRecord = master.portalGun$getFaceRecord(master.getFacing(), blockEntity.portalId); + if (masterRecord != null && blockEntity.portalGun$getFaceRecord(masterRecord.face(), masterRecord.portalId()) == null) { + blockEntity.portalGun$putFaceRecord(masterRecord.withMaster(false)); + } + } + blockEntity.portalGun$restoreMasterFaces(level, pos); + if (blockEntity.portalId != null + && !(level.getEntity(blockEntity.portalId) instanceof PortalGunPortalEntity) + && blockEntity.faceRecords.values().stream().anyMatch(record -> !record.portalId().equals(blockEntity.portalId))) { + return; + } if (!(level.getBlockEntity(blockEntity.masterPos) instanceof PortalGunPortalMasterBlockEntity master) || !master.matches(blockEntity.ownerId, blockEntity.portalId, blockEntity.side) - || !pos.equals(master.getBasePos())) { + || !master.containsPortalSpot(pos)) { level.removeBlock(pos, false); return; } if (!(level.getEntity(blockEntity.portalId) instanceof PortalGunPortalEntity portal) || portal.isRemoved() - || !pos.equals(portal.getBasePos()) + || !portal.containsPortalSpot(pos) || blockEntity.ownerId == null || !blockEntity.ownerId.equals(portal.getOwnerId()) || blockEntity.side != portal.getPortalSide()) { @@ -123,6 +158,7 @@ public class PortalGunPortalBaseBlockEntity extends BlockEntity { tag.putInt("MasterY", this.masterPos.getY()); tag.putInt("MasterZ", this.masterPos.getZ()); tag.putInt("PairTime", this.pairTime); + this.portalGun$saveFaceRecords(tag); } @Override @@ -134,6 +170,7 @@ public class PortalGunPortalBaseBlockEntity extends BlockEntity { this.side = tag.getInt("Side") == PortalGunPortalEntity.PortalSide.ORANGE.ordinal() ? PortalGunPortalEntity.PortalSide.ORANGE : PortalGunPortalEntity.PortalSide.BLUE; this.masterPos = new BlockPos(tag.getInt("MasterX"), tag.getInt("MasterY"), tag.getInt("MasterZ")); this.pairTime = tag.getInt("PairTime"); + this.portalGun$loadFaceRecords(tag); } @Override diff --git a/common-1.21.1/src/main/java/com/craisinlord/antarchy/content/block/entity/PortalGunPortalCellAccess.java b/common-1.21.1/src/main/java/com/craisinlord/antarchy/content/block/entity/PortalGunPortalCellAccess.java new file mode 100644 index 00000000..14c30c4c --- /dev/null +++ b/common-1.21.1/src/main/java/com/craisinlord/antarchy/content/block/entity/PortalGunPortalCellAccess.java @@ -0,0 +1,118 @@ +package com.craisinlord.antarchy.content.block.entity; + +import com.craisinlord.antarchy.content.portalgun.PortalGunPlacement; +import com.craisinlord.antarchy.content.portalgun.PortalGunSavedData; +import java.util.Map; +import java.util.UUID; +import net.minecraft.core.Direction; +import net.minecraft.nbt.CompoundTag; +import net.minecraft.nbt.ListTag; +import net.minecraft.nbt.Tag; +import net.minecraft.core.BlockPos; +import net.minecraft.server.level.ServerLevel; + +public interface PortalGunPortalCellAccess { + Map portalGun$getFaceRecords(); + + default PortalGunPortalFaceRecord portalGun$getFaceRecord(Direction face) { + return this.portalGun$getFaceRecords().get(face); + } + + default PortalGunPortalFaceRecord portalGun$getFaceRecord(Direction face, UUID portalId) { + PortalGunPortalFaceRecord record = this.portalGun$getFaceRecord(face); + return record != null && record.portalId().equals(portalId) ? record : null; + } + + default boolean portalGun$putFaceRecord(PortalGunPortalFaceRecord record) { + PortalGunPortalFaceRecord existing = this.portalGun$getFaceRecord(record.face()); + if (existing != null + && (!existing.ownerId().equals(record.ownerId()) + || !java.util.Objects.equals(existing.channelName(), record.channelName()) + || existing.side() != record.side())) { + return false; + } + this.portalGun$getFaceRecords().put(record.face(), record); + if (this instanceof net.minecraft.world.level.block.entity.BlockEntity blockEntity) { + blockEntity.setChanged(); + } + return true; + } + + default PortalGunPortalFaceRecord portalGun$removeFaceRecord(Direction face, UUID portalId) { + PortalGunPortalFaceRecord existing = this.portalGun$getFaceRecord(face, portalId); + if (existing == null) { + return null; + } + PortalGunPortalFaceRecord removed = this.portalGun$getFaceRecords().remove(face); + if (this instanceof net.minecraft.world.level.block.entity.BlockEntity blockEntity) { + blockEntity.setChanged(); + } + return removed; + } + + default void portalGun$updateFacePair(UUID portalId, UUID linkedPortalId, int pairTime) { + for (PortalGunPortalFaceRecord record : java.util.List.copyOf(this.portalGun$getFaceRecords().values())) { + if (record.portalId().equals(portalId)) { + this.portalGun$putFaceRecord(record.withPair(linkedPortalId, pairTime)); + } + } + } + + default void portalGun$restoreMasterFaces(ServerLevel level, BlockPos pos) { + for (PortalGunPortalFaceRecord record : java.util.List.copyOf(this.portalGun$getFaceRecords().values())) { + if (record.ownerId() == null || record.portalId() == null) { + continue; + } + if (!PortalGunSavedData.isRegistered(level, record.ownerId(), record.gunId(), record.channelName(), record.side(), record.portalId())) { + this.portalGun$removeFaceRecord(record.face(), record.portalId()); + continue; + } + if (!record.master() || !record.masterPos().equals(pos)) { + continue; + } + boolean complete = !record.portalSpots().isEmpty() && record.portalSpots().getFirst().equals(record.masterPos()); + for (int i = 0; complete && i < record.portalSpots().size(); i++) { + BlockPos spot = record.portalSpots().get(i); + BlockPos support = spot.relative(record.face().getOpposite()); + if (!level.hasChunkAt(spot) || !level.hasChunkAt(support) + || !level.getBlockState(support).isFaceSturdy(level, support, record.face()) + || !(level.getBlockEntity(spot) instanceof PortalGunPortalCellAccess cell)) { + complete = false; + continue; + } + PortalGunPortalFaceRecord footprintRecord = cell.portalGun$getFaceRecord(record.face(), record.portalId()); + complete = footprintRecord != null && footprintRecord.master() == (i == 0); + } + complete = complete && PortalGunPlacement.isRectangularFootprint( + record.portalSpots().toArray(BlockPos[]::new), + record.face(), + record.upAxis(), + record.portalWidth(), + record.portalHeight() + ); + if (complete) { + PortalGunPortalMasterBlockEntity.restorePortalEntity(level, record); + } + } + } + + default void portalGun$saveFaceRecords(CompoundTag tag) { + ListTag faces = new ListTag(); + for (PortalGunPortalFaceRecord record : this.portalGun$getFaceRecords().values()) { + faces.add(record.save()); + } + tag.put("PortalFaces", faces); + } + + default void portalGun$loadFaceRecords(CompoundTag tag) { + Map records = this.portalGun$getFaceRecords(); + records.clear(); + ListTag faces = tag.getList("PortalFaces", Tag.TAG_COMPOUND); + for (int i = 0; i < faces.size(); i++) { + PortalGunPortalFaceRecord record = PortalGunPortalFaceRecord.load(faces.getCompound(i)); + if (record != null) { + records.put(record.face(), record); + } + } + } +} diff --git a/common-1.21.1/src/main/java/com/craisinlord/antarchy/content/block/entity/PortalGunPortalFaceRecord.java b/common-1.21.1/src/main/java/com/craisinlord/antarchy/content/block/entity/PortalGunPortalFaceRecord.java new file mode 100644 index 00000000..806568b1 --- /dev/null +++ b/common-1.21.1/src/main/java/com/craisinlord/antarchy/content/block/entity/PortalGunPortalFaceRecord.java @@ -0,0 +1,151 @@ +package com.craisinlord.antarchy.content.block.entity; + +import com.craisinlord.antarchy.content.portalgun.PortalGunPlacement; +import com.craisinlord.antarchy.content.portalgun.PortalGunPortalEntity; +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import java.util.UUID; +import net.minecraft.core.BlockPos; +import net.minecraft.core.Direction; +import net.minecraft.nbt.CompoundTag; +import net.minecraft.nbt.ListTag; +import net.minecraft.nbt.Tag; + +public record PortalGunPortalFaceRecord( + UUID ownerId, + String ownerIdentity, + UUID gunId, + String channelName, + UUID portalId, + UUID linkedPortalId, + PortalGunPortalEntity.PortalSide side, + Direction face, + Direction upAxis, + BlockPos masterPos, + BlockPos basePos, + List portalSpots, + Set compensatedSpots, + int pairTime, + int portalWidth, + int portalHeight, + boolean master +) { + public PortalGunPortalFaceRecord { + masterPos = masterPos.immutable(); + basePos = basePos.immutable(); + portalSpots = portalSpots.stream().map(BlockPos::immutable).toList(); + compensatedSpots = Set.copyOf(compensatedSpots); + } + + public PortalGunPlacement placement() { + return PortalGunPlacement.fromStored(this.face, this.upAxis, this.masterPos, this.basePos, this.portalSpots.toArray(BlockPos[]::new), this.compensatedSpots, this.portalWidth, this.portalHeight); + } + + public PortalGunPortalFaceRecord withPair(UUID linkedPortalId, int pairTime) { + return new PortalGunPortalFaceRecord(this.ownerId, this.ownerIdentity, this.gunId, this.channelName, this.portalId, linkedPortalId, this.side, this.face, this.upAxis, this.masterPos, this.basePos, this.portalSpots, this.compensatedSpots, pairTime, this.portalWidth, this.portalHeight, this.master); + } + + public PortalGunPortalFaceRecord withMaster(boolean master) { + return new PortalGunPortalFaceRecord(this.ownerId, this.ownerIdentity, this.gunId, this.channelName, this.portalId, this.linkedPortalId, this.side, this.face, this.upAxis, this.masterPos, this.basePos, this.portalSpots, this.compensatedSpots, this.pairTime, this.portalWidth, this.portalHeight, master); + } + + public CompoundTag save() { + CompoundTag tag = new CompoundTag(); + tag.putUUID("OwnerId", this.ownerId); + tag.putString("OwnerIdentity", this.ownerIdentity == null ? "" : this.ownerIdentity); + if (this.gunId != null) { + tag.putUUID("GunId", this.gunId); + } + tag.putString("ChannelName", this.channelName == null ? "" : this.channelName); + tag.putUUID("PortalId", this.portalId); + if (this.linkedPortalId != null) { + tag.putUUID("LinkedPortalId", this.linkedPortalId); + } + tag.putInt("Side", this.side.ordinal()); + tag.putInt("Face", this.face.get3DDataValue()); + tag.putInt("UpAxis", this.upAxis.get3DDataValue()); + putPos(tag, "Master", this.masterPos); + putPos(tag, "Base", this.basePos); + tag.putInt("PairTime", this.pairTime); + tag.putInt("PortalWidth", this.portalWidth); + tag.putInt("PortalHeight", this.portalHeight); + tag.putBoolean("MasterRole", this.master); + ListTag spots = new ListTag(); + for (BlockPos pos : this.portalSpots) { + spots.add(savePos(pos)); + } + tag.put("PortalSpots", spots); + ListTag compensated = new ListTag(); + for (BlockPos pos : this.compensatedSpots) { + compensated.add(savePos(pos)); + } + tag.put("CompensatedSpots", compensated); + return tag; + } + + public static PortalGunPortalFaceRecord load(CompoundTag tag) { + if (!tag.hasUUID("OwnerId") || !tag.hasUUID("PortalId")) { + return null; + } + ListTag spotsTag = tag.getList("PortalSpots", Tag.TAG_COMPOUND); + List spots = new java.util.ArrayList<>(spotsTag.size()); + for (int i = 0; i < spotsTag.size(); i++) { + spots.add(loadPos(spotsTag.getCompound(i))); + } + ListTag compensatedTag = tag.getList("CompensatedSpots", Tag.TAG_COMPOUND); + Set compensated = new HashSet<>(); + for (int i = 0; i < compensatedTag.size(); i++) { + compensated.add(loadPos(compensatedTag.getCompound(i))); + } + PortalGunPortalEntity.PortalSide side = tag.getInt("Side") == PortalGunPortalEntity.PortalSide.ORANGE.ordinal() + ? PortalGunPortalEntity.PortalSide.ORANGE + : PortalGunPortalEntity.PortalSide.BLUE; + int width = net.minecraft.util.Mth.clamp(tag.getInt("PortalWidth"), 1, 16); + int height = net.minecraft.util.Mth.clamp(tag.getInt("PortalHeight"), 2, 16); + return new PortalGunPortalFaceRecord( + tag.getUUID("OwnerId"), + tag.getString("OwnerIdentity"), + tag.hasUUID("GunId") ? tag.getUUID("GunId") : null, + tag.getString("ChannelName"), + tag.getUUID("PortalId"), + tag.hasUUID("LinkedPortalId") ? tag.getUUID("LinkedPortalId") : null, + side, + Direction.from3DDataValue(tag.getInt("Face")), + Direction.from3DDataValue(tag.getInt("UpAxis")), + getPos(tag, "Master"), + getPos(tag, "Base"), + spots, + compensated, + tag.getInt("PairTime"), + width, + height, + tag.getBoolean("MasterRole") + ); + } + + private static void putPos(CompoundTag tag, String key, BlockPos pos) { + CompoundTag value = new CompoundTag(); + value.putInt("X", pos.getX()); + value.putInt("Y", pos.getY()); + value.putInt("Z", pos.getZ()); + tag.put(key, value); + } + + private static BlockPos getPos(CompoundTag tag, String key) { + CompoundTag value = tag.getCompound(key); + return new BlockPos(value.getInt("X"), value.getInt("Y"), value.getInt("Z")); + } + + private static CompoundTag savePos(BlockPos pos) { + CompoundTag tag = new CompoundTag(); + tag.putInt("X", pos.getX()); + tag.putInt("Y", pos.getY()); + tag.putInt("Z", pos.getZ()); + return tag; + } + + private static BlockPos loadPos(CompoundTag tag) { + return new BlockPos(tag.getInt("X"), tag.getInt("Y"), tag.getInt("Z")); + } +} diff --git a/common-1.21.1/src/main/java/com/craisinlord/antarchy/content/block/entity/PortalGunPortalMasterBlockEntity.java b/common-1.21.1/src/main/java/com/craisinlord/antarchy/content/block/entity/PortalGunPortalMasterBlockEntity.java index a0c1f10d..1b670bda 100644 --- a/common-1.21.1/src/main/java/com/craisinlord/antarchy/content/block/entity/PortalGunPortalMasterBlockEntity.java +++ b/common-1.21.1/src/main/java/com/craisinlord/antarchy/content/block/entity/PortalGunPortalMasterBlockEntity.java @@ -5,8 +5,10 @@ import com.craisinlord.antarchy.content.portalgun.PortalGunPortalEntity; import com.craisinlord.antarchy.content.portalgun.PortalGunPlacement; import com.craisinlord.antarchy.content.portalgun.PortalGunSavedData; import java.util.ArrayList; +import java.util.EnumMap; import java.util.HashSet; import java.util.List; +import java.util.Map; import java.util.Set; import java.util.UUID; import java.util.function.Supplier; @@ -30,14 +32,20 @@ import net.minecraft.core.registries.BuiltInRegistries; import net.minecraft.world.level.block.state.BlockState; import net.minecraft.world.phys.Vec3; -public class PortalGunPortalMasterBlockEntity extends BlockEntity { +public class PortalGunPortalMasterBlockEntity extends BlockEntity implements PortalGunPortalCellAccess { + private final Map faceRecords = new EnumMap<>(Direction.class); private UUID ownerId; + private String ownerIdentity; + private UUID gunId; + private String channelName; private UUID portalId; private PortalGunPortalEntity.PortalSide side = PortalGunPortalEntity.PortalSide.BLUE; private Direction facing = Direction.NORTH; private Direction upAxis = Direction.UP; private BlockPos basePos = BlockPos.ZERO; private BlockPos[] portalSpots = new BlockPos[] {BlockPos.ZERO, BlockPos.ZERO}; + private int portalWidth = 1; + private int portalHeight = 2; private Set compensatedSpots = Set.of(); private UUID linkedPortalId; private int pairTime; @@ -48,6 +56,9 @@ public class PortalGunPortalMasterBlockEntity extends BlockEntity { public void configure( UUID ownerId, + UUID gunId, + String ownerIdentity, + String channelName, UUID portalId, PortalGunPortalEntity.PortalSide side, Direction facing, @@ -55,20 +66,33 @@ public class PortalGunPortalMasterBlockEntity extends BlockEntity { BlockPos basePos, BlockPos[] portalSpots, Set compensatedSpots, - int pairTime + int pairTime, + int portalWidth, + int portalHeight ) { this.ownerId = ownerId; + this.ownerIdentity = ownerIdentity; + this.gunId = gunId; + this.channelName = channelName; this.portalId = portalId; this.side = side; this.facing = facing; this.upAxis = upAxis; this.basePos = basePos.immutable(); - this.portalSpots = new BlockPos[] {portalSpots[0].immutable(), portalSpots[1].immutable()}; + this.portalSpots = java.util.Arrays.stream(portalSpots).map(BlockPos::immutable).toArray(BlockPos[]::new); + this.portalWidth = portalWidth; + this.portalHeight = portalHeight; this.compensatedSpots = new HashSet<>(compensatedSpots); this.pairTime = pairTime; + this.portalGun$putFaceRecord(new PortalGunPortalFaceRecord(ownerId, ownerIdentity, gunId, channelName, portalId, this.linkedPortalId, side, facing, upAxis, this.worldPosition, this.basePos, List.of(this.portalSpots), this.compensatedSpots, pairTime, portalWidth, portalHeight, true)); this.setChanged(); } + @Override + public Map portalGun$getFaceRecords() { + return this.faceRecords; + } + public UUID getPortalId() { return this.portalId; } @@ -77,6 +101,36 @@ public class PortalGunPortalMasterBlockEntity extends BlockEntity { return this.ownerId; } + public UUID getGunId() { + return this.gunId; + } + + public String getChannelName() { + return this.channelName; + } + + public void adoptGunId(UUID gunId) { + if (this.gunId == null && gunId != null) { + this.gunId = gunId; + this.setChanged(); + } + } + + public void adoptChannelIdentity(UUID gunId, String channelName) { + boolean changed = false; + if (this.gunId == null && gunId != null) { + this.gunId = gunId; + changed = true; + } + if (channelName != null && !channelName.isEmpty() && !channelName.equals(this.channelName)) { + this.channelName = channelName; + changed = true; + } + if (changed) { + this.setChanged(); + } + } + public PortalGunPortalEntity.PortalSide getSide() { return this.side; } @@ -94,7 +148,16 @@ public class PortalGunPortalMasterBlockEntity extends BlockEntity { } public BlockPos[] getPortalSpots() { - return new BlockPos[] {this.portalSpots[0], this.portalSpots[1]}; + return this.portalSpots.clone(); + } + + public boolean containsPortalSpot(BlockPos pos) { + for (BlockPos portalSpot : this.portalSpots) { + if (portalSpot.equals(pos)) { + return true; + } + } + return false; } public Set getCompensatedSpots() { @@ -112,6 +175,10 @@ public class PortalGunPortalMasterBlockEntity extends BlockEntity { public void updatePair(UUID linkedPortalId, int pairTime) { this.linkedPortalId = linkedPortalId; this.pairTime = pairTime; + PortalGunPortalFaceRecord record = this.portalGun$getFaceRecord(this.facing, this.portalId); + if (record != null) { + this.portalGun$putFaceRecord(record.withPair(linkedPortalId, pairTime)); + } this.setChanged(); } @@ -120,11 +187,20 @@ public class PortalGunPortalMasterBlockEntity extends BlockEntity { } public void onBroken() { - if (!(this.level instanceof ServerLevel serverLevel) || this.portalId == null) { + if (!(this.level instanceof ServerLevel serverLevel)) { return; } - if (serverLevel.getEntity(this.portalId) instanceof PortalGunPortalEntity portal && !portal.isRemoved()) { - portal.discard(); + Set portalIds = new HashSet<>(); + if (this.portalId != null) { + portalIds.add(this.portalId); + } + for (PortalGunPortalFaceRecord record : this.faceRecords.values()) { + portalIds.add(record.portalId()); + } + for (UUID id : portalIds) { + if (serverLevel.getEntity(id) instanceof PortalGunPortalEntity portal && !portal.isRemoved()) { + portal.discard(); + } } } @@ -132,6 +208,12 @@ public class PortalGunPortalMasterBlockEntity extends BlockEntity { if (((level.getGameTime() + pos.asLong()) & 15L) != 0L) { return; } + blockEntity.portalGun$restoreMasterFaces(level, pos); + if (blockEntity.portalId != null + && !(level.getEntity(blockEntity.portalId) instanceof PortalGunPortalEntity) + && blockEntity.faceRecords.values().stream().anyMatch(record -> !record.portalId().equals(blockEntity.portalId))) { + return; + } if (!blockEntity.hasValidFootprint(level, pos)) { level.removeBlock(pos, false); return; @@ -146,6 +228,12 @@ public class PortalGunPortalMasterBlockEntity extends BlockEntity { level.removeBlock(pos, false); return; } + if (blockEntity.gunId == null && portal.getGunId() != null) { + blockEntity.adoptGunId(portal.getGunId()); + } else if (portal.getGunId() == null && blockEntity.gunId != null) { + portal.adoptGunId(blockEntity.gunId); + } + blockEntity.adoptChannelIdentity(portal.getGunId(), portal.getChannelName()); blockEntity.ensureLinkedPortal(level, portal); UUID linkedPortalId = portal.getLinkedPortalId(); if ((linkedPortalId == null && blockEntity.linkedPortalId != null) @@ -161,16 +249,24 @@ public class PortalGunPortalMasterBlockEntity extends BlockEntity { if (this.ownerId == null || this.portalId == null || !pos.equals(this.worldPosition)) { return false; } - if (!(level.getBlockEntity(this.basePos) instanceof PortalGunPortalBaseBlockEntity base) - || !base.matches(this.ownerId, this.portalId, this.side) - || !pos.equals(base.getMasterPos())) { + if (!PortalGunPlacement.isRectangularFootprint(this.portalSpots, this.facing, this.upAxis, this.portalWidth, this.portalHeight)) { return false; } - List supportPositions = new ArrayList<>(2); - for (BlockPos portalSpot : this.portalSpots) { + List supportPositions = new ArrayList<>(this.portalSpots.length); + for (int i = 0; i < this.portalSpots.length; i++) { + BlockPos portalSpot = this.portalSpots[i]; if (portalSpot == null || BlockPos.ZERO.equals(portalSpot)) { return false; } + if (i == 0) { + if (!portalSpot.equals(pos)) { + return false; + } + } else if (!(level.getBlockEntity(portalSpot) instanceof PortalGunPortalBaseBlockEntity base) + || !base.matches(this.ownerId, this.portalId, this.side) + || !pos.equals(base.getMasterPos())) { + return false; + } supportPositions.add(portalSpot.relative(this.facing.getOpposite())); } for (BlockPos supportPos : supportPositions) { @@ -186,23 +282,60 @@ public class PortalGunPortalMasterBlockEntity extends BlockEntity { if (this.portalId == null || this.ownerId == null) { return null; } + if (!PortalGunSavedData.isRegistered(level, this.ownerId, this.gunId, this.channelName, this.side, this.portalId)) { + return null; + } if (level.getEntity(this.portalId) instanceof PortalGunPortalEntity portal && !portal.isRemoved()) { return portal; } + PortalGunPortalFaceRecord record = this.portalGun$getFaceRecord(this.facing, this.portalId); + if (record != null) { + return restorePortalEntity(level, record); + } EntityType rawType = BuiltInRegistries.ENTITY_TYPE.get(ResourceLocation.fromNamespaceAndPath(Antarchy.MODID, "portal_gun_portal")); if (!(rawType instanceof EntityType)) { return null; } - PortalGunPlacement placement = PortalGunPlacement.fromStored(this.facing, this.upAxis, this.worldPosition, this.basePos, this.compensatedSpots); + PortalGunPlacement placement = PortalGunPlacement.fromStored(this.facing, this.upAxis, this.worldPosition, this.basePos, this.portalSpots, this.compensatedSpots, this.portalWidth, this.portalHeight); @SuppressWarnings("unchecked") PortalGunPortalEntity restored = new PortalGunPortalEntity((EntityType) rawType, level); restored.setUUID(this.portalId); - restored.configure(this.ownerId, this.side, placement); + restored.configure(this.ownerId, this.gunId, this.ownerIdentity == null ? String.valueOf(this.ownerId) : this.ownerIdentity, this.channelName == null ? String.valueOf(this.gunId) : this.channelName, this.side, placement); restored.restorePair(this.linkedPortalId, this.pairTime); Vec3 center = placement.center(); restored.moveTo(center.x, center.y, center.z, placement.yaw(), 0.0F); level.addFreshEntity(restored); - PortalGunSavedData.setPortal(level.getServer(), this.ownerId, this.side, restored.getUUID()); + PortalGunSavedData.setPortal(level, this.ownerId, this.gunId, this.channelName, this.side, restored.getUUID()); + return restored; + } + + public static PortalGunPortalEntity restorePortalEntity(ServerLevel level, PortalGunPortalFaceRecord record) { + if (record.ownerId() == null || record.portalId() == null + || !PortalGunSavedData.isRegistered(level, record.ownerId(), record.gunId(), record.channelName(), record.side(), record.portalId())) { + return null; + } + if (level.getEntity(record.portalId()) instanceof PortalGunPortalEntity portal && !portal.isRemoved()) { + return portal; + } + EntityType rawType = BuiltInRegistries.ENTITY_TYPE.get(ResourceLocation.fromNamespaceAndPath(Antarchy.MODID, "portal_gun_portal")); + if (rawType == null) { + return null; + } + PortalGunPlacement placement = record.placement(); + @SuppressWarnings("unchecked") + PortalGunPortalEntity restored = new PortalGunPortalEntity((EntityType) rawType, level); + restored.setUUID(record.portalId()); + restored.configure(record.ownerId(), record.gunId(), record.ownerIdentity(), record.channelName(), record.side(), placement); + restored.restorePair(record.linkedPortalId(), record.pairTime()); + Vec3 center = placement.center(); + restored.moveTo(center.x, center.y, center.z, placement.yaw(), 0.0F); + level.addFreshEntity(restored); + PortalGunSavedData.setPortal(level, record.ownerId(), record.gunId(), record.channelName(), record.side(), restored.getUUID()); + PortalGunPortalEntity linked = PortalGunSavedData.findLoadedPortal(level, record.ownerId(), record.gunId(), record.channelName(), record.side() == PortalGunPortalEntity.PortalSide.BLUE ? PortalGunPortalEntity.PortalSide.ORANGE : PortalGunPortalEntity.PortalSide.BLUE); + if (linked != null && linked != restored && !linked.isRemoved()) { + restored.linkTo(linked); + linked.linkTo(restored); + } return restored; } @@ -211,8 +344,10 @@ public class PortalGunPortalMasterBlockEntity extends BlockEntity { return; } PortalGunPortalEntity counterpart = PortalGunSavedData.findLoadedPortal( - level.getServer(), + level, this.ownerId, + this.gunId, + this.channelName, this.side == PortalGunPortalEntity.PortalSide.BLUE ? PortalGunPortalEntity.PortalSide.ORANGE : PortalGunPortalEntity.PortalSide.BLUE ); if (counterpart == null || counterpart == portal || counterpart.isRemoved()) { @@ -230,6 +365,15 @@ public class PortalGunPortalMasterBlockEntity extends BlockEntity { if (this.ownerId != null) { tag.putUUID("OwnerId", this.ownerId); } + if (this.ownerIdentity != null) { + tag.putString("OwnerIdentity", this.ownerIdentity); + } + if (this.gunId != null) { + tag.putUUID("GunId", this.gunId); + } + if (this.channelName != null) { + tag.putString("ChannelName", this.channelName); + } if (this.portalId != null) { tag.putUUID("PortalId", this.portalId); } @@ -243,6 +387,8 @@ public class PortalGunPortalMasterBlockEntity extends BlockEntity { tag.putInt("BaseY", this.basePos.getY()); tag.putInt("BaseZ", this.basePos.getZ()); tag.putInt("PairTime", this.pairTime); + tag.putInt("PortalWidth", this.portalWidth); + tag.putInt("PortalHeight", this.portalHeight); ListTag spots = new ListTag(); for (BlockPos portalSpot : this.portalSpots) { CompoundTag spot = new CompoundTag(); @@ -261,12 +407,16 @@ public class PortalGunPortalMasterBlockEntity extends BlockEntity { compensated.add(spot); } tag.put("CompensatedSpots", compensated); + this.portalGun$saveFaceRecords(tag); } @Override protected void loadAdditional(CompoundTag tag, HolderLookup.Provider registries) { super.loadAdditional(tag, registries); this.ownerId = tag.hasUUID("OwnerId") ? tag.getUUID("OwnerId") : null; + this.ownerIdentity = tag.contains("OwnerIdentity") ? tag.getString("OwnerIdentity") : String.valueOf(this.ownerId); + this.gunId = tag.hasUUID("GunId") ? tag.getUUID("GunId") : null; + this.channelName = tag.contains("ChannelName") ? tag.getString("ChannelName") : this.gunId == null ? "" : "Random Channel #" + this.gunId.hashCode(); this.portalId = tag.hasUUID("PortalId") ? tag.getUUID("PortalId") : null; this.linkedPortalId = tag.hasUUID("LinkedPortalId") ? tag.getUUID("LinkedPortalId") : null; this.side = tag.getInt("Side") == PortalGunPortalEntity.PortalSide.ORANGE.ordinal() ? PortalGunPortalEntity.PortalSide.ORANGE : PortalGunPortalEntity.PortalSide.BLUE; @@ -275,10 +425,15 @@ public class PortalGunPortalMasterBlockEntity extends BlockEntity { this.basePos = new BlockPos(tag.getInt("BaseX"), tag.getInt("BaseY"), tag.getInt("BaseZ")); this.pairTime = tag.getInt("PairTime"); ListTag spots = tag.getList("PortalSpots", Tag.TAG_COMPOUND); - for (int i = 0; i < Math.min(2, spots.size()); i++) { + if (!spots.isEmpty()) { + this.portalSpots = new BlockPos[spots.size()]; + } + for (int i = 0; i < spots.size(); i++) { CompoundTag spot = spots.getCompound(i); this.portalSpots[i] = new BlockPos(spot.getInt("X"), spot.getInt("Y"), spot.getInt("Z")); } + this.portalWidth = net.minecraft.util.Mth.clamp(tag.getInt("PortalWidth"), 1, 16); + this.portalHeight = net.minecraft.util.Mth.clamp(tag.getInt("PortalHeight"), 2, 16); ListTag compensated = tag.getList("CompensatedSpots", Tag.TAG_COMPOUND); Set compensatedSpots = new HashSet<>(); for (int i = 0; i < compensated.size(); i++) { @@ -286,6 +441,10 @@ public class PortalGunPortalMasterBlockEntity extends BlockEntity { compensatedSpots.add(new BlockPos(spot.getInt("X"), spot.getInt("Y"), spot.getInt("Z"))); } this.compensatedSpots = compensatedSpots; + this.portalGun$loadFaceRecords(tag); + if (this.faceRecords.isEmpty() && this.ownerId != null && this.portalId != null && this.portalSpots.length > 0) { + this.portalGun$putFaceRecord(new PortalGunPortalFaceRecord(this.ownerId, this.ownerIdentity, this.gunId, this.channelName, this.portalId, this.linkedPortalId, this.side, this.facing, this.upAxis, this.worldPosition, this.basePos, List.of(this.portalSpots), this.compensatedSpots, this.pairTime, this.portalWidth, this.portalHeight, true)); + } } @Override diff --git a/common-1.21.1/src/main/java/com/craisinlord/antarchy/content/block/entity/UpperBlockEntity.java b/common-1.21.1/src/main/java/com/craisinlord/antarchy/content/block/entity/UpperBlockEntity.java index 84671de3..bc30b922 100644 --- a/common-1.21.1/src/main/java/com/craisinlord/antarchy/content/block/entity/UpperBlockEntity.java +++ b/common-1.21.1/src/main/java/com/craisinlord/antarchy/content/block/entity/UpperBlockEntity.java @@ -20,6 +20,7 @@ import net.minecraft.world.entity.player.Inventory; import net.minecraft.world.inventory.AbstractContainerMenu; import net.minecraft.world.inventory.HopperMenu; import net.minecraft.world.item.ItemStack; +import net.minecraft.world.level.block.ChestBlock; import net.minecraft.world.level.block.entity.BlockEntity; import net.minecraft.world.level.block.entity.BlockEntityType; import net.minecraft.world.level.block.entity.RandomizableContainerBlockEntity; @@ -119,6 +120,10 @@ public class UpperBlockEntity extends RandomizableContainerBlockEntity { } private static Container getContainerAt(ServerLevel level, BlockPos pos) { + BlockState state = level.getBlockState(pos); + if (state.getBlock() instanceof ChestBlock chestBlock) { + return ChestBlock.getContainer(chestBlock, state, level, pos, false); + } BlockEntity blockEntity = level.getBlockEntity(pos); return blockEntity instanceof Container container ? container : null; } diff --git a/common-1.21.1/src/main/java/com/craisinlord/antarchy/content/client/PortalGunCrosshairRenderer.java b/common-1.21.1/src/main/java/com/craisinlord/antarchy/content/client/PortalGunCrosshairRenderer.java new file mode 100644 index 00000000..cf680add --- /dev/null +++ b/common-1.21.1/src/main/java/com/craisinlord/antarchy/content/client/PortalGunCrosshairRenderer.java @@ -0,0 +1,116 @@ +package com.craisinlord.antarchy.content.client; + +import com.craisinlord.antarchy.Antarchy; +import com.craisinlord.antarchy.content.item.PortalGunItem; +import com.craisinlord.antarchy.content.portalgun.PortalGunPortalEntity; +import com.craisinlord.antarchy.content.client.PortalGunIndicatorClientState; +import com.craisinlord.antarchy.content.network.PortalGunIndicatorPayload; +import com.craisinlord.antarchy.config.AntarchySettings; +import java.util.HashSet; +import java.util.Set; +import java.util.UUID; +import net.minecraft.client.Minecraft; +import net.minecraft.client.gui.GuiGraphics; +import net.minecraft.client.multiplayer.ClientLevel; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.world.entity.Entity; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.phys.AABB; + +public final class PortalGunCrosshairRenderer { + private static final ResourceLocation CROSSHAIR = ResourceLocation.fromNamespaceAndPath(Antarchy.MODID, "textures/gui/portal_gun_crosshair.png"); + private static final int WIDTH = 34; + private static final int HEIGHT = 33; + private static ClientLevel cachedLevel; + private static long cachedGameTime = Long.MIN_VALUE; + private static UUID cachedOwnerId; + private static Set cachedPortals = Set.of(); + + private PortalGunCrosshairRenderer() { + } + + public static void render(GuiGraphics guiGraphics) { + Minecraft minecraft = Minecraft.getInstance(); + if (PortalGunGrabClientState.isActive()) { + return; + } + if (minecraft.player == null || minecraft.screen != null || minecraft.options.hideGui || !minecraft.options.getCameraType().isFirstPerson()) { + return; + } + ItemStack held = minecraft.player.getMainHandItem(); + if (!(held.getItem() instanceof PortalGunItem)) { + held = minecraft.player.getOffhandItem(); + if (!(held.getItem() instanceof PortalGunItem)) { + return; + } + } + UUID gunId = PortalGunItem.getGunId(held); + UUID ownerId = PortalGunItem.getPortalOwnerId(held, minecraft.player.getUUID()); + String channelName = PortalGunItem.getChannelName(held); + PortalGunIndicatorClientState.requestIfNeeded(minecraft, gunId); + Set portals = portalsFor(minecraft, ownerId, channelName); + PortalGunIndicatorPayload serverState = PortalGunIndicatorClientState.get(gunId); + boolean blueAvailable = serverState == null + ? portals.contains(new PortalKey(channelName, PortalGunPortalEntity.PortalSide.BLUE)) + : serverState.blueAvailable(); + boolean orangeAvailable = serverState == null + ? portals.contains(new PortalKey(channelName, PortalGunPortalEntity.PortalSide.ORANGE)) + : serverState.orangeAvailable(); + + int left = (minecraft.getWindow().getGuiScaledWidth() - WIDTH) / 2; + int top = (minecraft.getWindow().getGuiScaledHeight() - HEIGHT) / 2; + guiGraphics.blit(CROSSHAIR, left + 6, top, 0.0F, 0.0F, 17, HEIGHT, WIDTH, HEIGHT); + guiGraphics.blit(CROSSHAIR, left + 11, top, 17.0F, 0.0F, 17, HEIGHT, WIDTH, HEIGHT); + if (!blueAvailable) { + guiGraphics.fill(left + 6, top, left + 23, top + HEIGHT, 0x99000000); + } + if (!orangeAvailable) { + guiGraphics.fill(left + 11, top, left + 28, top + HEIGHT, 0x99000000); + } + int indicatorSize = AntarchySettings.portalGunIndicatorSize(); + if (indicatorSize > 0) { + int dot = Math.max(1, Math.round(HEIGHT * indicatorSize / 100.0F)); + int gap = Math.max(1, dot / 3); + int indicatorLeft = left + (WIDTH - dot * 2 - gap) / 2; + int indicatorTop = top + HEIGHT + 2; + guiGraphics.fill(indicatorLeft, indicatorTop, indicatorLeft + dot, indicatorTop + dot, + blueAvailable ? 0xFF21A8FF : 0xFF33414A); + int orangeLeft = indicatorLeft + dot + gap; + guiGraphics.fill(orangeLeft, indicatorTop, orangeLeft + dot, indicatorTop + dot, + orangeAvailable ? 0xFFFF8A24 : 0xFF4A4035); + } + } + + private static Set portalsFor(Minecraft minecraft, UUID ownerId, String channelName) { + ClientLevel level = minecraft.level; + long gameTime = level.getGameTime(); + if (cachedLevel != level || cachedGameTime != gameTime || !ownerId.equals(cachedOwnerId)) { + Set portals = new HashSet<>(); + if (minecraft.player != null) { + AABB bounds = minecraft.player.getBoundingBox().inflate(128.0D); + for (PortalGunPortalEntity portal : level.getEntitiesOfClass(PortalGunPortalEntity.class, bounds, Entity::isAlive)) { + UUID portalOwnerId = portal.getOwnerId(); + String portalChannelName = portal.getChannelName(); + if (ownerId.equals(portalOwnerId) && portalChannelName != null && !portalChannelName.isEmpty()) { + portals.add(new PortalKey(portalChannelName, portal.getPortalSide())); + if (portal.getLinkedPortalId() != null) { + portals.add(new PortalKey(portalChannelName, PortalGunPortalEntity.PortalSide.BLUE)); + portals.add(new PortalKey(portalChannelName, PortalGunPortalEntity.PortalSide.ORANGE)); + } + } + } + } + cachedLevel = level; + cachedGameTime = gameTime; + cachedOwnerId = ownerId; + cachedPortals = Set.copyOf(portals); + } + if (channelName == null || channelName.isEmpty()) { + return Set.of(); + } + return cachedPortals; + } + + private record PortalKey(String channelName, PortalGunPortalEntity.PortalSide side) { + } +} diff --git a/common-1.21.1/src/main/java/com/craisinlord/antarchy/content/client/PortalGunEntityTransformationStack.java b/common-1.21.1/src/main/java/com/craisinlord/antarchy/content/client/PortalGunEntityTransformationStack.java index 2fc79ae4..552213f4 100644 --- a/common-1.21.1/src/main/java/com/craisinlord/antarchy/content/client/PortalGunEntityTransformationStack.java +++ b/common-1.21.1/src/main/java/com/craisinlord/antarchy/content/client/PortalGunEntityTransformationStack.java @@ -27,30 +27,43 @@ public final class PortalGunEntityTransformationStack { } public Vec3 moveEntity(PortalGunPortalEntity sourcePortal, PortalGunPortalEntity destinationPortal, float partialTick) { - Vec3 interpolated = new Vec3( + double eyeHeight = this.entity.getEyeHeight(); + Vec3 interpolatedEye = new Vec3( net.minecraft.util.Mth.lerp(partialTick, this.entity.xo, this.entity.getX()), - net.minecraft.util.Mth.lerp(partialTick, this.entity.yo, this.entity.getY()), + net.minecraft.util.Mth.lerp(partialTick, this.entity.yo, this.entity.getY()) + eyeHeight, net.minecraft.util.Mth.lerp(partialTick, this.entity.zo, this.entity.getZ()) ); - Vec3 previous = new Vec3(this.entity.xo, this.entity.yo, this.entity.zo); - Vec3 transformed = destinationPortal.position().add(PortalGunTransformUtil.transformPosition(sourcePortal, destinationPortal, interpolated.subtract(sourcePortal.position()))); - Vec3 transformedPrevious = destinationPortal.position().add(PortalGunTransformUtil.transformPosition(sourcePortal, destinationPortal, previous.subtract(sourcePortal.position()))); + Vec3 previousEye = new Vec3(this.entity.xo, this.entity.yo + eyeHeight, this.entity.zo); + Vec3 transformedEye = destinationPortal.position().add(PortalGunTransformUtil.transformPosition(sourcePortal, destinationPortal, interpolatedEye.subtract(sourcePortal.position()))); + Vec3 transformedPreviousEye = destinationPortal.position().add(PortalGunTransformUtil.transformPosition(sourcePortal, destinationPortal, previousEye.subtract(sourcePortal.position()))); + Vec3 transformed = transformedEye.subtract(0.0D, eyeHeight, 0.0D); + Vec3 transformedPrevious = transformedPreviousEye.subtract(0.0D, eyeHeight, 0.0D); Vec3 look = PortalGunTransformUtil.transformVector(sourcePortal, destinationPortal, this.entity.getLookAngle()).normalize(); + Vec3 previousLook = PortalGunTransformUtil.transformVector( + sourcePortal, + destinationPortal, + Vec3.directionFromRotation(this.entity.xRotO, this.entity.yRotO) + ).normalize(); Vec3 velocity = PortalGunTransformUtil.transformVector(sourcePortal, destinationPortal, this.entity.getDeltaMovement()); float yaw = PortalGunTransformUtil.yawFromLook(look); float pitch = PortalGunTransformUtil.pitchFromLook(look); + float previousYaw = PortalGunTransformUtil.yawFromLook(previousLook); + float previousPitch = PortalGunTransformUtil.pitchFromLook(previousLook); + float yawDelta = net.minecraft.util.Mth.wrapDegrees(yaw - this.entity.getYRot()); this.entity.setPos(transformed.x, transformed.y, transformed.z); this.entity.xo = transformedPrevious.x; this.entity.yo = transformedPrevious.y; this.entity.zo = transformedPrevious.z; this.entity.setYRot(yaw); this.entity.setXRot(pitch); - this.entity.yRotO = yaw; - this.entity.xRotO = pitch; + this.entity.yRotO = previousYaw; + this.entity.xRotO = previousPitch; this.entity.setDeltaMovement(velocity); if (this.entity instanceof LivingEntity living) { - living.setYHeadRot(yaw); - living.setYBodyRot(yaw); + living.setYHeadRot(living.getYHeadRot() + yawDelta); + living.setYBodyRot(living.yBodyRot + yawDelta); + living.yHeadRotO += yawDelta; + living.yBodyRotO += yawDelta; } return transformed; } @@ -73,13 +86,17 @@ public final class PortalGunEntityTransformationStack { float yRotO, float xRotO, float yHeadRot, + float yHeadRotO, float yBodyRot, + float yBodyRotO, Vec3 deltaMovement ) { private static EntityTransformation capture(Entity entity) { float yHeadRot = entity instanceof LivingEntity living ? living.getYHeadRot() : entity.getYRot(); + float yHeadRotO = entity instanceof LivingEntity living ? living.yHeadRotO : entity.yRotO; float yBodyRot = entity instanceof LivingEntity living ? living.yBodyRot : entity.getYRot(); - return new EntityTransformation(entity.getX(), entity.getY(), entity.getZ(), entity.xo, entity.yo, entity.zo, entity.getYRot(), entity.getXRot(), entity.yRotO, entity.xRotO, yHeadRot, yBodyRot, entity.getDeltaMovement()); + float yBodyRotO = entity instanceof LivingEntity living ? living.yBodyRotO : entity.yRotO; + return new EntityTransformation(entity.getX(), entity.getY(), entity.getZ(), entity.xo, entity.yo, entity.zo, entity.getYRot(), entity.getXRot(), entity.yRotO, entity.xRotO, yHeadRot, yHeadRotO, yBodyRot, yBodyRotO, entity.getDeltaMovement()); } private void restore(Entity entity) { @@ -94,7 +111,9 @@ public final class PortalGunEntityTransformationStack { entity.setDeltaMovement(this.deltaMovement); if (entity instanceof LivingEntity living) { living.setYHeadRot(this.yHeadRot); + living.yHeadRotO = this.yHeadRotO; living.setYBodyRot(this.yBodyRot); + living.yBodyRotO = this.yBodyRotO; } } } diff --git a/common-1.21.1/src/main/java/com/craisinlord/antarchy/content/client/PortalGunGrabClientState.java b/common-1.21.1/src/main/java/com/craisinlord/antarchy/content/client/PortalGunGrabClientState.java new file mode 100644 index 00000000..f6779c39 --- /dev/null +++ b/common-1.21.1/src/main/java/com/craisinlord/antarchy/content/client/PortalGunGrabClientState.java @@ -0,0 +1,20 @@ +package com.craisinlord.antarchy.content.client; + +public final class PortalGunGrabClientState { + private static boolean active; + + private PortalGunGrabClientState() { + } + + public static void update(boolean grabActive) { + active = grabActive; + } + + public static boolean isActive() { + return active; + } + + public static void clear() { + active = false; + } +} diff --git a/common-1.21.1/src/main/java/com/craisinlord/antarchy/content/client/PortalGunIndicatorClientState.java b/common-1.21.1/src/main/java/com/craisinlord/antarchy/content/client/PortalGunIndicatorClientState.java new file mode 100644 index 00000000..03ca1c14 --- /dev/null +++ b/common-1.21.1/src/main/java/com/craisinlord/antarchy/content/client/PortalGunIndicatorClientState.java @@ -0,0 +1,59 @@ +package com.craisinlord.antarchy.content.client; + +import com.craisinlord.antarchy.content.network.PortalGunIndicatorPayload; +import com.craisinlord.antarchy.content.network.PortalGunIndicatorRequestPayload; +import java.util.HashMap; +import java.util.Map; +import java.util.UUID; +import java.util.function.Consumer; +import net.minecraft.client.Minecraft; +import net.minecraft.client.multiplayer.ClientLevel; + +public final class PortalGunIndicatorClientState { + private static final Map STATES = new HashMap<>(); + private static ClientLevel level; + private static long lastRequestTick = Long.MIN_VALUE; + private static UUID lastRequestedGun; + private static Consumer requestSender = payload -> {}; + + private PortalGunIndicatorClientState() { + } + + public static void setRequestSender(Consumer sender) { + requestSender = sender; + } + + public static void requestIfNeeded(Minecraft minecraft, UUID gunId) { + ClientLevel currentLevel = minecraft.level; + if (currentLevel == null || gunId == null || minecraft.player == null) { + return; + } + if (level != currentLevel) { + level = currentLevel; + STATES.clear(); + lastRequestedGun = null; + lastRequestTick = Long.MIN_VALUE; + } + long currentTick = minecraft.player.tickCount; + if (!gunId.equals(lastRequestedGun) || currentTick - lastRequestTick >= 20L) { + lastRequestedGun = gunId; + lastRequestTick = currentTick; + requestSender.accept(new PortalGunIndicatorRequestPayload(gunId)); + } + } + + public static void update(PortalGunIndicatorPayload payload) { + STATES.put(payload.gunId(), payload); + } + + public static PortalGunIndicatorPayload get(UUID gunId) { + return gunId == null ? null : STATES.get(gunId); + } + + public static void clear() { + STATES.clear(); + level = null; + lastRequestedGun = null; + lastRequestTick = Long.MIN_VALUE; + } +} diff --git a/common-1.21.1/src/main/java/com/craisinlord/antarchy/content/client/PortalGunPortalRenderState.java b/common-1.21.1/src/main/java/com/craisinlord/antarchy/content/client/PortalGunPortalRenderState.java index 6b3b3f94..ff5af61e 100644 --- a/common-1.21.1/src/main/java/com/craisinlord/antarchy/content/client/PortalGunPortalRenderState.java +++ b/common-1.21.1/src/main/java/com/craisinlord/antarchy/content/client/PortalGunPortalRenderState.java @@ -3,6 +3,7 @@ package com.craisinlord.antarchy.content.client; import com.craisinlord.antarchy.content.portalgun.PortalGunWorldPortalShape; import java.util.ArrayDeque; import java.util.Deque; +import java.util.UUID; import net.minecraft.world.phys.AABB; import net.minecraft.world.phys.Vec3; import org.joml.Matrix4f; @@ -133,7 +134,8 @@ public final class PortalGunPortalRenderState { Matrix4f viewMatrix, Matrix4f projectionMatrix, int renderLevel, - boolean renderAll + boolean renderAll, + UUID destinationId ) { } } diff --git a/common-1.21.1/src/main/java/com/craisinlord/antarchy/content/client/PortalGunResetClientState.java b/common-1.21.1/src/main/java/com/craisinlord/antarchy/content/client/PortalGunResetClientState.java new file mode 100644 index 00000000..03676a76 --- /dev/null +++ b/common-1.21.1/src/main/java/com/craisinlord/antarchy/content/client/PortalGunResetClientState.java @@ -0,0 +1,51 @@ +package com.craisinlord.antarchy.content.client; + +import com.craisinlord.antarchy.content.network.PortalGunResetPayload; +import java.util.function.Consumer; +import net.minecraft.client.Minecraft; + +public final class PortalGunResetClientState { + private static boolean resetDown; + private static boolean attackDown; + private static boolean useDown; + private static boolean selectedSide; + + private PortalGunResetClientState() { + } + + public static boolean tick(Minecraft minecraft, boolean enabled, boolean resetPressed, Consumer sender) { + boolean canInput = enabled && minecraft.player != null && minecraft.level != null && minecraft.screen == null; + boolean active = canInput && resetPressed; + boolean attack = canInput && minecraft.options.keyAttack.isDown(); + boolean use = canInput && minecraft.options.keyUse.isDown(); + if (active && !resetDown) { + sender.accept(new PortalGunResetPayload(com.craisinlord.antarchy.content.portalgun.PortalGunResetManager.ARM)); + selectedSide = false; + } + if (active && attack && !attackDown) { + sender.accept(new PortalGunResetPayload(com.craisinlord.antarchy.content.portalgun.PortalGunResetManager.RESET_BLUE)); + selectedSide = true; + } + if (active && use && !useDown) { + selectedSide = true; + } + if (!active && resetDown) { + int action = !canInput || selectedSide + ? com.craisinlord.antarchy.content.portalgun.PortalGunResetManager.RELEASE + : com.craisinlord.antarchy.content.portalgun.PortalGunResetManager.RESET_ALL; + sender.accept(new PortalGunResetPayload(action)); + selectedSide = false; + } + resetDown = active; + attackDown = attack; + useDown = use; + return active; + } + + public static void clear() { + resetDown = false; + attackDown = false; + useDown = false; + selectedSide = false; + } +} diff --git a/common-1.21.1/src/main/java/com/craisinlord/antarchy/content/client/PortalGunRollClientState.java b/common-1.21.1/src/main/java/com/craisinlord/antarchy/content/client/PortalGunRollClientState.java index 84e38223..7b4c4cdf 100644 --- a/common-1.21.1/src/main/java/com/craisinlord/antarchy/content/client/PortalGunRollClientState.java +++ b/common-1.21.1/src/main/java/com/craisinlord/antarchy/content/client/PortalGunRollClientState.java @@ -7,10 +7,12 @@ import java.util.Map; import java.util.UUID; import net.minecraft.client.Minecraft; import net.minecraft.world.entity.Entity; +import net.minecraft.world.phys.AABB; import net.minecraft.world.phys.Vec3; public final class PortalGunRollClientState { - private static final int TELEPORT_COOLDOWN_TICKS = 40; + private static final double PORTAL_QUERY_RADIUS = 12.0D; + private static final int TELEPORT_COOLDOWN_TICKS = 3; private static final float ROLL_DECAY = 0.85F; private static final float ROLL_EPSILON = 0.05F; private static final Map TELEPORT_COOLDOWNS = new HashMap<>(); @@ -32,8 +34,11 @@ public final class PortalGunRollClientState { if (minecraft.level == null || entity == null) { return false; } - for (Entity renderEntity : minecraft.level.entitiesForRendering()) { - if (renderEntity instanceof PortalGunPortalEntity portal && portal.isAlive() && portal.intersectsEntityBounds(entity)) { + for (PortalGunPortalEntity portal : minecraft.level.getEntitiesOfClass( + PortalGunPortalEntity.class, + entity.getBoundingBox().inflate(PORTAL_QUERY_RADIUS), + Entity::isAlive)) { + if (portal.intersectsEntityBounds(entity)) { return true; } } @@ -67,10 +72,10 @@ public final class PortalGunRollClientState { } long gameTime = minecraft.level.getGameTime(); TELEPORT_COOLDOWNS.entrySet().removeIf(entry -> entry.getValue() <= gameTime); - for (Entity entity : minecraft.level.entitiesForRendering()) { - if (!(entity instanceof PortalGunPortalEntity portal) || !portal.isAlive()) { - continue; - } + for (PortalGunPortalEntity portal : minecraft.level.getEntitiesOfClass( + PortalGunPortalEntity.class, + cameraEntity.getBoundingBox().inflate(PORTAL_QUERY_RADIUS), + Entity::isAlive)) { PortalGunPortalEntity linkedPortal = portal.getLinkedPortal(); if (linkedPortal == null || !linkedPortal.isAlive()) { continue; @@ -81,11 +86,13 @@ public final class PortalGunRollClientState { if (!canTeleport(cameraEntity, gameTime)) { continue; } - Vec3 crossingProbe = portal.resolveCrossingProbe(cameraEntity); - if (crossingProbe == null) { + AABB currentBox = cameraEntity.getBoundingBox(); + AABB previousBox = currentBox.move(cameraEntity.xo - cameraEntity.getX(), cameraEntity.yo - cameraEntity.getY(), cameraEntity.zo - cameraEntity.getZ()); + PortalGunPortalEntity.PortalCrossing crossing = portal.resolveCrossing(cameraEntity, previousBox, currentBox); + if (crossing == null) { continue; } - applyPredictedTeleport(cameraEntity, portal, linkedPortal, crossingProbe, gameTime); + applyPredictedTeleport(cameraEntity, portal, linkedPortal, crossing, gameTime); break; } } @@ -98,26 +105,30 @@ public final class PortalGunRollClientState { return cooldownUntil <= gameTime; } - private static void applyPredictedTeleport(Entity entity, PortalGunPortalEntity sourcePortal, PortalGunPortalEntity destinationPortal, Vec3 currentProbe, long gameTime) { + private static void applyPredictedTeleport(Entity entity, PortalGunPortalEntity sourcePortal, PortalGunPortalEntity destinationPortal, PortalGunPortalEntity.PortalCrossing crossing, long gameTime) { Vec3 transformedLook = PortalGunTransformUtil.transformVector(sourcePortal, destinationPortal, entity.getLookAngle()).normalize(); Vec3 transformedUp = PortalGunTransformUtil.transformVector(sourcePortal, destinationPortal, PortalGunTransformUtil.upVectorFromLookAndRoll(entity.getLookAngle(), rollDegrees)).normalize(); - Vec3 relativeProbe = currentProbe.subtract(sourcePortal.position()); - Vec3 transformedProbe = PortalGunTransformUtil.transformPosition(sourcePortal, destinationPortal, relativeProbe); - Vec3 transformedEntityOffset = PortalGunTransformUtil.transformVector(sourcePortal, destinationPortal, entity.position().subtract(currentProbe)); - Vec3 destinationNormal = destinationPortal.getNormalVec().normalize(); - double exitClearance = Math.max(0.35D, entity.getBbWidth() * 0.5D + 0.16D); - Vec3 exitProbe = destinationPortal.position().add(transformedProbe).add(destinationNormal.scale(exitClearance - transformedProbe.dot(destinationNormal))); - Vec3 exitPos = exitProbe.add(transformedEntityOffset); - Vec3 transformedMotion = PortalGunTransformUtil.transformVector(sourcePortal, destinationPortal, entity.getDeltaMovement()); - double outward = transformedMotion.dot(destinationNormal); - if (outward < 0.42D) { - transformedMotion = transformedMotion.add(destinationNormal.scale(0.42D - outward)); - } + Vec3 resolvedMovement = entity.position().subtract(entity.xo, entity.yo, entity.zo); + PortalGunPortalEntity.PortalTransitTransform transit = sourcePortal.previewTransit(entity, destinationPortal, crossing, resolvedMovement); + Vec3 transformedMotion = transit.movement(); + transformedMotion = clampPortalMotion(transformedMotion); rollDegrees = PortalGunTransformUtil.rollFromOrientation(transformedLook, transformedUp); + Vec3 exitPos = transit.exitPosition(); entity.teleportTo(exitPos.x, exitPos.y, exitPos.z); entity.setDeltaMovement(transformedMotion); + float verticalMotion = (float) transformedMotion.y; + entity.fallDistance = 0.1F * (verticalMotion / -0.1F * verticalMotion / -0.1F); entity.setYRot(PortalGunTransformUtil.yawFromLook(transformedLook)); entity.setXRot(PortalGunTransformUtil.pitchFromLook(transformedLook)); + entity.setPortalCooldown(TELEPORT_COOLDOWN_TICKS); TELEPORT_COOLDOWNS.put(entity.getUUID(), gameTime + TELEPORT_COOLDOWN_TICKS); } + + private static Vec3 clampPortalMotion(Vec3 motion) { + return new Vec3(clampPortalMotionComponent(motion.x), clampPortalMotionComponent(motion.y), clampPortalMotionComponent(motion.z)); + } + + private static double clampPortalMotionComponent(double motion) { + return Math.abs(motion) > 0.99D ? motion / (Math.abs(motion) + 0.001D) : motion; + } } diff --git a/common-1.21.1/src/main/java/com/craisinlord/antarchy/content/client/PortalGunZoomClientState.java b/common-1.21.1/src/main/java/com/craisinlord/antarchy/content/client/PortalGunZoomClientState.java new file mode 100644 index 00000000..faf14641 --- /dev/null +++ b/common-1.21.1/src/main/java/com/craisinlord/antarchy/content/client/PortalGunZoomClientState.java @@ -0,0 +1,72 @@ +package com.craisinlord.antarchy.content.client; + +import com.craisinlord.antarchy.content.item.PortalGunItem; +import net.minecraft.client.Minecraft; +import net.minecraft.client.Options; +import net.minecraft.world.item.ItemStack; + +public final class PortalGunZoomClientState { + private static int progress; + private static boolean zooming; + private static Double originalSensitivity; + + private PortalGunZoomClientState() { + } + + public static void tick(Minecraft minecraft, boolean held, boolean togglePressed) { + if (minecraft.player == null || minecraft.level == null) { + zooming = false; + progress = 0; + restoreSensitivity(minecraft.options); + return; + } + + ItemStack main = minecraft.player.getMainHandItem(); + ItemStack off = minecraft.player.getOffhandItem(); + boolean holdingPortalGun = held && (main.getItem() instanceof PortalGunItem || off.getItem() instanceof PortalGunItem); + if (togglePressed && holdingPortalGun) { + zooming = !zooming; + if (zooming && originalSensitivity == null) { + originalSensitivity = minecraft.options.sensitivity().get(); + } + } + if (!holdingPortalGun) { + zooming = false; + } + + progress = Math.max(0, Math.min(5, progress + (zooming ? 1 : -1))); + if (originalSensitivity != null) { + if (progress == 0) { + restoreSensitivity(minecraft.options); + } + } + } + + public static double applyFov(double fov, float partialTick, Options options) { + if (progress == 0 && !zooming) { + restoreSensitivity(options); + return fov; + } + double easedProgress = zooming ? progress - 1.0D + partialTick : progress + 1.0D - partialTick; + double zoomFactor = zoomFactor(easedProgress); + if (originalSensitivity != null) { + options.sensitivity().set(originalSensitivity * zoomFactor); + } + return fov * zoomFactor; + } + + private static double zoomFactor(double progress) { + return 0.1D + 0.9D * (1.0D - Math.sin(Math.toRadians(90.0D * Math.max(0.0D, Math.min(1.0D, progress / 5.0D))))); + } + + private static double zoomFactor(int progress) { + return zoomFactor((double) progress); + } + + private static void restoreSensitivity(Options options) { + if (originalSensitivity != null) { + options.sensitivity().set(originalSensitivity); + originalSensitivity = null; + } + } +} diff --git a/common-1.21.1/src/main/java/com/craisinlord/antarchy/content/client/game/AntarchyComputerGames.java b/common-1.21.1/src/main/java/com/craisinlord/antarchy/content/client/game/AntarchyComputerGames.java index d379407f..391ae0b0 100644 --- a/common-1.21.1/src/main/java/com/craisinlord/antarchy/content/client/game/AntarchyComputerGames.java +++ b/common-1.21.1/src/main/java/com/craisinlord/antarchy/content/client/game/AntarchyComputerGames.java @@ -2,12 +2,8 @@ package com.craisinlord.antarchy.content.client.game; import com.craisinlord.antos.api.client.game.ComputerGame; import com.craisinlord.antos.api.client.game.ComputerGameRegistry; -import com.craisinlord.antarchy.content.network.AntarchyGameNetworking; -import com.craisinlord.antarchy.content.network.AntarchyGamePayload; -import com.craisinlord.antarchy.content.client.AntarchyGameClientState; import net.minecraft.client.gui.Font; import net.minecraft.client.gui.GuiGraphics; -import net.minecraft.core.BlockPos; import net.minecraft.resources.ResourceLocation; import java.util.function.IntPredicate; import java.util.function.Predicate; @@ -17,11 +13,11 @@ public final class AntarchyComputerGames { private AntarchyComputerGames() {} public static void register() { - registerIfMissing(game("basilisk", "BASILISK", pos -> { - BasiliskProgram program = new BasiliskProgram(true, 0, 0, score -> AntarchyGameNetworking.saveBasiliskScore(pos, score)); - var loaded = new java.util.concurrent.atomic.AtomicBoolean(); - AntarchyGameNetworking.requestBasiliskState(pos); - return new GameSession(() -> { if (!loaded.get()) loaded.set(loadScores(pos, AntarchyGamePayload.BASILISK_STATE, program::setStoredScores)); program.tick(); }, program::render, key -> loaded.get() && program.keyPressed(key), ignored -> false); + registerIfMissing(game("basilisk", "BASILISK", () -> { + // AntOS 1.2.1 creates game sessions without passing the computer position. + // Keep Basilisk playable without a position-bound score callback. + BasiliskProgram program = new BasiliskProgram(true, 0, 0, score -> {}); + return new GameSession(program::tick, program::render, program::keyPressed, ignored -> false); })); } @@ -29,18 +25,6 @@ public final class AntarchyComputerGames { if (ComputerGameRegistry.get(game.id()) == null) ComputerGameRegistry.register(game); } - private static boolean loadScores(BlockPos pos, int action, java.util.function.BiConsumer setter) { - String response = AntarchyGameClientState.getGameState(pos, action); - if (response == null) return false; - if (response.isBlank()) { AntarchyGameClientState.clearGameState(pos, action); return true; } - String[] scores = response.split("\0", 2); - if (scores.length != 2) return false; - try { setter.accept(Integer.parseInt(scores[0]), Integer.parseInt(scores[1])); } - catch (NumberFormatException ignored) { return false; } - AntarchyGameClientState.clearGameState(pos, action); - return true; - } - private static ComputerGame game(String path, String title, ProgramFactory factory) { ResourceLocation id = ResourceLocation.fromNamespaceAndPath("antarchy", path); ResourceLocation disk = ResourceLocation.fromNamespaceAndPath("antarchy", path + "_game"); @@ -48,12 +32,12 @@ public final class AntarchyComputerGames { @Override public ResourceLocation id() { return id; } @Override public ResourceLocation diskId() { return disk; } @Override public String title() { return title; } - @Override public Session create(BlockPos position) { return factory.create(position); } + @Override public Session create() { return factory.create(); } }; } @FunctionalInterface - private interface ProgramFactory { ComputerGame.Session create(BlockPos position); } + private interface ProgramFactory { ComputerGame.Session create(); } @FunctionalInterface private interface GameRenderer { void render(GuiGraphics graphics, Font font, int x, int y, int width, int height); } private static final class GameSession implements ComputerGame.Session { diff --git a/common-1.21.1/src/main/java/com/craisinlord/antarchy/content/client/renderer/PortalGunPortalRenderer.java b/common-1.21.1/src/main/java/com/craisinlord/antarchy/content/client/renderer/PortalGunPortalRenderer.java index 636e28cf..07603ae1 100644 --- a/common-1.21.1/src/main/java/com/craisinlord/antarchy/content/client/renderer/PortalGunPortalRenderer.java +++ b/common-1.21.1/src/main/java/com/craisinlord/antarchy/content/client/renderer/PortalGunPortalRenderer.java @@ -1,11 +1,15 @@ package com.craisinlord.antarchy.content.client.renderer; +import com.craisinlord.antarchy.Antarchy; +import com.craisinlord.antarchy.config.AntarchySettings; import com.craisinlord.antarchy.content.client.model.PortalGunPortalModel; import com.craisinlord.antarchy.content.portalgun.PortalGunPortalEntity; import com.mojang.blaze3d.vertex.PoseStack; import com.mojang.blaze3d.vertex.VertexConsumer; -import net.minecraft.client.renderer.MultiBufferSource; +import net.minecraft.client.GraphicsStatus; +import net.minecraft.client.Minecraft; import net.minecraft.client.renderer.LightTexture; +import net.minecraft.client.renderer.MultiBufferSource; import net.minecraft.client.renderer.RenderType; import net.minecraft.client.renderer.entity.EntityRendererProvider; import net.minecraft.client.renderer.texture.OverlayTexture; @@ -24,23 +28,61 @@ public class PortalGunPortalRenderer extends GeoEntityRenderer RENDERERS = new LinkedHashMap<>(8, 0.75F, true); + private static final Set PROXY_RENDERERS = java.util.Collections.newSetFromMap(new IdentityHashMap<>()); + private static final ThreadLocal SECTION_BUDGET_DEADLINE = ThreadLocal.withInitial(() -> 0L); + private static final ThreadLocal SCHEDULED_SECTION_REBUILDS = ThreadLocal.withInitial(() -> 0); + private static final ThreadLocal SKIP_SET_NOT_DIRTY = ThreadLocal.withInitial(() -> false); + private static ClientLevel ownerLevel; + + private PortalGunPortalRendererPool() { + } + + public static LevelRenderer acquire(Minecraft minecraft, UUID destinationId, int width, int height) { + if (ownerLevel != minecraft.level) { + clear(); + ownerLevel = minecraft.level; + } + PortalRenderer state = RENDERERS.get(destinationId); + if (state == null) { + LevelRenderer renderer = new LevelRenderer( + minecraft, + minecraft.getEntityRenderDispatcher(), + minecraft.getBlockEntityRenderDispatcher(), + minecraft.renderBuffers() + ); + PROXY_RENDERERS.add(renderer); + renderer.setLevel(minecraft.level); + renderer.resize(width, height); + state = new PortalRenderer(renderer, width, height); + RENDERERS.put(destinationId, state); + trim(); + } else if (state.width() != width || state.height() != height) { + state.renderer().resize(width, height); + RENDERERS.put(destinationId, new PortalRenderer(state.renderer(), width, height)); + } + return state.renderer(); + } + + public static boolean isProxy(LevelRenderer renderer) { + return PROXY_RENDERERS.contains(renderer); + } + + public static void beginFrame() { + SECTION_BUDGET_DEADLINE.set(System.nanoTime() + PORTAL_SECTION_BUDGET_NANOS); + SCHEDULED_SECTION_REBUILDS.set(0); + SKIP_SET_NOT_DIRTY.set(false); + } + + public static void beginSceneBudget() { + SECTION_BUDGET_DEADLINE.set(System.nanoTime() + PORTAL_SECTION_BUDGET_NANOS); + SCHEDULED_SECTION_REBUILDS.set(0); + SKIP_SET_NOT_DIRTY.set(false); + } + + public static int scheduledSectionRebuilds() { + return SCHEDULED_SECTION_REBUILDS.get(); + } + + public static long remainingSectionBudgetNanos() { + return Math.max(0L, SECTION_BUDGET_DEADLINE.get() - System.nanoTime()); + } + + public static void rebuildSection(LevelRenderer renderer, SectionRenderDispatcher dispatcher, SectionRenderDispatcher.RenderSection section, RenderRegionCache cache) { + if (!isProxy(renderer)) { + dispatcher.rebuildSectionSync(section, cache); + SKIP_SET_NOT_DIRTY.set(false); + return; + } + if (remainingSectionBudgetNanos() <= 0L) { + SKIP_SET_NOT_DIRTY.set(true); + return; + } + section.rebuildSectionAsync(dispatcher, cache); + SCHEDULED_SECTION_REBUILDS.set(SCHEDULED_SECTION_REBUILDS.get() + 1); + SKIP_SET_NOT_DIRTY.set(false); + } + + public static void rebuildSectionAsync(LevelRenderer renderer, SectionRenderDispatcher dispatcher, SectionRenderDispatcher.RenderSection section, RenderRegionCache cache) { + if (!isProxy(renderer)) { + section.rebuildSectionAsync(dispatcher, cache); + SKIP_SET_NOT_DIRTY.set(false); + return; + } + if (remainingSectionBudgetNanos() <= 0L) { + SKIP_SET_NOT_DIRTY.set(true); + return; + } + section.rebuildSectionAsync(dispatcher, cache); + SCHEDULED_SECTION_REBUILDS.set(SCHEDULED_SECTION_REBUILDS.get() + 1); + SKIP_SET_NOT_DIRTY.set(false); + } + + public static void setNotDirty(SectionRenderDispatcher.RenderSection section) { + boolean leaveDirty = SKIP_SET_NOT_DIRTY.get(); + SKIP_SET_NOT_DIRTY.set(false); + if (!leaveDirty) { + section.setNotDirty(); + } + } + + public static void clear() { + for (PortalRenderer state : new ArrayList<>(RENDERERS.values())) { + LevelRenderer renderer = state.renderer(); + PortalGunPortalViewAreaManager.releaseRenderer(renderer); + try { + renderer.close(); + } finally { + PROXY_RENDERERS.remove(renderer); + } + } + RENDERERS.clear(); + ownerLevel = null; + } + + private static void trim() { + Iterator> iterator = RENDERERS.entrySet().iterator(); + while (RENDERERS.size() > MAX_RENDERERS && iterator.hasNext()) { + PortalRenderer state = iterator.next().getValue(); + iterator.remove(); + LevelRenderer renderer = state.renderer(); + PortalGunPortalViewAreaManager.releaseRenderer(renderer); + try { + renderer.close(); + } finally { + PROXY_RENDERERS.remove(renderer); + } + } + } + + private record PortalRenderer(LevelRenderer renderer, int width, int height) { + } +} diff --git a/common-1.21.1/src/main/java/com/craisinlord/antarchy/content/client/renderer/PortalGunPortalViewAreaManager.java b/common-1.21.1/src/main/java/com/craisinlord/antarchy/content/client/renderer/PortalGunPortalViewAreaManager.java new file mode 100644 index 00000000..4c3d6a06 --- /dev/null +++ b/common-1.21.1/src/main/java/com/craisinlord/antarchy/content/client/renderer/PortalGunPortalViewAreaManager.java @@ -0,0 +1,403 @@ +package com.craisinlord.antarchy.content.client.renderer; + +import com.craisinlord.antarchy.mixins.client.LevelRendererPortalViewAreaAccessor; +import com.mojang.blaze3d.vertex.VertexBuffer; +import it.unimi.dsi.fastutil.objects.ObjectArrayList; +import java.util.Collections; +import java.util.IdentityHashMap; +import java.util.Iterator; +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.Set; +import java.util.UUID; +import net.minecraft.client.CloudStatus; +import net.minecraft.client.Minecraft; +import net.minecraft.client.multiplayer.ClientLevel; +import net.minecraft.client.renderer.LevelRenderer; +import net.minecraft.client.renderer.SectionOcclusionGraph; +import net.minecraft.client.renderer.ViewArea; +import net.minecraft.client.renderer.blockentity.BlockEntityRenderDispatcher; +import net.minecraft.client.renderer.chunk.SectionRenderDispatcher; +import net.minecraft.core.SectionPos; +import net.minecraft.world.level.Level; +import net.minecraft.world.level.block.entity.BlockEntity; +import net.minecraft.world.phys.HitResult; +import net.minecraft.world.phys.Vec3; + +public final class PortalGunPortalViewAreaManager { + private static final int MAX_VIEW_AREAS = 8; + private static final LinkedHashMap VIEW_AREAS = new LinkedHashMap<>(8, 0.75F, true); + private static final Map SECTION_OWNERS = Collections.synchronizedMap(new IdentityHashMap<>()); + private static ClientLevel ownerLevel; + private static int ownerViewDistance = -1; + + private PortalGunPortalViewAreaManager() { + } + + public static Scope enter(Minecraft minecraft, LevelRenderer renderer, UUID destinationId, Vec3 cameraPos, net.minecraft.client.Camera portalCamera) { + LevelRendererPortalViewAreaAccessor accessor = (LevelRendererPortalViewAreaAccessor) renderer; + int viewDistance = minecraft.options.getEffectiveRenderDistance(); + if (ownerLevel != minecraft.level || ownerViewDistance != viewDistance) { + clear(); + ownerLevel = minecraft.level; + ownerViewDistance = viewDistance; + } + Scope sodiumScope = SodiumCompat.isLoaded() ? new SodiumScope(SodiumCompat.beginPortalView(portalCamera)) : null; + ViewKey viewKey = new ViewKey(renderer, destinationId); + ViewState state = VIEW_AREAS.get(viewKey); + if (state == null) { + ViewArea area = accessor.antarchy$getViewArea(); + SectionOcclusionGraph graph = accessor.antarchy$getSectionOcclusionGraph(); + ObjectArrayList sections = accessor.antarchy$getVisibleSections(); + state = new ViewState(area, graph, sections, accessor.antarchy$getGlobalBlockEntities()); + state.cloudBuffer = accessor.antarchy$getCloudBuffer(); + state.generateClouds = accessor.antarchy$getGenerateClouds(); + state.prevCloudX = accessor.antarchy$getPrevCloudX(); + state.prevCloudY = accessor.antarchy$getPrevCloudY(); + state.prevCloudZ = accessor.antarchy$getPrevCloudZ(); + state.prevCloudColor = accessor.antarchy$getPrevCloudColor(); + state.prevCloudsType = accessor.antarchy$getPrevCloudsType(); + for (SectionRenderDispatcher.RenderSection section : area.sections) { + ((PortalViewSectionMarker) section).antarchy$markPortalViewSection(); + SECTION_OWNERS.put(section, state); + } + VIEW_AREAS.put(viewKey, state); + trimOldViews(); + } + state.area.repositionCamera(cameraPos.x, cameraPos.z); + int sectionX = SectionPos.posToSectionCoord(cameraPos.x); + int sectionY = SectionPos.posToSectionCoord(cameraPos.y); + int sectionZ = SectionPos.posToSectionCoord(cameraPos.z); + state.cameraPos = cameraPos; + state.lastSectionX = sectionX; + state.lastSectionY = sectionY; + state.lastSectionZ = sectionZ; + Scope scope = new VanillaScope(renderer, accessor, state, minecraft.getBlockEntityRenderDispatcher()); + accessor.antarchy$setViewArea(state.area); + accessor.antarchy$setSectionOcclusionGraph(state.graph); + accessor.antarchy$setVisibleSections(state.visibleSections); + accessor.antarchy$setGlobalBlockEntities(state.globalBlockEntities); + accessor.antarchy$setLastCameraSectionX(sectionX); + accessor.antarchy$setLastCameraSectionY(sectionY); + accessor.antarchy$setLastCameraSectionZ(sectionZ); + return sodiumScope == null ? scope : new CombinedScope(scope, sodiumScope); + } + + public static boolean routeSectionCompiled(SectionRenderDispatcher.RenderSection section) { + ViewState state = SECTION_OWNERS.get(section); + if (state == null) { + return ((PortalViewSectionMarker) section).antarchy$isPortalViewSection(); + } + state.graph.onSectionCompiled(section); + return true; + } + + public static Vec3 cameraForSection(SectionRenderDispatcher.RenderSection section, Vec3 fallback) { + ViewState state = SECTION_OWNERS.get(section); + return state == null || state.cameraPos == null ? fallback : state.cameraPos; + } + + public static boolean updateGlobalBlockEntities(SectionRenderDispatcher.RenderSection section, Iterable removed, Iterable added) { + ViewState state = SECTION_OWNERS.get(section); + if (state == null) { + return ((PortalViewSectionMarker) section).antarchy$isPortalViewSection(); + } + synchronized (state.globalBlockEntities) { + for (BlockEntity blockEntity : removed) { + state.globalBlockEntities.remove(blockEntity); + } + for (BlockEntity blockEntity : added) { + state.globalBlockEntities.add(blockEntity); + } + } + return true; + } + + public static void onSectionDirty(int sectionX, int sectionY, int sectionZ, boolean playerChanged) { + for (ViewState state : VIEW_AREAS.values()) { + int sectionRange = state.area.getViewDistance() + 1; + if (Math.abs((long) sectionX - state.lastSectionX) > sectionRange + || Math.abs((long) sectionZ - state.lastSectionZ) > sectionRange + || sectionY < ownerLevel.getMinSection() + || sectionY > ownerLevel.getMaxSection()) { + continue; + } + state.area.setDirty(sectionX, sectionY, sectionZ, playerChanged); + } + } + + public static void onChunkLoaded(net.minecraft.world.level.ChunkPos pos) { + for (ViewState state : VIEW_AREAS.values()) { + state.graph.onChunkLoaded(pos); + markChunkDirty(state, pos); + } + } + + public static void onChunkDropped(net.minecraft.world.level.ChunkPos pos) { + for (ViewState state : VIEW_AREAS.values()) { + markChunkDirty(state, pos); + state.graph.invalidate(); + } + } + + private static void markChunkDirty(ViewState state, net.minecraft.world.level.ChunkPos pos) { + int sectionRange = state.area.getViewDistance() + 1; + if (Math.abs((long) pos.x - state.lastSectionX) <= sectionRange + && Math.abs((long) pos.z - state.lastSectionZ) <= sectionRange) { + for (int sectionY = ownerLevel.getMinSection(); sectionY <= ownerLevel.getMaxSection(); sectionY++) { + state.area.setDirty(pos.x, sectionY, pos.z, true); + } + } + } + + public static void clear() { + SodiumCompat.clear(); + for (ViewState state : VIEW_AREAS.values()) { + state.graph.waitAndReset(state.area); + } + VIEW_AREAS.clear(); + SECTION_OWNERS.clear(); + ownerLevel = null; + ownerViewDistance = -1; + } + + public static void releaseRenderer(LevelRenderer renderer) { + Iterator> iterator = VIEW_AREAS.entrySet().iterator(); + while (iterator.hasNext()) { + Map.Entry entry = iterator.next(); + if (entry.getKey().renderer() == renderer) { + ViewState state = entry.getValue(); + state.graph.waitAndReset(state.area); + removeOwners(state); + iterator.remove(); + } + } + } + + private static void trimOldViews() { + Iterator> iterator = VIEW_AREAS.entrySet().iterator(); + while (VIEW_AREAS.size() > MAX_VIEW_AREAS && iterator.hasNext()) { + ViewState state = iterator.next().getValue(); + iterator.remove(); + state.graph.waitAndReset(state.area); + removeOwners(state); + } + } + + private static void removeOwners(ViewState state) { + for (SectionRenderDispatcher.RenderSection section : state.area.sections) { + SECTION_OWNERS.remove(section); + } + } + + private static final class ViewState { + private final ViewArea area; + private final SectionOcclusionGraph graph; + private final ObjectArrayList visibleSections; + private final Set globalBlockEntities; + private VertexBuffer cloudBuffer; + private boolean generateClouds = true; + private int prevCloudX = Integer.MIN_VALUE; + private int prevCloudY = Integer.MIN_VALUE; + private int prevCloudZ = Integer.MIN_VALUE; + private Vec3 prevCloudColor = Vec3.ZERO; + private CloudStatus prevCloudsType; + private int lastSectionX; + private int lastSectionY; + private int lastSectionZ; + private volatile Vec3 cameraPos; + private double transparentSortX = Double.NaN; + private double transparentSortY = Double.NaN; + private double transparentSortZ = Double.NaN; + private double prevCamX = Double.NaN; + private double prevCamY = Double.NaN; + private double prevCamZ = Double.NaN; + private double prevCamRotX = Double.NaN; + private double prevCamRotY = Double.NaN; + + private ViewState(ViewArea area, SectionOcclusionGraph graph, ObjectArrayList visibleSections, Set globalBlockEntities) { + this.area = area; + this.graph = graph; + this.visibleSections = visibleSections; + this.globalBlockEntities = globalBlockEntities; + } + } + + private record ViewKey(LevelRenderer renderer, UUID destinationId) { + } + + public interface Scope extends AutoCloseable { + @Override + void close(); + } + + private static final class CombinedScope implements Scope { + private final Scope viewAreaScope; + private final Scope sodiumScope; + + private CombinedScope(Scope viewAreaScope, Scope sodiumScope) { + this.viewAreaScope = viewAreaScope; + this.sodiumScope = sodiumScope; + } + + @Override + public void close() { + try { + viewAreaScope.close(); + } finally { + sodiumScope.close(); + } + } + } + + private static final class SodiumScope implements Scope { + private final SodiumCompat.TerrainScope terrainScope; + + private SodiumScope(SodiumCompat.TerrainScope terrainScope) { + this.terrainScope = terrainScope; + } + + @Override + public void close() { + terrainScope.close(); + } + } + + private static final class VanillaScope implements Scope { + private final LevelRendererPortalViewAreaAccessor accessor; + private final ViewArea previousArea; + private final SectionOcclusionGraph previousGraph; + private final ObjectArrayList previousVisibleSections; + private final Set previousGlobalBlockEntities; + private final VertexBuffer previousCloudBuffer; + private final boolean previousGenerateClouds; + private final int previousCloudX; + private final int previousCloudY; + private final int previousCloudZ; + private final Vec3 previousCloudColor; + private final CloudStatus previousCloudsType; + private final int previousRainSoundTime; + private final int previousSectionX; + private final int previousSectionY; + private final int previousSectionZ; + private final double previousPrevCamX; + private final double previousPrevCamY; + private final double previousPrevCamZ; + private final double previousPrevCamRotX; + private final double previousPrevCamRotY; + private final double previousTransparentSortX; + private final double previousTransparentSortY; + private final double previousTransparentSortZ; + private final Vec3 previousDispatcherCamera; + private final SectionRenderDispatcher dispatcher; + private final BlockEntityRenderDispatcher blockEntityDispatcher; + private final Level previousBlockEntityLevel; + private final net.minecraft.client.Camera previousBlockEntityCamera; + private final HitResult previousBlockEntityHitResult; + private final ViewState state; + private boolean closed; + + private VanillaScope(LevelRenderer renderer, LevelRendererPortalViewAreaAccessor accessor, ViewState state, BlockEntityRenderDispatcher blockEntityDispatcher) { + this.accessor = accessor; + this.state = state; + this.blockEntityDispatcher = blockEntityDispatcher; + this.previousBlockEntityLevel = blockEntityDispatcher.level; + this.previousBlockEntityCamera = blockEntityDispatcher.camera; + this.previousBlockEntityHitResult = blockEntityDispatcher.cameraHitResult; + this.previousArea = accessor.antarchy$getViewArea(); + this.previousGraph = accessor.antarchy$getSectionOcclusionGraph(); + this.previousVisibleSections = accessor.antarchy$getVisibleSections(); + this.previousGlobalBlockEntities = accessor.antarchy$getGlobalBlockEntities(); + this.previousCloudBuffer = accessor.antarchy$getCloudBuffer(); + this.previousGenerateClouds = accessor.antarchy$getGenerateClouds(); + this.previousCloudX = accessor.antarchy$getPrevCloudX(); + this.previousCloudY = accessor.antarchy$getPrevCloudY(); + this.previousCloudZ = accessor.antarchy$getPrevCloudZ(); + this.previousCloudColor = accessor.antarchy$getPrevCloudColor(); + this.previousCloudsType = accessor.antarchy$getPrevCloudsType(); + this.previousRainSoundTime = accessor.antarchy$getRainSoundTime(); + this.previousSectionX = accessor.antarchy$getLastCameraSectionX(); + this.previousSectionY = accessor.antarchy$getLastCameraSectionY(); + this.previousSectionZ = accessor.antarchy$getLastCameraSectionZ(); + this.previousPrevCamX = accessor.antarchy$getPrevCamX(); + this.previousPrevCamY = accessor.antarchy$getPrevCamY(); + this.previousPrevCamZ = accessor.antarchy$getPrevCamZ(); + this.previousPrevCamRotX = accessor.antarchy$getPrevCamRotX(); + this.previousPrevCamRotY = accessor.antarchy$getPrevCamRotY(); + this.previousTransparentSortX = accessor.antarchy$getTransparentSortX(); + this.previousTransparentSortY = accessor.antarchy$getTransparentSortY(); + this.previousTransparentSortZ = accessor.antarchy$getTransparentSortZ(); + this.dispatcher = renderer.getSectionRenderDispatcher(); + this.previousDispatcherCamera = dispatcher.getCameraPosition(); + accessor.antarchy$setLastCameraSectionX(state.lastSectionX); + accessor.antarchy$setLastCameraSectionY(state.lastSectionY); + accessor.antarchy$setLastCameraSectionZ(state.lastSectionZ); + accessor.antarchy$setPrevCamX(state.prevCamX); + accessor.antarchy$setPrevCamY(state.prevCamY); + accessor.antarchy$setPrevCamZ(state.prevCamZ); + accessor.antarchy$setPrevCamRotX(state.prevCamRotX); + accessor.antarchy$setPrevCamRotY(state.prevCamRotY); + accessor.antarchy$setTransparentSortX(state.transparentSortX); + accessor.antarchy$setTransparentSortY(state.transparentSortY); + accessor.antarchy$setTransparentSortZ(state.transparentSortZ); + accessor.antarchy$setCloudBuffer(state.cloudBuffer); + accessor.antarchy$setGenerateClouds(state.generateClouds); + accessor.antarchy$setPrevCloudX(state.prevCloudX); + accessor.antarchy$setPrevCloudY(state.prevCloudY); + accessor.antarchy$setPrevCloudZ(state.prevCloudZ); + accessor.antarchy$setPrevCloudColor(state.prevCloudColor); + accessor.antarchy$setPrevCloudsType(state.prevCloudsType); + } + + @Override + public void close() { + if (closed) { + return; + } + closed = true; + state.lastSectionX = accessor.antarchy$getLastCameraSectionX(); + state.lastSectionY = accessor.antarchy$getLastCameraSectionY(); + state.lastSectionZ = accessor.antarchy$getLastCameraSectionZ(); + state.prevCamX = accessor.antarchy$getPrevCamX(); + state.prevCamY = accessor.antarchy$getPrevCamY(); + state.prevCamZ = accessor.antarchy$getPrevCamZ(); + state.prevCamRotX = accessor.antarchy$getPrevCamRotX(); + state.prevCamRotY = accessor.antarchy$getPrevCamRotY(); + state.transparentSortX = accessor.antarchy$getTransparentSortX(); + state.transparentSortY = accessor.antarchy$getTransparentSortY(); + state.transparentSortZ = accessor.antarchy$getTransparentSortZ(); + state.cloudBuffer = accessor.antarchy$getCloudBuffer(); + state.generateClouds = accessor.antarchy$getGenerateClouds(); + state.prevCloudX = accessor.antarchy$getPrevCloudX(); + state.prevCloudY = accessor.antarchy$getPrevCloudY(); + state.prevCloudZ = accessor.antarchy$getPrevCloudZ(); + state.prevCloudColor = accessor.antarchy$getPrevCloudColor(); + state.prevCloudsType = accessor.antarchy$getPrevCloudsType(); + accessor.antarchy$setViewArea(previousArea); + accessor.antarchy$setSectionOcclusionGraph(previousGraph); + accessor.antarchy$setVisibleSections(previousVisibleSections); + accessor.antarchy$setGlobalBlockEntities(previousGlobalBlockEntities); + accessor.antarchy$setCloudBuffer(previousCloudBuffer); + accessor.antarchy$setGenerateClouds(previousGenerateClouds); + accessor.antarchy$setPrevCloudX(previousCloudX); + accessor.antarchy$setPrevCloudY(previousCloudY); + accessor.antarchy$setPrevCloudZ(previousCloudZ); + accessor.antarchy$setPrevCloudColor(previousCloudColor); + accessor.antarchy$setPrevCloudsType(previousCloudsType); + accessor.antarchy$setRainSoundTime(previousRainSoundTime); + accessor.antarchy$setLastCameraSectionX(previousSectionX); + accessor.antarchy$setLastCameraSectionY(previousSectionY); + accessor.antarchy$setLastCameraSectionZ(previousSectionZ); + accessor.antarchy$setPrevCamX(previousPrevCamX); + accessor.antarchy$setPrevCamY(previousPrevCamY); + accessor.antarchy$setPrevCamZ(previousPrevCamZ); + accessor.antarchy$setPrevCamRotX(previousPrevCamRotX); + accessor.antarchy$setPrevCamRotY(previousPrevCamRotY); + accessor.antarchy$setTransparentSortX(previousTransparentSortX); + accessor.antarchy$setTransparentSortY(previousTransparentSortY); + accessor.antarchy$setTransparentSortZ(previousTransparentSortZ); + dispatcher.setCamera(previousDispatcherCamera); + blockEntityDispatcher.prepare(previousBlockEntityLevel, previousBlockEntityCamera, previousBlockEntityHitResult); + } + } +} diff --git a/common-1.21.1/src/main/java/com/craisinlord/antarchy/content/client/renderer/PortalGunPortalViewRenderer.java b/common-1.21.1/src/main/java/com/craisinlord/antarchy/content/client/renderer/PortalGunPortalViewRenderer.java index e5155afe..ddc62d33 100644 --- a/common-1.21.1/src/main/java/com/craisinlord/antarchy/content/client/renderer/PortalGunPortalViewRenderer.java +++ b/common-1.21.1/src/main/java/com/craisinlord/antarchy/content/client/renderer/PortalGunPortalViewRenderer.java @@ -1,12 +1,18 @@ package com.craisinlord.antarchy.content.client.renderer; import com.craisinlord.antarchy.Antarchy; +import com.craisinlord.antarchy.config.AntarchySettings; import com.craisinlord.antarchy.content.client.PortalGunEntityTransformationStack; import com.craisinlord.antarchy.content.client.PortalGunPortalRenderState; +import com.craisinlord.antarchy.mixins.client.MinecraftMainRenderTargetAccessor; +import com.craisinlord.antarchy.mixins.client.LevelRendererPortalTargetsAccessor; +import com.craisinlord.antarchy.mixins.client.PostChainTargetAccessor; import com.craisinlord.antarchy.content.portalgun.PortalGunTransformUtil; import com.craisinlord.antarchy.content.portalgun.PortalGunPortalEntity; import com.craisinlord.antarchy.content.portalgun.PortalGunWorldPortalShape; +import com.craisinlord.antarchy.mixins.client.CameraBasisAccessor; import com.mojang.blaze3d.platform.GlStateManager; +import com.mojang.blaze3d.pipeline.RenderTarget; import com.mojang.blaze3d.pipeline.TextureTarget; import com.mojang.blaze3d.systems.RenderSystem; import com.mojang.blaze3d.vertex.BufferBuilder; @@ -17,218 +23,443 @@ import com.mojang.blaze3d.vertex.PoseStack; import com.mojang.blaze3d.vertex.Tesselator; import com.mojang.blaze3d.vertex.VertexFormat; import java.util.ArrayList; +import java.util.Collections; +import java.util.HashSet; +import java.util.IdentityHashMap; import java.util.List; +import java.util.Set; import net.minecraft.client.Camera; import net.minecraft.client.DeltaTracker; +import net.minecraft.client.GraphicsStatus; import net.minecraft.client.Minecraft; import net.minecraft.client.renderer.MultiBufferSource; import net.minecraft.client.renderer.GameRenderer; +import net.minecraft.client.renderer.LevelRenderer; +import net.minecraft.client.renderer.PostChain; +import net.minecraft.client.renderer.ShaderInstance; import net.minecraft.resources.ResourceLocation; import net.minecraft.util.Mth; import net.minecraft.world.entity.Entity; +import net.minecraft.world.phys.AABB; import net.minecraft.world.phys.Vec3; import org.joml.Matrix4f; import org.joml.Vector4f; import org.joml.Quaternionf; public final class PortalGunPortalViewRenderer { - private static final int MAX_RECURSION_DEPTH = 3; - private static final double SURFACE_OFFSET = 0.03125D; - private static final double CAMERA_OFFSET = 0.0625D; + private static final int MAX_RECURSION_DEPTH = 1; + private static final int MAX_PORTAL_VIEWS_PER_FRAME = 2; + private static final double RENDER_TARGET_SCALE = 1.0D; + private static final boolean ENABLE_DESTINATION_SCENE_RENDERING = true; + private static final int PORTAL_TARGET_FILTER = 9729; + private static final double PORTAL_APERTURE_SCALE = 1.0D; + private static final double SURFACE_OFFSET = 0.04D; private static final double OVERLAY_OFFSET = 0.03135D; private static final ResourceLocation BLUE_OVERLAY = ResourceLocation.fromNamespaceAndPath(Antarchy.MODID, "textures/vfx/portal_gun_portal_blue.png"); private static final ResourceLocation ORANGE_OVERLAY = ResourceLocation.fromNamespaceAndPath(Antarchy.MODID, "textures/vfx/portal_gun_portal_orange.png"); private static final TextureTarget[] PORTAL_TARGETS = new TextureTarget[MAX_RECURSION_DEPTH + 1]; + private static TextureTarget depthBackup; + private static ShaderInstance portalViewShader; private static boolean renderingPortalView; - private static long lastRenderLogTick = Long.MIN_VALUE; - private static long lastHookLogTick = Long.MIN_VALUE; + private static boolean loggedRendererActivation; + private static String lastPortalCandidateState = ""; + private static final Set loggedPortalSceneRenders = new HashSet<>(); + private static final Set loggedPortalViewComposites = new HashSet<>(); + private static boolean loggedMissingPortalViewShader; + private static final TargetAllocationFailure[] TARGET_ALLOCATION_FAILURES = new TargetAllocationFailure[MAX_RECURSION_DEPTH + 1]; + private static long lastRenderFailureTick = Long.MIN_VALUE; private PortalGunPortalViewRenderer() { } - public static boolean isEnabled() { - return false; + public static void setPortalViewShader(ShaderInstance shader) { + portalViewShader = shader; + Antarchy.LOGGER.info("Portal gun view shader registered"); } - public static void render(Camera camera, Matrix4f poseMatrix, DeltaTracker tickCounter) { + public static void clearPortalViewShader() { + portalViewShader = null; + } + + public static boolean isEnabled() { + return ENABLE_DESTINATION_SCENE_RENDERING && AntarchySettings.portalGunSeeThroughPortals(); + } + + public static void releaseTargets() { + PortalSceneRenderTrace.reset(); + for (int i = 0; i < PORTAL_TARGETS.length; i++) { + TextureTarget target = PORTAL_TARGETS[i]; + if (target != null) { + PortalStencilTarget.release(target); + target.destroyBuffers(); + PORTAL_TARGETS[i] = null; + } + } + java.util.Arrays.fill(TARGET_ALLOCATION_FAILURES, null); + if (depthBackup != null) { + depthBackup.destroyBuffers(); + depthBackup = null; + } + } + + public static void render(Camera camera, Matrix4f poseMatrix, Matrix4f projectionMatrix, DeltaTracker tickCounter) { if (!isEnabled()) { return; } if (renderingPortalView) { return; } + PortalGunPortalRendererPool.beginFrame(); Minecraft minecraft = Minecraft.getInstance(); - if (minecraft.level == null || minecraft.gameRenderer == null || camera == null || poseMatrix == null || tickCounter == null) { - logRenderUnavailable(minecraft, camera, poseMatrix, tickCounter); + if (minecraft.level == null || minecraft.gameRenderer == null || camera == null || poseMatrix == null || projectionMatrix == null || tickCounter == null) { return; } - logRenderHook(minecraft, camera); + if (!loggedRendererActivation) { + loggedRendererActivation = true; + Antarchy.LOGGER.info("Portal gun view renderer hook active"); + } Entity cameraEntity = minecraft.getCameraEntity(); if (cameraEntity == null) { return; } - List portals = collectVisiblePortals(minecraft, camera); - if (portals.isEmpty()) { - logRenderEmpty(minecraft, camera.getPosition()); - return; - } - ensureTargets(minecraft); - if (PORTAL_TARGETS[0] == null) { - return; - } Vec3 cameraPos = camera.getPosition(); Vec3 rootLook = new Vec3(camera.getLookVector()).normalize(); Vec3 rootUp = new Vec3(camera.getUpVector()).normalize(); Matrix4f rootViewMatrix = new Matrix4f().rotation(PortalGunTransformUtil.orientationQuaternion(rootLook, rootUp).conjugate(new Quaternionf())); - RenderContext rootContext = new RenderContext(cameraPos, rootLook, rootUp, rootViewMatrix); - renderingPortalView = true; - RenderSystem.backupProjectionMatrix(); - try { - logRenderCollection(minecraft, cameraPos, portals); + List portals = collectVisiblePortals(minecraft, cameraPos); + if (portals.isEmpty()) { + return; + } + if (portalViewShader == null) { + if (!loggedMissingPortalViewShader) { + loggedMissingPortalViewShader = true; + Antarchy.LOGGER.error("Portal gun destination view shader is unavailable; skipping destination render"); + } + minecraft.getMainRenderTarget().bindWrite(true); for (PortalGunPortalEntity portal : portals) { - PortalGunPortalEntity linkedPortal = portal.getLinkedPortal(); - if (linkedPortal == null || !linkedPortal.isAlive()) { - logRenderSkip(minecraft, portal); + drawPortalOverlay(minecraft, cameraPos, rootViewMatrix, portal); + } + return; + } + Vec3 effectiveCameraPos = cameraPos; + Matrix4f rootProjectionMatrix = new Matrix4f(projectionMatrix); + float partialTick = tickCounter.getGameTimeDeltaPartialTick(true); + int targetWidth = getPortalTargetWidth(minecraft); + int targetHeight = getPortalTargetHeight(minecraft); + IdentityHashMap rootScreenClips = new IdentityHashMap<>(); + for (PortalGunPortalEntity portal : portals) { + if (!portal.shouldRenderFront(effectiveCameraPos)) { + continue; + } + ScreenClip clip = computeScreenClip(cameraPos, rootViewMatrix, rootProjectionMatrix, portal, partialTick, targetWidth, targetHeight); + if (clip != null) { + rootScreenClips.put(portal, clip); + } + } + List renderablePortals = new ArrayList<>(); + for (PortalGunPortalEntity portal : portals) { + if (rootScreenClips.containsKey(portal)) { + renderablePortals.add(portal); + } + } + List selectedPortals = renderablePortals.stream() + .filter(portal -> { + PortalGunPortalEntity linked = portal.getLinkedPortal(); + return linked != null && linked.isAlive(); + }) + .sorted((left, right) -> Integer.compare(rootScreenClips.get(right).area(), rootScreenClips.get(left).area())) + .toList(); + Set selectedPortalSet = Collections.newSetFromMap(new IdentityHashMap<>()); + selectedPortalSet.addAll(selectedPortals); + List renderOrder = new ArrayList<>(portals.size()); + renderOrder.addAll(selectedPortals); + for (PortalGunPortalEntity portal : portals) { + if (!selectedPortalSet.contains(portal)) { + renderOrder.add(portal); + } + } + long linkedPortalCount = renderablePortals.stream() + .filter(portal -> portal.getLinkedPortal() != null && portal.getLinkedPortal().isAlive()) + .count(); + String candidateState = portals.stream() + .map(portal -> portal.getUUID() + ":" + (portal.getLinkedPortal() != null && portal.getLinkedPortal().isAlive() ? portal.getLinkedPortal().getUUID() : "unlinked") + ":" + rootScreenClips.containsKey(portal)) + .sorted() + .toList() + + "|selected=" + selectedPortals.stream().map(portal -> portal.getUUID().toString()).sorted().toList() + + "|shader=" + (portalViewShader != null) + + "|target=" + targetWidth + "x" + targetHeight; + if (!candidateState.equals(lastPortalCandidateState)) { + lastPortalCandidateState = candidateState; + if (!selectedPortals.isEmpty()) { + String selectedPairKey = selectedPortals.stream() + .map(portal -> portal.getUUID() + "->" + portal.getLinkedPortal().getUUID()) + .sorted() + .toList() + .toString(); + PortalSceneRenderTrace.arm(selectedPairKey); + } + Antarchy.LOGGER.info( + "Portal gun view candidate state changed portals={} frontFacing={} linked={} selected={} shader={} target={}x{}", + portals.size(), + renderablePortals.size(), + linkedPortalCount, + selectedPortals.size(), + portalViewShader != null, + targetWidth, + targetHeight + ); + } + if (selectedPortals.isEmpty()) { + minecraft.getMainRenderTarget().bindWrite(true); + for (PortalGunPortalEntity portal : portals) { + drawPortalOverlay(minecraft, cameraPos, rootViewMatrix, portal); + } + return; + } + RenderContext rootContext = new RenderContext(effectiveCameraPos, rootLook, rootUp, rootViewMatrix); + PortalViewBudget viewBudget = new PortalViewBudget(MAX_PORTAL_VIEWS_PER_FRAME); + renderingPortalView = true; + boolean projectionBackedUp = false; + try { + RenderSystem.backupProjectionMatrix(); + projectionBackedUp = true; + for (PortalGunPortalEntity portal : renderOrder) { + if (!selectedPortalSet.contains(portal)) { minecraft.getMainRenderTarget().bindWrite(true); - drawPortalOverlay(minecraft, cameraPos, poseMatrix, portal); + drawPortalOverlay(minecraft, cameraPos, rootViewMatrix, portal); + continue; + } + PortalGunPortalEntity linkedPortal = portal.getLinkedPortal(); + if (linkedPortal == null || !linkedPortal.isAlive()) { + minecraft.getMainRenderTarget().bindWrite(true); + drawPortalOverlay(minecraft, cameraPos, rootViewMatrix, portal); + continue; + } + boolean rendered; + try { + rendered = renderPortalSurfaceDirect(minecraft, cameraEntity, tickCounter, rootContext, rootProjectionMatrix, portal, linkedPortal, rootScreenClips.get(portal)); + } catch (RuntimeException exception) { + logRenderFailure(minecraft, exception); + RenderSystem.restoreProjectionMatrix(); + projectionBackedUp = false; + RenderSystem.backupProjectionMatrix(); + projectionBackedUp = true; + minecraft.getMainRenderTarget().bindWrite(true); + drawPortalOverlay(minecraft, cameraPos, rootViewMatrix, portal); + continue; + } + if (!rendered) { + minecraft.getMainRenderTarget().bindWrite(true); + drawPortalOverlay(minecraft, cameraPos, rootViewMatrix, portal); continue; } - int textureId = renderPortalSurface(minecraft, cameraEntity, tickCounter, rootContext, portal, linkedPortal, 0); RenderSystem.restoreProjectionMatrix(); + projectionBackedUp = false; RenderSystem.backupProjectionMatrix(); + projectionBackedUp = true; minecraft.getMainRenderTarget().bindWrite(true); - drawPortalQuad(cameraPos, poseMatrix, portal, textureId); - minecraft.getEntityRenderDispatcher().prepare(minecraft.level, camera, cameraEntity); - drawPortalOverlay(minecraft, cameraPos, poseMatrix, portal); + ScreenClip clip = rootScreenClips.get(portal); + String portalPairKey = portal.getUUID() + "->" + linkedPortal.getUUID(); + boolean composited; + try (PortalSceneRenderTrace.Phase ignored = PortalSceneRenderTrace.begin(portalPairKey, "direct-framebuffer", "target=" + targetWidth + "x" + targetHeight)) { + composited = true; + drawAnimatedPortalOverlay(minecraft, cameraPos, rootViewMatrix, portal); + } + if (composited && loggedPortalViewComposites.add(portalPairKey)) { + Antarchy.LOGGER.info("Portal gun destination view rendered directly source={} destination={} target={}x{}", portal.getId(), linkedPortal.getId(), targetWidth, targetHeight); + } } } catch (RuntimeException exception) { - Antarchy.LOGGER.error("Portal gun portal view render failed", exception); + logRenderFailure(minecraft, exception); } finally { - RenderSystem.restoreProjectionMatrix(); - renderingPortalView = false; - } - } - - private static void logRenderUnavailable(Minecraft minecraft, Camera camera, Matrix4f poseMatrix, DeltaTracker tickCounter) { - if (minecraft.level == null) { - return; - } - long gameTime = minecraft.level.getGameTime(); - if (lastHookLogTick != Long.MIN_VALUE && gameTime - lastHookLogTick < 100L) { - return; - } - lastHookLogTick = gameTime; - Antarchy.LOGGER.info("Portal gun render unavailable level={} gameRenderer={} camera={} pose={} tickCounter={}", minecraft.level != null, minecraft.gameRenderer != null, camera != null, poseMatrix != null, tickCounter != null); - } - - private static void logRenderHook(Minecraft minecraft, Camera camera) { - if (minecraft.level == null) { - return; - } - long gameTime = minecraft.level.getGameTime(); - if (lastHookLogTick != Long.MIN_VALUE && gameTime - lastHookLogTick < 100L) { - return; - } - lastHookLogTick = gameTime; - int portalCount = 0; - for (Entity entity : minecraft.level.entitiesForRendering()) { - if (entity instanceof PortalGunPortalEntity portal && portal.isAlive()) { - portalCount++; + try { + try { + restorePortalDrawState(); + } catch (RuntimeException exception) { + logRenderFailure(minecraft, exception); + } + try { + minecraft.getMainRenderTarget().bindWrite(true); + } catch (RuntimeException exception) { + logRenderFailure(minecraft, exception); + } + try { + minecraft.getEntityRenderDispatcher().prepare(minecraft.level, camera, minecraft.crosshairPickEntity); + } catch (RuntimeException exception) { + logRenderFailure(minecraft, exception); + } + try { + minecraft.levelRenderer.prepareCullFrustum(cameraPos, rootViewMatrix, rootProjectionMatrix); + } catch (RuntimeException exception) { + logRenderFailure(minecraft, exception); + } + if (projectionBackedUp) { + try { + RenderSystem.restoreProjectionMatrix(); + } catch (RuntimeException exception) { + logRenderFailure(minecraft, exception); + } + } + } finally { + renderingPortalView = false; } } - Antarchy.LOGGER.info("Portal gun render hook tick stage camera={} portals={}", camera.getPosition(), portalCount); - } - - private static List collectVisiblePortals(Minecraft minecraft, Camera camera) { - return collectVisiblePortals(minecraft, camera.getPosition()); } private static List collectVisiblePortals(Minecraft minecraft, Vec3 cameraPos) { - List portals = new ArrayList<>(); - for (Entity entity : minecraft.level.entitiesForRendering()) { - if (!(entity instanceof PortalGunPortalEntity portal) || !portal.isAlive()) { - continue; - } - if (portal.distanceToSqr(cameraPos) > 16384.0D) { - continue; - } - portals.add(portal); - } + AABB searchBounds = new AABB(cameraPos, cameraPos).inflate(128.0D); + List portals = minecraft.level.getEntitiesOfClass( + PortalGunPortalEntity.class, + searchBounds, + Entity::isAlive + ); + portals.removeIf(portal -> portal.distanceToSqr(cameraPos) > 16384.0D); portals.sort((left, right) -> Double.compare(left.distanceToSqr(cameraPos), right.distanceToSqr(cameraPos))); return portals; } - private static void logRenderEmpty(Minecraft minecraft, Vec3 cameraPos) { - if (minecraft.level == null) { - return; - } - long gameTime = minecraft.level.getGameTime(); - if (lastRenderLogTick != Long.MIN_VALUE && gameTime - lastRenderLogTick < 100L) { - return; - } - lastRenderLogTick = gameTime; - int portalCount = 0; - int linkedCount = 0; - int frontCount = 0; - for (Entity entity : minecraft.level.entitiesForRendering()) { - if (!(entity instanceof PortalGunPortalEntity portal) || !portal.isAlive()) { - continue; - } - portalCount++; - if (portal.getLinkedPortalId() != null) { - linkedCount++; - } - if (portal.shouldRenderFront(cameraPos)) { - frontCount++; - } - } - Antarchy.LOGGER.info("Portal gun render collected count=0 totalPortals={} linkedPortals={} frontPortals={} camera={}", portalCount, linkedCount, frontCount, cameraPos); - } - private static void logRenderCollection(Minecraft minecraft, Vec3 cameraPos, List portals) { - if (minecraft.level == null) { - return; - } - long gameTime = minecraft.level.getGameTime(); - if (lastRenderLogTick != Long.MIN_VALUE && gameTime - lastRenderLogTick < 100L) { - return; - } - lastRenderLogTick = gameTime; - Antarchy.LOGGER.info("Portal gun render collected count={} camera={}", portals.size(), cameraPos); - for (PortalGunPortalEntity portal : portals) { - PortalGunWorldPortalShape.PortalLocalCoords coords = portal.getWorldPortalShape().localCoords(cameraPos); - Antarchy.LOGGER.info("Portal gun render portal portal={} side={} linked={} center={} facing={} up={} cameraDepth={} cameraHorizontal={} cameraVertical={}", portal.getUUID(), portal.getPortalSide(), portal.getLinkedPortalId(), portal.position(), portal.getFacingDirection(), portal.getUpAxis(), coords.depth(), coords.horizontal(), coords.vertical()); - } - } - - private static void logRenderSkip(Minecraft minecraft, PortalGunPortalEntity portal) { - if (minecraft.level == null) { - return; - } - long gameTime = minecraft.level.getGameTime(); - if (lastRenderLogTick != Long.MIN_VALUE && gameTime - lastRenderLogTick < 20L) { - return; - } - lastRenderLogTick = gameTime; - Antarchy.LOGGER.info("Portal gun render skipped missing linked portal portal={} side={} linked={}", portal.getUUID(), portal.getPortalSide(), portal.getLinkedPortalId()); - } - - private static void ensureTargets(Minecraft minecraft) { - int width = minecraft.getWindow().getWidth(); - int height = minecraft.getWindow().getHeight(); + private static void ensureTarget(Minecraft minecraft, int depth) { + int width = getPortalTargetWidth(minecraft); + int height = getPortalTargetHeight(minecraft); if (width <= 0 || height <= 0) { return; } - for (int i = 0; i < PORTAL_TARGETS.length; i++) { - TextureTarget target = PORTAL_TARGETS[i]; - if (target == null) { - target = new TextureTarget(width, height, true, Minecraft.ON_OSX); - target.setClearColor(0.0F, 0.0F, 0.0F, 1.0F); - PORTAL_TARGETS[i] = target; - continue; + TextureTarget target = PORTAL_TARGETS[depth]; + if (target == null) { + target = new TextureTarget(width, height, true, Minecraft.ON_OSX); + target.setClearColor(0.0F, 0.0F, 0.0F, 1.0F); + PORTAL_TARGETS[depth] = target; + } else if (target.width != width || target.height != height) { + target.resize(width, height, Minecraft.ON_OSX); + } + target.setFilterMode(PORTAL_TARGET_FILTER); + } + + private static boolean ensureTargetSafely(Minecraft minecraft, int depth) { + int width = getPortalTargetWidth(minecraft); + int height = getPortalTargetHeight(minecraft); + TargetAllocationFailure failure = TARGET_ALLOCATION_FAILURES[depth]; + if (failure != null && failure.width() == width && failure.height() == height) { + return false; + } + try { + TARGET_ALLOCATION_FAILURES[depth] = null; + ensureTarget(minecraft, depth); + return PORTAL_TARGETS[depth] != null; + } catch (RuntimeException exception) { + TARGET_ALLOCATION_FAILURES[depth] = new TargetAllocationFailure(width, height); + Antarchy.LOGGER.error("Portal gun render target allocation failed at recursion depth {}", depth, exception); + return false; + } + } + + private static int getPortalTargetWidth(Minecraft minecraft) { + return Math.max(1, (int) Math.ceil(minecraft.getMainRenderTarget().width * RENDER_TARGET_SCALE)); + } + + private static int getPortalTargetHeight(Minecraft minecraft) { + return Math.max(1, (int) Math.ceil(minecraft.getMainRenderTarget().height * RENDER_TARGET_SCALE)); + } + + private static void logRenderFailure(Minecraft minecraft, RuntimeException exception) { + long gameTime = minecraft.level == null ? Long.MIN_VALUE : minecraft.level.getGameTime(); + if (lastRenderFailureTick == Long.MIN_VALUE || gameTime == Long.MIN_VALUE || gameTime - lastRenderFailureTick >= 100L) { + lastRenderFailureTick = gameTime; + Antarchy.LOGGER.error("Portal gun portal view render failed", exception); + } + } + + private static boolean renderPortalSurfaceDirect( + Minecraft minecraft, + Entity cameraEntity, + DeltaTracker tickCounter, + RenderContext currentContext, + Matrix4f baseProjectionMatrix, + PortalGunPortalEntity sourcePortal, + PortalGunPortalEntity destinationPortal, + ScreenClip clip + ) { + RenderTarget target = minecraft.getMainRenderTarget(); + if (clip == null || portalViewShader == null) { + return false; + } + int width = target.width; + int height = target.height; + if (depthBackup == null) { + depthBackup = new TextureTarget(width, height, true, Minecraft.ON_OSX); + } else if (depthBackup.width != width || depthBackup.height != height) { + depthBackup.resize(width, height, Minecraft.ON_OSX); + } + RenderContext transformedContext = transformContext(currentContext, sourcePortal, destinationPortal); + float partialTick = tickCounter.getGameTimeDeltaPartialTick(true); + Matrix4f clippedProjection = applyPortalClipPlane(baseProjectionMatrix, transformedContext, destinationPortal); + PortalCamera portalCamera = new PortalCamera(); + portalCamera.setup(minecraft.level, cameraEntity, partialTick, transformedContext.cameraPos(), transformedContext.look(), transformedContext.up()); + String portalPairKey = sourcePortal.getUUID() + "->" + destinationPortal.getUUID(); + LevelRenderer proxyRenderer = PortalGunPortalRendererPool.acquire(minecraft, destinationPortal.getUUID(), width, height); + LevelRendererPortalTargetsAccessor levelRendererTargets = (LevelRendererPortalTargetsAccessor) proxyRenderer; + PostChain entityEffect = levelRendererTargets.antarchy$getEntityEffect(); + PostChain transparencyChain = levelRendererTargets.antarchy$getTransparencyChain(); + PostChainTargetAccessor entityEffectTarget = entityEffect == null ? null : (PostChainTargetAccessor) entityEffect; + PostChainTargetAccessor transparencyTarget = transparencyChain == null ? null : (PostChainTargetAccessor) transparencyChain; + RenderTarget previousEntityEffectTarget = entityEffectTarget == null ? null : entityEffectTarget.antarchy$getScreenTarget(); + RenderTarget previousTransparencyTarget = transparencyTarget == null ? null : transparencyTarget.antarchy$getScreenTarget(); + PortalStencilTarget.Scope stencilScope = null; + boolean contextPushed = false; + try { + if (entityEffectTarget != null) { + entityEffectTarget.antarchy$setScreenTarget(target); } - if (target.width != width || target.height != height) { - target.resize(width, height, Minecraft.ON_OSX); + if (transparencyTarget != null) { + transparencyTarget.antarchy$setScreenTarget(target); + } + minecraft.gameRenderer.resetProjectionMatrix(clippedProjection); + int maskTexture = minecraft.getTextureManager().getTexture(BLUE_OVERLAY).getId(); + try (PortalSceneRenderTrace.Phase ignored = PortalSceneRenderTrace.begin(portalPairKey, "direct-stencil-mask", "clip=" + clip.width() + "x" + clip.height())) { + stencilScope = PortalStencilTarget.beginDirect(target, depthBackup, + () -> drawPortalQuad(currentContext.cameraPos(), currentContext.viewMatrix(), baseProjectionMatrix, width, height, sourcePortal, partialTick, maskTexture, clip)); + } + RenderSystem.enableScissor(clip.x(), clip.y(), clip.width(), clip.height()); + PortalGunPortalRenderState.pushPortalView(new PortalGunPortalRenderState.PortalRenderContext( + sourcePortal.getWorldPortalShape(), destinationPortal.getWorldPortalShape(), transformedContext.cameraPos(), + transformedContext.look(), transformedContext.up(), transformedContext.viewMatrix(), clippedProjection, + 0, true, destinationPortal.getUUID())); + contextPushed = true; + proxyRenderer.prepareCullFrustum(portalCamera.getPosition(), transformedContext.viewMatrix(), clippedProjection); + PortalGunPortalViewAreaManager.Scope areaScope = PortalGunPortalViewAreaManager.enter( + minecraft, proxyRenderer, destinationPortal.getUUID(), transformedContext.cameraPos(), portalCamera); + try { + try (PortalSceneRenderTrace.Phase ignored = PortalSceneRenderTrace.begin(portalPairKey, "direct-manual-scene", "target=" + width + "x" + height)) { + PortalGunProxySceneRenderer.render(minecraft, proxyRenderer, portalCamera, partialTick, + transformedContext.viewMatrix(), clippedProjection, portalPairKey); + } + } finally { + areaScope.close(); + } + PortalStencilTarget.activate(target); + RenderSystem.enableScissor(clip.x(), clip.y(), clip.width(), clip.height()); + minecraft.getEntityRenderDispatcher().prepare(minecraft.level, portalCamera, cameraEntity); + renderPortalTransitionEntities(minecraft, transformedContext.cameraPos(), partialTick, sourcePortal, destinationPortal); + return true; + } finally { + try { + if (stencilScope != null) { + stencilScope.close(); + } + if (contextPushed) { + PortalGunPortalRenderState.popPortalView(true); + } + } finally { + if (transparencyTarget != null) { + transparencyTarget.antarchy$setScreenTarget(previousTransparencyTarget); + } + if (entityEffectTarget != null) { + entityEffectTarget.antarchy$setScreenTarget(previousEntityEffectTarget); + } + restorePortalDrawState(); + target.bindWrite(true); + minecraft.gameRenderer.resetProjectionMatrix(baseProjectionMatrix); } } } @@ -237,81 +468,189 @@ public final class PortalGunPortalViewRenderer { Minecraft minecraft, Entity cameraEntity, DeltaTracker tickCounter, + PortalViewBudget viewBudget, RenderContext currentContext, + Matrix4f baseProjectionMatrix, PortalGunPortalEntity sourcePortal, PortalGunPortalEntity destinationPortal, - int depth + int depth, + ScreenClip precomputedClip ) { + String portalPairKey = sourcePortal.getUUID() + "->" + destinationPortal.getUUID(); + try (PortalSceneRenderTrace.Phase ignored = PortalSceneRenderTrace.begin(portalPairKey, "ensure-target", depth + ":" + getPortalTargetWidth(minecraft) + "x" + getPortalTargetHeight(minecraft))) { + if (!ensureTargetSafely(minecraft, depth)) { + return -1; + } + } TextureTarget target = PORTAL_TARGETS[depth]; RenderContext transformedContext = transformContext(currentContext, sourcePortal, destinationPortal); + float partialTick = tickCounter.getGameTimeDeltaPartialTick(true); + ScreenClip clip = precomputedClip == null + ? computeScreenClip(currentContext.cameraPos(), currentContext.viewMatrix(), baseProjectionMatrix, sourcePortal, partialTick, target.width, target.height) + : precomputedClip; + if (clip == null) { + return -1; + } + if (!viewBudget.tryReserve()) { + return -1; + } + Matrix4f clippedProjectionMatrix = applyPortalClipPlane(baseProjectionMatrix, transformedContext, destinationPortal); + boolean renderAll = true; PortalCamera portalCamera = new PortalCamera(); portalCamera.setup(minecraft.level, cameraEntity, tickCounter.getGameTimeDeltaPartialTick(true), transformedContext.cameraPos(), transformedContext.look(), transformedContext.up()); - target.bindWrite(true); - target.clear(Minecraft.ON_OSX); - double fov = minecraft.options.fov().get(); - Matrix4f baseProjectionMatrix = minecraft.gameRenderer.getProjectionMatrix(fov); - Matrix4f clippedProjectionMatrix = applyPortalClipPlane(baseProjectionMatrix, transformedContext, destinationPortal); - minecraft.gameRenderer.resetProjectionMatrix(clippedProjectionMatrix); - ScreenClip clip = computeScreenClip(minecraft, currentContext.cameraPos(), currentContext.viewMatrix(), baseProjectionMatrix, sourcePortal); - boolean clipEnabled = clip != null; - boolean renderAll = clip == null || clip.area() >= widthTimesHeight(minecraft); - if (clipEnabled) { - RenderSystem.enableScissor(clip.x(), clip.y(), clip.width(), clip.height()); + RenderTarget previousMainTarget = minecraft.getMainRenderTarget(); + MinecraftMainRenderTargetAccessor targetAccessor = (MinecraftMainRenderTargetAccessor) minecraft; + LevelRenderer proxyRenderer; + try (PortalSceneRenderTrace.Phase ignored = PortalSceneRenderTrace.begin(portalPairKey, "acquire-proxy", "target=" + target.width + "x" + target.height + " renderDistance=" + minecraft.options.getEffectiveRenderDistance())) { + proxyRenderer = PortalGunPortalRendererPool.acquire(minecraft, destinationPortal.getUUID(), target.width, target.height); } - PortalGunPortalRenderState.PortalRenderContext portalRenderContext = new PortalGunPortalRenderState.PortalRenderContext( - sourcePortal.getWorldPortalShape(), - destinationPortal.getWorldPortalShape(), - transformedContext.cameraPos(), - transformedContext.look(), - transformedContext.up(), - transformedContext.viewMatrix(), - clippedProjectionMatrix, - depth, - renderAll - ); - PortalGunPortalRenderState.pushPortalView(portalRenderContext); + LevelRendererPortalTargetsAccessor levelRendererTargets = (LevelRendererPortalTargetsAccessor) proxyRenderer; + PostChain entityEffect = levelRendererTargets.antarchy$getEntityEffect(); + PostChain transparencyChain = levelRendererTargets.antarchy$getTransparencyChain(); + PostChainTargetAccessor entityEffectTarget = entityEffect == null ? null : (PostChainTargetAccessor) entityEffect; + PostChainTargetAccessor transparencyTarget = transparencyChain == null ? null : (PostChainTargetAccessor) transparencyChain; + RenderTarget previousEntityEffectTarget = entityEffectTarget == null ? null : entityEffectTarget.antarchy$getScreenTarget(); + RenderTarget previousTransparencyTarget = transparencyTarget == null ? null : transparencyTarget.antarchy$getScreenTarget(); + targetAccessor.antarchy$setMainRenderTarget(target); + boolean portalRenderStatePushed = false; + PortalStencilTarget.Scope stencilScope = null; try { - minecraft.levelRenderer.prepareCullFrustum(portalCamera.getPosition(), transformedContext.viewMatrix(), clippedProjectionMatrix); - minecraft.levelRenderer.renderLevel(tickCounter, false, portalCamera, minecraft.gameRenderer, minecraft.gameRenderer.lightTexture(), transformedContext.viewMatrix(), clippedProjectionMatrix); - target.bindWrite(true); - minecraft.getEntityRenderDispatcher().prepare(minecraft.level, portalCamera, cameraEntity); - renderPortalTransitionEntities(minecraft, transformedContext.cameraPos(), tickCounter.getGameTimeDeltaPartialTick(true), sourcePortal, destinationPortal); + if (entityEffectTarget != null) { + entityEffectTarget.antarchy$setScreenTarget(target); + } + if (transparencyTarget != null) { + transparencyTarget.antarchy$setScreenTarget(target); + } + try (PortalSceneRenderTrace.Phase ignored = PortalSceneRenderTrace.begin(portalPairKey, "clear-and-stencil", "target=" + target.width + "x" + target.height + " clip=" + clip.width() + "x" + clip.height())) { + target.bindWrite(true); + RenderSystem.disableScissor(); + RenderSystem.depthMask(true); + RenderSystem.colorMask(true, true, true, true); + target.clear(Minecraft.ON_OSX); + minecraft.gameRenderer.resetProjectionMatrix(clippedProjectionMatrix); + int maskTextureId = minecraft.getTextureManager().getTexture(BLUE_OVERLAY).getId(); + stencilScope = PortalStencilTarget.begin( + target, + () -> drawPortalQuad(currentContext.cameraPos(), currentContext.viewMatrix(), baseProjectionMatrix, target.width, target.height, sourcePortal, partialTick, maskTextureId, clip) + ); + } + RenderSystem.enableScissor(clip.x(), clip.y(), clip.width(), clip.height()); + PortalGunPortalRenderState.PortalRenderContext portalRenderContext = new PortalGunPortalRenderState.PortalRenderContext( + sourcePortal.getWorldPortalShape(), + destinationPortal.getWorldPortalShape(), + transformedContext.cameraPos(), + transformedContext.look(), + transformedContext.up(), + transformedContext.viewMatrix(), + clippedProjectionMatrix, + depth, + renderAll, + destinationPortal.getUUID() + ); + PortalGunPortalRenderState.pushPortalView(portalRenderContext); + portalRenderStatePushed = true; + try (PortalSceneRenderTrace.Phase ignored = PortalSceneRenderTrace.begin(portalPairKey, "prepare-portal-view", "camera=" + transformedContext.cameraPos())) { + proxyRenderer.prepareCullFrustum(portalCamera.getPosition(), transformedContext.viewMatrix(), clippedProjectionMatrix); + } + try (PortalSceneRenderTrace.Phase ignored = PortalSceneRenderTrace.begin(portalPairKey, "activate-view-area", "destination=" + destinationPortal.getUUID())) { + PortalGunPortalViewAreaManager.Scope scope = PortalGunPortalViewAreaManager.enter(minecraft, proxyRenderer, destinationPortal.getUUID(), transformedContext.cameraPos(), portalCamera); + ignored.close(); + try { + try (PortalSceneRenderTrace.Phase sceneTrace = PortalSceneRenderTrace.begin(portalPairKey, "manual-scene-subpass", "target=" + target.width + "x" + target.height)) { + PortalGunProxySceneRenderer.render(minecraft, proxyRenderer, portalCamera, partialTick, transformedContext.viewMatrix(), clippedProjectionMatrix, portalPairKey); + } + } finally { + try (PortalSceneRenderTrace.Phase releaseTrace = PortalSceneRenderTrace.begin(portalPairKey, "release-view-area", "destination=" + destinationPortal.getUUID())) { + scope.close(); + } + } + } + PortalStencilTarget.activate(target); + RenderSystem.enableScissor(clip.x(), clip.y(), clip.width(), clip.height()); + try (PortalSceneRenderTrace.Phase ignored = PortalSceneRenderTrace.begin(portalPairKey, "transition-entities", "source=" + sourcePortal.getId())) { + target.bindWrite(true); + minecraft.getEntityRenderDispatcher().prepare(minecraft.level, portalCamera, cameraEntity); + renderPortalTransitionEntities(minecraft, transformedContext.cameraPos(), tickCounter.getGameTimeDeltaPartialTick(true), sourcePortal, destinationPortal); + } catch (RuntimeException exception) { + logRenderFailure(minecraft, exception); + } + if (loggedPortalSceneRenders.add(portalPairKey)) { + Antarchy.LOGGER.info("Portal gun destination scene render pass completed source={} destination={} depth={} target={}x{} texture={} scissor={}x{}+{},{} camera={}", sourcePortal.getId(), destinationPortal.getId(), depth, target.width, target.height, target.getColorTextureId(), clip.width(), clip.height(), clip.x(), clip.y(), transformedContext.cameraPos()); + } if (depth < MAX_RECURSION_DEPTH) { List nestedPortals = collectVisiblePortals(minecraft, transformedContext.cameraPos()); - Matrix4f nestedPose = new Matrix4f().identity(); for (PortalGunPortalEntity nestedPortal : nestedPortals) { + if (!viewBudget.hasRemaining()) { + break; + } PortalGunPortalEntity nestedLinked = nestedPortal.getLinkedPortal(); if (nestedLinked == null || !nestedLinked.isAlive() || nestedPortal == destinationPortal || nestedPortal == sourcePortal) { continue; } - if (clip != null && clip.area() < 6400) { + ScreenClip nestedClip = computeScreenClip(transformedContext.cameraPos(), transformedContext.viewMatrix(), baseProjectionMatrix, nestedPortal, partialTick, target.width, target.height); + if (nestedClip == null) { continue; } - int nestedTextureId = renderPortalSurface(minecraft, cameraEntity, tickCounter, transformedContext, nestedPortal, nestedLinked, depth + 1); - RenderSystem.restoreProjectionMatrix(); - RenderSystem.backupProjectionMatrix(); - target.bindWrite(true); - drawPortalQuad(transformedContext.cameraPos(), nestedPose, nestedPortal, nestedTextureId); - drawPortalOverlay(minecraft, transformedContext.cameraPos(), nestedPose, nestedPortal); + int nestedTextureId; + try { + nestedTextureId = renderPortalSurface(minecraft, cameraEntity, tickCounter, viewBudget, transformedContext, baseProjectionMatrix, nestedPortal, nestedLinked, depth + 1, nestedClip); + } catch (RuntimeException exception) { + logRenderFailure(minecraft, exception); + continue; + } + if (nestedTextureId < 0) { + continue; + } + try { + RenderSystem.restoreProjectionMatrix(); + RenderSystem.backupProjectionMatrix(); + target.bindWrite(true); + PortalStencilTarget.activate(target); + RenderSystem.enableScissor(clip.x(), clip.y(), clip.width(), clip.height()); + drawPortalQuad(transformedContext.cameraPos(), transformedContext.viewMatrix(), baseProjectionMatrix, target.width, target.height, nestedPortal, partialTick, nestedTextureId, nestedClip); + } catch (RuntimeException exception) { + logRenderFailure(minecraft, exception); + } } } } finally { - PortalGunPortalRenderState.popPortalView(renderAll); - } - if (clipEnabled) { - RenderSystem.disableScissor(); + try (PortalSceneRenderTrace.Phase ignored = PortalSceneRenderTrace.begin(portalPairKey, "restore-render-state", "depth=" + depth)) { + try { + if (stencilScope != null) { + stencilScope.close(); + } + if (portalRenderStatePushed) { + PortalGunPortalRenderState.popPortalView(renderAll); + } + } finally { + try { + restorePortalDrawState(); + } finally { + try { + if (transparencyTarget != null) { + transparencyTarget.antarchy$setScreenTarget(previousTransparencyTarget); + } + } finally { + try { + if (entityEffectTarget != null) { + entityEffectTarget.antarchy$setScreenTarget(previousEntityEffectTarget); + } + } finally { + targetAccessor.antarchy$setMainRenderTarget(previousMainTarget); + previousMainTarget.bindWrite(true); + } + } + } + } + } } return target.getColorTextureId(); } private static RenderContext transformContext(RenderContext currentContext, PortalGunPortalEntity sourcePortal, PortalGunPortalEntity destinationPortal) { - Vec3 destinationNormal = destinationPortal.getNormalVec().normalize(); Vec3 relativeEye = currentContext.cameraPos().subtract(sourcePortal.position()); - double depthOffset = Math.max(CAMERA_OFFSET, relativeEye.dot(sourcePortal.getNormalVec().normalize())); Vec3 transformedEyeOffset = PortalGunTransformUtil.transformRelativePosition(sourcePortal, destinationPortal, relativeEye); - Vec3 destinationEye = destinationPortal.position() - .add(transformedEyeOffset) - .add(destinationNormal.scale(depthOffset + CAMERA_OFFSET - transformedEyeOffset.dot(destinationNormal))); + Vec3 destinationEye = destinationPortal.position().add(transformedEyeOffset); Vec3 transformedLook = PortalGunTransformUtil.transformVector(sourcePortal, destinationPortal, currentContext.look()).normalize(); Vec3 transformedUp = PortalGunTransformUtil.transformVector(sourcePortal, destinationPortal, currentContext.up()).normalize(); Matrix4f viewMatrix = new Matrix4f().rotation(PortalGunTransformUtil.orientationQuaternion(transformedLook, transformedUp).conjugate(new Quaternionf())); @@ -326,13 +665,13 @@ public final class PortalGunPortalViewRenderer { return projectionMatrix; } Matrix4f clippedProjection = new Matrix4f(projectionMatrix); - Vector4f q = new Vector4f( - signNonZero(clipPlane.x()) / clippedProjection.m00(), - signNonZero(clipPlane.y()) / clippedProjection.m11(), - -1.0F, - (1.0F + clippedProjection.m22()) / clippedProjection.m32() - ); - float scale = 2.0F / clipPlane.dot(q); + Vector4f q = new Vector4f(signNonZero(clipPlane.x()), signNonZero(clipPlane.y()), 1.0F, 1.0F) + .mul(new Matrix4f(projectionMatrix).invert()); + float planeCornerDot = clipPlane.dot(q); + if (!Float.isFinite(planeCornerDot) || Math.abs(planeCornerDot) <= 1.0E-6F) { + return projectionMatrix; + } + float scale = 2.0F / planeCornerDot; Vector4f c = clipPlane.mul(scale, new Vector4f()); clippedProjection.m02(c.x()); clippedProjection.m12(c.y()); @@ -344,7 +683,7 @@ public final class PortalGunPortalViewRenderer { private static Vector4f portalPlaneToCameraSpace(RenderContext renderContext, Vec3 planePointWorld, Vec3 planeNormalWorld) { Vec3 look = renderContext.look().normalize(); Vec3 up = renderContext.up().normalize(); - Vec3 right = up.cross(look).normalize(); + Vec3 right = look.cross(up).normalize(); Vec3 relativePoint = planePointWorld.subtract(renderContext.cameraPos()); Vector4f point = new Vector4f( (float) relativePoint.dot(right), @@ -368,29 +707,28 @@ public final class PortalGunPortalViewRenderer { return value >= 0.0F ? 1.0F : -1.0F; } - private static ScreenClip computeScreenClip(Minecraft minecraft, Vec3 cameraPos, Matrix4f viewMatrix, Matrix4f projectionMatrix, PortalGunPortalEntity portal) { - Vec3[] worldCorners = portal.getWorldPortalShape().getCorners(SURFACE_OFFSET); - int width = minecraft.getWindow().getWidth(); - int height = minecraft.getWindow().getHeight(); + private static ScreenClip computeScreenClip(Vec3 cameraPos, Matrix4f viewMatrix, Matrix4f projectionMatrix, PortalGunPortalEntity portal, float partialTick, int width, int height) { + PortalGunWorldPortalShape renderShape = portal.getWorldPortalShape().scaleAperture(portal.getPortalVisualScale(partialTick)); + Vec3[] worldCorners = renderShape.getCorners(SURFACE_OFFSET); + List clipVertices = new java.util.ArrayList<>(worldCorners.length); + Matrix4f combined = new Matrix4f(projectionMatrix).mul(viewMatrix); + for (Vec3 worldCorner : worldCorners) { + Vec3 corner = worldCorner.subtract(cameraPos); + clipVertices.add(new Vector4f((float) corner.x, (float) corner.y, (float) corner.z, 1.0F).mul(combined)); + } + clipVertices = clipPolygon(clipVertices, false); + clipVertices = clipPolygon(clipVertices, true); + if (clipVertices.size() < 3) { + return null; + } float minX = Float.POSITIVE_INFINITY; float minY = Float.POSITIVE_INFINITY; float maxX = Float.NEGATIVE_INFINITY; float maxY = Float.NEGATIVE_INFINITY; - boolean visible = false; - Matrix4f combined = new Matrix4f(projectionMatrix).mul(viewMatrix); - for (Vec3 worldCorner : worldCorners) { - Vec3 corner = worldCorner.subtract(cameraPos); - Vector4f projected = new Vector4f((float) corner.x, (float) corner.y, (float) corner.z, 1.0F).mul(combined); - if (projected.w <= 0.001F) { - continue; - } + for (Vector4f projected : clipVertices) { float invW = 1.0F / projected.w; float ndcX = projected.x * invW; float ndcY = projected.y * invW; - if (ndcX < -1.25F || ndcX > 1.25F || ndcY < -1.25F || ndcY > 1.25F) { - continue; - } - visible = true; float screenX = (ndcX * 0.5F + 0.5F) * width; float screenY = (ndcY * 0.5F + 0.5F) * height; minX = Math.min(minX, screenX); @@ -398,21 +736,119 @@ public final class PortalGunPortalViewRenderer { maxX = Math.max(maxX, screenX); maxY = Math.max(maxY, screenY); } - if (!visible) { + if (maxX <= 0.0F || minX >= width || maxY <= 0.0F || minY >= height) { return null; } int scissorX = Mth.clamp((int) Math.floor(minX), 0, width - 1); int scissorMaxX = Mth.clamp((int) Math.ceil(maxX), scissorX + 1, width); int scissorYTop = Mth.clamp((int) Math.floor(minY), 0, height - 1); int scissorYBottom = Mth.clamp((int) Math.ceil(maxY), scissorYTop + 1, height); - int scissorY = height - scissorYBottom; + int scissorY = scissorYTop; int scissorWidth = Math.max(1, scissorMaxX - scissorX); int scissorHeight = Math.max(1, scissorYBottom - scissorYTop); return new ScreenClip(scissorX, scissorY, scissorWidth, scissorHeight); } - private static void drawPortalQuad(Vec3 cameraPos, Matrix4f poseMatrix, PortalGunPortalEntity portal, int textureId) { - drawTexturedPortal(cameraPos, poseMatrix, portal, textureId, SURFACE_OFFSET, 1.0F, 1.0F, 1.0F, 1.0F, false); + private static List clipPolygon(List vertices, boolean nearPlane) { + if (vertices.isEmpty()) { + return vertices; + } + List clipped = new java.util.ArrayList<>(vertices.size() + 2); + Vector4f previous = vertices.get(vertices.size() - 1); + float previousDistance = clipPlaneDistance(previous, nearPlane); + for (Vector4f current : vertices) { + float currentDistance = clipPlaneDistance(current, nearPlane); + boolean previousInside = previousDistance >= 0.0F; + boolean currentInside = currentDistance >= 0.0F; + if (previousInside != currentInside) { + float amount = previousDistance / (previousDistance - currentDistance); + clipped.add(new Vector4f( + previous.x + (current.x - previous.x) * amount, + previous.y + (current.y - previous.y) * amount, + previous.z + (current.z - previous.z) * amount, + previous.w + (current.w - previous.w) * amount + )); + } + if (currentInside) { + clipped.add(new Vector4f(current)); + } + previous = current; + previousDistance = currentDistance; + } + return clipped; + } + + private static float clipPlaneDistance(Vector4f vertex, boolean nearPlane) { + return nearPlane ? vertex.z + vertex.w : vertex.w - 0.001F; + } + + private static boolean drawPortalQuad(Vec3 cameraPos, Matrix4f viewMatrix, Matrix4f projectionMatrix, int width, int height, PortalGunPortalEntity portal, float partialTick, int textureId, ScreenClip clip) { + if (portalViewShader == null) { + if (!loggedMissingPortalViewShader) { + loggedMissingPortalViewShader = true; + Antarchy.LOGGER.error("Portal gun destination view shader became unavailable; skipping destination composite"); + } + return false; + } + if (clip == null || width <= 0 || height <= 0) { + return false; + } + PortalGunWorldPortalShape shape = portal.getWorldPortalShape().scaleAperture(portal.getPortalVisualScale(partialTick)); + Matrix4f combined = new Matrix4f(projectionMatrix).mul(viewMatrix); + double apertureInset = (1.0D - PORTAL_APERTURE_SCALE) * 0.5D; + double apertureStart = apertureInset; + double apertureEnd = 1.0D - apertureInset; + List polygon = new ArrayList<>(4); + polygon.add(projectPortalVertex(combined, cameraPos, shape, apertureStart, apertureStart)); + polygon.add(projectPortalVertex(combined, cameraPos, shape, apertureEnd, apertureStart)); + polygon.add(projectPortalVertex(combined, cameraPos, shape, apertureEnd, apertureEnd)); + polygon.add(projectPortalVertex(combined, cameraPos, shape, apertureStart, apertureEnd)); + polygon = clipPolygon(polygon, false); + polygon = clipPolygon(polygon, true); + if (polygon.size() < 3) { + return false; + } + BufferBuilder buffer = Tesselator.getInstance().begin(VertexFormat.Mode.TRIANGLES, DefaultVertexFormat.POSITION_TEX); + for (int index = 1; index + 1 < polygon.size(); index++) { + addPortalViewVertex(buffer, polygon.get(0)); + addPortalViewVertex(buffer, polygon.get(index)); + addPortalViewVertex(buffer, polygon.get(index + 1)); + } + MeshData mesh = buffer.build(); + if (mesh == null) { + return false; + } + try { + RenderSystem.disableBlend(); + RenderSystem.depthMask(false); + RenderSystem.enableDepthTest(); + RenderSystem.disableCull(); + portalViewShader.setSampler("Sampler0", textureId); + RenderSystem.setShader(() -> portalViewShader); + RenderSystem.setShaderTexture(0, textureId); + RenderSystem.setShaderColor(1.0F, 1.0F, 1.0F, 1.0F); + BufferUploader.drawWithShader(mesh); + } finally { + restorePortalDrawState(); + } + return true; + } + + private static Vector4f projectPortalVertex(Matrix4f combined, Vec3 cameraPos, PortalGunWorldPortalShape shape, double u, double v) { + Vec3 worldPosition = shape.center() + .add(shape.right().scale((u * 2.0D - 1.0D) * shape.halfWidth())) + .add(shape.up().scale((v * 2.0D - 1.0D) * shape.halfHeight())) + .add(shape.normal().scale(SURFACE_OFFSET)); + Vec3 relative = worldPosition.subtract(cameraPos); + return new Vector4f((float) relative.x, (float) relative.y, (float) relative.z, 1.0F).mul(combined); + } + + private static void addPortalViewVertex(BufferBuilder buffer, Vector4f clipPosition) { + float inverseW = 1.0F / clipPosition.w(); + float ndcX = clipPosition.x() * inverseW; + float ndcY = clipPosition.y() * inverseW; + float ndcZ = clipPosition.z() * inverseW; + buffer.addVertex(ndcX, ndcY, ndcZ).setUv(ndcX * 0.5F + 0.5F, ndcY * 0.5F + 0.5F); } private static void renderPortalTransitionEntities(Minecraft minecraft, Vec3 cameraPos, float partialTick, PortalGunPortalEntity sourcePortal, PortalGunPortalEntity destinationPortal) { @@ -421,18 +857,30 @@ public final class PortalGunPortalViewRenderer { } MultiBufferSource.BufferSource bufferSource = minecraft.renderBuffers().bufferSource(); PoseStack poseStack = new PoseStack(); - for (Entity entity : minecraft.level.entitiesForRendering()) { - if (!shouldRenderTransitionEntity(entity, sourcePortal, destinationPortal)) { - continue; - } - renderTransitionEntity(minecraft, cameraPos, partialTick, sourcePortal, destinationPortal, entity, poseStack, bufferSource); - for (Entity passenger : entity.getPassengers()) { - if (shouldRenderTransitionEntity(passenger, sourcePortal, destinationPortal)) { - renderTransitionEntity(minecraft, cameraPos, partialTick, sourcePortal, destinationPortal, passenger, poseStack, bufferSource); - } + AABB transitionBounds = sourcePortal.getWorldPortalShape().getBoundsForCulling(); + List renderEntities = minecraft.level.getEntities( + (Entity) null, + transitionBounds, + entity -> shouldRenderTransitionEntity(entity, sourcePortal, destinationPortal) + ); + Set renderedEntities = Collections.newSetFromMap(new IdentityHashMap<>()); + try { + for (Entity entity : renderEntities) { + renderTransitionEntityTree(minecraft, cameraPos, partialTick, sourcePortal, destinationPortal, entity, poseStack, bufferSource, renderedEntities); } + } finally { + bufferSource.endBatch(); + } + } + + private static void renderTransitionEntityTree(Minecraft minecraft, Vec3 cameraPos, float partialTick, PortalGunPortalEntity sourcePortal, PortalGunPortalEntity destinationPortal, Entity entity, PoseStack poseStack, MultiBufferSource.BufferSource bufferSource, Set renderedEntities) { + if (!shouldRenderTransitionEntity(entity, sourcePortal, destinationPortal) || !renderedEntities.add(entity)) { + return; + } + renderTransitionEntity(minecraft, cameraPos, partialTick, sourcePortal, destinationPortal, entity, poseStack, bufferSource); + for (Entity passenger : entity.getPassengers()) { + renderTransitionEntityTree(minecraft, cameraPos, partialTick, sourcePortal, destinationPortal, passenger, poseStack, bufferSource, renderedEntities); } - bufferSource.endBatch(); } private static boolean shouldRenderTransitionEntity(Entity entity, PortalGunPortalEntity sourcePortal, PortalGunPortalEntity destinationPortal) { @@ -449,8 +897,7 @@ public final class PortalGunPortalViewRenderer { transformationStack.push(); try { Vec3 transformedPosition = transformationStack.moveEntity(sourcePortal, destinationPortal, partialTick); - Vec3 transformedLook = PortalGunTransformUtil.transformVector(sourcePortal, destinationPortal, entity.getLookAngle()).normalize(); - float yaw = PortalGunTransformUtil.yawFromLook(transformedLook); + float yaw = Mth.rotLerp(partialTick, entity.yRotO, entity.getYRot()); int light = minecraft.getEntityRenderDispatcher().getPackedLightCoords(entity, partialTick); RenderSystem.enableDepthTest(); RenderSystem.depthMask(true); @@ -475,11 +922,11 @@ public final class PortalGunPortalViewRenderer { } } - private static int widthTimesHeight(Minecraft minecraft) { - return minecraft.getWindow().getWidth() * minecraft.getWindow().getHeight(); - } - private static void drawPortalOverlay(Minecraft minecraft, Vec3 cameraPos, Matrix4f poseMatrix, PortalGunPortalEntity portal) { + if (!portal.shouldRenderFront(cameraPos)) { + return; + } + RenderSystem.disableScissor(); float time = (minecraft.level.getGameTime() + minecraft.getTimer().getGameTimeDeltaPartialTick(false)) * 0.075F; float pulse = 0.72F + 0.18F * (float) Math.sin(time); float edge = 0.38F + 0.08F * (float) Math.cos(time * 1.7F); @@ -489,15 +936,65 @@ public final class PortalGunPortalViewRenderer { if (portal.getPortalSide() == PortalGunPortalEntity.PortalSide.BLUE) { drawTexturedPortal(cameraPos, poseMatrix, portal, BLUE_OVERLAY, OVERLAY_OFFSET, 0.78F, 0.94F, 1.0F, pulse, false); drawTexturedPortal(cameraPos, poseMatrix, portal, BLUE_OVERLAY, OVERLAY_OFFSET + 0.0008D, channelRed, channelGreen, channelBlue, edge, true); + drawAnimatedPortalOverlay(minecraft, cameraPos, poseMatrix, portal); return; } drawTexturedPortal(cameraPos, poseMatrix, portal, ORANGE_OVERLAY, OVERLAY_OFFSET, 1.0F, 0.84F, 0.46F, pulse, false); drawTexturedPortal(cameraPos, poseMatrix, portal, ORANGE_OVERLAY, OVERLAY_OFFSET + 0.0008D, channelRed, channelGreen, channelBlue, edge, true); + drawAnimatedPortalOverlay(minecraft, cameraPos, poseMatrix, portal); + } + + private static void drawAnimatedPortalOverlay(Minecraft minecraft, Vec3 cameraPos, Matrix4f poseMatrix, PortalGunPortalEntity portal) { + if (!AntarchySettings.portalGunFancyPortals() + || minecraft.options.graphicsMode().get() != GraphicsStatus.FANCY) { + return; + } + ResourceLocation texture = ResourceLocation.fromNamespaceAndPath(Antarchy.MODID, + "textures/entity/portal_gun/" + (portal.getPortalSide() == PortalGunPortalEntity.PortalSide.BLUE + ? "portal_blue_vortex.png" + : "portal_orange_vortex.png")); + float partialTick = minecraft.getTimer().getGameTimeDeltaPartialTick(true); + float portalScale = portal.getPortalVisualScale(partialTick); + Vec3 widthVec = portal.getWidthVec().normalize().scale(portal.getPortalWidth() / 2.0D * portalScale); + Vec3 upVec = portal.getUpVec().normalize().scale(portal.getPortalHeight() / 2.0D * portalScale); + Vec3 normalVec = portal.getNormalVec().normalize().scale(OVERLAY_OFFSET + 0.0016D); + Vec3 center = portal.position().add(normalVec); + Vec3 bottomLeft = center.subtract(widthVec).subtract(upVec).subtract(cameraPos); + Vec3 topLeft = center.subtract(widthVec).add(upVec).subtract(cameraPos); + Vec3 topRight = center.add(widthVec).add(upVec).subtract(cameraPos); + Vec3 bottomRight = center.add(widthVec).subtract(upVec).subtract(cameraPos); + float red = portal.getPortalSide() == PortalGunPortalEntity.PortalSide.BLUE ? 0.78F : 1.0F; + float green = portal.getPortalSide() == PortalGunPortalEntity.PortalSide.BLUE ? 0.94F : 0.84F; + float blue = portal.getPortalSide() == PortalGunPortalEntity.PortalSide.BLUE ? 1.0F : 0.46F; + float frameV = Math.floorMod((int) ((minecraft.level.getGameTime() + minecraft.getTimer().getGameTimeDeltaPartialTick(false)) / 5.0F), 4) * 0.25F; + float frameBottomV = frameV + 0.25F; + BufferBuilder buffer = Tesselator.getInstance().begin(VertexFormat.Mode.QUADS, DefaultVertexFormat.POSITION_TEX); + buffer.addVertex(poseMatrix, (float) bottomLeft.x, (float) bottomLeft.y, (float) bottomLeft.z).setUv(0.0F, frameBottomV); + buffer.addVertex(poseMatrix, (float) topLeft.x, (float) topLeft.y, (float) topLeft.z).setUv(0.0F, frameV); + buffer.addVertex(poseMatrix, (float) topRight.x, (float) topRight.y, (float) topRight.z).setUv(1.0F, frameV); + buffer.addVertex(poseMatrix, (float) bottomRight.x, (float) bottomRight.y, (float) bottomRight.z).setUv(1.0F, frameBottomV); + MeshData mesh = buffer.build(); + try { + RenderSystem.enableBlend(); + RenderSystem.defaultBlendFunc(); + RenderSystem.depthMask(false); + RenderSystem.enableDepthTest(); + RenderSystem.disableCull(); + RenderSystem.setShader(GameRenderer::getPositionTexShader); + RenderSystem.setShaderTexture(0, texture); + RenderSystem.setShaderColor(red, green, blue, 0.92F); + if (mesh != null) { + BufferUploader.drawWithShader(mesh); + } + } finally { + restorePortalDrawState(); + } } private static void drawTexturedPortal(Vec3 cameraPos, Matrix4f poseMatrix, PortalGunPortalEntity portal, ResourceLocation texture, double offset, float red, float green, float blue, float alpha, boolean additive) { - Vec3 widthVec = portal.getWidthVec().normalize().scale(0.5D); - Vec3 upVec = portal.getUpVec().normalize().scale(1.0D); + float portalScale = portal.getPortalVisualScale(Minecraft.getInstance().getTimer().getGameTimeDeltaPartialTick(true)); + Vec3 widthVec = portal.getWidthVec().normalize().scale(portal.getPortalWidth() / 2.0D * portalScale); + Vec3 upVec = portal.getUpVec().normalize().scale(portal.getPortalHeight() / 2.0D * portalScale); Vec3 normalVec = portal.getNormalVec().normalize().scale(offset); Vec3 center = portal.position().add(normalVec); Vec3 bottomLeft = center.subtract(widthVec).subtract(upVec).subtract(cameraPos); @@ -505,39 +1002,37 @@ public final class PortalGunPortalViewRenderer { Vec3 topRight = center.add(widthVec).add(upVec).subtract(cameraPos); Vec3 bottomRight = center.add(widthVec).subtract(upVec).subtract(cameraPos); - RenderSystem.enableBlend(); - if (additive) { - RenderSystem.blendFuncSeparate(GlStateManager.SourceFactor.SRC_ALPHA, GlStateManager.DestFactor.ONE, GlStateManager.SourceFactor.ONE, GlStateManager.DestFactor.ZERO); - } else { - RenderSystem.defaultBlendFunc(); - } - RenderSystem.depthMask(false); - RenderSystem.disableDepthTest(); - RenderSystem.disableCull(); - RenderSystem.setShader(GameRenderer::getPositionTexShader); - RenderSystem.setShaderTexture(0, texture); - RenderSystem.setShaderColor(red, green, blue, alpha); - BufferBuilder buffer = Tesselator.getInstance().begin(VertexFormat.Mode.QUADS, DefaultVertexFormat.POSITION_TEX); buffer.addVertex(poseMatrix, (float) bottomLeft.x, (float) bottomLeft.y, (float) bottomLeft.z).setUv(0.0F, 1.0F); buffer.addVertex(poseMatrix, (float) topLeft.x, (float) topLeft.y, (float) topLeft.z).setUv(0.0F, 0.0F); buffer.addVertex(poseMatrix, (float) topRight.x, (float) topRight.y, (float) topRight.z).setUv(1.0F, 0.0F); buffer.addVertex(poseMatrix, (float) bottomRight.x, (float) bottomRight.y, (float) bottomRight.z).setUv(1.0F, 1.0F); MeshData mesh = buffer.build(); - if (mesh != null) { - BufferUploader.drawWithShader(mesh); + try { + RenderSystem.enableBlend(); + if (additive) { + RenderSystem.blendFuncSeparate(GlStateManager.SourceFactor.SRC_ALPHA, GlStateManager.DestFactor.ONE, GlStateManager.SourceFactor.ONE, GlStateManager.DestFactor.ZERO); + } else { + RenderSystem.defaultBlendFunc(); + } + RenderSystem.depthMask(false); + RenderSystem.enableDepthTest(); + RenderSystem.disableCull(); + RenderSystem.setShader(GameRenderer::getPositionTexShader); + RenderSystem.setShaderTexture(0, texture); + RenderSystem.setShaderColor(red, green, blue, alpha); + if (mesh != null) { + BufferUploader.drawWithShader(mesh); + } + } finally { + restorePortalDrawState(); } - - RenderSystem.enableCull(); - RenderSystem.enableDepthTest(); - RenderSystem.depthMask(true); - RenderSystem.disableBlend(); - RenderSystem.setShaderColor(1.0F, 1.0F, 1.0F, 1.0F); } - private static void drawTexturedPortal(Vec3 cameraPos, Matrix4f poseMatrix, PortalGunPortalEntity portal, int textureId, double offset, float red, float green, float blue, float alpha, boolean additive) { - Vec3 widthVec = portal.getWidthVec().normalize().scale(0.5D); - Vec3 upVec = portal.getUpVec().normalize().scale(1.0D); + private static void drawTexturedPortal(Vec3 cameraPos, Matrix4f poseMatrix, PortalGunPortalEntity portal, int textureId, double offset, float red, float green, float blue, float alpha, boolean additive, float bottomLeftU, float bottomLeftV, float topLeftU, float topLeftV, float topRightU, float topRightV, float bottomRightU, float bottomRightV) { + float portalScale = portal.getPortalVisualScale(Minecraft.getInstance().getTimer().getGameTimeDeltaPartialTick(true)); + Vec3 widthVec = portal.getWidthVec().normalize().scale(portal.getPortalWidth() / 2.0D * portalScale); + Vec3 upVec = portal.getUpVec().normalize().scale(portal.getPortalHeight() / 2.0D * portalScale); Vec3 normalVec = portal.getNormalVec().normalize().scale(offset); Vec3 center = portal.position().add(normalVec); Vec3 bottomLeft = center.subtract(widthVec).subtract(upVec).subtract(cameraPos); @@ -545,32 +1040,40 @@ public final class PortalGunPortalViewRenderer { Vec3 topRight = center.add(widthVec).add(upVec).subtract(cameraPos); Vec3 bottomRight = center.add(widthVec).subtract(upVec).subtract(cameraPos); - RenderSystem.enableBlend(); - if (additive) { - RenderSystem.blendFuncSeparate(GlStateManager.SourceFactor.SRC_ALPHA, GlStateManager.DestFactor.ONE, GlStateManager.SourceFactor.ONE, GlStateManager.DestFactor.ZERO); - } else { - RenderSystem.defaultBlendFunc(); - } - RenderSystem.depthMask(false); - RenderSystem.disableDepthTest(); - RenderSystem.disableCull(); - RenderSystem.setShader(GameRenderer::getPositionTexShader); - RenderSystem.setShaderTexture(0, textureId); - RenderSystem.setShaderColor(red, green, blue, alpha); - BufferBuilder buffer = Tesselator.getInstance().begin(VertexFormat.Mode.QUADS, DefaultVertexFormat.POSITION_TEX); - buffer.addVertex(poseMatrix, (float) bottomLeft.x, (float) bottomLeft.y, (float) bottomLeft.z).setUv(0.0F, 1.0F); - buffer.addVertex(poseMatrix, (float) topLeft.x, (float) topLeft.y, (float) topLeft.z).setUv(0.0F, 0.0F); - buffer.addVertex(poseMatrix, (float) topRight.x, (float) topRight.y, (float) topRight.z).setUv(1.0F, 0.0F); - buffer.addVertex(poseMatrix, (float) bottomRight.x, (float) bottomRight.y, (float) bottomRight.z).setUv(1.0F, 1.0F); + buffer.addVertex(poseMatrix, (float) bottomLeft.x, (float) bottomLeft.y, (float) bottomLeft.z).setUv(bottomLeftU, bottomLeftV); + buffer.addVertex(poseMatrix, (float) topLeft.x, (float) topLeft.y, (float) topLeft.z).setUv(topLeftU, topLeftV); + buffer.addVertex(poseMatrix, (float) topRight.x, (float) topRight.y, (float) topRight.z).setUv(topRightU, topRightV); + buffer.addVertex(poseMatrix, (float) bottomRight.x, (float) bottomRight.y, (float) bottomRight.z).setUv(bottomRightU, bottomRightV); MeshData mesh = buffer.build(); - if (mesh != null) { - BufferUploader.drawWithShader(mesh); + try { + RenderSystem.enableBlend(); + if (additive) { + RenderSystem.blendFuncSeparate(GlStateManager.SourceFactor.SRC_ALPHA, GlStateManager.DestFactor.ONE, GlStateManager.SourceFactor.ONE, GlStateManager.DestFactor.ZERO); + } else { + RenderSystem.defaultBlendFunc(); + } + RenderSystem.depthMask(false); + RenderSystem.enableDepthTest(); + RenderSystem.disableCull(); + RenderSystem.setShader(GameRenderer::getPositionTexShader); + RenderSystem.setShaderTexture(0, textureId); + RenderSystem.setShaderColor(red, green, blue, alpha); + if (mesh != null) { + BufferUploader.drawWithShader(mesh); + } + } finally { + restorePortalDrawState(); } + } + private static void restorePortalDrawState() { + RenderSystem.disableScissor(); RenderSystem.enableCull(); RenderSystem.enableDepthTest(); RenderSystem.depthMask(true); + RenderSystem.colorMask(true, true, true, true); + RenderSystem.defaultBlendFunc(); RenderSystem.disableBlend(); RenderSystem.setShaderColor(1.0F, 1.0F, 1.0F, 1.0F); } @@ -584,19 +1087,44 @@ public final class PortalGunPortalViewRenderer { float pitch = PortalGunTransformUtil.pitchFromLook(normalizedLook); this.setPosition(position); this.setRotation(yaw, pitch); - Vec3 currentUp = new Vec3(this.getUpVector()).normalize(); - if (currentUp.dot(normalizedUp) < 0.0D) { - this.setRotation(yaw + 180.0F, -pitch); - } + Quaternionf orientation = PortalGunTransformUtil.orientationQuaternion(normalizedLook, normalizedUp); + this.rotation().set(orientation); + CameraBasisAccessor basis = (CameraBasisAccessor) (Object) this; + basis.antarchy$getForwards().set(0.0F, 0.0F, -1.0F).rotate(orientation); + basis.antarchy$getUp().set(0.0F, 1.0F, 0.0F).rotate(orientation); + basis.antarchy$getLeft().set(-1.0F, 0.0F, 0.0F).rotate(orientation); } } private record RenderContext(Vec3 cameraPos, Vec3 look, Vec3 up, Matrix4f viewMatrix) { } + private static final class PortalViewBudget { + private int remaining; + + private PortalViewBudget(int maximumViews) { + this.remaining = maximumViews; + } + + private boolean tryReserve() { + if (this.remaining <= 0) { + return false; + } + this.remaining--; + return true; + } + + private boolean hasRemaining() { + return this.remaining > 0; + } + } + private record ScreenClip(int x, int y, int width, int height) { private int area() { return this.width * this.height; } } + + private record TargetAllocationFailure(int width, int height) { + } } diff --git a/common-1.21.1/src/main/java/com/craisinlord/antarchy/content/client/renderer/PortalGunProxySceneRenderer.java b/common-1.21.1/src/main/java/com/craisinlord/antarchy/content/client/renderer/PortalGunProxySceneRenderer.java new file mode 100644 index 00000000..71a18308 --- /dev/null +++ b/common-1.21.1/src/main/java/com/craisinlord/antarchy/content/client/renderer/PortalGunProxySceneRenderer.java @@ -0,0 +1,193 @@ +package com.craisinlord.antarchy.content.client.renderer; + +import com.craisinlord.antarchy.mixins.client.LevelRendererPortalSceneInvoker; +import com.craisinlord.antarchy.mixins.client.LevelRendererPortalViewAreaAccessor; +import com.mojang.blaze3d.systems.RenderSystem; +import com.mojang.blaze3d.platform.Lighting; +import com.mojang.blaze3d.vertex.PoseStack; +import net.minecraft.client.Camera; +import net.minecraft.client.CloudStatus; +import net.minecraft.client.Minecraft; +import net.minecraft.client.renderer.FogRenderer; +import net.minecraft.client.renderer.LevelRenderer; +import net.minecraft.client.renderer.chunk.SectionRenderDispatcher; +import net.minecraft.client.renderer.MultiBufferSource; +import net.minecraft.client.renderer.RenderType; +import net.minecraft.client.particle.ParticleRenderType; +import net.minecraft.client.renderer.culling.Frustum; +import net.minecraft.core.BlockPos; +import net.minecraft.world.entity.Entity; +import net.minecraft.world.entity.LivingEntity; +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.level.block.entity.BlockEntity; +import net.minecraft.world.phys.Vec3; +import net.minecraft.world.phys.AABB; +import it.unimi.dsi.fastutil.objects.ObjectArrayList; +import org.joml.Matrix4f; +import org.joml.Matrix4fStack; + +public final class PortalGunProxySceneRenderer { + private PortalGunProxySceneRenderer() { + } + + public static void render(Minecraft minecraft, LevelRenderer renderer, Camera camera, float partialTick, Matrix4f modelViewMatrix, Matrix4f projectionMatrix, String tracePair) { + Vec3 cameraPosition = camera.getPosition(); + double cameraX = cameraPosition.x; + double cameraY = cameraPosition.y; + double cameraZ = cameraPosition.z; + Frustum frustum = new Frustum(modelViewMatrix, projectionMatrix); + frustum.prepare(cameraX, cameraY, cameraZ); + LevelRendererPortalSceneInvoker scene = (LevelRendererPortalSceneInvoker) renderer; + float renderDistance = minecraft.gameRenderer.getRenderDistance(); + BlockPos cameraBlockPos = BlockPos.containing(cameraX, cameraY, cameraZ); + boolean foggy = minecraft.level.effects().isFoggyAt(cameraBlockPos.getX(), cameraBlockPos.getY()) + || minecraft.gui.getBossOverlay().shouldCreateWorldFog(); + + try (PortalSceneRenderTrace.Phase ignored = PortalSceneRenderTrace.begin(tracePair, "sky", "camera=" + cameraPosition)) { + RenderSystem.setShaderGameTime(minecraft.level.getGameTime(), partialTick); + minecraft.getBlockEntityRenderDispatcher().prepare(minecraft.level, camera, minecraft.hitResult); + minecraft.getEntityRenderDispatcher().prepare(minecraft.level, camera, minecraft.crosshairPickEntity); + FogRenderer.setupColor(camera, partialTick, minecraft.level, minecraft.options.getEffectiveRenderDistance(), minecraft.gameRenderer.getDarkenWorldAmount(partialTick)); + FogRenderer.levelFogColor(); + FogRenderer.setupFog(camera, FogRenderer.FogMode.FOG_SKY, renderDistance, foggy, partialTick); + RenderSystem.setShader(net.minecraft.client.renderer.GameRenderer::getPositionShader); + renderer.renderSky(modelViewMatrix, projectionMatrix, partialTick, camera, foggy, + () -> FogRenderer.setupFog(camera, FogRenderer.FogMode.FOG_SKY, renderDistance, foggy, partialTick)); + } + + try (PortalSceneRenderTrace.Phase ignored = PortalSceneRenderTrace.begin(tracePair, "frustum-and-visibility", "renderDistance=" + rendererAccessDistance(renderer))) { + FogRenderer.setupFog(camera, FogRenderer.FogMode.FOG_TERRAIN, Math.max(renderDistance, 32.0F), foggy, partialTick); + scene.antarchy$setupPortalScene(camera, frustum, false, minecraft.player != null && minecraft.player.isSpectator()); + } + LevelRendererPortalViewAreaAccessor viewAreaAccessor = (LevelRendererPortalViewAreaAccessor) renderer; + ObjectArrayList fullVisibleSections = viewAreaAccessor.antarchy$getVisibleSections(); + ObjectArrayList budgetedVisibleSections = new ObjectArrayList<>(); + PortalGunPortalRendererPool.beginSceneBudget(); + int scannedVisibleSections = 0; + while (scannedVisibleSections < fullVisibleSections.size() && PortalGunPortalRendererPool.remainingSectionBudgetNanos() > 0L) { + budgetedVisibleSections.add(fullVisibleSections.get(scannedVisibleSections++)); + } + try (PortalSceneRenderTrace.Phase ignored = PortalSceneRenderTrace.begin(tracePair, "compile-visible-sections", "visibleTotal=" + fullVisibleSections.size() + " visibleScanned=" + scannedVisibleSections + " rebuildBudgetNanos=" + PortalGunPortalRendererPool.remainingSectionBudgetNanos())) { + viewAreaAccessor.antarchy$setVisibleSections(budgetedVisibleSections); + try { + scene.antarchy$compilePortalSections(camera); + } finally { + viewAreaAccessor.antarchy$setVisibleSections(fullVisibleSections); + } + } + PortalSceneRenderTrace.event(tracePair, "visible-sections-ready", "count=" + viewAreaAccessor.antarchy$getVisibleSections().size() + " scheduledRebuilds=" + PortalGunPortalRendererPool.scheduledSectionRebuilds()); + + try (PortalSceneRenderTrace.Phase ignored = PortalSceneRenderTrace.begin(tracePair, "opaque-terrain-layers", "sections=" + viewAreaAccessor.antarchy$getVisibleSections().size())) { + RenderSystem.enableDepthTest(); + RenderSystem.depthMask(true); + scene.antarchy$renderPortalSectionLayer(RenderType.solid(), cameraX, cameraY, cameraZ, modelViewMatrix, projectionMatrix); + scene.antarchy$renderPortalSectionLayer(RenderType.cutoutMipped(), cameraX, cameraY, cameraZ, modelViewMatrix, projectionMatrix); + scene.antarchy$renderPortalSectionLayer(RenderType.cutout(), cameraX, cameraY, cameraZ, modelViewMatrix, projectionMatrix); + } + + if (minecraft.level.effects().constantAmbientLight()) { + Lighting.setupNetherLevel(); + } else { + Lighting.setupLevel(); + } + + Matrix4fStack modelViewStack = RenderSystem.getModelViewStack(); + try (PortalSceneRenderTrace.Phase ignored = PortalSceneRenderTrace.begin(tracePair, "entity-scene-state", "enter-model-view")) { + modelViewStack.pushMatrix(); + modelViewStack.mul(modelViewMatrix); + RenderSystem.applyModelViewMatrix(); + } + PoseStack poseStack = new PoseStack(); + MultiBufferSource.BufferSource buffers = minecraft.renderBuffers().bufferSource(); + try { + int entityCount = 0; + try (PortalSceneRenderTrace.Phase ignored = PortalSceneRenderTrace.begin(tracePair, "entities", "dispatch-start")) { + for (Entity entity : minecraft.level.entitiesForRendering()) { + if (!minecraft.getEntityRenderDispatcher().shouldRender(entity, frustum, cameraX, cameraY, cameraZ) + && !entity.hasIndirectPassenger(minecraft.player)) { + continue; + } + if (entity == camera.getEntity() && !camera.isDetached() + && (!(entity instanceof LivingEntity living) || !living.isSleeping())) { + continue; + } + if (entity instanceof Player player && player == minecraft.player && !player.isSpectator() && entity != camera.getEntity()) { + continue; + } + scene.antarchy$renderPortalEntity(entity, cameraX, cameraY, cameraZ, partialTick, poseStack, buffers); + entityCount++; + } + } + buffers.endBatch(); + PortalSceneRenderTrace.event(tracePair, "entities-dispatched", "count=" + entityCount); + + int blockEntityCount = 0; + try (PortalSceneRenderTrace.Phase ignored = PortalSceneRenderTrace.begin(tracePair, "block-entities", "visibleSections=" + viewAreaAccessor.antarchy$getVisibleSections().size())) { + for (var section : viewAreaAccessor.antarchy$getVisibleSections()) { + for (BlockEntity blockEntity : section.getCompiled().getRenderableBlockEntities()) { + renderBlockEntity(minecraft, blockEntity, cameraX, cameraY, cameraZ, partialTick, poseStack, buffers); + blockEntityCount++; + } + } + synchronized (viewAreaAccessor.antarchy$getGlobalBlockEntities()) { + for (BlockEntity blockEntity : viewAreaAccessor.antarchy$getGlobalBlockEntities()) { + if (frustum.isVisible(new AABB(blockEntity.getBlockPos()).inflate(1.0D))) { + renderBlockEntity(minecraft, blockEntity, cameraX, cameraY, cameraZ, partialTick, poseStack, buffers); + blockEntityCount++; + } + } + } + } + buffers.endBatch(); + PortalSceneRenderTrace.event(tracePair, "block-entities-dispatched", "count=" + blockEntityCount); + + try (PortalSceneRenderTrace.Phase ignored = PortalSceneRenderTrace.begin(tracePair, "translucent-and-particles", "rendering")) { + minecraft.particleEngine.render(minecraft.gameRenderer.lightTexture(), camera, partialTick, frustum, type -> !type.isTranslucent()); + scene.antarchy$renderPortalSectionLayer(RenderType.translucent(), cameraX, cameraY, cameraZ, modelViewMatrix, projectionMatrix); + buffers.endBatch(RenderType.lines()); + buffers.endBatch(); + scene.antarchy$renderPortalSectionLayer(RenderType.tripwire(), cameraX, cameraY, cameraZ, modelViewMatrix, projectionMatrix); + minecraft.particleEngine.render(minecraft.gameRenderer.lightTexture(), camera, partialTick, frustum, ParticleRenderType::isTranslucent); + } + + try (PortalSceneRenderTrace.Phase ignored = PortalSceneRenderTrace.begin(tracePair, "clouds", minecraft.options.getCloudsType().toString())) { + if (minecraft.options.getCloudsType() != CloudStatus.OFF) { + renderer.renderClouds(poseStack, modelViewMatrix, projectionMatrix, partialTick, cameraX, cameraY, cameraZ); + } + } + + try (PortalSceneRenderTrace.Phase ignored = PortalSceneRenderTrace.begin(tracePair, "weather-and-border", "rendering")) { + RenderSystem.depthMask(false); + scene.antarchy$renderPortalWeather(minecraft.gameRenderer.lightTexture(), partialTick, cameraX, cameraY, cameraZ); + scene.antarchy$renderPortalWorldBorder(camera); + } + } finally { + try { + buffers.endBatch(); + } finally { + modelViewStack.popMatrix(); + RenderSystem.applyModelViewMatrix(); + RenderSystem.depthMask(true); + RenderSystem.enableDepthTest(); + RenderSystem.enableCull(); + RenderSystem.disableBlend(); + } + } + } + + private static int rendererAccessDistance(LevelRenderer renderer) { + return ((LevelRendererPortalViewAreaAccessor) renderer).antarchy$getViewArea().getViewDistance(); + } + + private static void renderBlockEntity(Minecraft minecraft, BlockEntity blockEntity, double cameraX, double cameraY, double cameraZ, + float partialTick, PoseStack poseStack, MultiBufferSource.BufferSource buffers) { + if (!blockEntity.hasLevel() || !blockEntity.getType().isValid(blockEntity.getBlockState())) { + return; + } + BlockPos pos = blockEntity.getBlockPos(); + poseStack.pushPose(); + poseStack.translate(pos.getX() - cameraX, pos.getY() - cameraY, pos.getZ() - cameraZ); + minecraft.getBlockEntityRenderDispatcher().render(blockEntity, partialTick, poseStack, buffers); + poseStack.popPose(); + } +} diff --git a/common-1.21.1/src/main/java/com/craisinlord/antarchy/content/client/renderer/PortalSceneRenderTrace.java b/common-1.21.1/src/main/java/com/craisinlord/antarchy/content/client/renderer/PortalSceneRenderTrace.java new file mode 100644 index 00000000..5be56e6b --- /dev/null +++ b/common-1.21.1/src/main/java/com/craisinlord/antarchy/content/client/renderer/PortalSceneRenderTrace.java @@ -0,0 +1,82 @@ +package com.craisinlord.antarchy.content.client.renderer; + +import com.craisinlord.antarchy.Antarchy; +import java.util.HashSet; +import java.util.Set; + +public final class PortalSceneRenderTrace { + private static final Set LOGGED_PHASES = new HashSet<>(); + private static long generation; + private static String armedKey = ""; + + private PortalSceneRenderTrace() { + } + + public static synchronized void arm(String key) { + if (!armedKey.equals(key)) { + armedKey = key; + generation++; + LOGGED_PHASES.clear(); + } + } + + public static synchronized void reset() { + armedKey = ""; + LOGGED_PHASES.clear(); + generation++; + } + + public static Phase begin(String pair, String phase, String detail) { + long currentGeneration; + boolean shouldLog; + synchronized (PortalSceneRenderTrace.class) { + currentGeneration = generation; + shouldLog = LOGGED_PHASES.add(currentGeneration + ":" + pair + ":" + phase); + } + if (shouldLog) { + Antarchy.LOGGER.info("Portal scene trace begin generation={} pair={} phase={} detail={}", currentGeneration, pair, phase, detail); + } + return new Phase(currentGeneration, pair, phase, shouldLog, System.nanoTime()); + } + + public static void event(String pair, String event, String detail) { + long currentGeneration; + boolean shouldLog; + synchronized (PortalSceneRenderTrace.class) { + currentGeneration = generation; + shouldLog = LOGGED_PHASES.add(currentGeneration + ":" + pair + ":event:" + event); + } + if (shouldLog) { + Antarchy.LOGGER.info("Portal scene trace event generation={} pair={} event={} detail={}", currentGeneration, pair, event, detail); + } + } + + public static final class Phase implements AutoCloseable { + private final long generation; + private final String pair; + private final String phase; + private final boolean logCompletion; + private final long startNanos; + private boolean closed; + + private Phase(long generation, String pair, String phase, boolean logCompletion, long startNanos) { + this.generation = generation; + this.pair = pair; + this.phase = phase; + this.logCompletion = logCompletion; + this.startNanos = startNanos; + } + + @Override + public void close() { + if (this.closed) { + return; + } + this.closed = true; + if (this.logCompletion) { + Antarchy.LOGGER.info("Portal scene trace complete generation={} pair={} phase={} durationMs={}", + this.generation, this.pair, this.phase, (System.nanoTime() - this.startNanos) / 1_000_000.0D); + } + } + } +} diff --git a/common-1.21.1/src/main/java/com/craisinlord/antarchy/content/client/renderer/PortalStencilTarget.java b/common-1.21.1/src/main/java/com/craisinlord/antarchy/content/client/renderer/PortalStencilTarget.java new file mode 100644 index 00000000..fe6562d7 --- /dev/null +++ b/common-1.21.1/src/main/java/com/craisinlord/antarchy/content/client/renderer/PortalStencilTarget.java @@ -0,0 +1,196 @@ +package com.craisinlord.antarchy.content.client.renderer; + +import com.mojang.blaze3d.pipeline.RenderTarget; +import com.mojang.blaze3d.systems.RenderSystem; +import java.util.Collections; +import java.util.IdentityHashMap; +import java.util.Map; +import java.util.Objects; +import java.util.Set; +import org.lwjgl.opengl.GL11; +import org.lwjgl.opengl.GL30; +import com.mojang.blaze3d.pipeline.TextureTarget; + +public final class PortalStencilTarget { + private static final Map ATTACHMENTS = Collections.synchronizedMap(new IdentityHashMap<>()); + private static final Set DIRECT_TARGETS = Collections.newSetFromMap(new IdentityHashMap<>()); + + private PortalStencilTarget() { + } + + public static void prepareMainTarget(RenderTarget target) { + try { + java.lang.reflect.Method enabled = target.getClass().getMethod("isStencilEnabled"); + if (!Boolean.TRUE.equals(enabled.invoke(target))) { + target.getClass().getMethod("enableStencil").invoke(target); + } + } catch (ReflectiveOperationException ignored) { + } + target.bindWrite(true); + } + + public static Scope beginDirect(RenderTarget target, TextureTarget depthBackup, Runnable drawApertureMask) { + target.bindWrite(true); + if (GL11.glGetInteger(GL11.GL_STENCIL_BITS) <= 0) { + throw new IllegalStateException("Main render target has no stencil attachment"); + } + depthBackup.copyDepthFrom(target); + DIRECT_TARGETS.add(target); + target.bindWrite(true); + RenderSystem.disableScissor(); + RenderSystem.stencilMask(0xFF); + RenderSystem.clearStencil(0); + GL11.glClear(GL11.GL_STENCIL_BUFFER_BIT); + RenderSystem.stencilFunc(GL11.GL_ALWAYS, 1, 0xFF); + RenderSystem.stencilOp(GL11.GL_KEEP, GL11.GL_KEEP, GL11.GL_REPLACE); + RenderSystem.colorMask(false, false, false, false); + RenderSystem.depthMask(false); + RenderSystem.disableDepthTest(); + GL11.glEnable(GL11.GL_STENCIL_TEST); + try { + Objects.requireNonNull(drawApertureMask).run(); + } catch (RuntimeException | Error exception) { + resetState(); + target.copyDepthFrom(depthBackup); + DIRECT_TARGETS.remove(target); + throw exception; + } + RenderSystem.colorMask(true, true, true, true); + RenderSystem.depthMask(true); + RenderSystem.stencilMask(0x00); + RenderSystem.stencilFunc(GL11.GL_EQUAL, 1, 0xFF); + RenderSystem.stencilOp(GL11.GL_KEEP, GL11.GL_KEEP, GL11.GL_KEEP); + GL11.glClear(GL11.GL_DEPTH_BUFFER_BIT); + RenderSystem.enableDepthTest(); + return new Scope(target, depthBackup); + } + + public static Scope begin(RenderTarget target, Runnable drawApertureMask) { + ensureAttachment(target); + target.bindWrite(true); + RenderSystem.disableScissor(); + RenderSystem.stencilMask(0xFF); + RenderSystem.clearStencil(0); + GL11.glClear(GL11.GL_STENCIL_BUFFER_BIT); + RenderSystem.stencilFunc(GL11.GL_ALWAYS, 1, 0xFF); + RenderSystem.stencilOp(GL11.GL_KEEP, GL11.GL_KEEP, GL11.GL_REPLACE); + RenderSystem.colorMask(false, false, false, false); + RenderSystem.depthMask(false); + RenderSystem.disableDepthTest(); + GL11.glEnable(GL11.GL_STENCIL_TEST); + try { + Objects.requireNonNull(drawApertureMask).run(); + } catch (RuntimeException | Error exception) { + resetState(); + throw exception; + } + RenderSystem.colorMask(true, true, true, true); + RenderSystem.depthMask(true); + RenderSystem.stencilMask(0x00); + RenderSystem.stencilFunc(GL11.GL_EQUAL, 1, 0xFF); + RenderSystem.stencilOp(GL11.GL_KEEP, GL11.GL_KEEP, GL11.GL_KEEP); + GL11.glClear(GL11.GL_DEPTH_BUFFER_BIT); + RenderSystem.enableDepthTest(); + return new Scope(target); + } + + public static void activate(RenderTarget target) { + if (!ATTACHMENTS.containsKey(target) && !DIRECT_TARGETS.contains(target)) { + return; + } + target.bindWrite(true); + GL11.glEnable(GL11.GL_STENCIL_TEST); + RenderSystem.stencilMask(0x00); + RenderSystem.stencilFunc(GL11.GL_EQUAL, 1, 0xFF); + RenderSystem.stencilOp(GL11.GL_KEEP, GL11.GL_KEEP, GL11.GL_KEEP); + } + + public static void release(RenderTarget target) { + DIRECT_TARGETS.remove(target); + Attachment attachment = ATTACHMENTS.remove(target); + if (attachment != null) { + GL30.glDeleteRenderbuffers(attachment.renderbuffer()); + } + } + + public static void clear() { + for (Attachment attachment : ATTACHMENTS.values()) { + GL30.glDeleteRenderbuffers(attachment.renderbuffer()); + } + ATTACHMENTS.clear(); + DIRECT_TARGETS.clear(); + } + + private static void ensureAttachment(RenderTarget target) { + Attachment attachment = ATTACHMENTS.get(target); + if (attachment != null + && attachment.framebuffer() == target.frameBufferId + && attachment.width() == target.width + && attachment.height() == target.height) { + return; + } + if (attachment != null) { + GL30.glDeleteRenderbuffers(attachment.renderbuffer()); + } + int previousFramebuffer = GL11.glGetInteger(GL30.GL_FRAMEBUFFER_BINDING); + int previousRenderbuffer = GL11.glGetInteger(GL30.GL_RENDERBUFFER_BINDING); + int renderbuffer = GL30.glGenRenderbuffers(); + try { + GL30.glBindFramebuffer(GL30.GL_FRAMEBUFFER, target.frameBufferId); + GL30.glBindRenderbuffer(GL30.GL_RENDERBUFFER, renderbuffer); + GL30.glRenderbufferStorage(GL30.GL_RENDERBUFFER, GL30.GL_DEPTH24_STENCIL8, target.width, target.height); + GL30.glFramebufferRenderbuffer(GL30.GL_FRAMEBUFFER, GL30.GL_DEPTH_STENCIL_ATTACHMENT, GL30.GL_RENDERBUFFER, renderbuffer); + int status = GL30.glCheckFramebufferStatus(GL30.GL_FRAMEBUFFER); + if (status != GL30.GL_FRAMEBUFFER_COMPLETE) { + GL30.glDeleteRenderbuffers(renderbuffer); + throw new IllegalStateException("Portal view framebuffer is incomplete with packed depth/stencil attachment: 0x" + Integer.toHexString(status)); + } + ATTACHMENTS.put(target, new Attachment(target.frameBufferId, renderbuffer, target.width, target.height)); + } finally { + GL30.glBindRenderbuffer(GL30.GL_RENDERBUFFER, previousRenderbuffer); + GL30.glBindFramebuffer(GL30.GL_FRAMEBUFFER, previousFramebuffer); + } + } + + private static void resetState() { + GL11.glDisable(GL11.GL_STENCIL_TEST); + RenderSystem.stencilMask(0xFF); + RenderSystem.stencilFunc(GL11.GL_ALWAYS, 0, 0xFF); + RenderSystem.stencilOp(GL11.GL_KEEP, GL11.GL_KEEP, GL11.GL_KEEP); + RenderSystem.colorMask(true, true, true, true); + RenderSystem.depthMask(true); + RenderSystem.enableDepthTest(); + } + + private record Attachment(int framebuffer, int renderbuffer, int width, int height) { + } + + public static final class Scope implements AutoCloseable { + private boolean closed; + private final RenderTarget target; + private final TextureTarget depthBackup; + + private Scope(RenderTarget target) { + this(target, null); + } + + private Scope(RenderTarget target, TextureTarget depthBackup) { + this.target = Objects.requireNonNull(target); + this.depthBackup = depthBackup; + } + + @Override + public void close() { + if (closed) { + return; + } + closed = true; + if (this.depthBackup != null) { + this.target.copyDepthFrom(this.depthBackup); + this.target.bindWrite(true); + DIRECT_TARGETS.remove(this.target); + } + resetState(); + } + } +} diff --git a/common-1.21.1/src/main/java/com/craisinlord/antarchy/content/client/renderer/PortalViewSectionMarker.java b/common-1.21.1/src/main/java/com/craisinlord/antarchy/content/client/renderer/PortalViewSectionMarker.java new file mode 100644 index 00000000..06eaaaa3 --- /dev/null +++ b/common-1.21.1/src/main/java/com/craisinlord/antarchy/content/client/renderer/PortalViewSectionMarker.java @@ -0,0 +1,7 @@ +package com.craisinlord.antarchy.content.client.renderer; + +public interface PortalViewSectionMarker { + void antarchy$markPortalViewSection(); + + boolean antarchy$isPortalViewSection(); +} diff --git a/common-1.21.1/src/main/java/com/craisinlord/antarchy/content/client/renderer/SodiumCompat.java b/common-1.21.1/src/main/java/com/craisinlord/antarchy/content/client/renderer/SodiumCompat.java new file mode 100644 index 00000000..cd6cc797 --- /dev/null +++ b/common-1.21.1/src/main/java/com/craisinlord/antarchy/content/client/renderer/SodiumCompat.java @@ -0,0 +1,117 @@ +package com.craisinlord.antarchy.content.client.renderer; + +import com.craisinlord.antarchy.Antarchy; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.util.ArrayDeque; +import java.util.Deque; +import net.minecraft.client.Camera; + +public final class SodiumCompat { + private static final String SODIUM_RENDERER_CLASS = "net.caffeinemc.mods.sodium.client.render.SodiumWorldRenderer"; + private static final ThreadLocal RENDER_STATE = ThreadLocal.withInitial(RenderState::new); + private static boolean restoreWarningLogged; + + private SodiumCompat() { + } + + public static boolean isLoaded() { + String resource = SODIUM_RENDERER_CLASS.replace('.', '/') + ".class"; + ClassLoader contextLoader = Thread.currentThread().getContextClassLoader(); + if (contextLoader != null && contextLoader.getResource(resource) != null) { + return true; + } + ClassLoader ownLoader = SodiumCompat.class.getClassLoader(); + return ownLoader != null && ownLoader.getResource(resource) != null; + } + + public static void captureTerrainSetup(Camera camera, Object viewport, boolean spectator, boolean updateChunks) { + RENDER_STATE.get().activeSetup = new TerrainSetup(camera, viewport, spectator, updateChunks); + } + + public static TerrainScope beginPortalView(Camera portalCamera) { + RenderState state = RENDER_STATE.get(); + TerrainSetup previousSetup = state.activeSetup; + state.previousSetups.push(new PreviousSetup(previousSetup)); + if (previousSetup != null) { + restoreTerrainSetup(previousSetup.withCamera(portalCamera), state); + } + return () -> { + PreviousSetup previous = state.previousSetups.pop(); + if (previous.setup() == null) { + state.activeSetup = null; + return; + } + restoreTerrainSetup(previous.setup(), state); + }; + } + + public static void clear() { + RENDER_STATE.remove(); + } + + private static void restoreTerrainSetup(TerrainSetup setup, RenderState state) { + try { + ClassLoader contextLoader = Thread.currentThread().getContextClassLoader(); + Class rendererClass = Class.forName(SODIUM_RENDERER_CLASS, false, + contextLoader == null ? SodiumCompat.class.getClassLoader() : contextLoader); + Object renderer = rendererClass.getMethod("instanceNullable").invoke(null); + if (renderer == null) { + return; + } + Method terrainSetup = null; + for (Method method : rendererClass.getMethods()) { + Class[] parameters = method.getParameterTypes(); + if (method.getName().equals("setupTerrain") + && parameters.length == 4 + && parameters[0].isInstance(setup.camera()) + && parameters[1].isInstance(setup.viewport()) + && parameters[2] == boolean.class + && parameters[3] == boolean.class) { + terrainSetup = method; + break; + } + } + if (terrainSetup == null) { + logRestoreFailure("Sodium terrain setup method was not found", null); + state.activeSetup = setup; + return; + } + terrainSetup.invoke(renderer, setup.camera(), setup.viewport(), setup.spectator(), setup.updateChunks()); + } catch (ClassNotFoundException | NoSuchMethodException | IllegalAccessException | InvocationTargetException | LinkageError exception) { + logRestoreFailure("Could not restore Sodium terrain after a portal view", exception); + state.activeSetup = setup; + } + } + + private static void logRestoreFailure(String message, Throwable exception) { + if (!restoreWarningLogged) { + restoreWarningLogged = true; + if (exception == null) { + Antarchy.LOGGER.warn(message); + } else { + Antarchy.LOGGER.warn(message, exception); + } + } + } + + @FunctionalInterface + public interface TerrainScope extends AutoCloseable { + @Override + void close(); + } + + private static final class RenderState { + private final Deque previousSetups = new ArrayDeque<>(); + private TerrainSetup activeSetup; + } + + private record PreviousSetup(TerrainSetup setup) { + } + + private record TerrainSetup(Camera camera, Object viewport, boolean spectator, boolean updateChunks) { + private TerrainSetup withCamera(Camera camera) { + return new TerrainSetup(camera, this.viewport, this.spectator, this.updateChunks); + } + } +} diff --git a/common-1.21.1/src/main/java/com/craisinlord/antarchy/content/computer/AntarchyArchiveLocators.java b/common-1.21.1/src/main/java/com/craisinlord/antarchy/content/computer/AntarchyArchiveLocators.java index d2f8fa50..64feaaae 100644 --- a/common-1.21.1/src/main/java/com/craisinlord/antarchy/content/computer/AntarchyArchiveLocators.java +++ b/common-1.21.1/src/main/java/com/craisinlord/antarchy/content/computer/AntarchyArchiveLocators.java @@ -2,11 +2,13 @@ package com.craisinlord.antarchy.content.computer; import com.craisinlord.antarchy.Antarchy; import com.craisinlord.antarchy.content.command.QueenLocateCommand; +import com.craisinlord.antarchy.content.entity.brutalfly.BrutalflyEntity; import com.craisinlord.antarchy.content.worldgen.elythia.KingsTreeGrid; import com.craisinlord.antos.api.archive.ArchiveLocatorRegistry; import net.minecraft.core.BlockPos; import net.minecraft.resources.ResourceLocation; import net.minecraft.world.level.levelgen.Heightmap; +import net.minecraft.world.phys.AABB; /** Archive coordinate lookups for royals that live on grids rather than in vanilla structures. */ public final class AntarchyArchiveLocators { @@ -24,6 +26,14 @@ public final class AntarchyArchiveLocators { Heightmap.Types.WORLD_SURFACE_WG, level, level.getChunkSource().randomState()); return new BlockPos(center.getX(), surfaceY, center.getZ()); }); + ArchiveLocatorRegistry.register(id("brutalfly"), (level, origin) -> { + AABB searchArea = AABB.ofSize(origin.getCenter(), 1_024.0D, 1_024.0D, 1_024.0D); + return level.getEntitiesOfClass(BrutalflyEntity.class, searchArea, BrutalflyEntity::isAlive) + .stream() + .min(java.util.Comparator.comparingDouble(entity -> entity.distanceToSqr(origin.getCenter()))) + .map(entity -> entity.blockPosition()) + .orElse(null); + }); } private static ResourceLocation id(String path) { diff --git a/common-1.21.1/src/main/java/com/craisinlord/antarchy/content/creative/CreativeTabContents.java b/common-1.21.1/src/main/java/com/craisinlord/antarchy/content/creative/CreativeTabContents.java index d25db275..80ff3128 100644 --- a/common-1.21.1/src/main/java/com/craisinlord/antarchy/content/creative/CreativeTabContents.java +++ b/common-1.21.1/src/main/java/com/craisinlord/antarchy/content/creative/CreativeTabContents.java @@ -4,6 +4,7 @@ import com.craisinlord.antarchy.content.AntarchyObjects; import com.craisinlord.antarchy.content.entity.glimmer.GlimmerVariant; import com.craisinlord.antarchy.content.item.GiantFryingPanToolHelper; import com.craisinlord.antarchy.content.item.GlimmerBottleItem; +import com.craisinlord.antarchy.content.item.PortalGunItem; import com.craisinlord.antarchy.content.item.royal.RoyalGearHelper; import com.craisinlord.antarchy.content.item.ultimate.UltimateGearHelper; import java.util.HashMap; @@ -425,7 +426,7 @@ public final class CreativeTabContents { entry("shrink_ray", "combat"), entry("growth_ray", "combat"), entry("gravity_gun", "combat"), - // entry("portal_gun", "combat"), // deferred — out of scope for 2.0 + entry("portal_gun", "combat"), entry("eye_of_the_storm", "combat"), entry("water_cannon", "combat"), entry("attitude_adjuster", "combat"), @@ -640,6 +641,14 @@ public final class CreativeTabContents { } continue; } + if ("portal_gun".equals(entry.path())) { + Item portalGun = item(entry.path()); + output.accept(portalGun); + output.accept(PortalGunItem.createGlobalVariant(portalGun, "Chell")); + output.accept(PortalGunItem.createGlobalVariant(portalGun, "Atlas")); + output.accept(PortalGunItem.createGlobalVariant(portalGun, "P-body")); + continue; + } output.accept(item(entry.path())); } for (String potionPath : ANTARCHY_POTION_FAMILIES) { @@ -669,6 +678,14 @@ public final class CreativeTabContents { output.insertAfter(vanillaItem(entry.insertAfterPath()), item(entry.path())); continue; } + if ("portal_gun".equals(entry.path())) { + Item portalGun = item(entry.path()); + output.accept(portalGun); + output.accept(PortalGunItem.createGlobalVariant(portalGun, "Chell")); + output.accept(PortalGunItem.createGlobalVariant(portalGun, "Atlas")); + output.accept(PortalGunItem.createGlobalVariant(portalGun, "P-body")); + continue; + } if ("combat".equals(tabPath)) { if (isUltimateArmor(entry.path())) { output.accept(UltimateGearHelper.createUltimateArmorStack(item(entry.path()), holders)); diff --git a/common-1.21.1/src/main/java/com/craisinlord/antarchy/content/gravity/ScgunsProjectileAccess.java b/common-1.21.1/src/main/java/com/craisinlord/antarchy/content/gravity/ScgunsProjectileAccess.java new file mode 100644 index 00000000..6b640e8a --- /dev/null +++ b/common-1.21.1/src/main/java/com/craisinlord/antarchy/content/gravity/ScgunsProjectileAccess.java @@ -0,0 +1,10 @@ +package com.craisinlord.antarchy.content.gravity; + +import net.minecraft.world.entity.LivingEntity; +import org.jetbrains.annotations.Nullable; + +/** Exposes Scorched Guns' base ProjectileEntity fields to mixins targeting its subclasses. */ +public interface ScgunsProjectileAccess { + @Nullable LivingEntity antarchy$getScgunsShooter(); + double antarchy$getScgunsModifiedGravity(); +} diff --git a/common-1.21.1/src/main/java/com/craisinlord/antarchy/content/item/BigBerthaItem.java b/common-1.21.1/src/main/java/com/craisinlord/antarchy/content/item/BigBerthaItem.java index 8f4ba348..22cb89a9 100644 --- a/common-1.21.1/src/main/java/com/craisinlord/antarchy/content/item/BigBerthaItem.java +++ b/common-1.21.1/src/main/java/com/craisinlord/antarchy/content/item/BigBerthaItem.java @@ -59,6 +59,8 @@ public class BigBerthaItem extends SwordItem implements GeoItem { private static final ResourceLocation TEXTURE_LOCATION = ResourceLocation.fromNamespaceAndPath("antarchy", "textures/item/big_bertha/big_bertha.png"); private static final ResourceLocation ANIMATION_LOCATION = ResourceLocation.fromNamespaceAndPath("antarchy", "animations/static_item.animation.json"); private static final String MODE_TAG = "antarchy.big_bertha_mode"; + public static final int MODE_TREMORSAURUS = 8; + public static final int MODE_FORSAKEN = 9; private static final String SPIN_TICKS_TAG = "antarchy.big_bertha_spin_ticks"; private static final String SPIN_DIRECTION_X_TAG = "antarchy.big_bertha_spin_direction_x"; private static final String SPIN_DIRECTION_Z_TAG = "antarchy.big_bertha_spin_direction_z"; @@ -161,7 +163,7 @@ public class BigBerthaItem extends SwordItem implements GeoItem { case NIGHTMARE -> applyNightmareHit(player, target, serverLevel); case KRAKEN -> applyKrakenHit(player, target, serverLevel); case HERCULES -> applyHerculesHit(player, target, serverLevel); - case MOLEVORE, TORETERROR -> { + case MOLEVORE, TORETERROR, TREMORSAURUS, FORSAKEN -> { } } @@ -389,6 +391,10 @@ public class BigBerthaItem extends SwordItem implements GeoItem { } } + public static int getModeId(ItemStack stack) { + return getMode(stack).id; + } + public boolean tryCycleModeFromInput(Level level, Player player, ItemStack stack) { if (player.getMainHandItem() != stack) { return false; @@ -650,6 +656,8 @@ public class BigBerthaItem extends SwordItem implements GeoItem { MOLEVORE(3, "tooltip.antarchy.big_bertha.mode.molevore", "message.antarchy.big_bertha.mode.molevore"), LUCID(4, "tooltip.antarchy.big_bertha.mode.lucid", "message.antarchy.big_bertha.mode.lucid"), TORETERROR(5, "tooltip.antarchy.big_bertha.mode.toreterror", "message.antarchy.big_bertha.mode.toreterror"), + TREMORSAURUS(MODE_TREMORSAURUS, "tooltip.antarchy.big_bertha.mode.tremorsaurus", "message.antarchy.big_bertha.mode.tremorsaurus"), + FORSAKEN(MODE_FORSAKEN, "tooltip.antarchy.big_bertha.mode.forsaken", "message.antarchy.big_bertha.mode.forsaken"), HERCULES(6, "tooltip.antarchy.big_bertha.mode.hercules", "message.antarchy.big_bertha.mode.hercules"); private final int id; @@ -671,7 +679,9 @@ public class BigBerthaItem extends SwordItem implements GeoItem { case KRAKEN -> HERCULES; case HERCULES -> MOLEVORE; case MOLEVORE -> TORETERROR; - case TORETERROR -> NONE; + case TORETERROR -> TREMORSAURUS; + case TREMORSAURUS -> FORSAKEN; + case FORSAKEN -> NONE; }; } diff --git a/common-1.21.1/src/main/java/com/craisinlord/antarchy/content/item/MinersDreamCaveGenerator.java b/common-1.21.1/src/main/java/com/craisinlord/antarchy/content/item/MinersDreamCaveGenerator.java deleted file mode 100644 index 4aa42bac..00000000 --- a/common-1.21.1/src/main/java/com/craisinlord/antarchy/content/item/MinersDreamCaveGenerator.java +++ /dev/null @@ -1,173 +0,0 @@ -package com.craisinlord.antarchy.content.item; - -import com.craisinlord.antarchy.content.AntarchyTags; -import it.unimi.dsi.fastutil.longs.LongOpenHashSet; -import java.util.ArrayList; -import java.util.HashSet; -import java.util.List; -import java.util.Set; -import net.minecraft.core.BlockPos; -import net.minecraft.core.Direction; -import net.minecraft.server.level.ServerLevel; -import net.minecraft.util.Mth; -import net.minecraft.util.RandomSource; -import net.minecraft.world.level.block.state.BlockState; - -public final class MinersDreamCaveGenerator { - private static final double MIN_HORIZONTAL_RADIUS = 2.0D; - private static final double MAX_HORIZONTAL_RADIUS = 5.0D; - private static final double MIN_VERTICAL_RADIUS = 1.5D; - private static final double MAX_VERTICAL_RADIUS = 4.0D; - private static final double MAX_VERTICAL_DEVIATION = 8.0D; - private static final double SOFT_VERTICAL_DEVIATION = 4.0D; - private static final int WORLD_MARGIN = 3; - - private MinersDreamCaveGenerator() { - } - - public record CaveNode(BlockPos center, double horizontalRadius, double verticalRadius) { - } - - public record CaveGenerationResult(List nodes, Set torchCheckpoints) { - } - - public static CaveGenerationResult generate(BlockPos start, Direction initialDirection, int length, - int torchSpacing, int minBuildHeight, int maxBuildHeight, RandomSource random) { - List nodes = new ArrayList<>(length); - Set torchCheckpoints = new HashSet<>(); - - double x = start.getX() + 0.5D; - double y = start.getY() + 0.5D; - double z = start.getZ() + 0.5D; - double startY = y; - - double headingX = initialDirection.getStepX(); - double headingZ = initialDirection.getStepZ(); - if (headingX == 0.0D && headingZ == 0.0D) { - headingX = 1.0D; - } - - double verticalVelocity = 0.0D; - double horizontalRadius = 3.0D; - double verticalRadius = 2.0D; - int minY = minBuildHeight + WORLD_MARGIN; - int maxY = maxBuildHeight - WORLD_MARGIN; - - int spacing = Math.max(4, torchSpacing); - int traveledSinceTorch = 0; - int nextTorchDistance = spacing + random.nextInt(5) - 2; - - for (int i = 0; i < length; i++) { - BlockPos center = BlockPos.containing(x, Mth.clamp(y, minY, maxY), z); - nodes.add(new CaveNode(center, horizontalRadius, verticalRadius)); - - traveledSinceTorch++; - if (traveledSinceTorch >= Math.max(1, nextTorchDistance)) { - torchCheckpoints.add(i); - traveledSinceTorch = 0; - nextTorchDistance = spacing + random.nextInt(5) - 2; - } - - double turnDegrees = random.nextGaussian() * 8.0D; - if (random.nextFloat() < 0.1F) { - turnDegrees = random.nextGaussian() * 15.0D; - } - turnDegrees = Mth.clamp(turnDegrees, -25.0D, 25.0D); - double turnRadians = Math.toRadians(turnDegrees); - double cos = Math.cos(turnRadians); - double sin = Math.sin(turnRadians); - double newHeadingX = headingX * cos - headingZ * sin; - double newHeadingZ = headingX * sin + headingZ * cos; - double headingLength = Math.sqrt(newHeadingX * newHeadingX + newHeadingZ * newHeadingZ); - headingX = newHeadingX / headingLength; - headingZ = newHeadingZ / headingLength; - - verticalVelocity += random.nextGaussian() * 0.15D; - double deviation = y - startY; - verticalVelocity -= deviation * 0.06D; - if (deviation > SOFT_VERTICAL_DEVIATION) { - verticalVelocity -= 0.08D; - } else if (deviation < -SOFT_VERTICAL_DEVIATION) { - verticalVelocity += 0.08D; - } - verticalVelocity = Mth.clamp(verticalVelocity, -0.6D, 0.6D); - - x += headingX; - z += headingZ; - y = Mth.clamp(y + verticalVelocity, startY - MAX_VERTICAL_DEVIATION, startY + MAX_VERTICAL_DEVIATION); - - horizontalRadius = Mth.clamp(horizontalRadius + random.nextGaussian() * 0.15D, MIN_HORIZONTAL_RADIUS, MAX_HORIZONTAL_RADIUS); - verticalRadius = Mth.clamp(verticalRadius + random.nextGaussian() * 0.12D, MIN_VERTICAL_RADIUS, MAX_VERTICAL_RADIUS); - if (random.nextFloat() < 0.05F) { - horizontalRadius = Mth.clamp(horizontalRadius + 0.6D, MIN_HORIZONTAL_RADIUS, MAX_HORIZONTAL_RADIUS); - verticalRadius = Mth.clamp(verticalRadius + 0.5D, MIN_VERTICAL_RADIUS, MAX_VERTICAL_RADIUS); - } - } - - return new CaveGenerationResult(nodes, torchCheckpoints); - } - - public static void collectNodeCandidates(ServerLevel level, CaveNode node, LongOpenHashSet visited, RandomSource random, List out) { - BlockPos center = node.center(); - int hr = Mth.ceil(node.horizontalRadius()); - double upExtent = node.verticalRadius() * 1.25D; - double downExtent = node.verticalRadius() * 0.65D; - int upR = Mth.ceil(upExtent); - int downR = Mth.ceil(downExtent); - int minBuildHeight = level.getMinBuildHeight(); - int maxBuildHeight = level.getMaxBuildHeight(); - - for (int dy = -downR; dy <= upR; dy++) { - double vExtent = dy >= 0 ? upExtent : downExtent; - if (vExtent <= 0.0D) { - continue; - } - double vTerm = (dy * dy) / (vExtent * vExtent); - if (vTerm > 1.05D) { - continue; - } - - for (int dx = -hr; dx <= hr; dx++) { - double hxTerm = (dx * dx) / (node.horizontalRadius() * node.horizontalRadius()); - for (int dz = -hr; dz <= hr; dz++) { - double hzTerm = (dz * dz) / (node.horizontalRadius() * node.horizontalRadius()); - double distance = hxTerm + vTerm + hzTerm; - if (distance > 1.0D) { - continue; - } - if (distance > 0.7D) { - double edgeChance = (1.0D - distance) / 0.3D * 0.85D; - if (random.nextFloat() > edgeChance) { - continue; - } - } - - BlockPos pos = center.offset(dx, dy, dz); - if (pos.getY() < minBuildHeight || pos.getY() >= maxBuildHeight) { - continue; - } - - long packed = pos.asLong(); - if (visited.contains(packed)) { - continue; - } - - BlockState state = level.getBlockState(pos); - if (!state.is(AntarchyTags.Blocks.MINERS_DREAM_CARVABLE)) { - continue; - } - if (level.getBlockEntity(pos) != null) { - continue; - } - - visited.add(packed); - out.add(pos); - } - } - } - } - - public static boolean isChunkLoaded(ServerLevel level, BlockPos pos) { - return level.isLoaded(pos); - } -} diff --git a/common-1.21.1/src/main/java/com/craisinlord/antarchy/content/item/MinersDreamExcavation.java b/common-1.21.1/src/main/java/com/craisinlord/antarchy/content/item/MinersDreamExcavation.java deleted file mode 100644 index 2042bb45..00000000 --- a/common-1.21.1/src/main/java/com/craisinlord/antarchy/content/item/MinersDreamExcavation.java +++ /dev/null @@ -1,152 +0,0 @@ -package com.craisinlord.antarchy.content.item; - -import it.unimi.dsi.fastutil.longs.LongOpenHashSet; -import java.util.ArrayList; -import java.util.List; -import java.util.Set; -import java.util.UUID; -import net.minecraft.core.BlockPos; -import net.minecraft.core.particles.BlockParticleOption; -import net.minecraft.core.particles.DustParticleOptions; -import net.minecraft.core.particles.ParticleTypes; -import net.minecraft.resources.ResourceKey; -import net.minecraft.server.level.ServerLevel; -import net.minecraft.sounds.SoundEvents; -import net.minecraft.sounds.SoundSource; -import net.minecraft.util.RandomSource; -import net.minecraft.world.level.Level; -import net.minecraft.world.level.block.Blocks; -import net.minecraft.world.level.block.state.BlockState; -import org.joml.Vector3f; - -public final class MinersDreamExcavation { - private static final Vector3f FLYTRAP_GOO_COLOR = new Vector3f(0.35F, 0.85F, 0.4F); - private static final int SLICES_BETWEEN_RUMBLE = 4; - - private final UUID owner; - private final ResourceKey dimension; - private final List nodes; - private final Set torchCheckpoints; - private final LongOpenHashSet visited = new LongOpenHashSet(); - - private int nodeIndex = 0; - private List currentNodePositions = null; - private int positionCursor = 0; - private int slicesSinceRumble = 0; - private boolean finished = false; - - public MinersDreamExcavation(UUID owner, ResourceKey dimension, List nodes, Set torchCheckpoints) { - this.owner = owner; - this.dimension = dimension; - this.nodes = nodes; - this.torchCheckpoints = torchCheckpoints; - } - - public UUID owner() { - return owner; - } - - public ResourceKey dimension() { - return dimension; - } - - public boolean isFinished() { - return finished; - } - - public void tick(ServerLevel level, int blockBudget) { - RandomSource random = level.getRandom(); - int remaining = blockBudget; - - while (remaining > 0 && !finished) { - if (currentNodePositions == null) { - if (nodeIndex >= nodes.size()) { - finished = true; - playCompletionSound(level); - return; - } - - MinersDreamCaveGenerator.CaveNode node = nodes.get(nodeIndex); - if (!MinersDreamCaveGenerator.isChunkLoaded(level, node.center())) { - advanceNode(level, node); - continue; - } - - currentNodePositions = new ArrayList<>(); - MinersDreamCaveGenerator.collectNodeCandidates(level, node, visited, random, currentNodePositions); - positionCursor = 0; - } - - if (currentNodePositions.isEmpty()) { - MinersDreamCaveGenerator.CaveNode node = nodes.get(nodeIndex); - advanceNode(level, node); - continue; - } - - int endIndex = Math.min(currentNodePositions.size(), positionCursor + remaining); - for (; positionCursor < endIndex; positionCursor++) { - BlockPos pos = currentNodePositions.get(positionCursor); - BlockState removedState = level.getBlockState(pos); - if (removedState.isAir()) { - continue; - } - level.setBlock(pos, Blocks.AIR.defaultBlockState(), 3); - remaining--; - if (random.nextFloat() < 0.12F) { - spawnBreakParticles(level, pos, removedState, random); - } - } - - if (positionCursor >= currentNodePositions.size()) { - MinersDreamCaveGenerator.CaveNode node = nodes.get(nodeIndex); - advanceNode(level, node); - } - } - } - - private void advanceNode(ServerLevel level, MinersDreamCaveGenerator.CaveNode node) { - RandomSource random = level.getRandom(); - spawnFrontDust(level, node, random); - slicesSinceRumble++; - if (slicesSinceRumble >= SLICES_BETWEEN_RUMBLE) { - slicesSinceRumble = 0; - playRumble(level, node); - } - - if (torchCheckpoints.contains(nodeIndex)) { - MinersDreamTorchPlacer.tryPlaceTorch(level, node.center(), random); - } - - currentNodePositions = null; - positionCursor = 0; - nodeIndex++; - } - - private void spawnBreakParticles(ServerLevel level, BlockPos pos, BlockState state, RandomSource random) { - level.sendParticles(new BlockParticleOption(ParticleTypes.BLOCK, state), - pos.getX() + 0.5D, pos.getY() + 0.5D, pos.getZ() + 0.5D, 2, 0.25D, 0.25D, 0.25D, 0.02D); - if (random.nextFloat() < 0.2F) { - level.sendParticles(new DustParticleOptions(FLYTRAP_GOO_COLOR, 1.0F), - pos.getX() + 0.5D, pos.getY() + 0.5D, pos.getZ() + 0.5D, 1, 0.2D, 0.2D, 0.2D, 0.0D); - } - } - - private void spawnFrontDust(ServerLevel level, MinersDreamCaveGenerator.CaveNode node, RandomSource random) { - BlockPos center = node.center(); - level.sendParticles(ParticleTypes.CLOUD, center.getX() + 0.5D, center.getY() + 0.5D, center.getZ() + 0.5D, - 3, node.horizontalRadius() * 0.4D, node.verticalRadius() * 0.3D, node.horizontalRadius() * 0.4D, 0.01D); - } - - private void playRumble(ServerLevel level, MinersDreamCaveGenerator.CaveNode node) { - BlockPos center = node.center(); - level.playSound(null, center, SoundEvents.STONE_BREAK, SoundSource.BLOCKS, 0.5F, 0.7F); - } - - private void playCompletionSound(ServerLevel level) { - if (nodes.isEmpty()) { - return; - } - BlockPos center = nodes.get(nodes.size() - 1).center(); - level.playSound(null, center, SoundEvents.BEACON_ACTIVATE, SoundSource.PLAYERS, 0.4F, 1.4F); - } -} diff --git a/common-1.21.1/src/main/java/com/craisinlord/antarchy/content/item/MinersDreamExcavationManager.java b/common-1.21.1/src/main/java/com/craisinlord/antarchy/content/item/MinersDreamExcavationManager.java deleted file mode 100644 index 4effa987..00000000 --- a/common-1.21.1/src/main/java/com/craisinlord/antarchy/content/item/MinersDreamExcavationManager.java +++ /dev/null @@ -1,81 +0,0 @@ -package com.craisinlord.antarchy.content.item; - -import com.craisinlord.antarchy.config.AntarchySettings; -import java.util.Iterator; -import java.util.Map; -import java.util.UUID; -import java.util.concurrent.ConcurrentHashMap; -import net.minecraft.core.BlockPos; -import net.minecraft.core.Direction; -import net.minecraft.server.level.ServerLevel; -import net.minecraft.util.Mth; -import net.minecraft.util.RandomSource; - -public final class MinersDreamExcavationManager { - private static final int MAX_ACTIVE_TASKS = 8; - private static final int SAFE_MIN_RANGE = 8; - private static final int SAFE_MAX_RANGE = 256; - private static final Map ACTIVE = new ConcurrentHashMap<>(); - - private MinersDreamExcavationManager() { - } - - public static boolean hasActiveExcavation(UUID owner) { - return ACTIVE.containsKey(owner); - } - - public static MinersDreamExcavation tryStart(ServerLevel level, UUID owner, BlockPos startPos, Direction direction) { - if (ACTIVE.containsKey(owner) || ACTIVE.size() >= MAX_ACTIVE_TASKS) { - return null; - } - - int minRange = Mth.clamp(AntarchySettings.minersDreamMinimumRange(), SAFE_MIN_RANGE, SAFE_MAX_RANGE); - int maxRange = Mth.clamp(AntarchySettings.minersDreamMaximumRange(), SAFE_MIN_RANGE, SAFE_MAX_RANGE); - if (minRange > maxRange) { - int swap = minRange; - minRange = maxRange; - maxRange = swap; - } - - RandomSource random = level.getRandom(); - int length = minRange == maxRange ? minRange : minRange + random.nextInt(maxRange - minRange + 1); - - MinersDreamCaveGenerator.CaveGenerationResult result = MinersDreamCaveGenerator.generate( - startPos, - direction, - length, - AntarchySettings.minersDreamTorchSpacing(), - level.getMinBuildHeight(), - level.getMaxBuildHeight(), - random - ); - - if (result.nodes().isEmpty()) { - return null; - } - - MinersDreamExcavation excavation = new MinersDreamExcavation(owner, level.dimension(), result.nodes(), result.torchCheckpoints()); - ACTIVE.put(owner, excavation); - return excavation; - } - - public static void tick(ServerLevel level) { - if (ACTIVE.isEmpty()) { - return; - } - - int budget = Math.max(1, AntarchySettings.minersDreamBlocksPerTick()); - Iterator> iterator = ACTIVE.entrySet().iterator(); - while (iterator.hasNext()) { - MinersDreamExcavation excavation = iterator.next().getValue(); - if (!excavation.dimension().equals(level.dimension())) { - continue; - } - - excavation.tick(level, budget); - if (excavation.isFinished()) { - iterator.remove(); - } - } - } -} diff --git a/common-1.21.1/src/main/java/com/craisinlord/antarchy/content/item/MinersDreamItem.java b/common-1.21.1/src/main/java/com/craisinlord/antarchy/content/item/MinersDreamItem.java deleted file mode 100644 index d0ec451f..00000000 --- a/common-1.21.1/src/main/java/com/craisinlord/antarchy/content/item/MinersDreamItem.java +++ /dev/null @@ -1,79 +0,0 @@ -package com.craisinlord.antarchy.content.item; - -import com.craisinlord.antarchy.config.AntarchySettings; -import java.util.List; -import net.minecraft.ChatFormatting; -import net.minecraft.core.BlockPos; -import net.minecraft.core.Direction; -import net.minecraft.network.chat.Component; -import net.minecraft.server.level.ServerLevel; -import net.minecraft.sounds.SoundEvents; -import net.minecraft.sounds.SoundSource; -import net.minecraft.world.InteractionResult; -import net.minecraft.world.entity.player.Player; -import net.minecraft.world.item.Item; -import net.minecraft.world.item.ItemStack; -import net.minecraft.world.item.TooltipFlag; -import net.minecraft.world.item.context.UseOnContext; -import net.minecraft.world.level.Level; - -public final class MinersDreamItem extends Item { - public MinersDreamItem(Properties properties) { - super(properties); - } - - @Override - public InteractionResult useOn(UseOnContext context) { - Level level = context.getLevel(); - Player player = context.getPlayer(); - if (player == null) { - return InteractionResult.PASS; - } - - if (!AntarchySettings.minersDreamEnabled()) { - if (!level.isClientSide()) { - player.displayClientMessage(Component.translatable("message.antarchy.miners_dream_disabled"), true); - } - return InteractionResult.FAIL; - } - - if (level.isClientSide()) { - return InteractionResult.SUCCESS; - } - - ServerLevel serverLevel = (ServerLevel) level; - BlockPos clickedPos = context.getClickedPos(); - Direction clickedFace = context.getClickedFace(); - Direction travelDirection = clickedFace.getAxis().isHorizontal() ? clickedFace : player.getDirection(); - BlockPos startPos = clickedPos.relative(travelDirection); - - if (!serverLevel.isInWorldBounds(startPos) || !MinersDreamCaveGenerator.isChunkLoaded(serverLevel, startPos)) { - return InteractionResult.FAIL; - } - - if (MinersDreamExcavationManager.hasActiveExcavation(player.getUUID())) { - player.displayClientMessage(Component.translatable("message.antarchy.miners_dream_busy"), true); - return InteractionResult.FAIL; - } - - MinersDreamExcavation excavation = MinersDreamExcavationManager.tryStart(serverLevel, player.getUUID(), startPos, travelDirection); - if (excavation == null) { - return InteractionResult.FAIL; - } - - serverLevel.playSound(null, clickedPos, SoundEvents.TNT_PRIMED, SoundSource.PLAYERS, 0.8F, 1.1F); - - if (!player.getAbilities().instabuild) { - context.getItemInHand().shrink(1); - } - - return InteractionResult.CONSUME; - } - - @Override - public void appendHoverText(ItemStack stack, Item.TooltipContext context, List tooltipComponents, TooltipFlag tooltipFlag) { - tooltipComponents.add(Component.translatable("tooltip.antarchy.miners_dream.line1").withStyle(ChatFormatting.GRAY)); - tooltipComponents.add(Component.translatable("tooltip.antarchy.miners_dream.line2").withStyle(ChatFormatting.GRAY)); - super.appendHoverText(stack, context, tooltipComponents, tooltipFlag); - } -} diff --git a/common-1.21.1/src/main/java/com/craisinlord/antarchy/content/item/MinersDreamTorchPlacer.java b/common-1.21.1/src/main/java/com/craisinlord/antarchy/content/item/MinersDreamTorchPlacer.java deleted file mode 100644 index 08e8f78b..00000000 --- a/common-1.21.1/src/main/java/com/craisinlord/antarchy/content/item/MinersDreamTorchPlacer.java +++ /dev/null @@ -1,89 +0,0 @@ -package com.craisinlord.antarchy.content.item; - -import java.util.List; -import net.minecraft.core.BlockPos; -import net.minecraft.core.Direction; -import net.minecraft.server.level.ServerLevel; -import net.minecraft.sounds.SoundSource; -import net.minecraft.util.RandomSource; -import net.minecraft.world.level.block.Blocks; -import net.minecraft.world.level.block.state.BlockState; -import net.minecraft.world.level.block.state.properties.BlockStateProperties; - -public final class MinersDreamTorchPlacer { - private MinersDreamTorchPlacer() { - } - - public static void tryPlaceTorch(ServerLevel level, BlockPos center, RandomSource random) { - BlockPos floorSpot = findFloorSpot(level, center); - boolean floorFirst = random.nextBoolean(); - - if (floorFirst) { - if (floorSpot != null && placeFloorTorch(level, floorSpot)) { - return; - } - placeWallTorch(level, center, random); - return; - } - - if (placeWallTorch(level, center, random)) { - return; - } - if (floorSpot != null) { - placeFloorTorch(level, floorSpot); - } - } - - private static BlockPos findFloorSpot(ServerLevel level, BlockPos center) { - for (int dy = -2; dy <= 1; dy++) { - BlockPos candidate = center.offset(0, dy, 0); - if (!isOpenForTorch(level, candidate)) { - continue; - } - BlockState below = level.getBlockState(candidate.below()); - if (below.isFaceSturdy(level, candidate.below(), Direction.UP)) { - return candidate; - } - } - return null; - } - - private static boolean placeFloorTorch(ServerLevel level, BlockPos pos) { - if (!isOpenForTorch(level, pos)) { - return false; - } - BlockState state = Blocks.TORCH.defaultBlockState(); - if (!state.canSurvive(level, pos)) { - return false; - } - level.setBlock(pos, state, 3); - level.playSound(null, pos, state.getSoundType().getPlaceSound(), SoundSource.BLOCKS, 1.0F, 1.0F); - return true; - } - - private static boolean placeWallTorch(ServerLevel level, BlockPos center, RandomSource random) { - List order = Direction.Plane.HORIZONTAL.shuffledCopy(random); - for (int dy = -1; dy <= 1; dy++) { - BlockPos candidate = center.offset(0, dy, 0); - if (!isOpenForTorch(level, candidate)) { - continue; - } - for (Direction wallSide : order) { - Direction facing = wallSide.getOpposite(); - BlockState state = Blocks.WALL_TORCH.defaultBlockState().setValue(BlockStateProperties.HORIZONTAL_FACING, facing); - if (!state.canSurvive(level, candidate)) { - continue; - } - level.setBlock(candidate, state, 3); - level.playSound(null, candidate, state.getSoundType().getPlaceSound(), SoundSource.BLOCKS, 1.0F, 1.0F); - return true; - } - } - return false; - } - - private static boolean isOpenForTorch(ServerLevel level, BlockPos pos) { - BlockState state = level.getBlockState(pos); - return (state.isAir() || state.canBeReplaced()) && level.getFluidState(pos).isEmpty(); - } -} diff --git a/common-1.21.1/src/main/java/com/craisinlord/antarchy/content/item/PortalGunItem.java b/common-1.21.1/src/main/java/com/craisinlord/antarchy/content/item/PortalGunItem.java index 53fa0932..3ff744e2 100644 --- a/common-1.21.1/src/main/java/com/craisinlord/antarchy/content/item/PortalGunItem.java +++ b/common-1.21.1/src/main/java/com/craisinlord/antarchy/content/item/PortalGunItem.java @@ -3,6 +3,8 @@ package com.craisinlord.antarchy.content.item; import com.craisinlord.antarchy.Antarchy; import com.craisinlord.antarchy.content.block.entity.PortalGunPortalBaseBlockEntity; import com.craisinlord.antarchy.content.block.entity.PortalGunPortalMasterBlockEntity; +import com.craisinlord.antarchy.content.block.entity.PortalGunPortalFaceRecord; +import com.craisinlord.antarchy.content.block.entity.PortalGunPortalCellAccess; import com.craisinlord.antarchy.content.client.model.ResourceBackedGeoItemModel; import com.craisinlord.antarchy.content.client.renderer.AnimatedHeldItemRenderer; import com.craisinlord.antarchy.content.network.PortalGunPrimaryPayload; @@ -11,11 +13,14 @@ import com.craisinlord.antarchy.content.portalgun.PortalGunPlacement; import com.craisinlord.antarchy.content.portalgun.PortalGunPortalEntity; import com.craisinlord.antarchy.content.portalgun.PortalGunProjectileEntity; import com.craisinlord.antarchy.content.portalgun.PortalGunSavedData; +import com.craisinlord.antarchy.content.portalgun.PortalGunResetManager; +import com.craisinlord.antarchy.config.AntarchySettings; import java.util.HashSet; import java.util.List; import java.util.Optional; import java.util.Set; import java.util.UUID; +import java.nio.charset.StandardCharsets; import java.util.function.Consumer; import java.util.function.Supplier; import net.minecraft.core.BlockPos; @@ -27,8 +32,6 @@ import net.minecraft.network.chat.Component; import net.minecraft.resources.ResourceLocation; import net.minecraft.server.level.ServerLevel; import net.minecraft.server.level.ServerPlayer; -import net.minecraft.sounds.SoundEvent; -import net.minecraft.sounds.SoundEvents; import net.minecraft.sounds.SoundSource; import net.minecraft.stats.Stats; import net.minecraft.world.InteractionHand; @@ -52,6 +55,7 @@ import net.minecraft.world.level.material.FluidState; import net.minecraft.world.phys.BlockHitResult; import net.minecraft.world.phys.HitResult; import net.minecraft.world.phys.Vec3; +import net.minecraft.util.Mth; import software.bernie.geckolib.animatable.GeoItem; import software.bernie.geckolib.animatable.client.GeoRenderProvider; import software.bernie.geckolib.animatable.instance.AnimatableInstanceCache; @@ -69,6 +73,15 @@ public class PortalGunItem extends Item implements GeoItem { private static final String LAST_SIDE_TAG = "antarchy.portal_gun_last_side"; private static final String MOON_SIDE_TAG = "antarchy.portal_gun_moon_side"; public static final String GUN_ID_TAG = "antarchy.portal_gun_id"; + public static final String CHANNEL_NAME_TAG = "antarchy.portal_gun_channel"; + public static final String OWNER_ID_TAG = "antarchy.portal_gun_owner"; + public static final String OWNER_NAME_TAG = "antarchy.portal_gun_owner_name"; + public static final UUID GLOBAL_OWNER_ID = UUID.nameUUIDFromBytes("Global".getBytes(StandardCharsets.UTF_8)); + public static final String PORTAL_WIDTH_TAG = "width"; + public static final String PORTAL_HEIGHT_TAG = "height"; + public static final String GRAB_STRENGTH_TAG = "grabStrength"; + private static final int MAX_PORTAL_WIDTH = 16; + private static final int MAX_PORTAL_HEIGHT = 16; private static final ResourceLocation MODEL_LOCATION = ResourceLocation.fromNamespaceAndPath(Antarchy.MODID, "geo/portal_gun.geo.json"); private static final ResourceLocation TEXTURE_LOCATION = ResourceLocation.fromNamespaceAndPath(Antarchy.MODID, "textures/models/item/portal_gun.png"); private static final ResourceLocation ANIMATION_LOCATION = ResourceLocation.fromNamespaceAndPath(Antarchy.MODID, "animations/portal_gun.animation.json"); @@ -104,81 +117,126 @@ public class PortalGunItem extends Item implements GeoItem { } ServerLevel serverLevel = (ServerLevel) level; if (player.isShiftKeyDown()) { - PortalGunSavedData.clearAllPortals(serverLevel.getServer(), player.getUUID()); - this.setLastSide(stack, null); - this.setMoonSide(stack, null); - level.playSound(null, player.getX(), player.getY(), player.getZ(), PortalGunPortalEntity.sound("portal_fizzle", SoundEvents.ENDER_CHEST_CLOSE), SoundSource.PLAYERS, 0.6F, 1.0F); + if (player instanceof ServerPlayer serverPlayer) { + this.resetPortals(serverPlayer, stack); + } return InteractionResultHolder.consume(stack); } - boolean fired = this.firePortal(serverLevel, player, stack, PortalGunPortalEntity.PortalSide.ORANGE); + if (player instanceof ServerPlayer serverPlayer && PortalGunResetManager.handleOrangeReset(serverPlayer, stack)) { + return InteractionResultHolder.consume(stack); + } + boolean fired = this.firePortal(serverLevel, player, stack, PortalGunPortalEntity.PortalSide.ORANGE, usedHand == InteractionHand.OFF_HAND); return fired ? InteractionResultHolder.consume(stack) : InteractionResultHolder.fail(stack); } - public void firePrimary(ServerLevel level, ServerPlayer player, ItemStack stack) { - this.firePortal(level, player, stack, PortalGunPortalEntity.PortalSide.BLUE); + public void firePrimary(ServerLevel level, ServerPlayer player, ItemStack stack, boolean offhand) { + this.firePortal(level, player, stack, PortalGunPortalEntity.PortalSide.BLUE, offhand); } - private boolean firePortal(ServerLevel level, Player player, ItemStack stack, PortalGunPortalEntity.PortalSide side) { + public void resetPortals(ServerPlayer player, ItemStack stack) { + UUID gunId = this.ensureGunId(stack, player.getUUID(), player.getGameProfile().getName()); + UUID ownerId = getPortalOwnerId(stack, player.getUUID()); + PortalGunSavedData.clearAllPortals(player.serverLevel(), ownerId, gunId, getChannelName(stack)); + this.setLastSide(stack, null); + this.setMoonSide(stack, null); + player.serverLevel().playSound(null, player.getX(), player.getY(), player.getZ(), PortalGunPortalEntity.sound("portal_fizzle"), SoundSource.PLAYERS, 0.6F, 1.0F); + } + + public void resetPortalSide(ServerPlayer player, ItemStack stack, PortalGunPortalEntity.PortalSide side) { + UUID gunId = this.ensureGunId(stack, player.getUUID(), player.getGameProfile().getName()); + UUID ownerId = getPortalOwnerId(stack, player.getUUID()); + PortalGunSavedData.clearPortalSide(player.serverLevel(), ownerId, gunId, getChannelName(stack), side); + player.serverLevel().playSound(null, player.getX(), player.getY(), player.getZ(), PortalGunPortalEntity.sound("portal_fizzle"), SoundSource.PLAYERS, 0.6F, 1.0F); + } + + public void fireMobPortal(ServerLevel level, LivingEntity shooter, ItemStack stack, PortalGunPortalEntity.PortalSide side) { + this.ensureGunId(stack, shooter.getUUID(), shooter.getName().getString()); + String firePath = side == PortalGunPortalEntity.PortalSide.BLUE ? "portal_gun_fire_blue" : "portal_gun_fire_orange"; + level.playSound(null, shooter.getX(), shooter.getY(), shooter.getZ(), PortalGunPortalEntity.sound(firePath), shooter.getSoundSource(), 0.65F, side == PortalGunPortalEntity.PortalSide.BLUE ? 1.15F : 0.88F); + this.spawnProjectile(level, shooter, stack, side); + } + + private boolean firePortal(ServerLevel level, Player player, ItemStack stack, PortalGunPortalEntity.PortalSide side, boolean offhand) { if (this.raycast(level, player).getType() == HitResult.Type.MISS) { - if (this.tryArmMoonShot(level, player, stack, side)) { + if (this.tryArmMoonShot(level, player, stack, side, offhand)) { return true; } - level.playSound(null, player.getX(), player.getY(), player.getZ(), PortalGunPortalEntity.sound("portal_gun_invalid_surface", SoundEvents.DISPENSER_FAIL), SoundSource.PLAYERS, 0.55F, 1.0F); - return false; } this.triggerFireAnimation(level, player, stack); - this.setLastSide(stack, side); - stack.hurtAndBreak(1, player, EquipmentSlot.MAINHAND); + this.ensureGunId(stack, player.getUUID(), player.getGameProfile().getName()); + stack.hurtAndBreak(1, player, offhand ? EquipmentSlot.OFFHAND : EquipmentSlot.MAINHAND); player.awardStat(Stats.ITEM_USED.get(this)); player.getCooldowns().addCooldown(this, 6); String firePath = side == PortalGunPortalEntity.PortalSide.BLUE ? "portal_gun_fire_blue" : "portal_gun_fire_orange"; - level.playSound(null, player.getX(), player.getY(), player.getZ(), PortalGunPortalEntity.sound(firePath, SoundEvents.BEACON_ACTIVATE), SoundSource.PLAYERS, 0.65F, side == PortalGunPortalEntity.PortalSide.BLUE ? 1.15F : 0.88F); + level.playSound(null, player.getX(), player.getY(), player.getZ(), PortalGunPortalEntity.sound(firePath), SoundSource.PLAYERS, 0.65F, side == PortalGunPortalEntity.PortalSide.BLUE ? 1.15F : 0.88F); this.spawnProjectile(level, player, stack, side); return true; } - public void handlePortalImpact(ServerLevel level, Player player, ItemStack stack, PortalGunPortalEntity.PortalSide side, BlockHitResult hitResult, Vec3 impactPos) { - PortalGunPortalEntity existing = this.findPlacedPortal(level, player.getUUID(), side); - PortalGunPlacement placement = this.findPlacement(level, player, side, hitResult, existing); + public void handlePortalImpact(ServerLevel level, LivingEntity shooter, ItemStack stack, UUID gunId, PortalGunPortalEntity.PortalSide side, BlockHitResult hitResult, Vec3 impactPos, int portalWidth, int portalHeight) { + UUID portalOwnerId = getPortalOwnerId(stack, shooter.getUUID()); + String portalOwnerIdentity = getPortalOwnerIdentity(stack, shooter.getUUID()); + String channelName = getChannelName(stack); + PortalGunPortalEntity existing = this.findPlacedPortal(level, portalOwnerId, gunId, channelName, side); + PortalGunPlacement placement = this.findPlacement(level, shooter, portalOwnerId, channelName, side, hitResult, existing, portalWidth, portalHeight); if (placement == null) { - Antarchy.LOGGER.info("Portal gun placement failed owner={} side={} hitBlock={} hitFace={} hitLocation={}", player.getUUID(), side, hitResult.getBlockPos(), hitResult.getDirection(), hitResult.getLocation()); - level.playSound(null, impactPos.x, impactPos.y, impactPos.z, PortalGunPortalEntity.sound("portal_gun_invalid_surface", SoundEvents.DISPENSER_FAIL), SoundSource.PLAYERS, 0.55F, 1.0F); + Antarchy.LOGGER.debug("Portal gun placement failed owner={} side={} hitBlock={} hitFace={} hitLocation={}", shooter.getUUID(), side, hitResult.getBlockPos(), hitResult.getDirection(), hitResult.getLocation()); + level.playSound(null, impactPos.x, impactPos.y, impactPos.z, PortalGunPortalEntity.sound("portal_gun_invalid_surface"), SoundSource.PLAYERS, 0.55F, 1.0F); + return; + } + if (existing != null && this.isSamePortalPlacement(existing, placement)) { + existing.restartOpeningAnimation(); + if (stack.getItem() == this) { + this.setLastSide(stack, side); + } + level.playSound(null, existing.getX(), existing.getY(), existing.getZ(), PortalGunPortalEntity.sound("portal_fizzle"), SoundSource.PLAYERS, 0.6F, 1.0F); return; } if (existing != null) { - Antarchy.LOGGER.info("Portal gun replacing existing portal owner={} side={} oldPortal={}", player.getUUID(), side, existing.getUUID()); + Antarchy.LOGGER.debug("Portal gun replacing existing portal owner={} side={} oldPortal={}", shooter.getUUID(), side, existing.getUUID()); existing.discard(); } if (this.isMoonSideArmed(stack, side)) { this.setMoonSide(stack, null); } - if (this.isMoonSideArmed(stack, this.otherSide(side))) { + if (shooter instanceof Player player && this.isMoonSideArmed(stack, this.otherSide(side))) { this.spawnBlackHole(level, player, stack, side, placement.center()); return; } PortalGunPortalEntity portal = new PortalGunPortalEntity(this.portalType.get(), level); - portal.configure(player.getUUID(), side, placement); + portal.configure(portalOwnerId, gunId, portalOwnerIdentity, channelName, side, placement); portal.moveTo(placement.center().x, placement.center().y, placement.center().z, placement.yaw(), 0.0F); - if (!this.placePortalBlocks(level, player.getUUID(), side, portal, placement)) { - Antarchy.LOGGER.info("Portal gun block placement failed owner={} side={} portal={} center={} facing={} up={} master={} base={}", player.getUUID(), side, portal.getUUID(), placement.center(), placement.facing(), placement.upAxis(), placement.masterPos(), placement.basePos()); - level.playSound(null, impactPos.x, impactPos.y, impactPos.z, PortalGunPortalEntity.sound("portal_gun_invalid_surface", SoundEvents.DISPENSER_FAIL), SoundSource.PLAYERS, 0.55F, 1.0F); + if (!this.placePortalBlocks(level, portalOwnerId, gunId, portalOwnerIdentity, channelName, side, portal, placement)) { + Antarchy.LOGGER.debug("Portal gun block placement failed owner={} side={} portal={} center={} facing={} up={} master={} base={}", shooter.getUUID(), side, portal.getUUID(), placement.center(), placement.facing(), placement.upAxis(), placement.masterPos(), placement.basePos()); + level.playSound(null, impactPos.x, impactPos.y, impactPos.z, PortalGunPortalEntity.sound("portal_gun_invalid_surface"), SoundSource.PLAYERS, 0.55F, 1.0F); return; } level.addFreshEntity(portal); - PortalGunSavedData.setPortal(level.getServer(), player.getUUID(), side, portal.getUUID()); - Antarchy.LOGGER.info("Portal gun portal placed owner={} side={} portal={} center={} facing={} up={} width={} master={} base={}", player.getUUID(), side, portal.getUUID(), placement.center(), placement.facing(), placement.upAxis(), placement.widthAxis(), placement.masterPos(), placement.basePos()); - PortalGunPortalEntity other = this.findCounterpart(level, player.getUUID(), side); + PortalGunSavedData.setPortal(level, portalOwnerId, gunId, channelName, side, portal.getUUID()); + PortalGunPortalEntity other = this.findCounterpart(level, portalOwnerId, gunId, channelName, side); if (other != null) { portal.linkTo(other); other.linkTo(portal); this.syncPortalBlocks(level, portal); this.syncPortalBlocks(level, other); - Antarchy.LOGGER.info("Portal gun portals linked owner={} placedSide={} placedPortal={} otherPortal={} otherSide={}", player.getUUID(), side, portal.getUUID(), other.getUUID(), other.getPortalSide()); - } else { - Antarchy.LOGGER.info("Portal gun portal waiting for counterpart owner={} side={} portal={}", player.getUUID(), side, portal.getUUID()); + } + if (stack.getItem() == this) { + this.setLastSide(stack, side); } String openPath = side == PortalGunPortalEntity.PortalSide.BLUE ? "portal_open_blue" : "portal_open_orange"; - level.playSound(null, portal.getX(), portal.getY(), portal.getZ(), PortalGunPortalEntity.sound(openPath, SoundEvents.END_PORTAL_SPAWN), SoundSource.PLAYERS, 0.45F, side == PortalGunPortalEntity.PortalSide.BLUE ? 1.05F : 0.92F); + level.playSound(null, portal.getX(), portal.getY(), portal.getZ(), PortalGunPortalEntity.sound(openPath), SoundSource.PLAYERS, 0.45F, side == PortalGunPortalEntity.PortalSide.BLUE ? 1.05F : 0.92F); + } + + private boolean isSamePortalPlacement(PortalGunPortalEntity portal, PortalGunPlacement placement) { + if (portal.getPortalWidth() != placement.width() + || portal.getPortalHeight() != placement.height() + || portal.getFacingDirection() != placement.facing() + || portal.getUpAxis() != placement.upAxis() + || !portal.getMasterPos().equals(placement.masterPos())) { + return false; + } + return new HashSet<>(java.util.Arrays.asList(portal.getPortalSpots())) + .equals(new HashSet<>(java.util.Arrays.asList(placement.portalSpots()))); } public ItemStack findMatchingGunStack(Player player, UUID gunId) { @@ -199,21 +257,22 @@ public class PortalGunItem extends Item implements GeoItem { return ItemStack.EMPTY; } - private boolean tryArmMoonShot(ServerLevel level, Player player, ItemStack stack, PortalGunPortalEntity.PortalSide side) { + private boolean tryArmMoonShot(ServerLevel level, Player player, ItemStack stack, PortalGunPortalEntity.PortalSide side, boolean offhand) { if (!level.dimensionType().hasSkyLight() || !level.isNight() || player.getXRot() > -40.0F) { return false; } - PortalGunPortalEntity existing = this.findPlacedPortal(level, player.getUUID(), side); + UUID ownerId = getPortalOwnerId(stack, player.getUUID()); + PortalGunPortalEntity existing = this.findPlacedPortal(level, ownerId, this.ensureGunId(stack, player.getUUID(), player.getGameProfile().getName()), getChannelName(stack), side); if (existing != null) { existing.discard(); } this.setMoonSide(stack, side); this.setLastSide(stack, side); this.triggerFireAnimation(level, player, stack); - stack.hurtAndBreak(1, player, EquipmentSlot.MAINHAND); + stack.hurtAndBreak(1, player, offhand ? EquipmentSlot.OFFHAND : EquipmentSlot.MAINHAND); player.awardStat(Stats.ITEM_USED.get(this)); player.getCooldowns().addCooldown(this, 6); - level.playSound(null, player.getX(), player.getY(), player.getZ(), PortalGunPortalEntity.sound(side == PortalGunPortalEntity.PortalSide.BLUE ? "portal_gun_fire_blue" : "portal_gun_fire_orange", SoundEvents.BEACON_ACTIVATE), SoundSource.PLAYERS, 0.65F, side == PortalGunPortalEntity.PortalSide.BLUE ? 1.15F : 0.88F); + level.playSound(null, player.getX(), player.getY(), player.getZ(), PortalGunPortalEntity.sound(side == PortalGunPortalEntity.PortalSide.BLUE ? "portal_gun_fire_blue" : "portal_gun_fire_orange"), SoundSource.PLAYERS, 0.65F, side == PortalGunPortalEntity.PortalSide.BLUE ? 1.15F : 0.88F); this.spawnShotTrail(level, player, player.getEyePosition().add(player.getLookAngle().scale(64.0D)), side); return true; } @@ -224,7 +283,7 @@ public class PortalGunItem extends Item implements GeoItem { blackHole.configure(player.getUUID()); blackHole.moveTo(spawnPos.x, spawnPos.y, spawnPos.z, 0.0F, 0.0F); level.addFreshEntity(blackHole); - level.playSound(null, spawnPos.x, spawnPos.y, spawnPos.z, PortalGunPortalEntity.sound("portal_gun_black_hole", SoundEvents.WARDEN_SONIC_BOOM), SoundSource.PLAYERS, 0.6F, 0.6F); + level.playSound(null, spawnPos.x, spawnPos.y, spawnPos.z, PortalGunPortalEntity.sound("portal_open_orange"), SoundSource.PLAYERS, 0.6F, 0.6F); return true; } @@ -241,20 +300,38 @@ public class PortalGunItem extends Item implements GeoItem { } } - private void spawnProjectile(ServerLevel level, Player player, ItemStack stack, PortalGunPortalEntity.PortalSide side) { - UUID gunId = this.ensureGunId(stack); - PortalGunProjectileEntity projectile = new PortalGunProjectileEntity(this.projectileType.get(), player, level); + private void spawnProjectile(ServerLevel level, LivingEntity shooter, ItemStack stack, PortalGunPortalEntity.PortalSide side) { + UUID gunId = this.ensureGunId(stack, shooter.getUUID(), shooter.getName().getString()); + PortalGunProjectileEntity projectile = new PortalGunProjectileEntity(this.projectileType.get(), shooter, level); projectile.configure(side, gunId, stack); - Vec3 look = player.getLookAngle(); - Vec3 start = player.getEyePosition().add(look.scale(0.4D)); + Vec3 look = shooter.getLookAngle(); + Vec3 start = shooter.getEyePosition(); + double launchSpeed = PortalGunProjectileEntity.MIN_LAUNCH_SPEED + level.getRandom().nextDouble() * PortalGunProjectileEntity.LAUNCH_SPEED_VARIANCE; + projectile.setProjectileSpeed(launchSpeed); projectile.setPos(start.x, start.y, start.z); - projectile.setDeltaMovement(look.scale(2.5D)); + projectile.setDeltaMovement(look.scale(projectile.getProjectileSpeed())); + projectile.syncLaunchState(); level.addFreshEntity(projectile); } - private PortalGunPortalEntity findCounterpart(ServerLevel level, UUID owner, PortalGunPortalEntity.PortalSide side) { + public static int getPortalWidth(ItemStack stack) { + net.minecraft.world.item.component.CustomData data = stack.get(DataComponents.CUSTOM_DATA); + return Mth.clamp(data == null ? 1 : data.copyTag().getInt(PORTAL_WIDTH_TAG), 1, MAX_PORTAL_WIDTH); + } + + public static int getPortalHeight(ItemStack stack) { + net.minecraft.world.item.component.CustomData data = stack.get(DataComponents.CUSTOM_DATA); + return Mth.clamp(data == null || !data.copyTag().contains(PORTAL_HEIGHT_TAG) ? 2 : data.copyTag().getInt(PORTAL_HEIGHT_TAG), 2, MAX_PORTAL_HEIGHT); + } + + public static int getGrabStrength(ItemStack stack) { + net.minecraft.world.item.component.CustomData data = stack.get(DataComponents.CUSTOM_DATA); + return Mth.clamp(data == null || !data.copyTag().contains(GRAB_STRENGTH_TAG) ? 4 : data.copyTag().getInt(GRAB_STRENGTH_TAG), 1, 4); + } + + private PortalGunPortalEntity findCounterpart(ServerLevel level, UUID owner, UUID gunId, String channelName, PortalGunPortalEntity.PortalSide side) { PortalGunPortalEntity.PortalSide otherSide = side == PortalGunPortalEntity.PortalSide.BLUE ? PortalGunPortalEntity.PortalSide.ORANGE : PortalGunPortalEntity.PortalSide.BLUE; - Optional otherId = PortalGunSavedData.getPortalId(level.getServer(), owner, otherSide); + Optional otherId = PortalGunSavedData.getPortalId(level.getServer(), owner, gunId, channelName, otherSide, level.dimension().location()); if (otherId.isEmpty()) { return null; } @@ -262,8 +339,8 @@ public class PortalGunItem extends Item implements GeoItem { return entity instanceof PortalGunPortalEntity portal ? portal : null; } - private PortalGunPortalEntity findPlacedPortal(ServerLevel level, UUID owner, PortalGunPortalEntity.PortalSide side) { - Optional portalId = PortalGunSavedData.getPortalId(level.getServer(), owner, side); + private PortalGunPortalEntity findPlacedPortal(ServerLevel level, UUID owner, UUID gunId, String channelName, PortalGunPortalEntity.PortalSide side) { + Optional portalId = PortalGunSavedData.getPortalId(level.getServer(), owner, gunId, channelName, side, level.dimension().location()); if (portalId.isEmpty()) { return null; } @@ -281,134 +358,245 @@ public class PortalGunItem extends Item implements GeoItem { return level.clip(new ClipContext(start, end, ClipContext.Block.COLLIDER, ClipContext.Fluid.NONE, player)); } - private PortalGunPlacement findPlacement(Level level, Player player, PortalGunPortalEntity.PortalSide side, BlockHitResult hitResult, PortalGunPortalEntity replacingPortal) { + private PortalGunPlacement findPlacement(Level level, LivingEntity shooter, UUID ownerId, String channelName, PortalGunPortalEntity.PortalSide side, BlockHitResult hitResult, PortalGunPortalEntity replacingPortal, int requestedWidth, int requestedHeight) { + int maxWidth = Mth.clamp(requestedWidth, 1, 16); + int maxHeight = Mth.clamp(requestedHeight, 2, 16); + boolean canResize = AntarchySettings.portalGunCanPortalsResizeWhenCreated(); + for (int height = maxHeight; height >= 2; height--) { + for (int width = maxWidth; width >= 1; width--) { + if (!canResize && (width != maxWidth || height != maxHeight)) { + continue; + } + PortalGunPlacement placement = this.findPlacementForSize(level, shooter, ownerId, channelName, side, hitResult, replacingPortal, width, height); + if (placement != null) { + return placement; + } + } + } + return null; + } + + private PortalGunPlacement findPlacementForSize(Level level, LivingEntity shooter, UUID ownerId, String channelName, PortalGunPortalEntity.PortalSide side, BlockHitResult hitResult, PortalGunPortalEntity replacingPortal, int width, int height) { Direction facing = hitResult.getDirection(); - Direction heightAxis = this.resolveUpAxis(player, facing); + Direction heightAxis = this.resolveUpAxis(shooter, facing); Direction widthAxis = PortalGunPlacement.widthAxis(facing, heightAxis); float yaw = PortalGunPlacement.yawFor(facing, heightAxis); BlockPos hitPos = hitResult.getBlockPos(); - Vec3 hitLocation = hitResult.getLocation(); - UUID replacingPortalId = replacingPortal == null ? null : replacingPortal.getUUID(); - PortalGunPlacement bestPlacement = null; - double bestScore = Double.MAX_VALUE; - for (int widthOffset = -3; widthOffset <= 3; widthOffset++) { - for (int heightOffset = -4; heightOffset <= 3; heightOffset++) { - BlockPos supportOrigin = hitPos.relative(widthAxis, widthOffset).relative(heightAxis, heightOffset); - if (!this.canPlaceAt(level, player.getUUID(), side, supportOrigin, facing, heightAxis, replacingPortalId)) { - continue; - } - BlockPos masterPos = supportOrigin.relative(facing); - BlockPos basePos = masterPos.relative(heightAxis); - BlockPos[] portalSpots = new BlockPos[] {masterPos, basePos}; - Vec3 firstCellCenter = masterPos.getCenter(); - Vec3 center = firstCellCenter - .add(Vec3.atLowerCornerOf(heightAxis.getNormal()).scale(0.5D)) - .subtract(Vec3.atLowerCornerOf(facing.getNormal()).scale(0.5D)); - Vec3 local = hitLocation.subtract(center); - double widthLocal = local.dot(Vec3.atLowerCornerOf(widthAxis.getNormal())); - double heightLocal = local.dot(Vec3.atLowerCornerOf(heightAxis.getNormal())); - double depthLocal = local.dot(Vec3.atLowerCornerOf(facing.getNormal())); - double widthDistance = Math.max(0.0D, Math.abs(widthLocal) - 0.5D); - double heightDistance = Math.max(0.0D, Math.abs(heightLocal) - 1.0D); - double score = widthDistance * widthDistance - + heightDistance * heightDistance - + Math.abs(depthLocal) * 0.25D - + supportOrigin.distSqr(hitPos) * 0.12D - + Math.abs(widthOffset) * 0.04D - + Math.abs(heightOffset) * 0.025D - + (widthOffset != 0 || heightOffset != 0 ? 0.02D : 0.0D); - if (score < bestScore) { - bestScore = score; - Set compensatedSpots = new HashSet<>(); - if (widthOffset != 0 || heightOffset != 0) { - compensatedSpots.add(hitPos.relative(facing)); - compensatedSpots.add(masterPos); - compensatedSpots.add(basePos); - compensatedSpots.add(supportOrigin); - compensatedSpots.add(basePos.relative(facing.getOpposite())); + int maxRadius = Math.max(width, height); + List radiusOrder = new java.util.ArrayList<>(maxRadius); + for (int radius = 0; radius < maxRadius; radius++) { + radiusOrder.add(radius); + } + int preferredRadius = maxRadius / 2; + radiusOrder.remove(Integer.valueOf(preferredRadius)); + radiusOrder.add(0, preferredRadius); + for (int radius : radiusOrder) { + for (int horizontalOffset = radius; horizontalOffset >= 0; horizontalOffset--) { + for (int verticalOffset = radius; verticalOffset >= 0; verticalOffset--) { + List supports = new java.util.ArrayList<>(width * height); + for (int widthCell = 1 - width; widthCell <= 0; widthCell++) { + for (int heightCell = 1 - height; heightCell <= 0; heightCell++) { + supports.add(this.referenceSupportPosition(hitPos, facing, heightAxis, horizontalOffset, verticalOffset, widthCell, heightCell)); + } } - bestPlacement = new PortalGunPlacement(center, facing, heightAxis, widthAxis, yaw, supportOrigin, masterPos, basePos, portalSpots, compensatedSpots); + if (!supports.contains(hitPos)) { + continue; + } + BlockPos[] portalSpots = new BlockPos[supports.size()]; + boolean valid = true; + int spotIndex = 0; + for (BlockPos supportPos : supports) { + BlockPos portalPos = supportPos.relative(facing); + BlockState supportState = level.getBlockState(supportPos); + BlockState portalState = level.getBlockState(portalPos); + if (!supportState.isFaceSturdy(level, supportPos, facing) + || !this.isPortalSpaceAvailable(level, portalPos, portalState, ownerId, channelName, side, facing)) { + valid = false; + break; + } + portalSpots[spotIndex++] = portalPos; + } + if (!valid) { + continue; + } + BlockPos masterPos = null; + for (BlockPos portalSpot : portalSpots) { + BlockState state = level.getBlockState(portalSpot); + boolean belongsToReplacingPortal = replacingPortal != null && replacingPortal.containsPortalSpot(portalSpot); + boolean existingPortalCell = level.getBlockEntity(portalSpot) instanceof PortalGunPortalCellAccess; + if ((state.canBeReplaced() || belongsToReplacingPortal || existingPortalCell) && level.getFluidState(portalSpot).isEmpty()) { + masterPos = portalSpot; + break; + } + } + if (masterPos == null) { + return null; + } + BlockPos[] orderedPortalSpots = new BlockPos[portalSpots.length]; + orderedPortalSpots[0] = masterPos; + int orderedSpotIndex = 1; + for (BlockPos portalSpot : portalSpots) { + if (!portalSpot.equals(masterPos)) { + orderedPortalSpots[orderedSpotIndex++] = portalSpot; + } + } + portalSpots = orderedPortalSpots; + Vec3 center = Vec3.ZERO; + Set compensatedSpots = new HashSet<>(); + for (BlockPos portalSpot : portalSpots) { + center = center.add(portalSpot.getCenter()); + if (horizontalOffset != 0 || verticalOffset != 0) { + compensatedSpots.add(portalSpot); + compensatedSpots.add(portalSpot.relative(facing.getOpposite())); + } + } + center = center.scale(1.0D / portalSpots.length) + .subtract(Vec3.atLowerCornerOf(facing.getNormal()).scale(0.5D)); + BlockPos supportOrigin = masterPos.relative(facing.getOpposite()); + BlockPos basePos = portalSpots.length > 1 ? portalSpots[1] : portalSpots[0]; + return new PortalGunPlacement(center, facing, heightAxis, widthAxis, yaw, supportOrigin, masterPos, basePos, portalSpots, compensatedSpots, width, height); } } } - return bestPlacement; + return null; } - private Direction resolveUpAxis(Player player, Direction facing) { + private BlockPos referenceSupportPosition(BlockPos origin, Direction facing, Direction upAxis, int horizontalOffset, int verticalOffset, int widthCell, int heightCell) { + int x; + int y; + int z; + if (facing.getAxis() != Direction.Axis.Y) { + if (facing.getAxis() == Direction.Axis.Z) { + x = horizontalOffset + widthCell; + z = 0; + } else { + x = 0; + z = horizontalOffset + widthCell; + } + y = -verticalOffset - heightCell; + } else if (upAxis == Direction.NORTH) { + x = horizontalOffset + widthCell; + y = 0; + z = verticalOffset + heightCell; + } else if (upAxis == Direction.SOUTH) { + x = horizontalOffset + widthCell; + y = 0; + z = -verticalOffset - heightCell; + } else if (upAxis == Direction.WEST) { + x = -verticalOffset - heightCell; + y = 0; + z = horizontalOffset + widthCell; + } else { + x = verticalOffset + heightCell; + y = 0; + z = horizontalOffset + widthCell; + } + return origin.offset(x, y, z); + } + + private Direction resolveUpAxis(LivingEntity shooter, Direction facing) { if (facing.getAxis() != Direction.Axis.Y) { return Direction.UP; } - Direction horizontal = player.getDirection(); + Direction horizontal = shooter.getDirection(); if (horizontal.getAxis() == Direction.Axis.Y) { return Direction.NORTH; } - return horizontal.getOpposite(); + return horizontal; } - private boolean canPlaceAt(Level level, UUID ownerId, PortalGunPortalEntity.PortalSide side, BlockPos supportOrigin, Direction facing, Direction heightAxis, UUID replacingPortalId) { - for (int h = 0; h < 2; h++) { - BlockPos supportPos = supportOrigin.relative(heightAxis, h); - BlockPos airPos = supportPos.relative(facing); - BlockState supportState = level.getBlockState(supportPos); - BlockState airState = level.getBlockState(airPos); - FluidState fluidState = level.getFluidState(airPos); - if (!supportState.isFaceSturdy(level, supportPos, facing)) { - return false; - } - if (!this.isPortalSpaceAvailable(level, airPos, airState, ownerId, side, replacingPortalId)) { - return false; - } - if (!fluidState.isEmpty()) { - return false; - } - } - return true; - } - - private boolean isPortalSpaceAvailable(Level level, BlockPos pos, BlockState state, UUID ownerId, PortalGunPortalEntity.PortalSide side, UUID replacingPortalId) { - if (state.isAir() || state.canBeReplaced()) { - return true; - } + private boolean isPortalSpaceAvailable(Level level, BlockPos pos, BlockState state, UUID ownerId, String channelName, PortalGunPortalEntity.PortalSide side, Direction face) { BlockEntity blockEntity = level.getBlockEntity(pos); - if (blockEntity instanceof PortalGunPortalMasterBlockEntity master) { + if (blockEntity instanceof PortalGunPortalCellAccess portalCell) { + PortalGunPortalFaceRecord record = portalCell.portalGun$getFaceRecord(face); + if (record == null) { + if (blockEntity instanceof PortalGunPortalMasterBlockEntity master && master.getPortalId() != null && master.getFacing() == face) { + return ownerId != null && ownerId.equals(master.getOwnerId()) && channelName.equals(master.getChannelName()) && master.getSide() == side; + } + if (blockEntity instanceof PortalGunPortalBaseBlockEntity base && base.getPortalId() != null) { + BlockEntity masterEntity = level.getBlockEntity(base.getMasterPos()); + if (!(masterEntity instanceof PortalGunPortalMasterBlockEntity master)) { + return false; + } + return master.getFacing() != face + || (ownerId != null + && ownerId.equals(base.getOwnerId()) + && channelName.equals(master.getChannelName()) + && base.getSide() == side + && master.getSide() == side); + } + return true; + } return ownerId != null - && ownerId.equals(master.getOwnerId()) - && master.getSide() == side - && (replacingPortalId == null || replacingPortalId.equals(master.getPortalId())); + && ownerId.equals(record.ownerId()) + && channelName.equals(record.channelName()) + && record.side() == side; } - if (blockEntity instanceof PortalGunPortalBaseBlockEntity base) { - return ownerId != null - && ownerId.equals(base.getOwnerId()) - && base.getSide() == side - && (replacingPortalId == null || replacingPortalId.equals(base.getPortalId())); - } - return false; + return state.getCollisionShape(level, pos).isEmpty(); } - private boolean placePortalBlocks(ServerLevel level, UUID ownerId, PortalGunPortalEntity.PortalSide side, PortalGunPortalEntity portal, PortalGunPlacement placement) { - if (!level.setBlock(placement.masterPos(), this.portalMasterBlock.get().defaultBlockState(), 3)) { - return false; + private boolean placePortalBlocks(ServerLevel level, UUID ownerId, UUID gunId, String ownerIdentity, String channelName, PortalGunPortalEntity.PortalSide side, PortalGunPortalEntity portal, PortalGunPlacement placement) { + int pairTime = level.getServer().getTickCount(); + for (int i = 0; i < placement.portalSpots().length; i++) { + BlockPos spot = placement.portalSpots()[i]; + BlockEntity existing = level.getBlockEntity(spot); + boolean masterRole = i == 0; + if (!(existing instanceof PortalGunPortalCellAccess)) { + BlockState block = masterRole ? this.portalMasterBlock.get().defaultBlockState() : this.portalBaseBlock.get().defaultBlockState(); + if (!level.setBlock(spot, block, 3)) { + for (BlockPos placed : placement.portalSpots()) { + BlockEntity cell = level.getBlockEntity(placed); + if (cell instanceof PortalGunPortalCellAccess portalCell) { + portalCell.portalGun$removeFaceRecord(placement.facing(), portal.getUUID()); + } + } + return false; + } + existing = level.getBlockEntity(spot); + } + if (!(existing instanceof PortalGunPortalCellAccess cell) + || !cell.portalGun$putFaceRecord(new PortalGunPortalFaceRecord( + ownerId, + ownerIdentity, + gunId, + channelName, + portal.getUUID(), + portal.getLinkedPortalId(), + side, + placement.facing(), + placement.upAxis(), + placement.masterPos(), + placement.basePos(), + java.util.Arrays.asList(placement.portalSpots()), + placement.compensatedSpots(), + pairTime, + placement.width(), + placement.height(), + masterRole + ))) { + return false; + } + if (masterRole && existing instanceof PortalGunPortalMasterBlockEntity master + && (master.getPortalId() == null || master.getPortalId().equals(portal.getUUID()))) { + master.configure(ownerId, gunId, ownerIdentity, channelName, portal.getUUID(), side, placement.facing(), placement.upAxis(), placement.basePos(), placement.portalSpots(), placement.compensatedSpots(), pairTime, placement.width(), placement.height()); + } else if (!masterRole && existing instanceof PortalGunPortalBaseBlockEntity base + && (base.getPortalId() == null || base.getPortalId().equals(portal.getUUID()))) { + base.configure(ownerId, portal.getUUID(), side, placement.masterPos()); + } } - if (!level.setBlock(placement.basePos(), this.portalBaseBlock.get().defaultBlockState(), 3)) { - level.setBlock(placement.masterPos(), Blocks.AIR.defaultBlockState(), 3); - return false; - } - if (!(level.getBlockEntity(placement.masterPos()) instanceof PortalGunPortalMasterBlockEntity master) || !(level.getBlockEntity(placement.basePos()) instanceof PortalGunPortalBaseBlockEntity base)) { - level.setBlock(placement.masterPos(), Blocks.AIR.defaultBlockState(), 3); - level.setBlock(placement.basePos(), Blocks.AIR.defaultBlockState(), 3); - return false; - } - master.configure(ownerId, portal.getUUID(), side, placement.facing(), placement.upAxis(), placement.basePos(), placement.portalSpots(), placement.compensatedSpots(), level.getServer().getTickCount()); - base.configure(ownerId, portal.getUUID(), side, placement.masterPos()); return true; } private void syncPortalBlocks(ServerLevel level, PortalGunPortalEntity portal) { - if (level.getBlockEntity(portal.getMasterPos()) instanceof PortalGunPortalMasterBlockEntity master) { - master.updatePair(portal.getLinkedPortalId(), portal.getPairTime()); - } - if (level.getBlockEntity(portal.getBasePos()) instanceof PortalGunPortalBaseBlockEntity base) { - base.updatePair(portal.getLinkedPortalId(), portal.getPairTime()); + for (BlockPos portalSpot : portal.getPortalSpots()) { + if (level.getBlockEntity(portalSpot) instanceof PortalGunPortalCellAccess cell) { + cell.portalGun$updateFacePair(portal.getUUID(), portal.getLinkedPortalId(), portal.getPairTime()); + } + if (portalSpot.equals(portal.getMasterPos()) && level.getBlockEntity(portalSpot) instanceof PortalGunPortalMasterBlockEntity master && portal.getUUID().equals(master.getPortalId())) { + master.updatePair(portal.getLinkedPortalId(), portal.getPairTime()); + } else if (!portalSpot.equals(portal.getMasterPos()) && level.getBlockEntity(portalSpot) instanceof PortalGunPortalBaseBlockEntity base && portal.getUUID().equals(base.getPortalId())) { + base.updatePair(portal.getLinkedPortalId(), portal.getPairTime()); + } } } @@ -452,16 +640,115 @@ public class PortalGunItem extends Item implements GeoItem { return side.name().equals(sideName); } + public static UUID getGunId(ItemStack stack) { + net.minecraft.world.item.component.CustomData customData = stack.get(DataComponents.CUSTOM_DATA); + if (customData == null || !customData.copyTag().hasUUID(GUN_ID_TAG)) { + return null; + } + return customData.copyTag().getUUID(GUN_ID_TAG); + } + private UUID ensureGunId(ItemStack stack) { net.minecraft.world.item.component.CustomData customData = stack.get(DataComponents.CUSTOM_DATA); + UUID gunId; if (customData != null && customData.copyTag().hasUUID(GUN_ID_TAG)) { - return customData.copyTag().getUUID(GUN_ID_TAG); + gunId = customData.copyTag().getUUID(GUN_ID_TAG); + } else { + gunId = UUID.randomUUID(); } - UUID gunId = UUID.randomUUID(); - net.minecraft.world.item.component.CustomData.update(DataComponents.CUSTOM_DATA, stack, tag -> tag.putUUID(GUN_ID_TAG, gunId)); + String channelName = "Random Channel #" + gunId.hashCode(); + net.minecraft.world.item.component.CustomData.update(DataComponents.CUSTOM_DATA, stack, tag -> { + tag.putUUID(GUN_ID_TAG, gunId); + if (!tag.contains(CHANNEL_NAME_TAG)) { + tag.putString(CHANNEL_NAME_TAG, channelName); + } + if (!tag.contains(LAST_SIDE_TAG)) { + tag.putString(LAST_SIDE_TAG, PortalGunPortalEntity.PortalSide.BLUE.name()); + } + }); return gunId; } + private UUID ensureGunId(ItemStack stack, UUID ownerId, String ownerName) { + UUID gunId = this.ensureGunId(stack); + net.minecraft.world.item.component.CustomData.update(DataComponents.CUSTOM_DATA, stack, tag -> { + if (!tag.contains(OWNER_ID_TAG)) { + tag.putString(OWNER_ID_TAG, ownerId.toString()); + tag.putString(OWNER_NAME_TAG, ownerName); + } + }); + return gunId; + } + + public static String getChannelName(ItemStack stack) { + net.minecraft.world.item.component.CustomData customData = stack.get(DataComponents.CUSTOM_DATA); + if (customData == null) { + return ""; + } + return customData.copyTag().getString(CHANNEL_NAME_TAG); + } + + public static String getPortalOwnerName(ItemStack stack) { + net.minecraft.world.item.component.CustomData customData = stack.get(DataComponents.CUSTOM_DATA); + return customData == null ? "" : customData.copyTag().getString(OWNER_NAME_TAG); + } + + public static UUID ensureGunIdentity(ItemStack stack) { + if (stack.isEmpty() || !(stack.getItem() instanceof PortalGunItem item)) { + return null; + } + return item.ensureGunId(stack); + } + + public static UUID ensureGunIdentity(ItemStack stack, Player owner) { + if (stack.isEmpty() || !(stack.getItem() instanceof PortalGunItem item)) { + return null; + } + return item.ensureGunId(stack, owner.getUUID(), owner.getGameProfile().getName()); + } + + public static UUID getPortalOwnerId(ItemStack stack, UUID fallback) { + net.minecraft.world.item.component.CustomData customData = stack.get(DataComponents.CUSTOM_DATA); + if (customData == null) { + return fallback; + } + String owner = customData.copyTag().getString(OWNER_ID_TAG); + if ("Global".equals(owner)) { + return GLOBAL_OWNER_ID; + } + try { + return owner.isEmpty() ? fallback : UUID.fromString(owner); + } catch (IllegalArgumentException ignored) { + return fallback; + } + } + + public static String getPortalOwnerIdentity(ItemStack stack, UUID fallback) { + net.minecraft.world.item.component.CustomData customData = stack.get(DataComponents.CUSTOM_DATA); + if (customData == null) { + return String.valueOf(fallback); + } + String owner = customData.copyTag().getString(OWNER_ID_TAG); + return owner.isEmpty() ? String.valueOf(fallback) : owner; + } + + public static ItemStack createGlobalVariant(Item item, String channelName) { + ItemStack stack = new ItemStack(item); + UUID gunId = UUID.nameUUIDFromBytes(("Global\u0000" + channelName).getBytes(StandardCharsets.UTF_8)); + net.minecraft.world.item.component.CustomData.update(DataComponents.CUSTOM_DATA, stack, tag -> { + tag.putUUID(GUN_ID_TAG, gunId); + tag.putString(OWNER_ID_TAG, "Global"); + tag.putString(OWNER_NAME_TAG, "Global"); + tag.putString(CHANNEL_NAME_TAG, channelName); + tag.putInt(PORTAL_WIDTH_TAG, 1); + tag.putInt(PORTAL_HEIGHT_TAG, 2); + tag.putInt(GRAB_STRENGTH_TAG, 4); + tag.putString(LAST_SIDE_TAG, PortalGunPortalEntity.PortalSide.BLUE.name()); + tag.putBoolean("lastFired", true); + }); + return stack; + } + private boolean matchesGunId(ItemStack stack, UUID gunId) { if (stack.isEmpty() || stack.getItem() != this) { return false; @@ -484,7 +771,17 @@ public class PortalGunItem extends Item implements GeoItem { public void appendHoverText(ItemStack stack, Item.TooltipContext context, List tooltipComponents, TooltipFlag tooltipFlag) { tooltipComponents.add(Component.translatable("tooltip.antarchy.portal_gun.primary").withStyle(ChatFormatting.GRAY)); tooltipComponents.add(Component.translatable("tooltip.antarchy.portal_gun.secondary").withStyle(ChatFormatting.GRAY)); + String ownerName = getPortalOwnerName(stack); + if (!ownerName.isEmpty()) { + tooltipComponents.add(Component.translatable("tooltip.antarchy.portal_gun.owner", ownerName).withStyle(ChatFormatting.GRAY)); + } + String channelName = getChannelName(stack); + if (!channelName.isEmpty()) { + tooltipComponents.add(Component.translatable("tooltip.antarchy.portal_gun.channel", channelName).withStyle(ChatFormatting.GRAY)); + } tooltipComponents.add(Component.translatable("tooltip.antarchy.portal_gun.reset").withStyle(ChatFormatting.DARK_GRAY)); + tooltipComponents.add(Component.translatable("tooltip.antarchy.portal_gun.size", getPortalWidth(stack), getPortalHeight(stack)).withStyle(ChatFormatting.GRAY)); + tooltipComponents.add(Component.translatable("tooltip.antarchy.portal_gun.grab_strength", getGrabStrength(stack)).withStyle(ChatFormatting.GRAY)); } @Override diff --git a/common-1.21.1/src/main/java/com/craisinlord/antarchy/content/network/PortalGunGrabPayload.java b/common-1.21.1/src/main/java/com/craisinlord/antarchy/content/network/PortalGunGrabPayload.java new file mode 100644 index 00000000..410ba1c3 --- /dev/null +++ b/common-1.21.1/src/main/java/com/craisinlord/antarchy/content/network/PortalGunGrabPayload.java @@ -0,0 +1,17 @@ +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 PortalGunGrabPayload() implements CustomPacketPayload { + public static final Type TYPE = new Type<>(ResourceLocation.fromNamespaceAndPath(Antarchy.MODID, "portal_gun_grab")); + public static final StreamCodec STREAM_CODEC = StreamCodec.unit(new PortalGunGrabPayload()); + + @Override + public Type type() { + return TYPE; + } +} diff --git a/common-1.21.1/src/main/java/com/craisinlord/antarchy/content/network/PortalGunGrabStatePayload.java b/common-1.21.1/src/main/java/com/craisinlord/antarchy/content/network/PortalGunGrabStatePayload.java new file mode 100644 index 00000000..04097b63 --- /dev/null +++ b/common-1.21.1/src/main/java/com/craisinlord/antarchy/content/network/PortalGunGrabStatePayload.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 PortalGunGrabStatePayload(boolean active) implements CustomPacketPayload { + public static final Type TYPE = new Type<>(ResourceLocation.fromNamespaceAndPath(Antarchy.MODID, "portal_gun_grab_state")); + public static final StreamCodec STREAM_CODEC = StreamCodec.composite( + ByteBufCodecs.BOOL, PortalGunGrabStatePayload::active, PortalGunGrabStatePayload::new + ); + + @Override + public Type type() { + return TYPE; + } +} diff --git a/common-1.21.1/src/main/java/com/craisinlord/antarchy/content/network/PortalGunIndicatorPayload.java b/common-1.21.1/src/main/java/com/craisinlord/antarchy/content/network/PortalGunIndicatorPayload.java new file mode 100644 index 00000000..684e1831 --- /dev/null +++ b/common-1.21.1/src/main/java/com/craisinlord/antarchy/content/network/PortalGunIndicatorPayload.java @@ -0,0 +1,26 @@ +package com.craisinlord.antarchy.content.network; + +import com.craisinlord.antarchy.Antarchy; +import io.netty.buffer.ByteBuf; +import java.util.UUID; +import net.minecraft.network.codec.StreamCodec; +import net.minecraft.network.protocol.common.custom.CustomPacketPayload; +import net.minecraft.resources.ResourceLocation; + +public record PortalGunIndicatorPayload(UUID gunId, boolean blueAvailable, boolean orangeAvailable) implements CustomPacketPayload { + public static final Type TYPE = new Type<>(ResourceLocation.fromNamespaceAndPath(Antarchy.MODID, "portal_gun_indicator")); + public static final StreamCodec STREAM_CODEC = StreamCodec.of( + (buffer, payload) -> { + buffer.writeLong(payload.gunId().getMostSignificantBits()); + buffer.writeLong(payload.gunId().getLeastSignificantBits()); + buffer.writeBoolean(payload.blueAvailable()); + buffer.writeBoolean(payload.orangeAvailable()); + }, + buffer -> new PortalGunIndicatorPayload(new UUID(buffer.readLong(), buffer.readLong()), buffer.readBoolean(), buffer.readBoolean()) + ); + + @Override + public Type type() { + return TYPE; + } +} diff --git a/common-1.21.1/src/main/java/com/craisinlord/antarchy/content/network/PortalGunIndicatorRequestPayload.java b/common-1.21.1/src/main/java/com/craisinlord/antarchy/content/network/PortalGunIndicatorRequestPayload.java new file mode 100644 index 00000000..6d16cb9a --- /dev/null +++ b/common-1.21.1/src/main/java/com/craisinlord/antarchy/content/network/PortalGunIndicatorRequestPayload.java @@ -0,0 +1,24 @@ +package com.craisinlord.antarchy.content.network; + +import com.craisinlord.antarchy.Antarchy; +import io.netty.buffer.ByteBuf; +import java.util.UUID; +import net.minecraft.network.codec.StreamCodec; +import net.minecraft.network.protocol.common.custom.CustomPacketPayload; +import net.minecraft.resources.ResourceLocation; + +public record PortalGunIndicatorRequestPayload(UUID gunId) implements CustomPacketPayload { + public static final Type TYPE = new Type<>(ResourceLocation.fromNamespaceAndPath(Antarchy.MODID, "portal_gun_indicator_request")); + public static final StreamCodec STREAM_CODEC = StreamCodec.of( + (buffer, payload) -> { + buffer.writeLong(payload.gunId().getMostSignificantBits()); + buffer.writeLong(payload.gunId().getLeastSignificantBits()); + }, + buffer -> new PortalGunIndicatorRequestPayload(new UUID(buffer.readLong(), buffer.readLong())) + ); + + @Override + public Type type() { + return TYPE; + } +} diff --git a/common-1.21.1/src/main/java/com/craisinlord/antarchy/content/network/PortalGunPrimaryPayload.java b/common-1.21.1/src/main/java/com/craisinlord/antarchy/content/network/PortalGunPrimaryPayload.java index e466a550..fa21e897 100644 --- a/common-1.21.1/src/main/java/com/craisinlord/antarchy/content/network/PortalGunPrimaryPayload.java +++ b/common-1.21.1/src/main/java/com/craisinlord/antarchy/content/network/PortalGunPrimaryPayload.java @@ -2,13 +2,17 @@ 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 PortalGunPrimaryPayload() implements CustomPacketPayload { +public record PortalGunPrimaryPayload(boolean offhand) implements CustomPacketPayload { public static final Type TYPE = new Type<>(ResourceLocation.fromNamespaceAndPath(Antarchy.MODID, "portal_gun_primary")); - public static final StreamCodec STREAM_CODEC = StreamCodec.unit(new PortalGunPrimaryPayload()); + public static final StreamCodec STREAM_CODEC = StreamCodec.composite( + ByteBufCodecs.BOOL, PortalGunPrimaryPayload::offhand, + PortalGunPrimaryPayload::new + ); @Override public Type type() { diff --git a/common-1.21.1/src/main/java/com/craisinlord/antarchy/content/network/PortalGunResetPayload.java b/common-1.21.1/src/main/java/com/craisinlord/antarchy/content/network/PortalGunResetPayload.java new file mode 100644 index 00000000..4e1aef8b --- /dev/null +++ b/common-1.21.1/src/main/java/com/craisinlord/antarchy/content/network/PortalGunResetPayload.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 PortalGunResetPayload(int action) implements CustomPacketPayload { + public static final Type TYPE = new Type<>(ResourceLocation.fromNamespaceAndPath(Antarchy.MODID, "portal_gun_reset")); + public static final StreamCodec STREAM_CODEC = StreamCodec.composite( + ByteBufCodecs.VAR_INT, PortalGunResetPayload::action, PortalGunResetPayload::new + ); + + @Override + public Type type() { + return TYPE; + } +} diff --git a/common-1.21.1/src/main/java/com/craisinlord/antarchy/content/portalgun/PortalGunBlackHoleEntity.java b/common-1.21.1/src/main/java/com/craisinlord/antarchy/content/portalgun/PortalGunBlackHoleEntity.java index 9be64f20..33b326e4 100644 --- a/common-1.21.1/src/main/java/com/craisinlord/antarchy/content/portalgun/PortalGunBlackHoleEntity.java +++ b/common-1.21.1/src/main/java/com/craisinlord/antarchy/content/portalgun/PortalGunBlackHoleEntity.java @@ -4,7 +4,6 @@ import java.util.List; import java.util.UUID; import net.minecraft.nbt.CompoundTag; 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; @@ -73,7 +72,7 @@ public class PortalGunBlackHoleEntity extends Entity implements GeoEntity { } this.pullNearby(serverLevel); if (this.ageTicks % 10 == 0) { - serverLevel.playSound(null, this.blockPosition(), SoundEvents.PORTAL_AMBIENT, SoundSource.HOSTILE, 0.5F, 0.6F); + serverLevel.playSound(null, this.blockPosition(), PortalGunPortalEntity.sound("portal_ambient"), SoundSource.HOSTILE, 0.5F, 0.6F); } } diff --git a/common-1.21.1/src/main/java/com/craisinlord/antarchy/content/portalgun/PortalGunCollisionHelper.java b/common-1.21.1/src/main/java/com/craisinlord/antarchy/content/portalgun/PortalGunCollisionHelper.java index e659d73b..63ffe32b 100644 --- a/common-1.21.1/src/main/java/com/craisinlord/antarchy/content/portalgun/PortalGunCollisionHelper.java +++ b/common-1.21.1/src/main/java/com/craisinlord/antarchy/content/portalgun/PortalGunCollisionHelper.java @@ -23,13 +23,12 @@ public final class PortalGunCollisionHelper { return collided; } AABB pathBox = startBox.expandTowards(movement).inflate(SEARCH_PADDING); - List nearby = entity.level().getEntities(entity, pathBox, candidate -> candidate instanceof PortalGunPortalEntity && candidate.isAlive()); + List nearby = PortalGunPortalEntity.findPortalsNearBounds(entity.level(), pathBox); if (nearby.isEmpty()) { return collided; } Vec3 adjusted = collided; - for (Entity candidate : nearby) { - PortalGunPortalEntity portal = (PortalGunPortalEntity) candidate; + for (PortalGunPortalEntity portal : nearby) { Vec3 candidateAdjusted = resolveCollisionAgainstPortal(entity, startBox, movement, adjusted, portal); if (candidateAdjusted.distanceToSqr(movement) < adjusted.distanceToSqr(movement)) { adjusted = candidateAdjusted; @@ -39,6 +38,10 @@ public final class PortalGunCollisionHelper { } private static Vec3 resolveCollisionAgainstPortal(Entity entity, AABB startBox, Vec3 movement, Vec3 collided, PortalGunPortalEntity portal) { + PortalGunPortalEntity linked = portal.getLinkedPortal(); + if (linked == null || !linked.isAlive() || portal.isTeleportCoolingDown(entity)) { + return collided; + } PortalGunWorldPortalShape shape = portal.getWorldPortalShape(); Vec3 normal = portal.getNormalVec().normalize(); double desiredNormal = movement.dot(normal); @@ -60,21 +63,21 @@ public final class PortalGunCollisionHelper { && !portal.getPortalInsides(entity).intersects(endBox) && !intersectsPortalWindow(startBox, shape) && !intersectsPortalWindow(endBox, shape) - && !segmentIntersectsPortalWindow(shape, startBox, endBox)) { + && !segmentIntersectsPortalWindow(entity, portal, startBox, endBox)) { return false; } - if (crossesPortal(shape, entity, startBox, movement)) { + Vec3 startProbe = portal.teleportProbePosition(entity, startBox); + Vec3 endProbe = portal.teleportProbePosition(entity, endBox); + if (portal.crossesPortal(startProbe, endProbe)) { return true; } - Vec3 startCenter = startBox.getCenter(); - Vec3 endCenter = endBox.getCenter(); - PortalGunWorldPortalShape.PortalLocalCoords startCoords = shape.localCoords(startCenter); - PortalGunWorldPortalShape.PortalLocalCoords endCoords = shape.localCoords(endCenter); + PortalGunWorldPortalShape.PortalLocalCoords startCoords = shape.localCoords(startProbe); + PortalGunWorldPortalShape.PortalLocalCoords endCoords = shape.localCoords(endProbe); return startCoords.depth() > 0.0D && endCoords.depth() >= -PLANE_TOLERANCE && Math.abs(endCoords.horizontal()) <= shape.halfWidth() + EDGE_PADDING && Math.abs(endCoords.vertical()) <= shape.halfHeight() + EDGE_PADDING - && projectedWindowOverlap(shape, startBox, endBox); + && projectedWindowOverlap(shape, startProbe, endProbe); } private static boolean intersectsPortalWindow(AABB box, PortalGunWorldPortalShape shape) { @@ -90,39 +93,17 @@ public final class PortalGunCollisionHelper { return false; } - private static boolean segmentIntersectsPortalWindow(PortalGunWorldPortalShape shape, AABB startBox, AABB endBox) { - for (Vec3 startCorner : corners(startBox)) { - Vec3 endCorner = matchSample(startCorner, startBox, endBox); - if (shape.crosses(startCorner, endCorner)) { - return true; - } - } - return false; + private static boolean segmentIntersectsPortalWindow(Entity entity, PortalGunPortalEntity portal, AABB startBox, AABB endBox) { + return portal.crossesPortal(portal.teleportProbePosition(entity, startBox), portal.teleportProbePosition(entity, endBox)); } - private static boolean projectedWindowOverlap(PortalGunWorldPortalShape shape, AABB startBox, AABB endBox) { - double minHorizontal = Double.POSITIVE_INFINITY; - double maxHorizontal = Double.NEGATIVE_INFINITY; - double minVertical = Double.POSITIVE_INFINITY; - double maxVertical = Double.NEGATIVE_INFINITY; - for (Vec3 corner : corners(startBox)) { - PortalGunWorldPortalShape.PortalLocalCoords coords = shape.localCoords(corner); - minHorizontal = Math.min(minHorizontal, coords.horizontal()); - maxHorizontal = Math.max(maxHorizontal, coords.horizontal()); - minVertical = Math.min(minVertical, coords.vertical()); - maxVertical = Math.max(maxVertical, coords.vertical()); - } - for (Vec3 corner : corners(endBox)) { - PortalGunWorldPortalShape.PortalLocalCoords coords = shape.localCoords(corner); - minHorizontal = Math.min(minHorizontal, coords.horizontal()); - maxHorizontal = Math.max(maxHorizontal, coords.horizontal()); - minVertical = Math.min(minVertical, coords.vertical()); - maxVertical = Math.max(maxVertical, coords.vertical()); - } - return maxHorizontal >= -shape.halfWidth() - BORDER_TOLERANCE - && minHorizontal <= shape.halfWidth() + BORDER_TOLERANCE - && maxVertical >= -shape.halfHeight() - BORDER_TOLERANCE - && minVertical <= shape.halfHeight() + BORDER_TOLERANCE; + private static boolean projectedWindowOverlap(PortalGunWorldPortalShape shape, Vec3 startProbe, Vec3 endProbe) { + PortalGunWorldPortalShape.PortalLocalCoords start = shape.localCoords(startProbe); + PortalGunWorldPortalShape.PortalLocalCoords end = shape.localCoords(endProbe); + return Math.abs(start.horizontal()) <= shape.halfWidth() + BORDER_TOLERANCE + && Math.abs(start.vertical()) <= shape.halfHeight() + BORDER_TOLERANCE + && Math.abs(end.horizontal()) <= shape.halfWidth() + BORDER_TOLERANCE + && Math.abs(end.vertical()) <= shape.halfHeight() + BORDER_TOLERANCE; } private static Vec3 applyBorderCollision(Entity entity, AABB startBox, Vec3 movement, PortalGunPortalEntity portal) { @@ -143,51 +124,6 @@ public final class PortalGunCollisionHelper { return collided.distanceToSqr(movement) < 1.0E-7D ? movement : collided; } - private static boolean crossesPortal(PortalGunWorldPortalShape shape, Entity entity, AABB startBox, Vec3 movement) { - AABB endBox = startBox.move(movement); - for (Vec3 startSample : portalSamples(entity, startBox, true)) { - Vec3 endSample = matchSample(startSample, startBox, endBox); - if (shape.crosses(startSample, endSample)) { - return true; - } - } - return false; - } - - private static List portalSamples(Entity entity, AABB box, boolean includeEye) { - Vec3 center = new Vec3((box.minX + box.maxX) * 0.5D, (box.minY + box.maxY) * 0.5D, (box.minZ + box.maxZ) * 0.5D); - Vec3 topCenter = new Vec3(center.x, box.maxY, center.z); - Vec3 bottomCenter = new Vec3(center.x, box.minY, center.z); - if (includeEye) { - Vec3 eye = new Vec3(entity.getX(), entity.getY() + entity.getEyeHeight(), entity.getZ()); - return List.of(center, eye, topCenter, bottomCenter); - } - return List.of(center, topCenter, bottomCenter); - } - - private static Vec3 matchSample(Vec3 startSample, AABB startBox, AABB endBox) { - double xLerp = axisRatio(startSample.x, startBox.minX, startBox.maxX); - double yLerp = axisRatio(startSample.y, startBox.minY, startBox.maxY); - double zLerp = axisRatio(startSample.z, startBox.minZ, startBox.maxZ); - return new Vec3( - lerp(endBox.minX, endBox.maxX, xLerp), - lerp(endBox.minY, endBox.maxY, yLerp), - lerp(endBox.minZ, endBox.maxZ, zLerp) - ); - } - - private static double axisRatio(double value, double min, double max) { - double size = max - min; - if (Math.abs(size) < 1.0E-6D) { - return 0.5D; - } - return (value - min) / size; - } - - private static double lerp(double min, double max, double delta) { - return min + (max - min) * delta; - } - private static Vec3[] corners(AABB box) { return new Vec3[] { new Vec3(box.minX, box.minY, box.minZ), diff --git a/common-1.21.1/src/main/java/com/craisinlord/antarchy/content/portalgun/PortalGunGrabManager.java b/common-1.21.1/src/main/java/com/craisinlord/antarchy/content/portalgun/PortalGunGrabManager.java new file mode 100644 index 00000000..084a842d --- /dev/null +++ b/common-1.21.1/src/main/java/com/craisinlord/antarchy/content/portalgun/PortalGunGrabManager.java @@ -0,0 +1,280 @@ +package com.craisinlord.antarchy.content.portalgun; + +import com.craisinlord.antarchy.content.item.PortalGunItem; +import com.craisinlord.antarchy.content.AntarchyTags; +import com.craisinlord.antarchy.Antarchy; +import com.craisinlord.antarchy.config.AntarchySettings; +import java.util.HashMap; +import java.util.Map; +import java.util.UUID; +import net.minecraft.server.MinecraftServer; +import net.minecraft.server.level.ServerLevel; +import net.minecraft.server.level.ServerPlayer; +import net.minecraft.core.BlockPos; +import net.minecraft.core.registries.BuiltInRegistries; +import net.minecraft.world.level.block.state.properties.BlockStateProperties; +import net.minecraft.world.level.block.state.properties.DoubleBlockHalf; +import net.minecraft.world.level.block.entity.BlockEntity; +import net.minecraft.nbt.CompoundTag; +import net.minecraft.world.entity.item.FallingBlockEntity; +import net.minecraft.world.entity.Entity; +import net.minecraft.world.entity.MoverType; +import net.minecraft.world.entity.projectile.AbstractHurtingProjectile; +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.level.block.state.BlockState; +import net.minecraft.world.level.block.Blocks; +import net.minecraft.tags.BlockTags; +import net.minecraft.world.level.block.RenderShape; +import net.minecraft.world.level.ClipContext; +import net.minecraft.world.phys.AABB; +import net.minecraft.world.phys.BlockHitResult; +import net.minecraft.world.phys.EntityHitResult; +import net.minecraft.world.phys.HitResult; +import net.minecraft.world.phys.Vec3; +import java.util.function.BiConsumer; +import com.craisinlord.antarchy.content.network.PortalGunGrabStatePayload; + +public final class PortalGunGrabManager { + private static final double REACH = 5.0D; + private static final double BASE_HOLD_DISTANCE = 3.5D; + private static final double MAX_HOLD_DISTANCE = 6.0D; + private static final Map GRABS = new HashMap<>(); + private static BiConsumer stateSender = (player, payload) -> {}; + + private PortalGunGrabManager() { + } + + public static void setStateSender(BiConsumer sender) { + stateSender = sender; + } + + public static void toggle(ServerPlayer player) { + GrabState active = GRABS.remove(player.getUUID()); + if (active != null) { + release(active); + stateSender.accept(player, new PortalGunGrabStatePayload(false)); + return; + } + ItemStack gunStack = getHeldPortalGun(player); + if (gunStack.isEmpty()) { + return; + } + int grabStrength = PortalGunItem.getGrabStrength(gunStack); + Entity target = findTarget(player, grabStrength); + if (target == null) { + target = findBlockTarget(player, grabStrength); + if (target == null) { + return; + } + } + for (Map.Entry entry : new HashMap<>(GRABS).entrySet()) { + if (entry.getValue().target() == target) { + GRABS.remove(entry.getKey()); + release(entry.getValue()); + ServerPlayer previousHolder = player.serverLevel().getServer().getPlayerList().getPlayer(entry.getKey()); + if (previousHolder != null) { + stateSender.accept(previousHolder, new PortalGunGrabStatePayload(false)); + } + } + } + double holdDistance = BASE_HOLD_DISTANCE; + if (target.getBbWidth() > 2.0F) { + holdDistance = Math.min(MAX_HOLD_DISTANCE, holdDistance + target.getBbWidth() - 2.0D); + } + GrabState state = new GrabState(player.getUUID(), player.serverLevel(), target, holdDistance, target.isNoGravity()); + GRABS.put(player.getUUID(), state); + target.setNoGravity(true); + stateSender.accept(player, new PortalGunGrabStatePayload(true)); + } + + public static void tick(MinecraftServer server) { + for (Map.Entry entry : new HashMap<>(GRABS).entrySet()) { + GrabState state = entry.getValue(); + ServerPlayer player = server.getPlayerList().getPlayer(state.playerId()); + Entity target = state.target(); + if (player == null || !player.isAlive() || player.serverLevel() != state.level() || !isHoldingPortalGun(player) + || !target.isAlive() || target.isRemoved() || target.level() != state.level() + || target.distanceTo(player) > state.holdDistance() + 6.25D) { + GRABS.remove(entry.getKey()); + release(state); + if (player != null) { + stateSender.accept(player, new PortalGunGrabStatePayload(false)); + } + continue; + } + Vec3 holdPosition = player.getEyePosition(1.0F).add(player.getLookAngle().scale(state.holdDistance())); + if (target.position().distanceTo(holdPosition) > 5.0D) { + target.teleportTo(player.getX(), player.getY(), player.getZ()); + } + Vec3 velocity = holdPosition.subtract(target.getX(), target.getBoundingBox().getCenter().y, target.getZ()); + target.setDeltaMovement(velocity); + target.move(MoverType.SELF, velocity); + velocity = holdPosition.subtract(target.getX(), target.getBoundingBox().getCenter().y, target.getZ()); + target.setDeltaMovement(velocity); + target.setNoGravity(true); + if (target instanceof FallingBlockEntity fallingBlock) { + fallingBlock.time = 2; + } + if (target instanceof AbstractHurtingProjectile projectile) { + projectile.accelerationPower = 0.0D; + } + target.fallDistance = (float) -(velocity.y * velocity.y); + target.setOnGround(false); + target.hasImpulse = true; + target.hurtMarked = true; + } + } + + public static void clear(MinecraftServer server) { + for (GrabState state : GRABS.values()) { + release(state); + ServerPlayer player = server.getPlayerList().getPlayer(state.playerId()); + if (player != null) { + stateSender.accept(player, new PortalGunGrabStatePayload(false)); + } + } + GRABS.clear(); + } + + private static Entity findTarget(ServerPlayer player, int grabStrength) { + Vec3 start = player.getEyePosition(); + Vec3 look = player.getLookAngle(); + Vec3 end = start.add(look.scale(REACH)); + BlockHitResult blockHit = player.serverLevel().clip(new ClipContext(start, end, ClipContext.Block.COLLIDER, ClipContext.Fluid.NONE, player)); + double maxDistance = blockHit.getType() == HitResult.Type.BLOCK ? start.distanceTo(blockHit.getLocation()) : REACH; + AABB searchBounds = player.getBoundingBox().expandTowards(look.scale(maxDistance)).inflate(1.0D); + EntityHitResult closest = null; + double closestDistance = maxDistance * maxDistance; + for (Entity candidate : player.serverLevel().getEntities(player, searchBounds, entity -> canGrab(entity, grabStrength))) { + var hit = candidate.getBoundingBox().inflate(candidate.getPickRadius()).clip(start, end); + if (hit.isEmpty()) { + continue; + } + double distance = start.distanceToSqr(hit.get()); + if (distance < closestDistance) { + closestDistance = distance; + closest = new EntityHitResult(candidate, hit.get()); + } + } + return closest == null ? null : closest.getEntity(); + } + + private static Entity findBlockTarget(ServerPlayer player, int grabStrength) { + if (grabStrength < 2) { + return null; + } + Vec3 start = player.getEyePosition(); + Vec3 end = start.add(player.getLookAngle().scale(REACH)); + BlockHitResult hit = player.serverLevel().clip(new ClipContext(start, end, ClipContext.Block.OUTLINE, ClipContext.Fluid.NONE, player)); + if (hit.getType() != HitResult.Type.BLOCK) { + return null; + } + BlockPos pos = hit.getBlockPos(); + ServerLevel level = player.serverLevel(); + BlockState state = level.getBlockState(pos); + if (state.isAir() || state.is(AntarchyTags.Blocks.GRAVITY_GUN_BLACKLIST)) { + return null; + } + BlockPos basePos = pos; + BlockPos upperPos = null; + if (state.hasProperty(BlockStateProperties.DOUBLE_BLOCK_HALF)) { + basePos = state.getValue(BlockStateProperties.DOUBLE_BLOCK_HALF) == DoubleBlockHalf.UPPER ? pos.below() : pos; + BlockState baseState = level.getBlockState(basePos); + BlockPos candidateUpper = basePos.above(); + BlockState upperState = level.getBlockState(candidateUpper); + if (!baseState.is(state.getBlock()) || !baseState.hasProperty(BlockStateProperties.DOUBLE_BLOCK_HALF) + || baseState.getValue(BlockStateProperties.DOUBLE_BLOCK_HALF) != DoubleBlockHalf.LOWER + || !upperState.is(state.getBlock()) || !upperState.hasProperty(BlockStateProperties.DOUBLE_BLOCK_HALF) + || upperState.getValue(BlockStateProperties.DOUBLE_BLOCK_HALF) != DoubleBlockHalf.UPPER) { + return null; + } + state = baseState; + upperPos = candidateUpper; + } + if (state.getDestroySpeed(level, basePos) < 0.0F) { + return null; + } + if (AntarchySettings.portalGunEntityGrabWeightBase() == 0 + && !Antarchy.MODID.equals(BuiltInRegistries.BLOCK.getKey(state.getBlock()).getNamespace())) { + return null; + } + BlockEntity blockEntity = level.getBlockEntity(basePos); + if (blockEntity != null && grabStrength < 3) { + return null; + } + if (!state.isSolidRender(level, basePos) + && state.getRenderShape() != RenderShape.ENTITYBLOCK_ANIMATED + && !state.is(Blocks.SNOW) + && !state.is(Blocks.GLOWSTONE) + && !state.is(Blocks.CAKE) + && !state.is(Blocks.TNT) + && !state.is(Blocks.ICE) + && !state.is(BlockTags.SLABS) + && !state.is(Blocks.ANVIL) + && !state.is(BlockTags.STAIRS)) { + return null; + } + CompoundTag blockEntityData = blockEntity == null ? null : blockEntity.saveWithId(level.registryAccess()); + if (blockEntity != null) { + level.removeBlockEntity(basePos); + } + if (upperPos != null) { + level.removeBlock(upperPos, false); + } + FallingBlockEntity fallingBlock = FallingBlockEntity.fall(level, basePos, state); + if (fallingBlock == null) { + return null; + } + if (blockEntityData != null) { + fallingBlock.blockData = blockEntityData; + } + fallingBlock.setDeltaMovement(Vec3.ZERO); + fallingBlock.setNoGravity(true); + fallingBlock.hurtMarked = true; + return fallingBlock; + } + + private static boolean canGrab(Entity entity, int grabStrength) { + if (!entity.isAlive() || entity instanceof Player || entity instanceof PortalGunPortalEntity || entity.isPassenger()) { + return false; + } + double volume = entity.getBbWidth() * entity.getBbHeight() * entity.getBbWidth(); + double strengthScale = switch (grabStrength) { + case 1 -> 1.0D; + case 2 -> 1.5D; + case 3 -> 2.25D; + default -> 3.0D; + }; + int grabWeightBase = AntarchySettings.portalGunEntityGrabWeightBase(); + boolean modEntityExempt = grabWeightBase == 0 + && Antarchy.MODID.equals(BuiltInRegistries.ENTITY_TYPE.getKey(entity.getType()).getNamespace()); + return (modEntityExempt || Math.cbrt(volume) < grabWeightBase * strengthScale / 100.0D) && entity.isPickable(); + } + + private static ItemStack getHeldPortalGun(ServerPlayer player) { + if (player.getMainHandItem().getItem() instanceof PortalGunItem) { + return player.getMainHandItem(); + } + if (player.getOffhandItem().getItem() instanceof PortalGunItem) { + return player.getOffhandItem(); + } + return ItemStack.EMPTY; + } + + private static boolean isHoldingPortalGun(ServerPlayer player) { + return !getHeldPortalGun(player).isEmpty(); + } + + private static void release(GrabState state) { + Entity target = state.target(); + if (target.isAlive() && !target.isRemoved()) { + target.setNoGravity(state.hadNoGravity()); + target.hasImpulse = true; + target.hurtMarked = true; + } + } + + private record GrabState(UUID playerId, ServerLevel level, Entity target, double holdDistance, boolean hadNoGravity) { + } +} diff --git a/common-1.21.1/src/main/java/com/craisinlord/antarchy/content/portalgun/PortalGunIndicatorSync.java b/common-1.21.1/src/main/java/com/craisinlord/antarchy/content/portalgun/PortalGunIndicatorSync.java new file mode 100644 index 00000000..84e94568 --- /dev/null +++ b/common-1.21.1/src/main/java/com/craisinlord/antarchy/content/portalgun/PortalGunIndicatorSync.java @@ -0,0 +1,49 @@ +package com.craisinlord.antarchy.content.portalgun; + +import com.craisinlord.antarchy.content.item.PortalGunItem; +import com.craisinlord.antarchy.content.network.PortalGunIndicatorPayload; +import com.craisinlord.antarchy.content.network.PortalGunIndicatorRequestPayload; +import java.util.UUID; +import java.util.function.BiConsumer; +import net.minecraft.server.level.ServerPlayer; +import net.minecraft.world.item.ItemStack; + +public final class PortalGunIndicatorSync { + private static BiConsumer sender = (player, payload) -> {}; + + private PortalGunIndicatorSync() { + } + + public static void setSender(BiConsumer payloadSender) { + sender = payloadSender; + } + + public static void handleRequest(ServerPlayer player, PortalGunIndicatorRequestPayload request) { + ItemStack main = player.getMainHandItem(); + ItemStack off = player.getOffhandItem(); + ItemStack stack = main.getItem() instanceof PortalGunItem && request.gunId().equals(PortalGunItem.getGunId(main)) + ? main + : off.getItem() instanceof PortalGunItem && request.gunId().equals(PortalGunItem.getGunId(off)) ? off : ItemStack.EMPTY; + if (stack.isEmpty()) { + return; + } + UUID ownerId = PortalGunItem.getPortalOwnerId(stack, player.getUUID()); + boolean blue = PortalGunSavedData.getPortalId( + player.serverLevel().getServer(), + ownerId, + request.gunId(), + PortalGunItem.getChannelName(stack), + PortalGunPortalEntity.PortalSide.BLUE, + player.serverLevel().dimension().location() + ).isPresent(); + boolean orange = PortalGunSavedData.getPortalId( + player.serverLevel().getServer(), + ownerId, + request.gunId(), + PortalGunItem.getChannelName(stack), + PortalGunPortalEntity.PortalSide.ORANGE, + player.serverLevel().dimension().location() + ).isPresent(); + sender.accept(player, new PortalGunIndicatorPayload(request.gunId(), blue, orange)); + } +} diff --git a/common-1.21.1/src/main/java/com/craisinlord/antarchy/content/portalgun/PortalGunPlacement.java b/common-1.21.1/src/main/java/com/craisinlord/antarchy/content/portalgun/PortalGunPlacement.java index 944f4291..36ace529 100644 --- a/common-1.21.1/src/main/java/com/craisinlord/antarchy/content/portalgun/PortalGunPlacement.java +++ b/common-1.21.1/src/main/java/com/craisinlord/antarchy/content/portalgun/PortalGunPlacement.java @@ -1,6 +1,7 @@ package com.craisinlord.antarchy.content.portalgun; import java.util.Set; +import java.util.HashSet; import net.minecraft.core.BlockPos; import net.minecraft.core.Direction; import net.minecraft.world.phys.Vec3; @@ -15,13 +16,18 @@ public record PortalGunPlacement( BlockPos masterPos, BlockPos basePos, BlockPos[] portalSpots, - Set compensatedSpots + Set compensatedSpots, + int width, + int height ) { - public static PortalGunPlacement fromStored(Direction facing, Direction upAxis, BlockPos masterPos, BlockPos basePos, Set compensatedSpots) { + public static PortalGunPlacement fromStored(Direction facing, Direction upAxis, BlockPos masterPos, BlockPos basePos, BlockPos[] portalSpots, Set compensatedSpots, int width, int height) { Direction widthAxis = widthAxis(facing, upAxis); BlockPos supportOrigin = masterPos.relative(facing.getOpposite()); - Vec3 center = masterPos.getCenter() - .add(Vec3.atLowerCornerOf(upAxis.getNormal()).scale(0.5D)) + Vec3 center = Vec3.ZERO; + for (BlockPos portalSpot : portalSpots) { + center = center.add(portalSpot.getCenter()); + } + center = center.scale(1.0D / portalSpots.length) .subtract(Vec3.atLowerCornerOf(facing.getNormal()).scale(0.5D)); return new PortalGunPlacement( center, @@ -32,8 +38,10 @@ public record PortalGunPlacement( supportOrigin, masterPos, basePos, - new BlockPos[] {masterPos, basePos}, - compensatedSpots + portalSpots, + compensatedSpots, + width, + height ); } @@ -47,4 +55,47 @@ public record PortalGunPlacement( public static float yawFor(Direction facing, Direction upAxis) { return facing.getAxis() == Direction.Axis.Y ? upAxis.toYRot() : facing.toYRot(); } + + public static boolean isRectangularFootprint(BlockPos[] portalSpots, Direction facing, Direction upAxis, int width, int height) { + if (portalSpots == null || portalSpots.length != width * height || portalSpots.length == 0) { + return false; + } + Direction widthAxis = widthAxis(facing, upAxis); + BlockPos origin = portalSpots[0]; + Set cells = new HashSet<>(portalSpots.length); + int minWidth = Integer.MAX_VALUE; + int maxWidth = Integer.MIN_VALUE; + int minHeight = Integer.MAX_VALUE; + int maxHeight = Integer.MIN_VALUE; + for (BlockPos spot : portalSpots) { + if (spot == null) { + return false; + } + int dx = spot.getX() - origin.getX(); + int dy = spot.getY() - origin.getY(); + int dz = spot.getZ() - origin.getZ(); + int depth = dx * facing.getStepX() + dy * facing.getStepY() + dz * facing.getStepZ(); + int widthCell = dx * widthAxis.getStepX() + dy * widthAxis.getStepY() + dz * widthAxis.getStepZ(); + int heightCell = dx * upAxis.getStepX() + dy * upAxis.getStepY() + dz * upAxis.getStepZ(); + if (depth != 0 || !cells.add(((long) widthCell << 32) ^ (heightCell & 0xffffffffL))) { + return false; + } + minWidth = Math.min(minWidth, widthCell); + maxWidth = Math.max(maxWidth, widthCell); + minHeight = Math.min(minHeight, heightCell); + maxHeight = Math.max(maxHeight, heightCell); + } + if (maxWidth - minWidth != width - 1 || maxHeight - minHeight != height - 1 + || (minWidth != 0 && maxWidth != 0) || (minHeight != 0 && maxHeight != 0)) { + return false; + } + for (int widthCell = minWidth; widthCell <= maxWidth; widthCell++) { + for (int heightCell = minHeight; heightCell <= maxHeight; heightCell++) { + if (!cells.contains(((long) widthCell << 32) ^ (heightCell & 0xffffffffL))) { + return false; + } + } + } + return true; + } } diff --git a/common-1.21.1/src/main/java/com/craisinlord/antarchy/content/portalgun/PortalGunPortalEntity.java b/common-1.21.1/src/main/java/com/craisinlord/antarchy/content/portalgun/PortalGunPortalEntity.java index 0b163288..61986260 100644 --- a/common-1.21.1/src/main/java/com/craisinlord/antarchy/content/portalgun/PortalGunPortalEntity.java +++ b/common-1.21.1/src/main/java/com/craisinlord/antarchy/content/portalgun/PortalGunPortalEntity.java @@ -2,16 +2,21 @@ package com.craisinlord.antarchy.content.portalgun; import com.craisinlord.antarchy.Antarchy; import com.craisinlord.antarchy.content.block.entity.PortalGunPortalBaseBlockEntity; +import com.craisinlord.antarchy.content.block.entity.PortalGunPortalCellAccess; +import com.craisinlord.antarchy.content.block.entity.PortalGunPortalFaceRecord; import com.craisinlord.antarchy.content.block.entity.PortalGunPortalMasterBlockEntity; import com.craisinlord.antarchy.mixins.AbstractArrowAccessor; +import com.craisinlord.antarchy.mixins.FallingBlockEntityAccessor; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Optional; +import java.util.Random; import java.util.Set; import java.util.UUID; +import java.awt.Color; import net.minecraft.core.BlockPos; import net.minecraft.core.Direction; import net.minecraft.core.registries.BuiltInRegistries; @@ -19,14 +24,15 @@ import net.minecraft.nbt.CompoundTag; import net.minecraft.nbt.ListTag; import net.minecraft.nbt.Tag; import net.minecraft.network.protocol.game.ClientboundSetEntityMotionPacket; +import net.minecraft.network.protocol.game.ClientboundTeleportEntityPacket; import net.minecraft.network.syncher.EntityDataAccessor; import net.minecraft.network.syncher.EntityDataSerializers; import net.minecraft.network.syncher.SynchedEntityData; import net.minecraft.resources.ResourceLocation; +import net.minecraft.util.Mth; import net.minecraft.server.level.ServerLevel; import net.minecraft.server.level.ServerPlayer; import net.minecraft.sounds.SoundEvent; -import net.minecraft.sounds.SoundEvents; import net.minecraft.sounds.SoundSource; import net.minecraft.world.damagesource.DamageSource; import net.minecraft.world.entity.Entity; @@ -34,6 +40,7 @@ import net.minecraft.world.entity.EntityType; import net.minecraft.world.entity.LivingEntity; import net.minecraft.world.entity.player.Player; import net.minecraft.world.entity.projectile.AbstractArrow; +import net.minecraft.world.entity.item.FallingBlockEntity; import net.minecraft.world.level.Level; import net.minecraft.world.level.block.state.BlockState; import net.minecraft.world.level.block.entity.BlockEntity; @@ -55,32 +62,35 @@ public class PortalGunPortalEntity extends Entity implements GeoEntity { } private static final EntityDataAccessor SIDE = SynchedEntityData.defineId(PortalGunPortalEntity.class, EntityDataSerializers.INT); + private static final EntityDataAccessor> OWNER_ID = SynchedEntityData.defineId(PortalGunPortalEntity.class, EntityDataSerializers.OPTIONAL_UUID); + private static final EntityDataAccessor> GUN_ID = SynchedEntityData.defineId(PortalGunPortalEntity.class, EntityDataSerializers.OPTIONAL_UUID); + private static final EntityDataAccessor CHANNEL_NAME = SynchedEntityData.defineId(PortalGunPortalEntity.class, EntityDataSerializers.STRING); private static final EntityDataAccessor FACING = SynchedEntityData.defineId(PortalGunPortalEntity.class, EntityDataSerializers.INT); private static final EntityDataAccessor UP_AXIS = SynchedEntityData.defineId(PortalGunPortalEntity.class, EntityDataSerializers.INT); - private static final EntityDataAccessor CLOSING = SynchedEntityData.defineId(PortalGunPortalEntity.class, EntityDataSerializers.BOOLEAN); private static final EntityDataAccessor> LINKED_PORTAL = SynchedEntityData.defineId(PortalGunPortalEntity.class, EntityDataSerializers.OPTIONAL_UUID); + private static final EntityDataAccessor LINKED_PORTAL_ENTITY_ID = SynchedEntityData.defineId(PortalGunPortalEntity.class, EntityDataSerializers.INT); private static final EntityDataAccessor CHANNEL_RED = SynchedEntityData.defineId(PortalGunPortalEntity.class, EntityDataSerializers.INT); private static final EntityDataAccessor CHANNEL_GREEN = SynchedEntityData.defineId(PortalGunPortalEntity.class, EntityDataSerializers.INT); private static final EntityDataAccessor CHANNEL_BLUE = SynchedEntityData.defineId(PortalGunPortalEntity.class, EntityDataSerializers.INT); + private static final EntityDataAccessor PORTAL_WIDTH = SynchedEntityData.defineId(PortalGunPortalEntity.class, EntityDataSerializers.INT); + private static final EntityDataAccessor PORTAL_HEIGHT = SynchedEntityData.defineId(PortalGunPortalEntity.class, EntityDataSerializers.INT); + private static final EntityDataAccessor OPENING_ANIMATION_ID = SynchedEntityData.defineId(PortalGunPortalEntity.class, EntityDataSerializers.INT); private static final RawAnimation OPEN_ANIM = RawAnimation.begin().thenPlay("open"); private static final RawAnimation IDLE_ANIM = RawAnimation.begin().thenLoop("idle"); - private static final RawAnimation CLOSE_ANIM = RawAnimation.begin().thenPlay("close"); - private static final int OPEN_TICKS = 10; - private static final int CLOSE_TICKS = 10; - private static final int MAX_LIFETIME_TICKS = 20 * 60 * 20; - private static final int TELEPORT_COOLDOWN_TICKS = 40; - private static final double PLAYER_PORTAL_INSIDE_SHIFT = 0.05D; - private static final double HALF_WIDTH = 0.5D; - private static final double HALF_HEIGHT = 1.0D; + private static final int OPEN_TICKS = 5; + public static final int TELEPORT_COOLDOWN_TICKS = 3; + private static final double PORTAL_INSIDE_SHIFT = 0.05D; + private static final Set LOGGED_CROSSING_PATHS = new HashSet<>(); private static final double HALF_DEPTH = 0.35D; - private static final double MIN_EXIT_MOMENTUM = 0.42D; private final AnimatableInstanceCache geoCache = GeckoLibUtil.createInstanceCache(this); private final Map teleportCooldowns = new HashMap<>(); - private final Map portalEntityDepths = new HashMap<>(); - private Set lastScanEntities = new HashSet<>(); private UUID ownerId; + private String ownerIdentity; + private UUID gunId; + private String channelName; private UUID linkedPortalId; private int ageTicks; + private int lastOpeningAnimationId; private int pairTime; private BlockPos supportOrigin = BlockPos.ZERO; private BlockPos masterPos = BlockPos.ZERO; @@ -98,40 +108,56 @@ public class PortalGunPortalEntity extends Entity implements GeoEntity { @Override protected void defineSynchedData(SynchedEntityData.Builder builder) { builder.define(SIDE, PortalSide.BLUE.ordinal()); + builder.define(OWNER_ID, Optional.empty()); + builder.define(GUN_ID, Optional.empty()); + builder.define(CHANNEL_NAME, ""); builder.define(FACING, Direction.NORTH.get3DDataValue()); builder.define(UP_AXIS, Direction.UP.get3DDataValue()); - builder.define(CLOSING, false); builder.define(LINKED_PORTAL, Optional.empty()); + builder.define(LINKED_PORTAL_ENTITY_ID, -1); builder.define(CHANNEL_RED, 58); builder.define(CHANNEL_GREEN, 166); builder.define(CHANNEL_BLUE, 255); + builder.define(PORTAL_WIDTH, 1); + builder.define(PORTAL_HEIGHT, 2); + builder.define(OPENING_ANIMATION_ID, 0); } - public void configure(UUID ownerId, PortalSide side, PortalGunPlacement placement) { + public void configure(UUID ownerId, UUID gunId, String ownerIdentity, String channelName, PortalSide side, PortalGunPlacement placement) { this.ownerId = ownerId; + this.ownerIdentity = ownerIdentity; + this.gunId = gunId; + this.channelName = channelName; + this.entityData.set(OWNER_ID, Optional.ofNullable(ownerId)); + this.entityData.set(GUN_ID, Optional.ofNullable(gunId)); + this.entityData.set(CHANNEL_NAME, channelName == null ? "" : channelName); this.entityData.set(SIDE, side.ordinal()); this.entityData.set(FACING, placement.facing().get3DDataValue()); this.entityData.set(UP_AXIS, placement.upAxis().get3DDataValue()); - int[] channelColor = generateChannelColor(ownerId, side); + this.entityData.set(PORTAL_WIDTH, placement.width()); + this.entityData.set(PORTAL_HEIGHT, placement.height()); + int[] channelColor = generateChannelColor(ownerIdentity, channelName, side); this.entityData.set(CHANNEL_RED, channelColor[0]); this.entityData.set(CHANNEL_GREEN, channelColor[1]); this.entityData.set(CHANNEL_BLUE, channelColor[2]); this.supportOrigin = placement.supportOrigin().immutable(); this.masterPos = placement.masterPos().immutable(); this.basePos = placement.basePos().immutable(); - this.portalSpots = new BlockPos[] {placement.portalSpots()[0].immutable(), placement.portalSpots()[1].immutable()}; + this.portalSpots = java.util.Arrays.stream(placement.portalSpots()).map(BlockPos::immutable).toArray(BlockPos[]::new); this.compensatedSpots = Set.copyOf(placement.compensatedSpots()); } public void linkTo(PortalGunPortalEntity other) { this.linkedPortalId = other.getUUID(); this.entityData.set(LINKED_PORTAL, Optional.of(other.getUUID())); + this.entityData.set(LINKED_PORTAL_ENTITY_ID, other.getId()); this.pairTime = (int) this.level().getGameTime(); } public void restorePair(UUID linkedPortalId, int pairTime) { this.linkedPortalId = linkedPortalId; this.entityData.set(LINKED_PORTAL, Optional.ofNullable(linkedPortalId)); + this.entityData.set(LINKED_PORTAL_ENTITY_ID, -1); this.pairTime = pairTime; } @@ -157,7 +183,45 @@ public class PortalGunPortalEntity extends Entity implements GeoEntity { } public UUID getOwnerId() { - return this.ownerId; + return this.entityData.get(OWNER_ID).orElse(this.ownerId); + } + + public UUID getGunId() { + return this.entityData.get(GUN_ID).orElse(this.gunId); + } + + public String getChannelName() { + String synchronizedName = this.entityData.get(CHANNEL_NAME); + return synchronizedName.isEmpty() ? this.channelName : synchronizedName; + } + + public void adoptChannelIdentity(UUID gunId, String channelName) { + if (this.gunId == null && gunId != null) { + this.gunId = gunId; + this.entityData.set(GUN_ID, Optional.of(gunId)); + } + if (channelName != null && !channelName.isEmpty() && !channelName.equals(this.channelName)) { + this.channelName = channelName; + this.entityData.set(CHANNEL_NAME, channelName); + } + if (this.level() instanceof ServerLevel serverLevel && serverLevel.hasChunkAt(this.masterPos) + && serverLevel.getBlockEntity(this.masterPos) instanceof PortalGunPortalMasterBlockEntity master + && this.getUUID().equals(master.getPortalId())) { + master.adoptChannelIdentity(gunId, channelName); + } + } + + public void adoptGunId(UUID gunId) { + if (this.gunId != null || gunId == null) { + return; + } + this.gunId = gunId; + this.entityData.set(GUN_ID, Optional.of(gunId)); + if (this.level() instanceof ServerLevel serverLevel && serverLevel.hasChunkAt(this.masterPos) + && serverLevel.getBlockEntity(this.masterPos) instanceof com.craisinlord.antarchy.content.block.entity.PortalGunPortalMasterBlockEntity master + && this.getUUID().equals(master.getPortalId())) { + master.adoptGunId(gunId); + } } public int getChannelRed() { @@ -177,7 +241,27 @@ public class PortalGunPortalEntity extends Entity implements GeoEntity { } public BlockPos[] getPortalSpots() { - return new BlockPos[] {this.portalSpots[0], this.portalSpots[1]}; + return this.portalSpots.clone(); + } + + public boolean containsPortalSpot(BlockPos pos) { + for (BlockPos portalSpot : this.portalSpots) { + if (portalSpot.equals(pos)) { + return true; + } + } + return false; + } + + public int getPortalWidth() { return this.entityData.get(PORTAL_WIDTH); } + + public int getPortalHeight() { return this.entityData.get(PORTAL_HEIGHT); } + + public void restartOpeningAnimation() { + int animationId = this.entityData.get(OPENING_ANIMATION_ID) + 1; + this.entityData.set(OPENING_ANIMATION_ID, animationId); + this.lastOpeningAnimationId = animationId; + this.ageTicks = 0; } public Set getCompensatedSpots() { @@ -193,12 +277,17 @@ public class PortalGunPortalEntity extends Entity implements GeoEntity { if (linkedId == null) { return null; } - List entities = this.level().getEntities(this, new AABB(-3.0E7D, -3.0E7D, -3.0E7D, 3.0E7D, 3.0E7D, 3.0E7D), entity -> linkedId.equals(entity.getUUID())); - if (entities.isEmpty()) { - return null; + Entity linkedEntity; + if (this.level() instanceof ServerLevel serverLevel) { + linkedEntity = serverLevel.getEntity(linkedId); + } else { + int linkedEntityId = this.entityData.get(LINKED_PORTAL_ENTITY_ID); + if (linkedEntityId < 0) { + return null; + } + linkedEntity = this.level().getEntity(linkedEntityId); } - Entity entity = entities.getFirst(); - return entity instanceof PortalGunPortalEntity portal ? portal : null; + return linkedEntity instanceof PortalGunPortalEntity portal && !portal.isRemoved() ? portal : null; } public Vec3 getNormalVec() { @@ -214,7 +303,7 @@ public class PortalGunPortalEntity extends Entity implements GeoEntity { } public PortalGunWorldPortalShape getWorldPortalShape() { - return new PortalGunWorldPortalShape(this.position(), this.getNormalVec().normalize(), this.getUpVec().normalize(), this.getWidthVec().normalize(), HALF_WIDTH, HALF_HEIGHT, HALF_DEPTH); + return new PortalGunWorldPortalShape(this.position(), this.getNormalVec().normalize(), this.getUpVec().normalize(), this.getWidthVec().normalize(), this.getPortalWidth() / 2.0D, this.getPortalHeight() / 2.0D, HALF_DEPTH); } public AABB getPlane() { @@ -234,10 +323,7 @@ public class PortalGunPortalEntity extends Entity implements GeoEntity { } public AABB getPortalInsides(Entity entity) { - if (!(entity instanceof Player)) { - return this.getPortalInsides(); - } - double shiftAmount = Math.min(PLAYER_PORTAL_INSIDE_SHIFT, Math.abs(this.getWorldPortalShape().localCoords(entity.position()).depth())); + double shiftAmount = Math.min(PORTAL_INSIDE_SHIFT, Math.abs(this.getWorldPortalShape().localCoords(entity.position()).depth())); return this.getPortalInsides().move(this.getNormalVec().normalize().scale(shiftAmount)); } @@ -252,43 +338,100 @@ public class PortalGunPortalEntity extends Entity implements GeoEntity { } public Vec3 teleportProbePosition(Entity entity) { - if (entity instanceof Player player) { - return player.getEyePosition(); - } - if (entity instanceof LivingEntity living) { - return entity.position().add(0.0D, living.getBbHeight() * 0.5D, 0.0D); - } - return entity.position(); + return entity.position().add(0.0D, this.getTeleportProbeHeight(entity), 0.0D); } - public Vec3 previousTeleportProbePosition(Entity entity) { - double prevX = entity.xo; - double prevY = entity.yo; - double prevZ = entity.zo; - if (entity instanceof Player player) { - return new Vec3(prevX, prevY + player.getEyeHeight(player.getPose()), prevZ); + public Vec3 teleportProbePosition(Entity entity, AABB bounds) { + double x = (bounds.minX + bounds.maxX) * 0.5D; + double y = bounds.minY; + double z = (bounds.minZ + bounds.maxZ) * 0.5D; + y += this.getTeleportProbeHeight(entity); + return new Vec3(x, y, z); + } + + private double getTeleportProbeHeight(Entity entity) { + return entity instanceof LivingEntity living ? living.getEyeHeight(living.getPose()) : entity.getEyeHeight(); + } + + public PortalCrossing resolveCrossing(Entity entity, AABB previousBox, AABB currentBox) { + Vec3 previousProbe = this.teleportProbePosition(entity, previousBox); + Vec3 currentProbe = this.teleportProbePosition(entity, currentBox); + Vec3 normal = this.getNormalVec().normalize(); + if (entity instanceof Player && Math.abs(normal.y) < 0.5D) { + double offset = Math.min(0.05D, Math.abs(this.position().subtract(entity.position()).dot(normal))); + Vec3 shift = normal.scale(offset); + if (this.getScanRange().move(shift).contains(currentProbe) + || !this.getPortalInsides().move(shift).contains(currentProbe)) { + return null; + } + PortalGunWorldPortalShape shape = this.getWorldPortalShape(); + AABB flatPlane = shape.getFlatPlane(); + boolean aligned; + if (Math.abs(normal.y) > 0.5D) { + aligned = currentBox.minX >= flatPlane.minX && currentBox.maxX <= flatPlane.maxX + && currentBox.minZ >= flatPlane.minZ && currentBox.maxZ <= flatPlane.maxZ; + } else { + aligned = currentBox.minY >= flatPlane.minY && currentBox.maxY <= flatPlane.maxY; + } + return aligned ? new PortalCrossing(currentProbe, offset) : null; } - if (entity instanceof LivingEntity living) { - return new Vec3(prevX, prevY + living.getBbHeight() * 0.5D, prevZ); - } - return new Vec3(prevX, prevY, prevZ); + return this.crossesPortal(previousProbe, currentProbe) ? new PortalCrossing(currentProbe, 0.0D) : null; } public Vec3 resolveCrossingProbe(Entity entity) { AABB currentBox = entity.getBoundingBox(); AABB previousBox = currentBox.move(entity.xo - entity.getX(), entity.yo - entity.getY(), entity.zo - entity.getZ()); - for (Vec3 currentSample : portalSamples(entity, currentBox, false)) { - Vec3 previousSample = matchPreviousSample(currentSample, currentBox, previousBox); - if (this.crossesPortal(previousSample, currentSample)) { - return currentSample; + PortalCrossing crossing = this.resolveCrossing(entity, previousBox, currentBox); + return crossing == null ? null : crossing.probe(); + } + + public PortalTransitTransform previewTransit(Entity entity, PortalGunPortalEntity destination, PortalCrossing crossing, Vec3 movement) { + Vec3 sourcePlaneCenter = this.position().add(this.getNormalVec().normalize().scale(crossing.normalOffset())); + Vec3 destinationPlaneCenter = destination.position().add(destination.getNormalVec().normalize().scale(crossing.normalOffset())); + Vec3 relativeProbe = crossing.probe().subtract(sourcePlaneCenter); + Vec3 transformedProbe = PortalGunTransformUtil.transformPosition(this, destination, relativeProbe); + Vec3 entityOffset = PortalGunTransformUtil.transformVector(this, destination, entity.position().subtract(crossing.probe())); + Vec3 requestedPosition = destinationPlaneCenter.add(transformedProbe).add(entityOffset); + Vec3 transformedMovement = PortalGunTransformUtil.transformVector(this, destination, movement); + Vec3 exitPosition = putEntityWithinExitBounds(entity, requestedPosition, legacyExitBounds(destination, entity, destination.getNormalVec().normalize())); + return new PortalTransitTransform(requestedPosition, exitPosition, transformedMovement); + } + + public static void teleportEntityAfterMovement(Entity entity, AABB previousBox, Vec3 resolvedMovement) { + if (entity.level().isClientSide || !entity.isAlive() || entity.isPassenger() || entity instanceof PortalGunPortalEntity) { + return; + } + AABB currentBox = entity.getBoundingBox(); + if (previousBox.getCenter().distanceToSqr(currentBox.getCenter()) <= 1.0E-10D) { + return; + } + AABB sweptBounds = previousBox.minmax(currentBox).inflate(1.0D); + for (PortalGunPortalEntity portal : findPortalsNearBounds(entity.level(), sweptBounds)) { + if (!(portal.level() instanceof ServerLevel serverLevel) || !portal.canTeleportEntity(entity)) { + continue; + } + PortalGunPortalEntity linked = portal.findLinkedPortal(serverLevel); + if (linked == null || !linked.isAlive()) { + continue; + } + PortalCrossing crossing = portal.resolveCrossing(entity, previousBox, currentBox); + if (crossing != null) { + logCrossingPath(entity, "movement"); + portal.teleportEntity(entity, linked, crossing.probe(), resolvedMovement, crossing.normalOffset()); + return; } } - Vec3 previousProbe = this.previousTeleportProbePosition(entity); - Vec3 currentProbe = this.teleportProbePosition(entity); - if (this.crossesPortal(previousProbe, currentProbe)) { - return currentProbe; + } + + public static List findPortalsNearBounds(Level level, AABB bounds) { + List portals = new java.util.ArrayList<>(); + AABB searchBounds = bounds.inflate(10.0D); + for (PortalGunPortalEntity portal : level.getEntitiesOfClass(PortalGunPortalEntity.class, searchBounds, PortalGunPortalEntity::isAlive)) { + if (portal.getWorldPortalShape().getBoundsForCulling().intersects(bounds)) { + portals.add(portal); + } } - return this.overlapsPortalVolume(entity) ? this.portalVolumeProbe(entity) : null; + return portals; } public boolean containsPoint(Vec3 position) { @@ -322,10 +465,20 @@ public class PortalGunPortalEntity extends Entity implements GeoEntity { return this.getWorldPortalShape().shouldRenderFront(cameraPos); } + public float getPortalVisualScale(float partialTick) { + return Mth.clamp((this.ageTicks - 1.0F + partialTick) / OPEN_TICKS, 0.0F, 1.0F); + } + @Override public void tick() { super.tick(); - this.ageTicks++; + int openingAnimationId = this.entityData.get(OPENING_ANIMATION_ID); + if (openingAnimationId != this.lastOpeningAnimationId) { + this.lastOpeningAnimationId = openingAnimationId; + this.ageTicks = 0; + } else { + this.ageTicks++; + } this.yRotO = this.getYRot(); this.xRotO = this.getXRot(); if (this.level().isClientSide) { @@ -334,6 +487,14 @@ public class PortalGunPortalEntity extends Entity implements GeoEntity { if (!(this.level() instanceof ServerLevel serverLevel)) { return; } + if (this.ownerId != null) { + PortalGunSavedData.getChannelNameForPortal(serverLevel.getServer(), this.ownerId, serverLevel.dimension().location(), this.getUUID()) + .ifPresent(channelName -> this.adoptChannelIdentity(this.gunId, channelName)); + if (this.gunId == null) { + PortalGunSavedData.getGunIdForPortal(serverLevel.getServer(), this.ownerId, serverLevel.dimension().location(), this.getUUID()) + .ifPresent(gunId -> this.adoptChannelIdentity(gunId, this.channelName)); + } + } this.tickTeleportCooldowns(); if (this.ownerId == null || !this.isStillRegistered(serverLevel)) { this.discard(); @@ -343,32 +504,28 @@ public class PortalGunPortalEntity extends Entity implements GeoEntity { this.discard(); return; } - if (this.ageTicks >= MAX_LIFETIME_TICKS - CLOSE_TICKS) { - this.entityData.set(CLOSING, true); - } - if (this.ageTicks >= MAX_LIFETIME_TICKS) { - this.discard(); - return; - } PortalGunPortalEntity linked = this.findLinkedPortal(serverLevel); if (linked == null || !linked.isAlive()) { if (this.ageTicks % 100 == 0) { - Antarchy.LOGGER.info("Portal gun portal has no loaded linked portal portal={} owner={} side={} linked={}", this.getUUID(), this.ownerId, this.getPortalSide(), this.linkedPortalId); + Antarchy.LOGGER.debug("Portal gun portal has no loaded linked portal portal={} owner={} side={} linked={}", this.getUUID(), this.ownerId, this.getPortalSide(), this.linkedPortalId); } return; } if (this.ageTicks % 80 == 0) { - this.level().playSound(null, this.blockPosition(), sound("portal_ambient", SoundEvents.PORTAL_AMBIENT), SoundSource.BLOCKS, 0.18F, this.getPortalSide() == PortalSide.BLUE ? 1.05F : 0.92F); + this.level().playSound(null, this.blockPosition(), sound("portal_ambient"), SoundSource.BLOCKS, 0.18F, this.getPortalSide() == PortalSide.BLUE ? 1.05F : 0.92F); } this.tickTeleport(linked); } private boolean isStillRegistered(ServerLevel level) { - return this.ownerId != null && PortalGunSavedData.isRegistered(level.getServer(), this.ownerId, this.getPortalSide(), this.getUUID()); + return this.ownerId != null && PortalGunSavedData.isRegistered(level, this.ownerId, this.gunId, this.channelName, this.getPortalSide(), this.getUUID()); } private boolean hasValidSurface() { Direction facing = this.getFacingDirection(); + if (!PortalGunPlacement.isRectangularFootprint(this.portalSpots, this.getFacingDirection(), this.getUpAxis(), this.getPortalWidth(), this.getPortalHeight())) { + return false; + } for (int i = 0; i < this.portalSpots.length; i++) { BlockPos portalSpot = this.portalSpots[i]; BlockPos supportPos = portalSpot.relative(facing.getOpposite()); @@ -377,263 +534,173 @@ public class PortalGunPortalEntity extends Entity implements GeoEntity { return false; } BlockEntity blockEntity = this.level().getBlockEntity(portalSpot); - if (i == 0) { - if (!(blockEntity instanceof PortalGunPortalMasterBlockEntity master) || !master.matches(this.ownerId, this.getUUID(), this.getPortalSide())) { - return false; - } - } else { - if (!(blockEntity instanceof PortalGunPortalBaseBlockEntity base) || !base.matches(this.ownerId, this.getUUID(), this.getPortalSide())) { - return false; - } - } + if (!(blockEntity instanceof PortalGunPortalCellAccess cell)) { + return false; + } + PortalGunPortalFaceRecord record = cell.portalGun$getFaceRecord(facing, this.getUUID()); + if (record != null) { + if (!this.ownerId.equals(record.ownerId()) || record.side() != this.getPortalSide() || record.master() != (i == 0)) { + return false; + } + } else if (i == 0) { + if (!(blockEntity instanceof PortalGunPortalMasterBlockEntity master) || !master.matches(this.ownerId, this.getUUID(), this.getPortalSide())) { + return false; + } + } else if (!(blockEntity instanceof PortalGunPortalBaseBlockEntity base) || !base.matches(this.ownerId, this.getUUID(), this.getPortalSide())) { + return false; + } } return true; } private void tickTeleport(PortalGunPortalEntity linked) { List entities = this.level().getEntities(this, this.getScanRange(), this::canTeleportEntity); - this.updatePortalEntityScan(linked, entities); - if (!entities.isEmpty() && this.ageTicks % 20 == 0) { - Antarchy.LOGGER.info("Portal gun teleport scan portal={} side={} linked={} count={} scan={}", this.getUUID(), this.getPortalSide(), linked.getUUID(), entities.size(), this.getScanRange()); - } for (Entity entity : entities) { - Vec3 crossingProbe = this.resolveCrossingProbe(entity); - if (crossingProbe == null) { - if (this.ageTicks % 40 == 0) { - PortalGunWorldPortalShape.PortalLocalCoords coords = this.getWorldPortalShape().localCoords(this.teleportProbePosition(entity)); - PortalBounds bounds = this.portalBounds(entity.getBoundingBox()); - Antarchy.LOGGER.info("Portal gun entity in scan did not cross portal={} entity={} entityType={} depth={} horizontal={} vertical={} boxDepthMin={} boxDepthMax={} boxHorizontalMin={} boxHorizontalMax={} boxVerticalMin={} boxVerticalMax={} movement={}", this.getUUID(), entity.getUUID(), EntityType.getKey(entity.getType()), coords.depth(), coords.horizontal(), coords.vertical(), bounds.minDepth(), bounds.maxDepth(), bounds.minHorizontal(), bounds.maxHorizontal(), bounds.minVertical(), bounds.maxVertical(), entity.getDeltaMovement()); - } + AABB currentBox = entity.getBoundingBox(); + AABB previousBox = currentBox.move(entity.xo - entity.getX(), entity.yo - entity.getY(), entity.zo - entity.getZ()); + PortalCrossing crossing = this.resolveCrossing(entity, previousBox, currentBox); + if (crossing == null) { continue; } - Antarchy.LOGGER.info("Portal gun teleport crossing portal={} side={} linked={} entity={} entityType={} probe={}", this.getUUID(), this.getPortalSide(), linked.getUUID(), entity.getUUID(), EntityType.getKey(entity.getType()), crossingProbe); - this.teleportEntity(entity, linked, crossingProbe); + logCrossingPath(entity, "portal_tick"); + Vec3 resolvedMovement = entity.position().subtract(entity.xo, entity.yo, entity.zo); + this.teleportEntity(entity, linked, crossing.probe(), resolvedMovement, crossing.normalOffset()); } } - private void updatePortalEntityScan(PortalGunPortalEntity linked, List entities) { - Set currentScan = new HashSet<>(); - for (Entity entity : entities) { - UUID entityId = entity.getUUID(); - currentScan.add(entityId); - PortalBounds bounds = this.portalBounds(entity.getBoundingBox()); - int depthBucket = (int) Math.round(bounds.minDepth() * 1000.0D); - this.portalEntityDepths.put(entityId, depthBucket); - if (!this.lastScanEntities.contains(entityId)) { - Antarchy.LOGGER.info("Portal gun entity entered scan portal={} linked={} entity={} entityType={} minDepth={} maxDepth={} minHorizontal={} maxHorizontal={} minVertical={} maxVertical={} motion={}", this.getUUID(), linked.getUUID(), entityId, EntityType.getKey(entity.getType()), bounds.minDepth(), bounds.maxDepth(), bounds.minHorizontal(), bounds.maxHorizontal(), bounds.minVertical(), bounds.maxVertical(), entity.getDeltaMovement()); - } + private static void logCrossingPath(Entity entity, String path) { + String entityType = BuiltInRegistries.ENTITY_TYPE.getKey(entity.getType()).toString(); + if (LOGGED_CROSSING_PATHS.add(entityType + ":" + path)) { + Antarchy.LOGGER.info("Portal gun entity crossing path entityType={} path={}", entityType, path); } - for (UUID previous : this.lastScanEntities) { - if (!currentScan.contains(previous)) { - Integer depth = this.portalEntityDepths.remove(previous); - Antarchy.LOGGER.info("Portal gun entity left scan portal={} linked={} entity={} lastDepth={}", this.getUUID(), linked.getUUID(), previous, depth); - } - } - this.lastScanEntities = currentScan; } private boolean canTeleportEntity(Entity entity) { - if (!entity.isAlive() || entity instanceof PortalGunPortalEntity || entity.isPassenger() || entity.isVehicle()) { + if (!entity.isAlive() || entity instanceof PortalGunPortalEntity || entity.isPassenger()) { return false; } - return !this.teleportCooldowns.containsKey(entity.getUUID()); + return !this.isTeleportCoolingDown(entity); } - private boolean isInsidePortal(Vec3 position) { - return this.getWorldPortalShape().contains(position, 0.0D); + public boolean isTeleportCoolingDown(Entity entity) { + return this.teleportCooldowns.containsKey(entity.getUUID()) || entity.getPortalCooldown() > 0; } - private PortalLocalCoords localCoords(Vec3 position) { - PortalGunWorldPortalShape.PortalLocalCoords coords = this.getWorldPortalShape().localCoords(position); - return new PortalLocalCoords(coords.horizontal(), coords.vertical(), coords.depth()); + private static Vec3 clampPortalMotion(Vec3 motion) { + return new Vec3(clampPortalMotionComponent(motion.x), clampPortalMotionComponent(motion.y), clampPortalMotionComponent(motion.z)); } - private boolean overlapsPortalVolume(Entity entity) { - PortalBounds bounds = this.portalBounds(entity.getBoundingBox()); - double edgePadding = entity instanceof Player ? 0.22D : 0.14D; - double frontDepth = entity instanceof Player ? 0.5D : 0.36D; - double backDepth = entity instanceof Player ? 0.28D : 0.22D; - double normalVelocity = entity.getDeltaMovement().dot(this.getNormalVec().normalize()); - boolean movingIntoPortal = normalVelocity < -0.01D; - boolean straddlingPlane = bounds.minDepth() <= 0.03D && bounds.maxDepth() >= -0.03D; - boolean inFrame = bounds.maxHorizontal() >= -HALF_WIDTH - edgePadding - && bounds.minHorizontal() <= HALF_WIDTH + edgePadding - && bounds.maxVertical() >= -HALF_HEIGHT - edgePadding - && bounds.minVertical() <= HALF_HEIGHT + edgePadding - && bounds.maxDepth() >= -backDepth - && bounds.minDepth() <= frontDepth; - return inFrame && (movingIntoPortal || straddlingPlane); + private static double clampPortalMotionComponent(double motion) { + return Math.abs(motion) > 0.99D ? motion / (Math.abs(motion) + 0.001D) : motion; } - private Vec3 portalVolumeProbe(Entity entity) { - Vec3 probe = this.teleportProbePosition(entity); - PortalGunWorldPortalShape shape = this.getWorldPortalShape(); - PortalGunWorldPortalShape.PortalLocalCoords coords = shape.localCoords(probe); - double horizontal = clamp(coords.horizontal(), -HALF_WIDTH + 0.05D, HALF_WIDTH - 0.05D); - double vertical = clamp(coords.vertical(), -HALF_HEIGHT + 0.05D, HALF_HEIGHT - 0.05D); - return shape.center() - .add(shape.right().scale(horizontal)) - .add(shape.up().scale(vertical)) - .subtract(shape.normal().scale(0.01D)); - } - - private PortalBounds portalBounds(AABB bounds) { - PortalGunWorldPortalShape shape = this.getWorldPortalShape(); - double minHorizontal = Double.POSITIVE_INFINITY; - double maxHorizontal = Double.NEGATIVE_INFINITY; - double minVertical = Double.POSITIVE_INFINITY; - double maxVertical = Double.NEGATIVE_INFINITY; - double minDepth = Double.POSITIVE_INFINITY; - double maxDepth = Double.NEGATIVE_INFINITY; - for (Vec3 corner : boxCorners(bounds)) { - PortalGunWorldPortalShape.PortalLocalCoords coords = shape.localCoords(corner); - minHorizontal = Math.min(minHorizontal, coords.horizontal()); - maxHorizontal = Math.max(maxHorizontal, coords.horizontal()); - minVertical = Math.min(minVertical, coords.vertical()); - maxVertical = Math.max(maxVertical, coords.vertical()); - minDepth = Math.min(minDepth, coords.depth()); - maxDepth = Math.max(maxDepth, coords.depth()); + private void teleportEntity(Entity entity, PortalGunPortalEntity destination, Vec3 currentProbe, Vec3 entryMotion, double normalOffset) { + List group = new java.util.ArrayList<>(); + collectTeleportGroup(entity, null, group); + Vec3 rootOriginalPosition = entity.position(); + PortalTransitTransform transitTransform = this.previewTransit(entity, destination, new PortalCrossing(currentProbe, normalOffset), entryMotion); + Vec3 exitNormal = destination.getNormalVec().normalize(); + Vec3 requestedExitPos = transitTransform.requestedPosition(); + Vec3 exitPos = transitTransform.exitPosition(); + Vec3 transformedMotion = transitTransform.movement(); + this.level().playSound(null, this.getX(), this.getY(), this.getZ(), sound("portal_enter"), SoundSource.PLAYERS, 0.7F, 0.96F + this.random.nextFloat() * 0.08F); + for (EntityTeleportState state : group) { + Entity member = state.entity(); + member.stopRiding(); } - return new PortalBounds(minHorizontal, maxHorizontal, minVertical, maxVertical, minDepth, maxDepth); - } - - private static Vec3[] boxCorners(AABB bounds) { - return new Vec3[] { - new Vec3(bounds.minX, bounds.minY, bounds.minZ), - new Vec3(bounds.minX, bounds.minY, bounds.maxZ), - new Vec3(bounds.minX, bounds.maxY, bounds.minZ), - new Vec3(bounds.minX, bounds.maxY, bounds.maxZ), - new Vec3(bounds.maxX, bounds.minY, bounds.minZ), - new Vec3(bounds.maxX, bounds.minY, bounds.maxZ), - new Vec3(bounds.maxX, bounds.maxY, bounds.minZ), - new Vec3(bounds.maxX, bounds.maxY, bounds.maxZ) - }; - } - - private static double clamp(double value, double min, double max) { - return Math.max(min, Math.min(max, value)); - } - - private List portalSamples(Entity entity, AABB box, boolean previous) { - Vec3 center = new Vec3((box.minX + box.maxX) * 0.5D, (box.minY + box.maxY) * 0.5D, (box.minZ + box.maxZ) * 0.5D); - Vec3 topCenter = new Vec3(center.x, box.maxY, center.z); - Vec3 bottomCenter = new Vec3(center.x, box.minY, center.z); - if (entity instanceof Player player) { - Vec3 eye = previous ? this.previousTeleportProbePosition(player) : this.teleportProbePosition(player); - return List.of(center, eye, topCenter, bottomCenter); + for (EntityTeleportState state : group) { + Entity member = state.entity(); + Vec3 memberOffset = PortalGunTransformUtil.transformVector(this, destination, state.position().subtract(rootOriginalPosition)); + Vec3 memberExitPos = putEntityWithinExitBounds(member, requestedExitPos.add(memberOffset), legacyExitBounds(destination, member, exitNormal)); + Vec3 memberLook = PortalGunTransformUtil.transformVector(this, destination, state.look()).normalize(); + Vec3 memberMotion = member == entity + ? transformedMotion + : PortalGunTransformUtil.transformVector(this, destination, state.motion()); + memberMotion = clampPortalMotion(memberMotion); + float yaw = PortalGunTransformUtil.yawFromLook(memberLook); + float pitch = PortalGunTransformUtil.pitchFromLook(memberLook); + if (member instanceof ServerPlayer player && destination.level() instanceof ServerLevel destinationLevel) { + player.teleportTo(destinationLevel, memberExitPos.x, memberExitPos.y, memberExitPos.z, yaw, pitch); + } else { + member.teleportTo(memberExitPos.x, memberExitPos.y, memberExitPos.z); + member.setYRot(yaw); + member.setXRot(pitch); + member.setYHeadRot(yaw); + member.setYBodyRot(yaw); + } + member.setDeltaMovement(memberMotion); + member.hasImpulse = true; + member.hurtMarked = true; + if (member instanceof ServerPlayer player) { + player.connection.send(new ClientboundSetEntityMotionPacket(player)); + } + member.xo = member.getX(); + member.yo = member.getY(); + member.zo = member.getZ(); + member.yRotO = yaw; + member.xRotO = pitch; + member.setOnGround(false); + member.horizontalCollision = false; + member.verticalCollision = false; + member.verticalCollisionBelow = false; + float verticalMotion = (float) memberMotion.y; + member.fallDistance = 0.1F * (verticalMotion / -0.1F * verticalMotion / -0.1F); + this.applyTeleportCooldown(member, destination); + this.handleSpecialEntityPostTeleport(member); } - if (entity instanceof LivingEntity) { - return List.of(center, topCenter, bottomCenter); - } - return List.of(center); - } - - private Vec3 matchPreviousSample(Vec3 currentSample, AABB currentBox, AABB previousBox) { - double xLerp = axisRatio(currentSample.x, currentBox.minX, currentBox.maxX); - double yLerp = axisRatio(currentSample.y, currentBox.minY, currentBox.maxY); - double zLerp = axisRatio(currentSample.z, currentBox.minZ, currentBox.maxZ); - return new Vec3( - lerp(previousBox.minX, previousBox.maxX, xLerp), - lerp(previousBox.minY, previousBox.maxY, yLerp), - lerp(previousBox.minZ, previousBox.maxZ, zLerp) - ); - } - - private static double axisRatio(double value, double min, double max) { - double size = max - min; - if (Math.abs(size) < 1.0E-6D) { - return 0.5D; - } - return (value - min) / size; - } - - private static double lerp(double min, double max, double delta) { - return min + (max - min) * delta; - } - - private void teleportEntity(Entity entity, PortalGunPortalEntity destination, Vec3 currentProbe) { - Vec3 relativeProbe = currentProbe.subtract(this.position()); - Vec3 transformedProbe = PortalGunTransformUtil.transformPosition(this, destination, relativeProbe); - Vec3 entityOffsetFromProbe = entity.position().subtract(currentProbe); - Vec3 transformedEntityOffset = PortalGunTransformUtil.transformVector(this, destination, entityOffsetFromProbe); - Vec3 transformedLook = PortalGunTransformUtil.transformVector(this, destination, entity.getLookAngle()).normalize(); - Vec3 entryMotion = this.portalEntryMomentum(entity, currentProbe); - Vec3 transformedMotion = this.exitMomentum(entity, destination, PortalGunTransformUtil.transformVector(this, destination, entryMotion)); - double exitClearance = Math.max(0.35D, entity.getBbWidth() * 0.5D + 0.16D); - Vec3 exitProbe = destination.position().add(transformedProbe).add(destination.getNormalVec().normalize().scale(exitClearance - transformedProbe.dot(destination.getNormalVec().normalize()))); - Vec3 requestedExitPos = exitProbe.add(transformedEntityOffset); - Vec3 exitPos = this.resolveSafeExitPosition(entity, destination, requestedExitPos); - float yaw = PortalGunTransformUtil.yawFromLook(transformedLook); - float pitch = PortalGunTransformUtil.pitchFromLook(transformedLook); - this.applyTeleportCooldown(entity, destination); - this.level().playSound(null, this.getX(), this.getY(), this.getZ(), sound("portal_enter", SoundEvents.PORTAL_TRAVEL), SoundSource.PLAYERS, 0.7F, 0.96F + this.random.nextFloat() * 0.08F); - if (entity instanceof ServerPlayer player && destination.level() instanceof ServerLevel destinationLevel) { - player.teleportTo(destinationLevel, exitPos.x, exitPos.y, exitPos.z, yaw, pitch); - } else { - entity.teleportTo(exitPos.x, exitPos.y, exitPos.z); - entity.setYRot(yaw); - entity.setXRot(pitch); - entity.setYHeadRot(yaw); - entity.setYBodyRot(yaw); - } - entity.setDeltaMovement(transformedMotion); - entity.hasImpulse = true; - entity.hurtMarked = true; - if (entity instanceof ServerPlayer player) { - player.connection.send(new ClientboundSetEntityMotionPacket(player)); - } - entity.fallDistance = 0.0F; - destination.level().playSound(null, exitPos.x, exitPos.y, exitPos.z, sound("portal_exit", SoundEvents.PORTAL_TRAVEL), SoundSource.PLAYERS, 0.7F, 0.96F + this.random.nextFloat() * 0.08F); - this.handleSpecialEntityPostTeleport(entity); - Antarchy.LOGGER.info("Portal gun teleported entity source={} destination={} entity={} entityType={} requestedExit={} exit={} inputVelocity={} outputVelocity={} yaw={} pitch={} sourceFacing={} sourceUp={} destinationFacing={} destinationUp={}", this.getUUID(), destination.getUUID(), entity.getUUID(), EntityType.getKey(entity.getType()), requestedExitPos, exitPos, entryMotion, transformedMotion, yaw, pitch, this.getFacingDirection(), this.getUpAxis(), destination.getFacingDirection(), destination.getUpAxis()); - } - - private Vec3 portalEntryMomentum(Entity entity, Vec3 currentProbe) { - Vec3 velocity = entity.getDeltaMovement(); - Vec3 probeVelocity = currentProbe.subtract(this.previousTeleportProbePosition(entity)); - Vec3 positionVelocity = entity.position().subtract(entity.xo, entity.yo, entity.zo); - Vec3 selected = velocity; - if (probeVelocity.lengthSqr() > selected.lengthSqr()) { - selected = probeVelocity; - } - if (positionVelocity.lengthSqr() > selected.lengthSqr()) { - selected = positionVelocity; - } - return selected; - } - - private Vec3 exitMomentum(Entity entity, PortalGunPortalEntity destination, Vec3 transformedMotion) { - Vec3 normal = destination.getNormalVec().normalize(); - double outward = transformedMotion.dot(normal); - if (outward >= MIN_EXIT_MOMENTUM) { - return transformedMotion; - } - if (entity.getDeltaMovement().lengthSqr() <= 1.0E-5D && transformedMotion.lengthSqr() <= 1.0E-5D) { - return normal.scale(MIN_EXIT_MOMENTUM); - } - return transformedMotion.add(normal.scale(MIN_EXIT_MOMENTUM - outward)); - } - - private Vec3 resolveSafeExitPosition(Entity entity, PortalGunPortalEntity destination, Vec3 requestedExitPos) { - Vec3 normal = destination.getNormalVec().normalize(); - if (this.canPlaceEntityAt(entity, requestedExitPos)) { - return requestedExitPos; - } - for (int i = 1; i <= 24; i++) { - Vec3 candidate = requestedExitPos.add(normal.scale(i * 0.125D)); - if (this.canPlaceEntityAt(entity, candidate)) { - Antarchy.LOGGER.info("Portal gun adjusted blocked exit entity={} destination={} requested={} adjusted={} distance={}", entity.getUUID(), destination.getUUID(), requestedExitPos, candidate, i * 0.125D); - return candidate; + for (EntityTeleportState state : group) { + if (state.vehicle() != null && state.entity().isAlive()) { + state.entity().startRiding(state.vehicle(), true); } } - Vec3 fallback = destination.position().add(normal.scale(Math.max(1.0D, entity.getBbWidth() + 0.5D))); - Antarchy.LOGGER.info("Portal gun could not find clear exit entity={} destination={} requested={} fallback={}", entity.getUUID(), destination.getUUID(), requestedExitPos, fallback); - return fallback; + if (destination.level() instanceof ServerLevel destinationLevel) { + for (EntityTeleportState state : group) { + Entity member = state.entity(); + if (!(member instanceof ServerPlayer)) { + destinationLevel.getChunkSource().broadcastAndSend(member, new ClientboundTeleportEntityPacket(member)); + destinationLevel.getChunkSource().broadcastAndSend(member, new ClientboundSetEntityMotionPacket(member)); + } + } + } + destination.level().playSound(null, exitPos.x, exitPos.y, exitPos.z, sound("portal_exit"), SoundSource.PLAYERS, 0.7F, 0.96F + this.random.nextFloat() * 0.08F); } - private boolean canPlaceEntityAt(Entity entity, Vec3 position) { - AABB movedBounds = entity.getBoundingBox().move(position.x - entity.getX(), position.y - entity.getY(), position.z - entity.getZ()); - return this.level().noCollision(entity, movedBounds); + private void collectTeleportGroup(Entity entity, Entity vehicle, List group) { + group.add(new EntityTeleportState(entity, vehicle, entity.position(), entity.getLookAngle(), entity.getDeltaMovement())); + for (Entity passenger : entity.getPassengers()) { + collectTeleportGroup(passenger, entity, group); + } + } + + private static Vec3 putEntityWithinExitBounds(Entity entity, Vec3 position, AABB exitBounds) { + AABB entityBounds = entity.getBoundingBox().move(position.x - entity.getX(), position.y - entity.getY(), position.z - entity.getZ()); + double x = position.x; + double y = position.y; + double z = position.z; + if (entityBounds.maxX > exitBounds.maxX) { + x += exitBounds.maxX - entityBounds.maxX; + } + if (entityBounds.minX < exitBounds.minX) { + x += exitBounds.minX - entityBounds.minX; + } + if (y + entity.getEyeHeight() > exitBounds.maxY) { + y += exitBounds.maxY - y - entity.getEyeHeight(); + } + if (y < exitBounds.minY) { + y += exitBounds.minY - y + 0.001D; + } + if (entityBounds.maxZ > exitBounds.maxZ) { + z += exitBounds.maxZ - entityBounds.maxZ; + } + if (entityBounds.minZ < exitBounds.minZ) { + z += exitBounds.minZ - entityBounds.minZ; + } + return new Vec3(x, y, z); + } + + private static AABB legacyExitBounds(PortalGunPortalEntity destination, Entity entity, Vec3 exitNormal) { + double maxDimension = Math.max(entity.getBbWidth(), entity.getBbHeight()); + return destination.getScanRange().expandTowards(exitNormal.scale(-maxDimension)); } private void tickTeleportCooldowns() { @@ -653,9 +720,13 @@ public class PortalGunPortalEntity extends Entity implements GeoEntity { UUID entityId = entity.getUUID(); this.teleportCooldowns.put(entityId, TELEPORT_COOLDOWN_TICKS); destination.teleportCooldowns.put(entityId, TELEPORT_COOLDOWN_TICKS); + entity.setPortalCooldown(TELEPORT_COOLDOWN_TICKS); } private void handleSpecialEntityPostTeleport(Entity entity) { + if (entity instanceof FallingBlockEntity) { + ((FallingBlockEntityAccessor) entity).antarchy$setTime(2); + } if (entity instanceof AbstractArrow arrow) { ((AbstractArrowAccessor) arrow).antarchy$setInGround(false); ((AbstractArrowAccessor) arrow).antarchy$setInGroundTime(0); @@ -687,6 +758,15 @@ public class PortalGunPortalEntity extends Entity implements GeoEntity { if (this.ownerId != null) { tag.putUUID("OwnerId", this.ownerId); } + if (this.ownerIdentity != null) { + tag.putString("OwnerIdentity", this.ownerIdentity); + } + if (this.gunId != null) { + tag.putUUID("GunId", this.gunId); + } + if (this.channelName != null) { + tag.putString("ChannelName", this.channelName); + } if (this.linkedPortalId != null) { tag.putUUID("LinkedPortalId", this.linkedPortalId); } @@ -698,6 +778,8 @@ public class PortalGunPortalEntity extends Entity implements GeoEntity { tag.putInt("ChannelRed", this.getChannelRed()); tag.putInt("ChannelGreen", this.getChannelGreen()); tag.putInt("ChannelBlue", this.getChannelBlue()); + tag.putInt("PortalWidth", this.getPortalWidth()); + tag.putInt("PortalHeight", this.getPortalHeight()); tag.putInt("SupportX", this.supportOrigin.getX()); tag.putInt("SupportY", this.supportOrigin.getY()); tag.putInt("SupportZ", this.supportOrigin.getZ()); @@ -732,6 +814,12 @@ public class PortalGunPortalEntity extends Entity implements GeoEntity { if (tag.hasUUID("OwnerId")) { this.ownerId = tag.getUUID("OwnerId"); } + this.ownerIdentity = tag.contains("OwnerIdentity") ? tag.getString("OwnerIdentity") : String.valueOf(this.ownerId); + this.gunId = tag.hasUUID("GunId") ? tag.getUUID("GunId") : null; + this.channelName = tag.contains("ChannelName") ? tag.getString("ChannelName") : this.gunId == null ? "" : "Random Channel #" + this.gunId.hashCode(); + this.entityData.set(OWNER_ID, Optional.ofNullable(this.ownerId)); + this.entityData.set(GUN_ID, Optional.ofNullable(this.gunId)); + this.entityData.set(CHANNEL_NAME, this.channelName); if (tag.hasUUID("LinkedPortalId")) { this.linkedPortalId = tag.getUUID("LinkedPortalId"); this.entityData.set(LINKED_PORTAL, Optional.of(this.linkedPortalId)); @@ -741,24 +829,29 @@ public class PortalGunPortalEntity extends Entity implements GeoEntity { this.entityData.set(SIDE, tag.getInt("Side")); this.entityData.set(FACING, tag.getInt("Facing")); this.entityData.set(UP_AXIS, tag.getInt("UpAxis")); - if (tag.contains("ChannelRed")) { - this.entityData.set(CHANNEL_RED, tag.getInt("ChannelRed")); - this.entityData.set(CHANNEL_GREEN, tag.getInt("ChannelGreen")); - this.entityData.set(CHANNEL_BLUE, tag.getInt("ChannelBlue")); - } else if (this.ownerId != null) { - int[] channelColor = generateChannelColor(this.ownerId, this.getPortalSide()); + if (this.ownerId != null && ("Global".equals(this.ownerIdentity) || !tag.contains("ChannelRed"))) { + int[] channelColor = generateChannelColor(this.ownerIdentity, this.channelName, this.getPortalSide()); this.entityData.set(CHANNEL_RED, channelColor[0]); this.entityData.set(CHANNEL_GREEN, channelColor[1]); this.entityData.set(CHANNEL_BLUE, channelColor[2]); + } else if (tag.contains("ChannelRed")) { + this.entityData.set(CHANNEL_RED, tag.getInt("ChannelRed")); + this.entityData.set(CHANNEL_GREEN, tag.getInt("ChannelGreen")); + this.entityData.set(CHANNEL_BLUE, tag.getInt("ChannelBlue")); } this.supportOrigin = new BlockPos(tag.getInt("SupportX"), tag.getInt("SupportY"), tag.getInt("SupportZ")); this.masterPos = new BlockPos(tag.getInt("MasterX"), tag.getInt("MasterY"), tag.getInt("MasterZ")); this.basePos = new BlockPos(tag.getInt("BaseX"), tag.getInt("BaseY"), tag.getInt("BaseZ")); ListTag spots = tag.getList("PortalSpots", Tag.TAG_COMPOUND); - for (int i = 0; i < Math.min(2, spots.size()); i++) { + if (!spots.isEmpty()) { + this.portalSpots = new BlockPos[spots.size()]; + } + for (int i = 0; i < spots.size(); i++) { CompoundTag spot = spots.getCompound(i); this.portalSpots[i] = new BlockPos(spot.getInt("X"), spot.getInt("Y"), spot.getInt("Z")); } + this.entityData.set(PORTAL_WIDTH, Mth.clamp(tag.getInt("PortalWidth"), 1, 16)); + this.entityData.set(PORTAL_HEIGHT, Mth.clamp(tag.getInt("PortalHeight"), 2, 16)); ListTag compensated = tag.getList("CompensatedSpots", Tag.TAG_COMPOUND); Set compensatedSpots = new HashSet<>(); for (int i = 0; i < compensated.size(); i++) { @@ -772,21 +865,30 @@ public class PortalGunPortalEntity extends Entity implements GeoEntity { public void remove(Entity.RemovalReason reason) { Level level = this.level(); UUID ownerId = this.ownerId; + boolean destroyPortal = reason.shouldDestroy(); + if (destroyPortal && !level.isClientSide && ownerId != null && level instanceof ServerLevel serverLevel) { + PortalGunSavedData.clearPortal(serverLevel.getServer(), ownerId, this.gunId, this.channelName, this.getPortalSide(), this.getUUID(), serverLevel.dimension().location()); + } super.remove(reason); - if (!level.isClientSide && ownerId != null && level instanceof ServerLevel serverLevel) { - PortalGunSavedData.clearPortal(serverLevel.getServer(), ownerId, this.getPortalSide(), this.getUUID()); + if (destroyPortal && !level.isClientSide && ownerId != null && level instanceof ServerLevel serverLevel) { this.clearPortalBlocks(serverLevel); } } private void clearPortalBlocks(ServerLevel level) { for (BlockPos portalSpot : this.portalSpots) { - if (level.getBlockEntity(portalSpot) instanceof PortalGunPortalMasterBlockEntity master && this.getUUID().equals(master.getPortalId())) { - level.removeBlock(portalSpot, false); - continue; - } - if (level.getBlockEntity(portalSpot) instanceof PortalGunPortalBaseBlockEntity base && this.getUUID().equals(base.getPortalId())) { - level.removeBlock(portalSpot, false); + if (level.getBlockEntity(portalSpot) instanceof PortalGunPortalCellAccess cell) { + PortalGunPortalFaceRecord removed = cell.portalGun$removeFaceRecord(this.getFacingDirection(), this.getUUID()); + if (!cell.portalGun$getFaceRecords().isEmpty()) { + continue; + } + if (cell instanceof PortalGunPortalMasterBlockEntity master && this.getUUID().equals(master.getPortalId())) { + level.removeBlock(portalSpot, false); + } else if (cell instanceof PortalGunPortalBaseBlockEntity base && this.getUUID().equals(base.getPortalId())) { + level.removeBlock(portalSpot, false); + } else if (removed != null) { + level.removeBlock(portalSpot, false); + } } } } @@ -826,10 +928,7 @@ public class PortalGunPortalEntity extends Entity implements GeoEntity { } private PlayState portalController(AnimationState state) { - state.getController().setAnimationSpeed(this.entityData.get(CLOSING) || this.ageTicks < OPEN_TICKS ? 4.0D : 1.0D); - if (this.entityData.get(CLOSING)) { - return state.setAndContinue(CLOSE_ANIM); - } + state.getController().setAnimationSpeed(this.ageTicks < OPEN_TICKS ? 4.0D : 1.0D); if (this.ageTicks < OPEN_TICKS) { return state.setAndContinue(OPEN_ANIM); } @@ -841,24 +940,48 @@ public class PortalGunPortalEntity extends Entity implements GeoEntity { return this.geoCache; } - public static SoundEvent sound(String path, SoundEvent fallback) { - return BuiltInRegistries.SOUND_EVENT.getOptional(ResourceLocation.fromNamespaceAndPath(Antarchy.MODID, path)).orElse(fallback); + public static SoundEvent sound(String path) { + ResourceLocation soundId = ResourceLocation.fromNamespaceAndPath(Antarchy.MODID, path); + return BuiltInRegistries.SOUND_EVENT.getOptional(soundId) + .orElseThrow(() -> new IllegalStateException("Missing portal gun sound event: " + soundId)); } - private static int[] generateChannelColor(UUID ownerId, PortalSide side) { - int seed = ownerId == null ? side.ordinal() : ownerId.hashCode() ^ side.ordinal() * 0x45d9f3b; - int wobbleA = Math.floorMod(seed, 54); - int wobbleB = Math.floorMod(seed >> 8, 46); - int wobbleC = Math.floorMod(seed >> 16, 38); - if (side == PortalSide.BLUE) { - return new int[] {42 + wobbleC, 128 + wobbleB, 218 + wobbleA / 3}; + private static int[] generateChannelColor(String ownerIdentity, String channelName, PortalSide side) { + if ("Global".equals(ownerIdentity)) { + int[] globalColors = switch (channelName) { + case "Chell" -> new int[] {361215, 16756742}; + case "Atlas" -> new int[] {5482192, 4064209}; + case "P-body" -> new int[] {16373344, 8394260}; + default -> null; + }; + if (globalColors != null) { + int color = globalColors[side == PortalSide.BLUE ? 0 : 1]; + return new int[] {color >> 16 & 0xFF, color >> 8 & 0xFF, color & 0xFF}; + } } - return new int[] {230 + wobbleA / 5, 108 + wobbleB, 28 + wobbleC}; + String owner = String.valueOf(ownerIdentity); + String identity = owner + "_" + String.valueOf(channelName); + Random random = new Random(); + random.setSeed((long) Math.abs(identity.hashCode() * owner.hashCode())); + int firstColor = Math.round(1.6777215E7F * random.nextFloat()); + float[] hsb = Color.RGBtoHSB(firstColor >> 16 & 0xFF, firstColor >> 8 & 0xFF, firstColor & 0xFF, null); + hsb[2] = 0.65F + 0.25F * hsb[2]; + int portalAColor = Color.HSBtoRGB(hsb[0], hsb[1], hsb[2]); + float oppositeHue = hsb[0] + 0.5F; + if (oppositeHue > 1.0F) { + oppositeHue -= 1.0F; + } + int portalBColor = Color.HSBtoRGB(oppositeHue, hsb[1], hsb[2]); + int color = side == PortalSide.BLUE ? portalAColor : portalBColor; + return new int[] {color >> 16 & 0xFF, color >> 8 & 0xFF, color & 0xFF}; } - private record PortalLocalCoords(double horizontal, double vertical, double depth) { + public record PortalCrossing(Vec3 probe, double normalOffset) { } - private record PortalBounds(double minHorizontal, double maxHorizontal, double minVertical, double maxVertical, double minDepth, double maxDepth) { + public record PortalTransitTransform(Vec3 requestedPosition, Vec3 exitPosition, Vec3 movement) { + } + + private record EntityTeleportState(Entity entity, Entity vehicle, Vec3 position, Vec3 look, Vec3 motion) { } } diff --git a/common-1.21.1/src/main/java/com/craisinlord/antarchy/content/portalgun/PortalGunProjectileEntity.java b/common-1.21.1/src/main/java/com/craisinlord/antarchy/content/portalgun/PortalGunProjectileEntity.java index 42c030e3..cce00a1d 100644 --- a/common-1.21.1/src/main/java/com/craisinlord/antarchy/content/portalgun/PortalGunProjectileEntity.java +++ b/common-1.21.1/src/main/java/com/craisinlord/antarchy/content/portalgun/PortalGunProjectileEntity.java @@ -1,40 +1,72 @@ package com.craisinlord.antarchy.content.portalgun; import com.craisinlord.antarchy.content.item.PortalGunItem; +import com.craisinlord.antarchy.config.AntarchySettings; +import java.util.Comparator; +import java.util.LinkedHashMap; +import java.util.Map; import java.util.UUID; import net.minecraft.core.BlockPos; import net.minecraft.core.component.DataComponents; +import net.minecraft.core.registries.Registries; import net.minecraft.nbt.CompoundTag; import net.minecraft.network.syncher.EntityDataAccessor; import net.minecraft.network.syncher.EntityDataSerializers; import net.minecraft.network.syncher.SynchedEntityData; +import net.minecraft.resources.ResourceLocation; import net.minecraft.server.level.ServerLevel; +import net.minecraft.server.level.TicketType; +import net.minecraft.tags.TagKey; import net.minecraft.world.entity.Entity; import net.minecraft.world.entity.EntityType; +import net.minecraft.world.entity.EntityDimensions; import net.minecraft.world.entity.LivingEntity; +import net.minecraft.world.entity.Pose; import net.minecraft.world.entity.player.Player; import net.minecraft.world.entity.projectile.ThrowableItemProjectile; import net.minecraft.world.item.Item; import net.minecraft.world.item.ItemStack; import net.minecraft.world.item.Items; +import net.minecraft.world.level.ClipContext; +import net.minecraft.world.level.ChunkPos; import net.minecraft.world.level.Level; +import net.minecraft.world.level.block.Block; +import net.minecraft.world.level.block.Blocks; +import net.minecraft.world.level.block.StainedGlassBlock; +import net.minecraft.world.level.block.StainedGlassPaneBlock; +import net.minecraft.world.level.block.TintedGlassBlock; +import net.minecraft.world.level.block.state.BlockState; import net.minecraft.world.phys.BlockHitResult; import net.minecraft.world.phys.EntityHitResult; +import net.minecraft.world.phys.HitResult; import net.minecraft.world.phys.Vec3; public class PortalGunProjectileEntity extends ThrowableItemProjectile { + private static final TagKey GLASS_BLOCKS = TagKey.create(Registries.BLOCK, ResourceLocation.fromNamespaceAndPath("c", "glass_blocks")); + private static final TagKey GLASS_PANES = TagKey.create(Registries.BLOCK, ResourceLocation.fromNamespaceAndPath("c", "glass_panes")); + private static final int MAX_TICKETED_PROJECTILES = 20; + private static final int PROJECTILE_TICKET_LEVEL = 31; + private static final TicketType PROJECTILE_TICKET = TicketType.create("antarchy_portal_projectile", Comparator.comparing(UUID::toString)); + private static final LinkedHashMap PROJECTILE_TICKETS = new LinkedHashMap<>(); private static final EntityDataAccessor SIDE = SynchedEntityData.defineId(PortalGunProjectileEntity.class, EntityDataSerializers.INT); private static final EntityDataAccessor SHOOTER_ID = SynchedEntityData.defineId(PortalGunProjectileEntity.class, EntityDataSerializers.INT); private static final EntityDataAccessor DISTANCE = SynchedEntityData.defineId(PortalGunProjectileEntity.class, EntityDataSerializers.INT); - private static final EntityDataAccessor SPAWN_X = SynchedEntityData.defineId(PortalGunProjectileEntity.class, EntityDataSerializers.INT); - private static final EntityDataAccessor SPAWN_Y = SynchedEntityData.defineId(PortalGunProjectileEntity.class, EntityDataSerializers.INT); - private static final EntityDataAccessor SPAWN_Z = SynchedEntityData.defineId(PortalGunProjectileEntity.class, EntityDataSerializers.INT); + private static final EntityDataAccessor SPAWN_X = SynchedEntityData.defineId(PortalGunProjectileEntity.class, EntityDataSerializers.LONG); + private static final EntityDataAccessor SPAWN_Y = SynchedEntityData.defineId(PortalGunProjectileEntity.class, EntityDataSerializers.LONG); + private static final EntityDataAccessor SPAWN_Z = SynchedEntityData.defineId(PortalGunProjectileEntity.class, EntityDataSerializers.LONG); + private static final EntityDataAccessor MAX_TRAVEL_DISTANCE_DATA = SynchedEntityData.defineId(PortalGunProjectileEntity.class, EntityDataSerializers.INT); private static final EntityDataAccessor VELOCITY_X = SynchedEntityData.defineId(PortalGunProjectileEntity.class, EntityDataSerializers.FLOAT); private static final EntityDataAccessor VELOCITY_Y = SynchedEntityData.defineId(PortalGunProjectileEntity.class, EntityDataSerializers.FLOAT); private static final EntityDataAccessor VELOCITY_Z = SynchedEntityData.defineId(PortalGunProjectileEntity.class, EntityDataSerializers.FLOAT); - private static final int MAX_FLIGHT_TICKS = 80; + private static final EntityDataAccessor PROJECTILE_SPEED = SynchedEntityData.defineId(PortalGunProjectileEntity.class, EntityDataSerializers.FLOAT); + private static final EntityDataAccessor PASS_THROUGH_GLASS = SynchedEntityData.defineId(PortalGunProjectileEntity.class, EntityDataSerializers.BOOLEAN); + private static final EntityDataAccessor PASS_THROUGH_LIQUID = SynchedEntityData.defineId(PortalGunProjectileEntity.class, EntityDataSerializers.BOOLEAN); + public static final double MIN_LAUNCH_SPEED = 4.98D; + public static final double LAUNCH_SPEED_VARIANCE = 0.02D; + public static final double MAX_TRAVEL_DISTANCE = 10000.0D; private UUID gunId; private boolean spawnSynced; + private boolean ticketRegistered; public int portalWidth = 1; public int portalHeight = 2; @@ -53,30 +85,55 @@ public class PortalGunProjectileEntity extends ThrowableItemProjectile { this.noCulling = true; } + @Override + public EntityDimensions getDimensions(Pose pose) { + return EntityDimensions.fixed(0.3F, 0.3F); + } + @Override protected void defineSynchedData(SynchedEntityData.Builder builder) { super.defineSynchedData(builder); builder.define(SIDE, PortalGunPortalEntity.PortalSide.BLUE.ordinal()); builder.define(SHOOTER_ID, -1); builder.define(DISTANCE, 0); - builder.define(SPAWN_X, 0); - builder.define(SPAWN_Y, 0); - builder.define(SPAWN_Z, 0); + builder.define(SPAWN_X, Double.doubleToRawLongBits(0.0D)); + builder.define(SPAWN_Y, Double.doubleToRawLongBits(0.0D)); + builder.define(SPAWN_Z, Double.doubleToRawLongBits(0.0D)); + builder.define(MAX_TRAVEL_DISTANCE_DATA, (int) MAX_TRAVEL_DISTANCE); builder.define(VELOCITY_X, 0.0F); builder.define(VELOCITY_Y, 0.0F); builder.define(VELOCITY_Z, 0.0F); + builder.define(PROJECTILE_SPEED, 4.99F); + builder.define(PASS_THROUGH_GLASS, false); + builder.define(PASS_THROUGH_LIQUID, false); } public void configure(PortalGunPortalEntity.PortalSide side, UUID gunId, ItemStack gunStack) { this.entityData.set(SIDE, side.ordinal()); this.gunId = gunId; this.setItem(gunStack.copyWithCount(1)); + this.portalWidth = PortalGunItem.getPortalWidth(gunStack); + this.portalHeight = PortalGunItem.getPortalHeight(gunStack); + this.entityData.set(MAX_TRAVEL_DISTANCE_DATA, AntarchySettings.portalGunMaxShootDistance()); + this.entityData.set(PASS_THROUGH_GLASS, AntarchySettings.portalGunCanFireThroughGlass()); + this.entityData.set(PASS_THROUGH_LIQUID, AntarchySettings.portalGunCanFireThroughLiquid()); Entity owner = this.getOwner(); this.entityData.set(SHOOTER_ID, owner == null ? -1 : owner.getId()); - this.syncSpawnPosition(this.blockPosition()); + } + + public void syncLaunchState() { + this.syncSpawnPosition(this.position()); this.syncVelocity(); } + public void setProjectileSpeed(double speed) { + this.entityData.set(PROJECTILE_SPEED, (float) speed); + } + + public double getProjectileSpeed() { + return this.entityData.get(PROJECTILE_SPEED); + } + public PortalGunPortalEntity.PortalSide getPortalSide() { return this.entityData.get(SIDE) == PortalGunPortalEntity.PortalSide.ORANGE.ordinal() ? PortalGunPortalEntity.PortalSide.ORANGE : PortalGunPortalEntity.PortalSide.BLUE; } @@ -89,8 +146,16 @@ public class PortalGunProjectileEntity extends ThrowableItemProjectile { return this.entityData.get(DISTANCE); } - public BlockPos getSyncedSpawnPos() { - return new BlockPos(this.entityData.get(SPAWN_X), this.entityData.get(SPAWN_Y), this.entityData.get(SPAWN_Z)); + public int getMaxTravelDistance() { + return this.entityData.get(MAX_TRAVEL_DISTANCE_DATA); + } + + public Vec3 getSyncedSpawnPosition() { + return new Vec3( + Double.longBitsToDouble(this.entityData.get(SPAWN_X)), + Double.longBitsToDouble(this.entityData.get(SPAWN_Y)), + Double.longBitsToDouble(this.entityData.get(SPAWN_Z)) + ); } public Vec3 getSyncedVelocity() { @@ -124,22 +189,82 @@ public class PortalGunProjectileEntity extends ThrowableItemProjectile { @Override public void tick() { - super.tick(); - if (!this.spawnSynced) { - this.syncSpawnPosition(this.blockPosition()); + if (this.level().isClientSide && !this.spawnSynced) { + Vec3 spawnPosition = this.getSyncedSpawnPosition(); + this.setPos(spawnPosition.x, spawnPosition.y, spawnPosition.z); + this.setDeltaMovement(this.getSyncedVelocity()); + this.spawnSynced = true; + } + if (!this.level().isClientSide && !this.isRemoved()) { + this.updateProjectileTicket(); + } + this.baseTick(); + if (this.isRemoved()) { + return; + } + if (!this.spawnSynced && !this.level().isClientSide) { + this.syncSpawnPosition(this.position()); + this.syncVelocity(); + this.spawnSynced = true; } - this.syncVelocity(); this.entityData.set(DISTANCE, this.tickCount); - if (this.tickCount > MAX_FLIGHT_TICKS) { + int maxTravelDistance = this.getMaxTravelDistance(); + Vec3 spawnPosition = this.getSyncedSpawnPosition(); + if (this.tickCount > maxTravelDistance / 5.0D + 10.0D + || this.position().distanceToSqr(spawnPosition) > (maxTravelDistance + 5.0D) * (maxTravelDistance + 5.0D)) { this.discard(); return; } Vec3 motion = this.getDeltaMovement(); + Vec3 start = this.position(); + Vec3 end = start.add(motion); + ClipContext.Fluid fluidMode = this.entityData.get(PASS_THROUGH_LIQUID) ? ClipContext.Fluid.NONE : ClipContext.Fluid.ANY; + HitResult hit = this.level().clip(new ClipContext(start, end, ClipContext.Block.COLLIDER, fluidMode, this)); + if (hit instanceof BlockHitResult blockHit) { + BlockPos hitBlockPos = blockHit.getBlockPos(); + net.minecraft.world.level.block.state.BlockState hitBlockState = this.level().getBlockState(hitBlockPos); + boolean passThroughGlass = this.shouldPassThroughGlass(blockHit); + if (!hitBlockState.isAir()) { + hitBlockState.entityInside(this.level(), hitBlockPos, this); + if (this.isRemoved()) { + return; + } + } + if (!passThroughGlass) { + this.onHit(blockHit); + if (this.isRemoved()) { + return; + } + } + this.setPos(blockHit.getLocation().subtract(motion.scale(0.98D))); + } if (motion.lengthSqr() > 1.0E-6D) { double horizontal = Math.sqrt(motion.x * motion.x + motion.z * motion.z); this.setYRot((float) (net.minecraft.util.Mth.atan2(motion.z, motion.x) * (180.0D / Math.PI)) - 90.0F); this.setXRot((float) (-(net.minecraft.util.Mth.atan2(motion.y, horizontal) * (180.0D / Math.PI)))); } + this.setPos(this.position().add(motion)); + this.checkInsideBlocks(); + if (this.isRemoved()) { + return; + } + if (!this.level().isClientSide && !this.isRemoved()) { + this.updateProjectileTicket(); + } + if (this.isInWater()) { + for (int i = 0; i < 4; i++) { + float backtrack = 0.25F; + this.level().addParticle( + net.minecraft.core.particles.ParticleTypes.BUBBLE, + this.getX() - motion.x * backtrack, + this.getY() - motion.y * backtrack, + this.getZ() - motion.z * backtrack, + motion.x, + motion.y, + motion.z + ); + } + } if (this.level().isClientSide) { Vec3 point = this.position(); if (this.getPortalSide() == PortalGunPortalEntity.PortalSide.BLUE) { @@ -152,6 +277,63 @@ public class PortalGunProjectileEntity extends ThrowableItemProjectile { } } + @Override + public void remove(Entity.RemovalReason reason) { + this.releaseProjectileTicket(); + super.remove(reason); + } + + private void updateProjectileTicket() { + if (!(this.level() instanceof ServerLevel serverLevel)) { + return; + } + if (!AntarchySettings.portalGunCanPortalProjectilesChunkload()) { + this.releaseProjectileTicket(); + return; + } + ChunkPos chunkPos = this.chunkPosition(); + UUID ticketId = this.getUUID(); + ProjectileTicket current = PROJECTILE_TICKETS.get(ticketId); + if (current != null && current.level == serverLevel && current.chunkPos.equals(chunkPos)) { + return; + } + if (current != null) { + current.level.getChunkSource().removeRegionTicket(PROJECTILE_TICKET, current.chunkPos, PROJECTILE_TICKET_LEVEL, ticketId); + PROJECTILE_TICKETS.remove(ticketId); + } + while (PROJECTILE_TICKETS.size() >= MAX_TICKETED_PROJECTILES) { + Map.Entry oldest = PROJECTILE_TICKETS.entrySet().iterator().next(); + oldest.getValue().level.getChunkSource().removeRegionTicket(PROJECTILE_TICKET, oldest.getValue().chunkPos, PROJECTILE_TICKET_LEVEL, oldest.getKey()); + PROJECTILE_TICKETS.remove(oldest.getKey()); + } + serverLevel.getChunkSource().addRegionTicket(PROJECTILE_TICKET, chunkPos, PROJECTILE_TICKET_LEVEL, ticketId); + PROJECTILE_TICKETS.put(ticketId, new ProjectileTicket(serverLevel, chunkPos)); + this.ticketRegistered = true; + } + + private void releaseProjectileTicket() { + if (!this.ticketRegistered) { + return; + } + UUID ticketId = this.getUUID(); + ProjectileTicket ticket = PROJECTILE_TICKETS.remove(ticketId); + if (ticket != null) { + ticket.level.getChunkSource().removeRegionTicket(PROJECTILE_TICKET, ticket.chunkPos, PROJECTILE_TICKET_LEVEL, ticketId); + } + this.ticketRegistered = false; + } + + public static void releaseAllProjectileTickets() { + for (Map.Entry entry : PROJECTILE_TICKETS.entrySet()) { + ProjectileTicket ticket = entry.getValue(); + ticket.level.getChunkSource().removeRegionTicket(PROJECTILE_TICKET, ticket.chunkPos, PROJECTILE_TICKET_LEVEL, entry.getKey()); + } + PROJECTILE_TICKETS.clear(); + } + + private record ProjectileTicket(ServerLevel level, ChunkPos chunkPos) { + } + @Override protected void onHitEntity(EntityHitResult result) { } @@ -161,13 +343,30 @@ public class PortalGunProjectileEntity extends ThrowableItemProjectile { if (!this.level().isClientSide && this.level() instanceof ServerLevel serverLevel) { this.spawnImpactParticles(serverLevel, result.getLocation()); ItemStack sourceStack = this.resolveSourceStack(); - if (this.getOwner() instanceof Player player && sourceStack.getItem() instanceof PortalGunItem portalGunItem) { - portalGunItem.handlePortalImpact(serverLevel, player, sourceStack, this.getPortalSide(), result, this.position()); + if (this.getOwner() instanceof LivingEntity shooter && sourceStack.getItem() instanceof PortalGunItem portalGunItem) { + portalGunItem.handlePortalImpact(serverLevel, shooter, sourceStack, this.gunId, this.getPortalSide(), result, result.getLocation(), this.portalWidth, this.portalHeight); } } this.discard(); } + private boolean shouldPassThroughGlass(BlockHitResult hit) { + if (!this.entityData.get(PASS_THROUGH_GLASS)) { + return false; + } + BlockPos blockPos = hit.getBlockPos(); + BlockState state = this.level().getBlockState(blockPos); + Block block = state.getBlock(); + return state.is(GLASS_BLOCKS) + || state.is(GLASS_PANES) + || block == Blocks.GLASS + || block == Blocks.GLASS_PANE + || block == Blocks.TINTED_GLASS + || block instanceof StainedGlassBlock + || block instanceof StainedGlassPaneBlock + || block instanceof TintedGlassBlock; + } + private void spawnImpactParticles(ServerLevel level, Vec3 impactPos) { if (this.getPortalSide() == PortalGunPortalEntity.PortalSide.BLUE) { level.sendParticles(net.minecraft.core.particles.ParticleTypes.SOUL_FIRE_FLAME, impactPos.x, impactPos.y, impactPos.z, 10, 0.08D, 0.08D, 0.08D, 0.01D); @@ -178,10 +377,10 @@ public class PortalGunProjectileEntity extends ThrowableItemProjectile { level.sendParticles(net.minecraft.core.particles.ParticleTypes.CRIT, impactPos.x, impactPos.y, impactPos.z, 6, 0.04D, 0.04D, 0.04D, 0.01D); } - private void syncSpawnPosition(BlockPos pos) { - this.entityData.set(SPAWN_X, pos.getX()); - this.entityData.set(SPAWN_Y, pos.getY()); - this.entityData.set(SPAWN_Z, pos.getZ()); + private void syncSpawnPosition(Vec3 pos) { + this.entityData.set(SPAWN_X, Double.doubleToRawLongBits(pos.x)); + this.entityData.set(SPAWN_Y, Double.doubleToRawLongBits(pos.y)); + this.entityData.set(SPAWN_Z, Double.doubleToRawLongBits(pos.z)); this.spawnSynced = true; } @@ -207,12 +406,20 @@ public class PortalGunProjectileEntity extends ThrowableItemProjectile { tag.putInt("Side", this.entityData.get(SIDE)); tag.putInt("ShooterId", this.entityData.get(SHOOTER_ID)); tag.putInt("Distance", this.entityData.get(DISTANCE)); - tag.putInt("SpawnX", this.entityData.get(SPAWN_X)); - tag.putInt("SpawnY", this.entityData.get(SPAWN_Y)); - tag.putInt("SpawnZ", this.entityData.get(SPAWN_Z)); + Vec3 spawnPosition = this.getSyncedSpawnPosition(); + tag.putInt("SpawnX", net.minecraft.util.Mth.floor(spawnPosition.x)); + tag.putInt("SpawnY", net.minecraft.util.Mth.floor(spawnPosition.y)); + tag.putInt("SpawnZ", net.minecraft.util.Mth.floor(spawnPosition.z)); + tag.putDouble("SpawnPosX", spawnPosition.x); + tag.putDouble("SpawnPosY", spawnPosition.y); + tag.putDouble("SpawnPosZ", spawnPosition.z); + tag.putInt("MaxTravelDistance", this.entityData.get(MAX_TRAVEL_DISTANCE_DATA)); tag.putFloat("VelocityX", this.entityData.get(VELOCITY_X)); tag.putFloat("VelocityY", this.entityData.get(VELOCITY_Y)); tag.putFloat("VelocityZ", this.entityData.get(VELOCITY_Z)); + tag.putFloat("ProjectileSpeed", this.entityData.get(PROJECTILE_SPEED)); + tag.putBoolean("PassThroughGlass", this.entityData.get(PASS_THROUGH_GLASS)); + tag.putBoolean("PassThroughLiquid", this.entityData.get(PASS_THROUGH_LIQUID)); tag.putInt("PortalWidth", this.portalWidth); tag.putInt("PortalHeight", this.portalHeight); if (this.gunId != null) { @@ -226,14 +433,20 @@ public class PortalGunProjectileEntity extends ThrowableItemProjectile { this.entityData.set(SIDE, tag.getInt("Side")); this.entityData.set(SHOOTER_ID, tag.getInt("ShooterId")); this.entityData.set(DISTANCE, tag.getInt("Distance")); - this.entityData.set(SPAWN_X, tag.getInt("SpawnX")); - this.entityData.set(SPAWN_Y, tag.getInt("SpawnY")); - this.entityData.set(SPAWN_Z, tag.getInt("SpawnZ")); + this.entityData.set(SPAWN_X, Double.doubleToRawLongBits(tag.contains("SpawnPosX") ? tag.getDouble("SpawnPosX") : tag.getInt("SpawnX"))); + this.entityData.set(SPAWN_Y, Double.doubleToRawLongBits(tag.contains("SpawnPosY") ? tag.getDouble("SpawnPosY") : tag.getInt("SpawnY"))); + this.entityData.set(SPAWN_Z, Double.doubleToRawLongBits(tag.contains("SpawnPosZ") ? tag.getDouble("SpawnPosZ") : tag.getInt("SpawnZ"))); + this.entityData.set(MAX_TRAVEL_DISTANCE_DATA, tag.contains("MaxTravelDistance") + ? Math.max(1, tag.getInt("MaxTravelDistance")) + : AntarchySettings.portalGunMaxShootDistance()); this.entityData.set(VELOCITY_X, tag.getFloat("VelocityX")); this.entityData.set(VELOCITY_Y, tag.getFloat("VelocityY")); this.entityData.set(VELOCITY_Z, tag.getFloat("VelocityZ")); - this.portalWidth = Math.max(1, tag.getInt("PortalWidth")); - this.portalHeight = Math.max(2, tag.getInt("PortalHeight")); + this.entityData.set(PROJECTILE_SPEED, tag.contains("ProjectileSpeed") ? tag.getFloat("ProjectileSpeed") : 4.99F); + this.entityData.set(PASS_THROUGH_GLASS, tag.contains("PassThroughGlass") ? tag.getBoolean("PassThroughGlass") : AntarchySettings.portalGunCanFireThroughGlass()); + this.entityData.set(PASS_THROUGH_LIQUID, tag.contains("PassThroughLiquid") ? tag.getBoolean("PassThroughLiquid") : AntarchySettings.portalGunCanFireThroughLiquid()); + this.portalWidth = net.minecraft.util.Mth.clamp(tag.getInt("PortalWidth"), 1, 16); + this.portalHeight = net.minecraft.util.Mth.clamp(tag.getInt("PortalHeight"), 2, 16); this.spawnSynced = true; if (tag.hasUUID("GunId")) { this.gunId = tag.getUUID("GunId"); diff --git a/common-1.21.1/src/main/java/com/craisinlord/antarchy/content/portalgun/PortalGunResetManager.java b/common-1.21.1/src/main/java/com/craisinlord/antarchy/content/portalgun/PortalGunResetManager.java new file mode 100644 index 00000000..3a37a81f --- /dev/null +++ b/common-1.21.1/src/main/java/com/craisinlord/antarchy/content/portalgun/PortalGunResetManager.java @@ -0,0 +1,86 @@ +package com.craisinlord.antarchy.content.portalgun; + +import com.craisinlord.antarchy.content.item.PortalGunItem; +import java.util.HashMap; +import java.util.Map; +import java.util.UUID; +import net.minecraft.server.MinecraftServer; +import net.minecraft.server.level.ServerPlayer; +import net.minecraft.world.item.ItemStack; + +public final class PortalGunResetManager { + public static final int ARM = 0; + public static final int RELEASE = 1; + public static final int RESET_ALL = 2; + public static final int RESET_BLUE = 3; + private static final Map STATES = new HashMap<>(); + + private PortalGunResetManager() { + } + + public static void handleInput(ServerPlayer player, int action) { + ItemStack stack = heldPortalGun(player); + if (stack.isEmpty()) { + STATES.remove(player.getUUID()); + return; + } + UUID gunId = PortalGunItem.ensureGunIdentity(stack, player); + if (action == ARM) { + STATES.put(player.getUUID(), new ResetState(gunId, player.serverLevel().getGameTime(), false)); + return; + } + ResetState state = activeState(player, gunId); + if (action == RESET_ALL) { + PortalGunItem item = (PortalGunItem) stack.getItem(); + item.resetPortals(player, stack); + STATES.remove(player.getUUID()); + } else if (action == RESET_BLUE && state != null && !state.sideReset()) { + ((PortalGunItem) stack.getItem()).resetPortalSide(player, stack, PortalGunPortalEntity.PortalSide.BLUE); + STATES.put(player.getUUID(), new ResetState(gunId, state.startedAt(), true)); + } else if (action == RELEASE) { + STATES.remove(player.getUUID()); + } + } + + public static boolean handleOrangeReset(ServerPlayer player, ItemStack stack) { + if (!(stack.getItem() instanceof PortalGunItem)) { + return false; + } + UUID gunId = PortalGunItem.getGunId(stack); + ResetState state = activeState(player, gunId); + if (state == null) { + return false; + } + if (!state.sideReset()) { + ((PortalGunItem) stack.getItem()).resetPortalSide(player, stack, PortalGunPortalEntity.PortalSide.ORANGE); + STATES.put(player.getUUID(), new ResetState(gunId, state.startedAt(), true)); + } + return true; + } + + public static void clear(MinecraftServer server) { + STATES.clear(); + } + + private static ResetState activeState(ServerPlayer player, UUID gunId) { + ResetState state = STATES.get(player.getUUID()); + if (state == null || gunId == null || !state.gunId().equals(gunId) + || player.serverLevel().getGameTime() - state.startedAt() > 100L) { + STATES.remove(player.getUUID()); + return null; + } + return state; + } + + private static ItemStack heldPortalGun(ServerPlayer player) { + ItemStack main = player.getMainHandItem(); + if (main.getItem() instanceof PortalGunItem) { + return main; + } + ItemStack offhand = player.getOffhandItem(); + return offhand.getItem() instanceof PortalGunItem ? offhand : ItemStack.EMPTY; + } + + private record ResetState(UUID gunId, long startedAt, boolean sideReset) { + } +} diff --git a/common-1.21.1/src/main/java/com/craisinlord/antarchy/content/portalgun/PortalGunSavedData.java b/common-1.21.1/src/main/java/com/craisinlord/antarchy/content/portalgun/PortalGunSavedData.java index f636bb99..35620513 100644 --- a/common-1.21.1/src/main/java/com/craisinlord/antarchy/content/portalgun/PortalGunSavedData.java +++ b/common-1.21.1/src/main/java/com/craisinlord/antarchy/content/portalgun/PortalGunSavedData.java @@ -1,12 +1,17 @@ package com.craisinlord.antarchy.content.portalgun; import java.util.HashMap; +import java.util.HashSet; +import java.nio.charset.StandardCharsets; import java.util.Map; +import java.util.Objects; import java.util.Optional; +import java.util.Set; import java.util.UUID; import net.minecraft.core.HolderLookup; import net.minecraft.nbt.CompoundTag; import net.minecraft.nbt.ListTag; +import net.minecraft.resources.ResourceLocation; import net.minecraft.server.MinecraftServer; import net.minecraft.server.level.ServerLevel; import net.minecraft.world.entity.Entity; @@ -16,9 +21,14 @@ public final class PortalGunSavedData extends SavedData { private static final String ID = "antarchy_portal_gun_pairs"; private static final String ENTRIES_KEY = "Entries"; private static final String OWNER_KEY = "Owner"; + private static final String DIMENSION_KEY = "Dimension"; + private static final String GUN_KEY = "Gun"; private static final String BLUE_KEY = "Blue"; private static final String ORANGE_KEY = "Orange"; - private final Map pairs = new HashMap<>(); + private static final String CHANNELS_KEY = "Channels"; + private final Map pairs = new HashMap<>(); + private final Map legacyPairs = new HashMap<>(); + private final Map channelGunIds = new HashMap<>(); public static PortalGunSavedData create() { return new PortalGunSavedData(); @@ -31,7 +41,20 @@ public final class PortalGunSavedData extends SavedData { UUID owner = entryTag.getUUID(OWNER_KEY); UUID blue = entryTag.hasUUID(BLUE_KEY) ? entryTag.getUUID(BLUE_KEY) : null; UUID orange = entryTag.hasUUID(ORANGE_KEY) ? entryTag.getUUID(ORANGE_KEY) : null; - data.pairs.put(owner, new PairRecord(blue, orange)); + PairRecord record = new PairRecord(blue, orange); + ResourceLocation dimension = entryTag.contains(DIMENSION_KEY) ? ResourceLocation.tryParse(entryTag.getString(DIMENSION_KEY)) : null; + if (dimension == null) { + data.legacyPairs.put(owner, record); + } else { + UUID gunId = entryTag.hasUUID(GUN_KEY) ? entryTag.getUUID(GUN_KEY) : null; + data.pairs.put(new PairKey(owner, dimension, gunId), record); + } + } + for (net.minecraft.nbt.Tag rawChannel : tag.getList(CHANNELS_KEY, net.minecraft.nbt.Tag.TAG_COMPOUND)) { + CompoundTag channelTag = (CompoundTag) rawChannel; + if (channelTag.hasUUID(OWNER_KEY) && channelTag.hasUUID(GUN_KEY) && channelTag.contains("Channel")) { + data.channelGunIds.put(new OwnerChannelKey(channelTag.getUUID(OWNER_KEY), channelTag.getString("Channel")), channelTag.getUUID(GUN_KEY)); + } } return data; } @@ -39,21 +62,44 @@ public final class PortalGunSavedData extends SavedData { @Override public CompoundTag save(CompoundTag tag, HolderLookup.Provider registries) { ListTag entries = new ListTag(); - for (Map.Entry entry : this.pairs.entrySet()) { + for (Map.Entry entry : this.pairs.entrySet()) { CompoundTag entryTag = new CompoundTag(); - entryTag.putUUID(OWNER_KEY, entry.getKey()); - if (entry.getValue().bluePortalId() != null) { - entryTag.putUUID(BLUE_KEY, entry.getValue().bluePortalId()); - } - if (entry.getValue().orangePortalId() != null) { - entryTag.putUUID(ORANGE_KEY, entry.getValue().orangePortalId()); + entryTag.putUUID(OWNER_KEY, entry.getKey().owner()); + entryTag.putString(DIMENSION_KEY, entry.getKey().dimension().toString()); + if (entry.getKey().gunId() != null) { + entryTag.putUUID(GUN_KEY, entry.getKey().gunId()); } + writePair(entryTag, entry.getValue()); entries.add(entryTag); } + for (Map.Entry entry : this.legacyPairs.entrySet()) { + CompoundTag entryTag = new CompoundTag(); + entryTag.putUUID(OWNER_KEY, entry.getKey()); + writePair(entryTag, entry.getValue()); + entries.add(entryTag); + } + ListTag channels = new ListTag(); + for (Map.Entry entry : this.channelGunIds.entrySet()) { + CompoundTag channelTag = new CompoundTag(); + channelTag.putUUID(OWNER_KEY, entry.getKey().owner()); + channelTag.putString("Channel", entry.getKey().channelName()); + channelTag.putUUID(GUN_KEY, entry.getValue()); + channels.add(channelTag); + } tag.put(ENTRIES_KEY, entries); + tag.put(CHANNELS_KEY, channels); return tag; } + private static void writePair(CompoundTag tag, PairRecord record) { + if (record.bluePortalId() != null) { + tag.putUUID(BLUE_KEY, record.bluePortalId()); + } + if (record.orangePortalId() != null) { + tag.putUUID(ORANGE_KEY, record.orangePortalId()); + } + } + private static PortalGunSavedData get(MinecraftServer server) { return server.overworld().getDataStorage().computeIfAbsent( new SavedData.Factory<>(PortalGunSavedData::create, PortalGunSavedData::load, null), @@ -61,35 +107,95 @@ public final class PortalGunSavedData extends SavedData { ); } - public static Optional getPortalId(MinecraftServer server, UUID owner, PortalGunPortalEntity.PortalSide side) { - PairRecord record = get(server).pairs.get(owner); + public static Optional getPortalId(MinecraftServer server, UUID owner, UUID gunId, String channelName, PortalGunPortalEntity.PortalSide side, ResourceLocation dimension) { + PortalGunSavedData data = get(server); + UUID resolvedGunId = data.resolveChannelGunId(owner, channelName, gunId); + data.migrateLegacyPair(server, owner); + data.adoptLegacyPairs(server, owner, resolvedGunId, channelName); + PairRecord record = data.pairs.get(new PairKey(owner, dimension, resolvedGunId)); if (record == null) { return Optional.empty(); } + data.synchronizeLoadedPortal(server, owner, dimension, record.bluePortalId(), resolvedGunId, channelName); + data.synchronizeLoadedPortal(server, owner, dimension, record.orangePortalId(), resolvedGunId, channelName); return Optional.ofNullable(side == PortalGunPortalEntity.PortalSide.BLUE ? record.bluePortalId() : record.orangePortalId()); } - public static boolean isRegistered(MinecraftServer server, UUID owner, PortalGunPortalEntity.PortalSide side, UUID portalId) { - return getPortalId(server, owner, side).map(portalId::equals).orElse(false); + public static Optional getPortalId(MinecraftServer server, UUID owner, UUID gunId, PortalGunPortalEntity.PortalSide side, ResourceLocation dimension) { + return getPortalId(server, owner, gunId, String.valueOf(gunId), side, dimension); } - public static void setPortal(MinecraftServer server, UUID owner, PortalGunPortalEntity.PortalSide side, UUID portalId) { + public static Optional getGunIdForPortal(MinecraftServer server, UUID owner, ResourceLocation dimension, UUID portalId) { PortalGunSavedData data = get(server); - PairRecord current = data.pairs.getOrDefault(owner, new PairRecord(null, null)); + data.migrateLegacyPair(server, owner); + for (Map.Entry entry : data.pairs.entrySet()) { + PairKey key = entry.getKey(); + if (key.owner().equals(owner) && key.dimension().equals(dimension) && key.gunId() != null + && (portalId.equals(entry.getValue().bluePortalId()) || portalId.equals(entry.getValue().orangePortalId()))) { + return Optional.of(key.gunId()); + } + } + return Optional.empty(); + } + + public static Optional getChannelNameForPortal(MinecraftServer server, UUID owner, ResourceLocation dimension, UUID portalId) { + PortalGunSavedData data = get(server); + for (Map.Entry entry : data.pairs.entrySet()) { + PairKey key = entry.getKey(); + if (!key.owner().equals(owner) || !key.dimension().equals(dimension) + || (!portalId.equals(entry.getValue().bluePortalId()) && !portalId.equals(entry.getValue().orangePortalId()))) { + continue; + } + return data.channelGunIds.entrySet().stream() + .filter(channel -> channel.getKey().owner().equals(owner) && channel.getValue().equals(key.gunId())) + .map(channel -> channel.getKey().channelName()) + .findFirst(); + } + return Optional.empty(); + } + + public static boolean isRegistered(ServerLevel level, UUID owner, UUID gunId, PortalGunPortalEntity.PortalSide side, UUID portalId) { + return isRegistered(level, owner, gunId, String.valueOf(gunId), side, portalId); + } + + public static boolean isRegistered(ServerLevel level, UUID owner, UUID gunId, String channelName, PortalGunPortalEntity.PortalSide side, UUID portalId) { + return getPortalId(level.getServer(), owner, gunId, channelName, side, level.dimension().location()).map(portalId::equals).orElse(false); + } + + public static void setPortal(ServerLevel level, UUID owner, UUID gunId, PortalGunPortalEntity.PortalSide side, UUID portalId) { + setPortal(level, owner, gunId, String.valueOf(gunId), side, portalId); + } + + public static void setPortal(ServerLevel level, UUID owner, UUID gunId, String channelName, PortalGunPortalEntity.PortalSide side, UUID portalId) { + PortalGunSavedData data = get(level.getServer()); + ResourceLocation dimension = level.dimension().location(); + UUID resolvedGunId = data.resolveChannelGunId(owner, channelName, gunId); + data.migrateLegacyPair(level.getServer(), owner); + data.adoptLegacyPairs(level.getServer(), owner, resolvedGunId, channelName); + PairKey key = new PairKey(owner, dimension, resolvedGunId); + PairRecord current = data.pairs.getOrDefault(key, new PairRecord(null, null)); PairRecord updated = side == PortalGunPortalEntity.PortalSide.BLUE ? new PairRecord(portalId, current.orangePortalId()) : new PairRecord(current.bluePortalId(), portalId); if (updated.bluePortalId() == null && updated.orangePortalId() == null) { - data.pairs.remove(owner); + data.pairs.remove(key); } else { - data.pairs.put(owner, updated); + data.pairs.put(key, updated); } data.setDirty(); } - public static void clearPortal(MinecraftServer server, UUID owner, PortalGunPortalEntity.PortalSide side, UUID portalId) { + public static void clearPortal(MinecraftServer server, UUID owner, UUID gunId, PortalGunPortalEntity.PortalSide side, UUID portalId, ResourceLocation dimension) { + clearPortal(server, owner, gunId, String.valueOf(gunId), side, portalId, dimension); + } + + public static void clearPortal(MinecraftServer server, UUID owner, UUID gunId, String channelName, PortalGunPortalEntity.PortalSide side, UUID portalId, ResourceLocation dimension) { PortalGunSavedData data = get(server); - PairRecord current = data.pairs.get(owner); + UUID resolvedGunId = data.resolveChannelGunId(owner, channelName, gunId); + data.migrateLegacyPair(server, owner); + data.adoptLegacyPairs(server, owner, resolvedGunId, channelName); + PairKey key = new PairKey(owner, dimension, resolvedGunId); + PairRecord current = data.pairs.get(key); if (current == null) { return; } @@ -102,50 +208,249 @@ public final class PortalGunSavedData extends SavedData { orange = null; } if (blue == null && orange == null) { - data.pairs.remove(owner); + data.pairs.remove(key); } else { - data.pairs.put(owner, new PairRecord(blue, orange)); + data.pairs.put(key, new PairRecord(blue, orange)); } data.setDirty(); } - public static void clearAllPortals(MinecraftServer server, UUID owner) { + public static void clearAllPortals(ServerLevel level, UUID owner, UUID gunId) { + clearAllPortals(level, owner, gunId, String.valueOf(gunId)); + } + + public static void clearAllPortals(ServerLevel level, UUID owner, UUID gunId, String channelName) { + MinecraftServer server = level.getServer(); PortalGunSavedData data = get(server); - PairRecord current = data.pairs.remove(owner); - data.setDirty(); - if (current == null) { - return; + UUID resolvedGunId = data.resolveChannelGunId(owner, channelName, gunId); + data.migrateLegacyPair(server, owner); + data.adoptLegacyPairs(server, owner, resolvedGunId, channelName); + Set portalIds = new HashSet<>(); + PairRecord legacy = gunId == null ? data.legacyPairs.remove(owner) : null; + if (legacy != null) { + addPortalIds(portalIds, legacy); + } + data.pairs.entrySet().removeIf(entry -> { + if (!entry.getKey().owner().equals(owner) || !entry.getKey().dimension().equals(level.dimension().location()) + || !Objects.equals(entry.getKey().gunId(), resolvedGunId)) { + return false; + } + addPortalIds(portalIds, entry.getValue()); + return true; + }); + data.setDirty(); + for (UUID portalId : portalIds) { + discardIfLoaded(server, portalId); } - discardIfLoaded(server, current.bluePortalId()); - discardIfLoaded(server, current.orangePortalId()); } - public static PortalGunPortalEntity findLoadedPortal(MinecraftServer server, UUID owner, PortalGunPortalEntity.PortalSide side) { - Optional portalId = getPortalId(server, owner, side); + public static void clearPortalSide(ServerLevel level, UUID owner, UUID gunId, PortalGunPortalEntity.PortalSide side) { + clearPortalSide(level, owner, gunId, String.valueOf(gunId), side); + } + + public static void clearPortalSide(ServerLevel level, UUID owner, UUID gunId, String channelName, PortalGunPortalEntity.PortalSide side) { + MinecraftServer server = level.getServer(); + PortalGunSavedData data = get(server); + UUID resolvedGunId = data.resolveChannelGunId(owner, channelName, gunId); + data.migrateLegacyPair(server, owner); + data.adoptLegacyPairs(server, owner, resolvedGunId, channelName); + Set portalIds = new HashSet<>(); + for (Map.Entry entry : new HashMap<>(data.pairs).entrySet()) { + PairKey key = entry.getKey(); + if (!key.owner().equals(owner) || !key.dimension().equals(level.dimension().location()) || !Objects.equals(key.gunId(), resolvedGunId)) { + continue; + } + PairRecord pair = entry.getValue(); + UUID removed = side == PortalGunPortalEntity.PortalSide.BLUE ? pair.bluePortalId() : pair.orangePortalId(); + if (removed == null) { + continue; + } + portalIds.add(removed); + PairRecord remaining = side == PortalGunPortalEntity.PortalSide.BLUE + ? new PairRecord(null, pair.orangePortalId()) + : new PairRecord(pair.bluePortalId(), null); + if (remaining.bluePortalId() == null && remaining.orangePortalId() == null) { + data.pairs.remove(key); + } else { + data.pairs.put(key, remaining); + } + } + data.setDirty(); + for (UUID portalId : portalIds) { + discardIfLoaded(server, portalId); + } + } + + public static PortalGunPortalEntity findLoadedPortal(ServerLevel level, UUID owner, UUID gunId, PortalGunPortalEntity.PortalSide side) { + Optional portalId = getPortalId(level.getServer(), owner, gunId, side, level.dimension().location()); if (portalId.isEmpty()) { return null; } + Entity entity = level.getEntity(portalId.get()); + return entity instanceof PortalGunPortalEntity portal && !portal.isRemoved() ? portal : null; + } + + public static PortalGunPortalEntity findLoadedPortal(ServerLevel level, UUID owner, UUID gunId, String channelName, PortalGunPortalEntity.PortalSide side) { + Optional portalId = getPortalId(level.getServer(), owner, gunId, channelName, side, level.dimension().location()); + if (portalId.isEmpty()) { + return null; + } + Entity entity = level.getEntity(portalId.get()); + return entity instanceof PortalGunPortalEntity portal && !portal.isRemoved() ? portal : null; + } + + private UUID resolveChannelGunId(UUID owner, String channelName, UUID gunId) { + if (channelName == null || channelName.isEmpty()) { + return gunId; + } + OwnerChannelKey key = new OwnerChannelKey(owner, channelName); + UUID existing = this.channelGunIds.get(key); + if (existing != null) { + return existing; + } + UUID channelGunId = UUID.nameUUIDFromBytes((owner + "\u0000" + channelName).getBytes(StandardCharsets.UTF_8)); + this.channelGunIds.put(key, channelGunId); + if (gunId != null && !gunId.equals(channelGunId)) { + this.migrateGunPairs(owner, gunId, channelGunId); + } + this.setDirty(); + return channelGunId; + } + + private void migrateGunPairs(UUID owner, UUID previousGunId, UUID channelGunId) { + for (Map.Entry entry : new HashMap<>(this.pairs).entrySet()) { + PairKey previousKey = entry.getKey(); + if (!previousKey.owner().equals(owner) || !previousGunId.equals(previousKey.gunId())) { + continue; + } + PairKey channelKey = new PairKey(owner, previousKey.dimension(), channelGunId); + PairRecord existing = this.pairs.get(channelKey); + PairRecord migrated = mergePairs(existing, entry.getValue()); + this.pairs.remove(previousKey); + this.pairs.put(channelKey, migrated); + } + } + + private static PairRecord mergePairs(PairRecord preferred, PairRecord fallback) { + if (preferred == null) { + return fallback; + } + return new PairRecord( + preferred.bluePortalId() == null ? fallback.bluePortalId() : preferred.bluePortalId(), + preferred.orangePortalId() == null ? fallback.orangePortalId() : preferred.orangePortalId() + ); + } + + private void migrateLegacyPair(MinecraftServer server, UUID owner) { + PairRecord legacy = this.legacyPairs.get(owner); + if (legacy == null) { + return; + } + ServerLevel blueLevel = findPortalLevel(server, legacy.bluePortalId()); + ServerLevel orangeLevel = findPortalLevel(server, legacy.orangePortalId()); + UUID legacyBlue = legacy.bluePortalId(); + UUID legacyOrange = legacy.orangePortalId(); + if (blueLevel != null) { + Entity blueEntity = blueLevel.getEntity(legacyBlue); + UUID blueGunId = blueEntity instanceof PortalGunPortalEntity portal ? portal.getGunId() : null; + PairKey key = new PairKey(owner, blueLevel.dimension().location(), blueGunId); + this.pairs.put(key, withPortal(this.pairs.get(key), PortalGunPortalEntity.PortalSide.BLUE, legacyBlue)); + legacyBlue = null; + } + if (orangeLevel != null) { + Entity orangeEntity = orangeLevel.getEntity(legacyOrange); + UUID orangeGunId = orangeEntity instanceof PortalGunPortalEntity portal ? portal.getGunId() : null; + PairKey key = new PairKey(owner, orangeLevel.dimension().location(), orangeGunId); + this.pairs.put(key, withPortal(this.pairs.get(key), PortalGunPortalEntity.PortalSide.ORANGE, legacyOrange)); + legacyOrange = null; + } + if (!Objects.equals(legacy.bluePortalId(), legacyBlue) || !Objects.equals(legacy.orangePortalId(), legacyOrange)) { + if (legacyBlue == null && legacyOrange == null) { + this.legacyPairs.remove(owner); + } else { + this.legacyPairs.put(owner, new PairRecord(legacyBlue, legacyOrange)); + } + this.setDirty(); + } + } + + private void adoptLegacyPairs(MinecraftServer server, UUID owner, UUID gunId, String channelName) { + if (gunId == null) { + return; + } + boolean changed = false; + for (Map.Entry entry : new HashMap<>(this.pairs).entrySet()) { + PairKey legacyKey = entry.getKey(); + if (!legacyKey.owner().equals(owner) || legacyKey.gunId() != null) { + continue; + } + PairKey ownedKey = new PairKey(owner, legacyKey.dimension(), gunId); + if (this.pairs.containsKey(ownedKey)) { + continue; + } + this.pairs.remove(legacyKey); + this.pairs.put(ownedKey, entry.getValue()); + synchronizeLoadedPortal(server, owner, legacyKey.dimension(), entry.getValue().bluePortalId(), gunId, channelName); + synchronizeLoadedPortal(server, owner, legacyKey.dimension(), entry.getValue().orangePortalId(), gunId, channelName); + changed = true; + } + if (changed) { + this.setDirty(); + } + } + + private static void synchronizeLoadedPortal(MinecraftServer server, UUID owner, ResourceLocation dimension, UUID portalId, UUID gunId, String channelName) { + if (portalId == null) { + return; + } + ServerLevel level = server.getLevel(net.minecraft.resources.ResourceKey.create(net.minecraft.core.registries.Registries.DIMENSION, dimension)); + if (level == null || !(level.getEntity(portalId) instanceof PortalGunPortalEntity portal) || !owner.equals(portal.getOwnerId())) { + return; + } + portal.adoptChannelIdentity(gunId, channelName); + } + + private static PairRecord withPortal(PairRecord current, PortalGunPortalEntity.PortalSide side, UUID portalId) { + PairRecord pair = current == null ? new PairRecord(null, null) : current; + return side == PortalGunPortalEntity.PortalSide.BLUE + ? new PairRecord(pair.bluePortalId() == null ? portalId : pair.bluePortalId(), pair.orangePortalId()) + : new PairRecord(pair.bluePortalId(), pair.orangePortalId() == null ? portalId : pair.orangePortalId()); + } + + private static ServerLevel findPortalLevel(MinecraftServer server, UUID portalId) { + if (portalId == null) { + return null; + } for (ServerLevel level : server.getAllLevels()) { - Entity entity = level.getEntity(portalId.get()); - if (entity instanceof PortalGunPortalEntity portal && !portal.isRemoved()) { - return portal; + if (level.getEntity(portalId) instanceof PortalGunPortalEntity portal && !portal.isRemoved()) { + return level; } } return null; } + private static void addPortalIds(Set portalIds, PairRecord pair) { + if (pair.bluePortalId() != null) { + portalIds.add(pair.bluePortalId()); + } + if (pair.orangePortalId() != null) { + portalIds.add(pair.orangePortalId()); + } + } + private static void discardIfLoaded(MinecraftServer server, UUID portalId) { - if (portalId == null) { - return; - } - for (ServerLevel level : server.getAllLevels()) { - if (level.getEntity(portalId) instanceof PortalGunPortalEntity portal) { - portal.discard(); - return; - } + ServerLevel level = findPortalLevel(server, portalId); + if (level != null && level.getEntity(portalId) instanceof PortalGunPortalEntity portal) { + portal.discard(); } } + private record PairKey(UUID owner, ResourceLocation dimension, UUID gunId) { + } + + private record OwnerChannelKey(UUID owner, String channelName) { + } + private record PairRecord(UUID bluePortalId, UUID orangePortalId) { } } diff --git a/common-1.21.1/src/main/java/com/craisinlord/antarchy/content/portalgun/PortalGunTransformUtil.java b/common-1.21.1/src/main/java/com/craisinlord/antarchy/content/portalgun/PortalGunTransformUtil.java index 3ad4ec8b..f6f69546 100644 --- a/common-1.21.1/src/main/java/com/craisinlord/antarchy/content/portalgun/PortalGunTransformUtil.java +++ b/common-1.21.1/src/main/java/com/craisinlord/antarchy/content/portalgun/PortalGunTransformUtil.java @@ -14,19 +14,19 @@ public final class PortalGunTransformUtil { } public static Quaternionf createTransform(PortalGunPortalEntity sourcePortal, PortalGunPortalEntity destinationPortal) { - return createTransform(sourcePortal.getWidthVec(), sourcePortal.getUpVec(), sourcePortal.getNormalVec().scale(-1.0D), destinationPortal.getWidthVec(), destinationPortal.getUpVec(), destinationPortal.getNormalVec()); + return createTransform(sourcePortal.getWidthVec(), sourcePortal.getUpVec(), sourcePortal.getNormalVec().scale(-1.0D), destinationPortal.getWidthVec().scale(-1.0D), destinationPortal.getUpVec(), destinationPortal.getNormalVec()); } public static Quaternionf createTransform(Vec3 sourceWidth, Vec3 sourceUp, Vec3 sourceForward, Vec3 destinationWidth, Vec3 destinationUp, Vec3 destinationForward) { Matrix3f sourceBasis = new Matrix3f( - (float) sourceWidth.x, (float) sourceUp.x, (float) sourceForward.x, - (float) sourceWidth.y, (float) sourceUp.y, (float) sourceForward.y, - (float) sourceWidth.z, (float) sourceUp.z, (float) sourceForward.z + (float) sourceWidth.x, (float) sourceWidth.y, (float) sourceWidth.z, + (float) sourceUp.x, (float) sourceUp.y, (float) sourceUp.z, + (float) sourceForward.x, (float) sourceForward.y, (float) sourceForward.z ); Matrix3f destinationBasis = new Matrix3f( - (float) destinationWidth.x, (float) destinationUp.x, (float) destinationForward.x, - (float) destinationWidth.y, (float) destinationUp.y, (float) destinationForward.y, - (float) destinationWidth.z, (float) destinationUp.z, (float) destinationForward.z + (float) destinationWidth.x, (float) destinationWidth.y, (float) destinationWidth.z, + (float) destinationUp.x, (float) destinationUp.y, (float) destinationUp.z, + (float) destinationForward.x, (float) destinationForward.y, (float) destinationForward.z ); Matrix3f transform = destinationBasis.mul(sourceBasis.transpose(new Matrix3f()), new Matrix3f()); return transform.getNormalizedRotation(new Quaternionf()); @@ -48,9 +48,9 @@ public final class PortalGunTransformUtil { Vec3 destinationWidth = destinationPortal.getWidthVec().normalize(); Vec3 destinationUp = destinationPortal.getUpVec().normalize(); Vec3 destinationNormal = destinationPortal.getNormalVec().normalize(); - return destinationWidth.scale(relativePosition.dot(sourceWidth)) + return destinationWidth.scale(-relativePosition.dot(sourceWidth)) .add(destinationUp.scale(relativePosition.dot(sourceUp))) - .add(destinationNormal.scale(relativePosition.dot(sourceNormal))); + .add(destinationNormal.scale(-relativePosition.dot(sourceNormal))); } public static Vec3 transformVector(PortalGunPortalEntity sourcePortal, PortalGunPortalEntity destinationPortal, Vec3 vector) { @@ -60,7 +60,7 @@ public final class PortalGunTransformUtil { Vec3 destinationWidth = destinationPortal.getWidthVec().normalize(); Vec3 destinationUp = destinationPortal.getUpVec().normalize(); Vec3 destinationOut = destinationPortal.getNormalVec().normalize(); - return destinationWidth.scale(vector.dot(sourceWidth)) + return destinationWidth.scale(-vector.dot(sourceWidth)) .add(destinationUp.scale(vector.dot(sourceUp))) .add(destinationOut.scale(vector.dot(sourceInto))); } @@ -102,11 +102,11 @@ public final class PortalGunTransformUtil { public static Quaternionf orientationQuaternion(Vec3 look, Vec3 up) { Vec3 forward = look.normalize(); Vec3 correctedUp = up.subtract(forward.scale(up.dot(forward))).normalize(); - Vec3 right = correctedUp.cross(forward).normalize(); + Vec3 right = forward.cross(correctedUp).normalize(); Matrix3f basis = new Matrix3f( - (float) right.x, (float) correctedUp.x, (float) (-forward.x), - (float) right.y, (float) correctedUp.y, (float) (-forward.y), - (float) right.z, (float) correctedUp.z, (float) (-forward.z) + (float) right.x, (float) right.y, (float) right.z, + (float) correctedUp.x, (float) correctedUp.y, (float) correctedUp.z, + (float) -forward.x, (float) -forward.y, (float) -forward.z ); return basis.getNormalizedRotation(new Quaternionf()); } diff --git a/common-1.21.1/src/main/java/com/craisinlord/antarchy/content/portalgun/PortalGunWorldPortalShape.java b/common-1.21.1/src/main/java/com/craisinlord/antarchy/content/portalgun/PortalGunWorldPortalShape.java index a8394276..89a043d3 100644 --- a/common-1.21.1/src/main/java/com/craisinlord/antarchy/content/portalgun/PortalGunWorldPortalShape.java +++ b/common-1.21.1/src/main/java/com/craisinlord/antarchy/content/portalgun/PortalGunWorldPortalShape.java @@ -35,6 +35,11 @@ public record PortalGunWorldPortalShape( return this.createPlaneAround(offset, this.halfDepth); } + public PortalGunWorldPortalShape scaleAperture(double scale) { + double safeScale = Math.max(0.0D, scale); + return new PortalGunWorldPortalShape(this.center, this.normal, this.up, this.right, this.halfWidth * safeScale, this.halfHeight * safeScale, this.halfDepth); + } + public Vec3[] getCorners(double normalOffset) { Vec3 center = this.center.add(this.normal.scale(normalOffset)); Vec3 width = this.right.scale(this.halfWidth); @@ -76,13 +81,7 @@ public record PortalGunWorldPortalShape( } public boolean shouldRenderFront(Vec3 cameraPos) { - PortalLocalCoords coords = this.localCoords(cameraPos); - if (coords.depth() <= -0.25D) { - return false; - } - double depthScale = Math.min(DEFAULT_SCAN_DISTANCE, Math.max(0.0D, coords.depth())); - return Math.abs(coords.horizontal()) <= this.halfWidth + depthScale - && Math.abs(coords.vertical()) <= this.halfHeight + depthScale; + return this.localCoords(cameraPos).depth() > 0.0D; } public boolean intersectsFront(AABB box, double tolerance) { @@ -128,8 +127,8 @@ public record PortalGunWorldPortalShape( double horizontalOuter = this.halfWidth + borderThickness; double verticalOuter = this.halfHeight + borderThickness; return List.of( - this.createLocalBox(-horizontalOuter, this.halfWidth, -verticalOuter, verticalOuter, -this.halfDepth, this.halfDepth), - this.createLocalBox(-this.halfWidth, horizontalOuter, -verticalOuter, verticalOuter, -this.halfDepth, this.halfDepth), + this.createLocalBox(-horizontalOuter, -this.halfWidth, -verticalOuter, verticalOuter, -this.halfDepth, this.halfDepth), + this.createLocalBox(this.halfWidth, horizontalOuter, -verticalOuter, verticalOuter, -this.halfDepth, this.halfDepth), this.createLocalBox(-this.halfWidth, this.halfWidth, -verticalOuter, -this.halfHeight, -this.halfDepth, this.halfDepth), this.createLocalBox(-this.halfWidth, this.halfWidth, this.halfHeight, verticalOuter, -this.halfDepth, this.halfDepth) ); diff --git a/common-1.21.1/src/main/java/com/craisinlord/antarchy/content/time/TimeDilationFieldEntity.java b/common-1.21.1/src/main/java/com/craisinlord/antarchy/content/time/TimeDilationFieldEntity.java index 0b5c166a..da939800 100644 --- a/common-1.21.1/src/main/java/com/craisinlord/antarchy/content/time/TimeDilationFieldEntity.java +++ b/common-1.21.1/src/main/java/com/craisinlord/antarchy/content/time/TimeDilationFieldEntity.java @@ -95,6 +95,12 @@ public class TimeDilationFieldEntity extends Entity { || this.anchorId != null && this.anchorId.equals(entity.getUUID())) { return false; } + if (this.ownerId != null && this.level() instanceof ServerLevel level + && level.getEntity(this.ownerId) instanceof QueenEntity + && entity instanceof LivingEntity living + && !com.craisinlord.antarchy.content.effect.RoyalEffectEligibility.canApplyDilated(living)) { + return false; + } if (this.chronosphere && !(entity instanceof LivingEntity || entity instanceof Projectile || entity instanceof ItemEntity)) { return false; } diff --git a/common-1.21.1/src/main/java/com/craisinlord/antarchy/content/time/TimeDilationManager.java b/common-1.21.1/src/main/java/com/craisinlord/antarchy/content/time/TimeDilationManager.java index 5663c71b..8f4eed34 100644 --- a/common-1.21.1/src/main/java/com/craisinlord/antarchy/content/time/TimeDilationManager.java +++ b/common-1.21.1/src/main/java/com/craisinlord/antarchy/content/time/TimeDilationManager.java @@ -146,8 +146,7 @@ public final class TimeDilationManager { if (!(entity instanceof TimeDilationEntityAccess access) || entity instanceof TimeDilationFieldEntity) { continue; } - if (entity.getType().is(com.craisinlord.antarchy.content.AntarchyTags.Entities.TIME_DILATION_IMMUNE) - || (entity instanceof net.minecraft.world.entity.LivingEntity living && !RoyalEffectEligibility.canApplyDilated(living))) { + if (entity.getType().is(com.craisinlord.antarchy.content.AntarchyTags.Entities.TIME_DILATION_IMMUNE)) { if (RoyalEffectHooks.dilatedHolder() != null && entity instanceof net.minecraft.world.entity.LivingEntity living) { living.removeEffect(RoyalEffectHooks.dilatedHolder()); } diff --git a/common-1.21.1/src/main/java/com/craisinlord/antarchy/content/worldgen/thoraxis/ThoraxisBiomeSource.java b/common-1.21.1/src/main/java/com/craisinlord/antarchy/content/worldgen/thoraxis/ThoraxisBiomeSource.java index 18ec058a..91f382c0 100644 --- a/common-1.21.1/src/main/java/com/craisinlord/antarchy/content/worldgen/thoraxis/ThoraxisBiomeSource.java +++ b/common-1.21.1/src/main/java/com/craisinlord/antarchy/content/worldgen/thoraxis/ThoraxisBiomeSource.java @@ -2,9 +2,11 @@ package com.craisinlord.antarchy.content.worldgen.thoraxis; import com.craisinlord.antarchy.Antarchy; import com.mojang.serialization.Codec; +import com.mojang.serialization.DataResult; import com.mojang.serialization.MapCodec; import com.mojang.serialization.codecs.RecordCodecBuilder; import it.unimi.dsi.fastutil.longs.Long2DoubleOpenHashMap; +import java.util.List; import java.util.stream.Stream; import net.minecraft.core.BlockPos; import net.minecraft.core.Holder; @@ -56,7 +58,8 @@ public final class ThoraxisBiomeSource extends BiomeSource { Codec.INT.optionalFieldOf("dream_dunes_max_y", 160).forGetter(ThoraxisBiomeSource::dreamDunesMaxY), Codec.INT.optionalFieldOf("umbral_hills_max_y", 160).forGetter(ThoraxisBiomeSource::umbralHillsMaxY), Codec.INT.optionalFieldOf("lucid_pools_min_y", 116).forGetter(ThoraxisBiomeSource::lucidPoolsMinY), - Codec.INT.optionalFieldOf("nadir_forest_min_y", 128).forGetter(ThoraxisBiomeSource::nadirForestMinY) + Codec.INT.optionalFieldOf("nadir_forest_min_y", 128).forGetter(ThoraxisBiomeSource::nadirForestMinY), + RegionalBiomeOverlay.CODEC.listOf().optionalFieldOf("regional_overlays", List.of()).forGetter(ThoraxisBiomeSource::regionalOverlays) ).apply(instance, ThoraxisBiomeSource::new)); private final Climate.ParameterList> parameters; @@ -65,6 +68,7 @@ public final class ThoraxisBiomeSource extends BiomeSource { private final int umbralHillsMaxY; private final int lucidPoolsMinY; private final int nadirForestMinY; + private final List regionalOverlays; private final int dreamDunesMaxQuartY; private final int umbralHillsMaxQuartY; private final int lucidPoolsMinQuartY; @@ -76,19 +80,21 @@ public final class ThoraxisBiomeSource extends BiomeSource { private Holder umbralHillsBiome; private Holder lucidPoolsBiome; private Holder nadirForestBiome; + private List resolvedRegionalOverlays = List.of(); private static final ThreadLocal REGION_CACHE = ThreadLocal.withInitial(() -> new Long2DoubleOpenHashMap(512)); private static final ThreadLocal VERTICAL_CACHE = ThreadLocal.withInitial(() -> new Long2DoubleOpenHashMap(64)); - public ThoraxisBiomeSource(Climate.ParameterList> parameters, int dreamDunesMaxY, int umbralHillsMaxY, int lucidPoolsMinY, int nadirForestMinY) { + public ThoraxisBiomeSource(Climate.ParameterList> parameters, int dreamDunesMaxY, int umbralHillsMaxY, int lucidPoolsMinY, int nadirForestMinY, List regionalOverlays) { this.parameters = parameters; this.delegate = MultiNoiseBiomeSource.createFromList(parameters); this.dreamDunesMaxY = dreamDunesMaxY; this.umbralHillsMaxY = umbralHillsMaxY; this.lucidPoolsMinY = lucidPoolsMinY; this.nadirForestMinY = nadirForestMinY; + this.regionalOverlays = regionalOverlays; this.dreamDunesMaxQuartY = QuartPos.fromBlock(dreamDunesMaxY); this.umbralHillsMaxQuartY = QuartPos.fromBlock(umbralHillsMaxY); this.lucidPoolsMinQuartY = QuartPos.fromBlock(lucidPoolsMinY); @@ -109,6 +115,7 @@ public final class ThoraxisBiomeSource extends BiomeSource { this.umbralHillsBiome = this.findBiome(UMBRAL_HILLS); this.lucidPoolsBiome = this.findBiome(LUCID_POOLS); this.nadirForestBiome = this.findBiome(NADIR_FOREST); + this.resolvedRegionalOverlays = this.resolveRegionalOverlays(); this.biomesResolved = true; } } @@ -133,6 +140,10 @@ public final class ThoraxisBiomeSource extends BiomeSource { return this.umbralHillsMaxY; } + private List regionalOverlays() { + return this.regionalOverlays; + } + @Override protected MapCodec codec() { return CODEC; @@ -179,6 +190,12 @@ public final class ThoraxisBiomeSource extends BiomeSource { return this.umbralHillsBiome; } + for (ResolvedRegionalBiomeOverlay overlay : this.resolvedRegionalOverlays) { + if (region >= overlay.minNoise() && region < overlay.maxNoise()) { + return overlay.biome(); + } + } + Holder delegateBiome = this.delegate.getNoiseBiome(x, y, z, sampler); return this.nightmareWastesBiome != null ? this.nightmareWastesBiome : delegateBiome; } @@ -198,6 +215,37 @@ public final class ThoraxisBiomeSource extends BiomeSource { .orElse(null); } + private Holder findBiome(ResourceLocation location) { + ResourceKey key = ResourceKey.create(Registries.BIOME, location); + return this.findBiome(key); + } + + private List resolveRegionalOverlays() { + return this.regionalOverlays.stream().map(overlay -> { + Holder biome = this.findBiome(overlay.biome()); + if (biome == null) { + throw new IllegalStateException("Thoraxis regional overlay biome " + overlay.biome() + " must be listed in the biome source parameters"); + } + return new ResolvedRegionalBiomeOverlay(biome, overlay.minNoise(), overlay.maxNoise()); + }).toList(); + } + + private record RegionalBiomeOverlay(ResourceLocation biome, double minNoise, double maxNoise) { + private static final Codec CODEC = RecordCodecBuilder.create(instance -> instance.group( + ResourceLocation.CODEC.fieldOf("biome").forGetter(RegionalBiomeOverlay::biome), + Codec.DOUBLE.fieldOf("min_noise").forGetter(RegionalBiomeOverlay::minNoise), + Codec.DOUBLE.fieldOf("max_noise").forGetter(RegionalBiomeOverlay::maxNoise) + ).apply(instance, RegionalBiomeOverlay::new)).flatXmap( + overlay -> overlay.minNoise() >= 0.0D && overlay.maxNoise() <= 1.0D && overlay.minNoise() < overlay.maxNoise() + ? DataResult.success(overlay) + : DataResult.error(() -> "Regional biome overlay noise bounds must satisfy 0 <= min_noise < max_noise <= 1"), + DataResult::success + ); + } + + private record ResolvedRegionalBiomeOverlay(Holder biome, double minNoise, double maxNoise) { + } + private double cachedRegionNoise(int quartX, int quartZ) { long key = net.minecraft.world.level.ChunkPos.asLong(quartX, quartZ); Long2DoubleOpenHashMap cache = REGION_CACHE.get(); 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 6d5570af..34ae51ba 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 @@ -1,5 +1,6 @@ package com.craisinlord.antarchy.mixins; +import com.craisinlord.antarchy.content.client.renderer.SodiumCompat; import java.util.List; import java.util.Set; import org.objectweb.asm.tree.ClassNode; @@ -28,6 +29,17 @@ public final class AntarchyMixinPlugin implements IMixinConfigPlugin { if (!classPresent(targetClassName)) { return false; } + if (SodiumCompat.isLoaded() + && (mixinClassName.endsWith(".LevelRendererPortalViewAreaAccessor") + || mixinClassName.endsWith(".LevelRendererPortalViewAreaMixin") + || mixinClassName.endsWith(".LevelRendererPortalChunkBudgetMixin") + || mixinClassName.endsWith(".PortalViewChunkCacheMixin") + || mixinClassName.endsWith(".PortalViewSectionSortingMixin"))) { + return false; + } + if (mixinClassName.endsWith(".SodiumWorldRendererMixin")) { + return SodiumCompat.isLoaded(); + } if (mixinClassName.startsWith("com.craisinlord.antarchy.mixins.compat.scguns.")) { return classPresent("top.ribs.scguns.entity.projectile.ProjectileEntity"); } diff --git a/common-1.21.1/src/main/java/com/craisinlord/antarchy/mixins/FallingBlockEntityAccessor.java b/common-1.21.1/src/main/java/com/craisinlord/antarchy/mixins/FallingBlockEntityAccessor.java new file mode 100644 index 00000000..06836788 --- /dev/null +++ b/common-1.21.1/src/main/java/com/craisinlord/antarchy/mixins/FallingBlockEntityAccessor.java @@ -0,0 +1,11 @@ +package com.craisinlord.antarchy.mixins; + +import net.minecraft.world.entity.item.FallingBlockEntity; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.gen.Accessor; + +@Mixin(FallingBlockEntity.class) +public interface FallingBlockEntityAccessor { + @Accessor("time") + void antarchy$setTime(int time); +} diff --git a/common-1.21.1/src/main/java/com/craisinlord/antarchy/mixins/GrindstoneMenuMixin.java b/common-1.21.1/src/main/java/com/craisinlord/antarchy/mixins/GrindstoneMenuMixin.java index 104f118d..428af84a 100644 --- a/common-1.21.1/src/main/java/com/craisinlord/antarchy/mixins/GrindstoneMenuMixin.java +++ b/common-1.21.1/src/main/java/com/craisinlord/antarchy/mixins/GrindstoneMenuMixin.java @@ -1,6 +1,7 @@ package com.craisinlord.antarchy.mixins; import com.craisinlord.antarchy.content.item.InnateEnchantmentHelper; +import com.craisinlord.antarchy.content.item.GiantFryingPanItem; import net.minecraft.world.inventory.GrindstoneMenu; import net.minecraft.world.item.ItemStack; import org.spongepowered.asm.mixin.Mixin; @@ -19,6 +20,11 @@ public abstract class GrindstoneMenuMixin { @Inject(method = "computeResult", at = @At("RETURN"), cancellable = true) private void antarchy$blockInnateOnlyNoOp(ItemStack firstInput, ItemStack secondInput, CallbackInfoReturnable cir) { + if (firstInput.getItem() instanceof GiantFryingPanItem || secondInput.getItem() instanceof GiantFryingPanItem) { + cir.setReturnValue(ItemStack.EMPTY); + return; + } + if (!firstInput.isEmpty() && secondInput.isEmpty() && InnateEnchantmentHelper.hasInnateNonCurseEnchantments(firstInput) && !InnateEnchantmentHelper.hasRemovableNonCurseEnchantments(firstInput)) { diff --git a/common-1.21.1/src/main/java/com/craisinlord/antarchy/mixins/PortalGunEntityCollisionMixin.java b/common-1.21.1/src/main/java/com/craisinlord/antarchy/mixins/PortalGunEntityCollisionMixin.java index f5fa8a2e..fadcd00e 100644 --- a/common-1.21.1/src/main/java/com/craisinlord/antarchy/mixins/PortalGunEntityCollisionMixin.java +++ b/common-1.21.1/src/main/java/com/craisinlord/antarchy/mixins/PortalGunEntityCollisionMixin.java @@ -1,7 +1,7 @@ package com.craisinlord.antarchy.mixins; -import com.craisinlord.antarchy.content.gravity.AntarchyGravityApi; import com.craisinlord.antarchy.content.portalgun.PortalGunCollisionHelper; +import com.craisinlord.antarchy.content.portalgun.PortalGunPortalEntity; import net.minecraft.world.entity.Entity; import net.minecraft.world.phys.AABB; import net.minecraft.world.phys.Vec3; @@ -10,11 +10,37 @@ 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.CallbackInfoReturnable; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; @Mixin(Entity.class) public abstract class PortalGunEntityCollisionMixin { @Unique private AABB antarchy$portalGunStartBox; + @Unique + private AABB antarchy$portalGunMoveStartBox; + @Unique + private Vec3 antarchy$portalGunMoveStartPosition; + + @Inject(method = "move", at = @At("HEAD")) + private void antarchy$capturePortalGunMovementStart(net.minecraft.world.entity.MoverType type, Vec3 movement, CallbackInfo ci) { + Entity entity = (Entity) (Object) this; + this.antarchy$portalGunMoveStartBox = entity.getBoundingBox(); + this.antarchy$portalGunMoveStartPosition = entity.position(); + } + + @Inject(method = "move", at = @At("RETURN")) + private void antarchy$teleportMobOnPortalCrossing(net.minecraft.world.entity.MoverType type, Vec3 movement, CallbackInfo ci) { + Entity entity = (Entity) (Object) this; + if (this.antarchy$portalGunMoveStartBox != null && this.antarchy$portalGunMoveStartPosition != null) { + PortalGunPortalEntity.teleportEntityAfterMovement( + entity, + this.antarchy$portalGunMoveStartBox, + entity.position().subtract(this.antarchy$portalGunMoveStartPosition) + ); + } + this.antarchy$portalGunMoveStartBox = null; + this.antarchy$portalGunMoveStartPosition = null; + } @Inject(method = "collide", at = @At("HEAD")) private void antarchy$capturePortalGunStartBox(Vec3 movement, CallbackInfoReturnable cir) { @@ -24,10 +50,11 @@ public abstract class PortalGunEntityCollisionMixin { @Inject(method = "collide", at = @At("RETURN"), cancellable = true) private void antarchy$restorePortalCrossingMotion(Vec3 movement, CallbackInfoReturnable cir) { Entity entity = (Entity) (Object) this; - if (this.antarchy$portalGunStartBox == null || AntarchyGravityApi.isGravityInverted(entity)) { + if (this.antarchy$portalGunStartBox == null) { return; } Vec3 adjusted = PortalGunCollisionHelper.resolveCollision(entity, this.antarchy$portalGunStartBox, movement, cir.getReturnValue()); cir.setReturnValue(adjusted); } + } diff --git a/common-1.21.1/src/main/java/com/craisinlord/antarchy/mixins/PortalGunZombieMixin.java b/common-1.21.1/src/main/java/com/craisinlord/antarchy/mixins/PortalGunZombieMixin.java new file mode 100644 index 00000000..bc44a124 --- /dev/null +++ b/common-1.21.1/src/main/java/com/craisinlord/antarchy/mixins/PortalGunZombieMixin.java @@ -0,0 +1,29 @@ +package com.craisinlord.antarchy.mixins; + +import com.craisinlord.antarchy.content.item.PortalGunItem; +import com.craisinlord.antarchy.content.portalgun.PortalGunPortalEntity; +import net.minecraft.server.level.ServerLevel; +import net.minecraft.world.entity.monster.Zombie; +import net.minecraft.world.item.ItemStack; +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(Zombie.class) +public abstract class PortalGunZombieMixin { + @Inject(method = "tick", at = @At("TAIL")) + private void antarchy$firePortalGun(CallbackInfo ci) { + Zombie zombie = (Zombie) (Object) this; + if (!(zombie.level() instanceof ServerLevel serverLevel)) { + return; + } + ItemStack stack = zombie.getMainHandItem(); + if (stack.getItem() instanceof PortalGunItem portalGun && zombie.getRandom().nextFloat() < 0.008F) { + PortalGunPortalEntity.PortalSide side = zombie.getRandom().nextBoolean() + ? PortalGunPortalEntity.PortalSide.BLUE + : PortalGunPortalEntity.PortalSide.ORANGE; + portalGun.fireMobPortal(serverLevel, zombie, stack, side); + } + } +} diff --git a/common-1.21.1/src/main/java/com/craisinlord/antarchy/mixins/client/CameraBasisAccessor.java b/common-1.21.1/src/main/java/com/craisinlord/antarchy/mixins/client/CameraBasisAccessor.java new file mode 100644 index 00000000..abfce3eb --- /dev/null +++ b/common-1.21.1/src/main/java/com/craisinlord/antarchy/mixins/client/CameraBasisAccessor.java @@ -0,0 +1,18 @@ +package com.craisinlord.antarchy.mixins.client; + +import net.minecraft.client.Camera; +import org.joml.Vector3f; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.gen.Accessor; + +@Mixin(Camera.class) +public interface CameraBasisAccessor { + @Accessor("forwards") + Vector3f antarchy$getForwards(); + + @Accessor("up") + Vector3f antarchy$getUp(); + + @Accessor("left") + Vector3f antarchy$getLeft(); +} diff --git a/common-1.21.1/src/main/java/com/craisinlord/antarchy/mixins/client/LevelRendererPortalChunkBudgetMixin.java b/common-1.21.1/src/main/java/com/craisinlord/antarchy/mixins/client/LevelRendererPortalChunkBudgetMixin.java new file mode 100644 index 00000000..55218df1 --- /dev/null +++ b/common-1.21.1/src/main/java/com/craisinlord/antarchy/mixins/client/LevelRendererPortalChunkBudgetMixin.java @@ -0,0 +1,65 @@ +package com.craisinlord.antarchy.mixins.client; + +import com.craisinlord.antarchy.content.client.renderer.PortalGunPortalRendererPool; +import net.minecraft.client.multiplayer.ClientLevel; +import net.minecraft.client.renderer.LevelRenderer; +import net.minecraft.client.renderer.chunk.RenderRegionCache; +import net.minecraft.client.renderer.chunk.SectionRenderDispatcher; +import net.minecraft.world.level.lighting.LevelLightEngine; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Redirect; + +@Mixin(LevelRenderer.class) +public abstract class LevelRendererPortalChunkBudgetMixin { + @Redirect( + method = "renderLevel", + at = @At(value = "INVOKE", target = "Lnet/minecraft/client/multiplayer/ClientLevel;pollLightUpdates()V") + ) + private void antarchy$skipProxyLightQueuePoll(ClientLevel level) { + if (!PortalGunPortalRendererPool.isProxy((LevelRenderer) (Object) this)) { + level.pollLightUpdates(); + } + } + + @Redirect( + method = "renderLevel", + at = @At(value = "INVOKE", target = "Lnet/minecraft/world/level/lighting/LevelLightEngine;runLightUpdates()I") + ) + private int antarchy$skipProxyLightQueueDrain(LevelLightEngine lightEngine) { + return PortalGunPortalRendererPool.isProxy((LevelRenderer) (Object) this) ? 0 : lightEngine.runLightUpdates(); + } + + @Redirect( + method = "compileSections", + at = @At( + value = "INVOKE", + target = "Lnet/minecraft/client/renderer/chunk/SectionRenderDispatcher;rebuildSectionSync(Lnet/minecraft/client/renderer/chunk/SectionRenderDispatcher$RenderSection;Lnet/minecraft/client/renderer/chunk/RenderRegionCache;)V" + ) + ) + private void antarchy$queuePortalSyncRebuilds(SectionRenderDispatcher dispatcher, SectionRenderDispatcher.RenderSection section, RenderRegionCache cache) { + PortalGunPortalRendererPool.rebuildSection((LevelRenderer) (Object) this, dispatcher, section, cache); + } + + @Redirect( + method = "compileSections", + at = @At( + value = "INVOKE", + target = "Lnet/minecraft/client/renderer/chunk/SectionRenderDispatcher$RenderSection;rebuildSectionAsync(Lnet/minecraft/client/renderer/chunk/SectionRenderDispatcher;Lnet/minecraft/client/renderer/chunk/RenderRegionCache;)V" + ) + ) + private void antarchy$budgetPortalAsyncRebuilds(SectionRenderDispatcher.RenderSection section, SectionRenderDispatcher dispatcher, RenderRegionCache cache) { + PortalGunPortalRendererPool.rebuildSectionAsync((LevelRenderer) (Object) this, dispatcher, section, cache); + } + + @Redirect( + method = "compileSections", + at = @At( + value = "INVOKE", + target = "Lnet/minecraft/client/renderer/chunk/SectionRenderDispatcher$RenderSection;setNotDirty()V" + ) + ) + private void antarchy$keepUnscheduledPortalSectionsDirty(SectionRenderDispatcher.RenderSection section) { + PortalGunPortalRendererPool.setNotDirty(section); + } +} diff --git a/common-1.21.1/src/main/java/com/craisinlord/antarchy/mixins/client/LevelRendererPortalLifecycleMixin.java b/common-1.21.1/src/main/java/com/craisinlord/antarchy/mixins/client/LevelRendererPortalLifecycleMixin.java new file mode 100644 index 00000000..19921874 --- /dev/null +++ b/common-1.21.1/src/main/java/com/craisinlord/antarchy/mixins/client/LevelRendererPortalLifecycleMixin.java @@ -0,0 +1,55 @@ +package com.craisinlord.antarchy.mixins.client; + +import com.craisinlord.antarchy.content.client.PortalGunPortalRenderState; +import com.craisinlord.antarchy.content.client.renderer.PortalGunPortalViewAreaManager; +import com.craisinlord.antarchy.content.client.renderer.PortalGunPortalRendererPool; +import com.craisinlord.antarchy.content.client.renderer.PortalGunPortalViewRenderer; +import com.craisinlord.antarchy.content.client.renderer.PortalStencilTarget; +import net.minecraft.client.Minecraft; +import net.minecraft.client.multiplayer.ClientLevel; +import net.minecraft.client.renderer.LevelRenderer; +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(LevelRenderer.class) +public abstract class LevelRendererPortalLifecycleMixin { + @Inject(method = "renderLevel", at = @At("HEAD")) + private void antarchy$preparePortalStencil(CallbackInfo ci) { + if (!PortalGunPortalRendererPool.isProxy((LevelRenderer) (Object) this)) { + PortalStencilTarget.prepareMainTarget(Minecraft.getInstance().getMainRenderTarget()); + } + } + + @Inject(method = "setLevel", at = @At("HEAD")) + private void antarchy$clearPortalViewsOnLevelChange(ClientLevel level, CallbackInfo ci) { + if (PortalGunPortalRendererPool.isProxy((LevelRenderer) (Object) this)) { + return; + } + PortalGunPortalRendererPool.clear(); + PortalGunPortalViewAreaManager.clear(); + } + + @Inject(method = "close", at = @At("HEAD")) + private void antarchy$clearPortalViewsOnClose(CallbackInfo ci) { + if (PortalGunPortalRendererPool.isProxy((LevelRenderer) (Object) this)) { + return; + } + PortalGunPortalRendererPool.clear(); + PortalGunPortalViewAreaManager.clear(); + PortalGunPortalViewRenderer.releaseTargets(); + } + + @Inject(method = "allChanged", at = @At("TAIL")) + private void antarchy$clearPortalViewsOnRendererRebuild(CallbackInfo ci) { + if (PortalGunPortalRendererPool.isProxy((LevelRenderer) (Object) this)) { + return; + } + if (PortalGunPortalRenderState.getContext() == null) { + PortalGunPortalRendererPool.clear(); + PortalGunPortalViewAreaManager.clear(); + PortalGunPortalViewRenderer.releaseTargets(); + } + } +} diff --git a/common-1.21.1/src/main/java/com/craisinlord/antarchy/mixins/client/LevelRendererPortalSceneInvoker.java b/common-1.21.1/src/main/java/com/craisinlord/antarchy/mixins/client/LevelRendererPortalSceneInvoker.java new file mode 100644 index 00000000..7789063a --- /dev/null +++ b/common-1.21.1/src/main/java/com/craisinlord/antarchy/mixins/client/LevelRendererPortalSceneInvoker.java @@ -0,0 +1,34 @@ +package com.craisinlord.antarchy.mixins.client; + +import com.mojang.blaze3d.vertex.PoseStack; +import net.minecraft.client.Camera; +import net.minecraft.client.renderer.culling.Frustum; +import net.minecraft.client.renderer.LevelRenderer; +import net.minecraft.client.renderer.RenderType; +import net.minecraft.client.renderer.MultiBufferSource; +import net.minecraft.client.renderer.LightTexture; +import net.minecraft.world.entity.Entity; +import org.joml.Matrix4f; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.gen.Invoker; + +@Mixin(LevelRenderer.class) +public interface LevelRendererPortalSceneInvoker { + @Invoker("setupRender") + void antarchy$setupPortalScene(Camera camera, Frustum frustum, boolean capturedFrustum, boolean spectator); + + @Invoker("compileSections") + void antarchy$compilePortalSections(Camera camera); + + @Invoker("renderSectionLayer") + void antarchy$renderPortalSectionLayer(RenderType renderType, double cameraX, double cameraY, double cameraZ, Matrix4f modelViewMatrix, Matrix4f projectionMatrix); + + @Invoker("renderEntity") + void antarchy$renderPortalEntity(Entity entity, double cameraX, double cameraY, double cameraZ, float partialTick, PoseStack poseStack, MultiBufferSource bufferSource); + + @Invoker("renderSnowAndRain") + void antarchy$renderPortalWeather(LightTexture lightTexture, float partialTick, double cameraX, double cameraY, double cameraZ); + + @Invoker("renderWorldBorder") + void antarchy$renderPortalWorldBorder(Camera camera); +} diff --git a/common-1.21.1/src/main/java/com/craisinlord/antarchy/mixins/client/LevelRendererPortalTargetsAccessor.java b/common-1.21.1/src/main/java/com/craisinlord/antarchy/mixins/client/LevelRendererPortalTargetsAccessor.java new file mode 100644 index 00000000..c76d4d1f --- /dev/null +++ b/common-1.21.1/src/main/java/com/craisinlord/antarchy/mixins/client/LevelRendererPortalTargetsAccessor.java @@ -0,0 +1,15 @@ +package com.craisinlord.antarchy.mixins.client; + +import net.minecraft.client.renderer.LevelRenderer; +import net.minecraft.client.renderer.PostChain; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.gen.Accessor; + +@Mixin(LevelRenderer.class) +public interface LevelRendererPortalTargetsAccessor { + @Accessor("entityEffect") + PostChain antarchy$getEntityEffect(); + + @Accessor("transparencyChain") + PostChain antarchy$getTransparencyChain(); +} diff --git a/common-1.21.1/src/main/java/com/craisinlord/antarchy/mixins/client/LevelRendererPortalViewAreaAccessor.java b/common-1.21.1/src/main/java/com/craisinlord/antarchy/mixins/client/LevelRendererPortalViewAreaAccessor.java new file mode 100644 index 00000000..9edbabed --- /dev/null +++ b/common-1.21.1/src/main/java/com/craisinlord/antarchy/mixins/client/LevelRendererPortalViewAreaAccessor.java @@ -0,0 +1,160 @@ +package com.craisinlord.antarchy.mixins.client; + +import it.unimi.dsi.fastutil.objects.ObjectArrayList; +import com.mojang.blaze3d.vertex.VertexBuffer; +import net.minecraft.client.CloudStatus; +import net.minecraft.client.renderer.LevelRenderer; +import net.minecraft.client.renderer.SectionOcclusionGraph; +import net.minecraft.client.renderer.ViewArea; +import net.minecraft.client.renderer.chunk.SectionRenderDispatcher; +import net.minecraft.world.level.block.entity.BlockEntity; +import net.minecraft.world.phys.Vec3; +import java.util.Set; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Mutable; +import org.spongepowered.asm.mixin.gen.Accessor; + +@Mixin(LevelRenderer.class) +public interface LevelRendererPortalViewAreaAccessor { + @Accessor("viewArea") + ViewArea antarchy$getViewArea(); + + @Accessor("viewArea") + void antarchy$setViewArea(ViewArea viewArea); + + @Accessor("sectionOcclusionGraph") + SectionOcclusionGraph antarchy$getSectionOcclusionGraph(); + + @Mutable + @Accessor("sectionOcclusionGraph") + void antarchy$setSectionOcclusionGraph(SectionOcclusionGraph graph); + + @Accessor("visibleSections") + ObjectArrayList antarchy$getVisibleSections(); + + @Mutable + @Accessor("visibleSections") + void antarchy$setVisibleSections(ObjectArrayList sections); + + @Accessor("globalBlockEntities") + Set antarchy$getGlobalBlockEntities(); + + @Mutable + @Accessor("globalBlockEntities") + void antarchy$setGlobalBlockEntities(Set blockEntities); + + @Accessor("lastCameraSectionX") + int antarchy$getLastCameraSectionX(); + + @Accessor("lastCameraSectionX") + void antarchy$setLastCameraSectionX(int value); + + @Accessor("lastCameraSectionY") + int antarchy$getLastCameraSectionY(); + + @Accessor("lastCameraSectionY") + void antarchy$setLastCameraSectionY(int value); + + @Accessor("lastCameraSectionZ") + int antarchy$getLastCameraSectionZ(); + + @Accessor("lastCameraSectionZ") + void antarchy$setLastCameraSectionZ(int value); + + @Accessor("prevCamX") + double antarchy$getPrevCamX(); + + @Accessor("prevCamX") + void antarchy$setPrevCamX(double value); + + @Accessor("prevCamY") + double antarchy$getPrevCamY(); + + @Accessor("prevCamY") + void antarchy$setPrevCamY(double value); + + @Accessor("prevCamZ") + double antarchy$getPrevCamZ(); + + @Accessor("prevCamZ") + void antarchy$setPrevCamZ(double value); + + @Accessor("prevCamRotX") + double antarchy$getPrevCamRotX(); + + @Accessor("prevCamRotX") + void antarchy$setPrevCamRotX(double value); + + @Accessor("prevCamRotY") + double antarchy$getPrevCamRotY(); + + @Accessor("prevCamRotY") + void antarchy$setPrevCamRotY(double value); + + @Accessor("xTransparentOld") + double antarchy$getTransparentSortX(); + + @Accessor("xTransparentOld") + void antarchy$setTransparentSortX(double value); + + @Accessor("yTransparentOld") + double antarchy$getTransparentSortY(); + + @Accessor("yTransparentOld") + void antarchy$setTransparentSortY(double value); + + @Accessor("zTransparentOld") + double antarchy$getTransparentSortZ(); + + @Accessor("zTransparentOld") + void antarchy$setTransparentSortZ(double value); + + @Accessor("cloudBuffer") + VertexBuffer antarchy$getCloudBuffer(); + + @Accessor("cloudBuffer") + void antarchy$setCloudBuffer(VertexBuffer cloudBuffer); + + @Accessor("generateClouds") + boolean antarchy$getGenerateClouds(); + + @Accessor("generateClouds") + void antarchy$setGenerateClouds(boolean generateClouds); + + @Accessor("prevCloudX") + int antarchy$getPrevCloudX(); + + @Accessor("prevCloudX") + void antarchy$setPrevCloudX(int value); + + @Accessor("prevCloudY") + int antarchy$getPrevCloudY(); + + @Accessor("prevCloudY") + void antarchy$setPrevCloudY(int value); + + @Accessor("prevCloudZ") + int antarchy$getPrevCloudZ(); + + @Accessor("prevCloudZ") + void antarchy$setPrevCloudZ(int value); + + @Accessor("prevCloudColor") + Vec3 antarchy$getPrevCloudColor(); + + @Accessor("prevCloudColor") + void antarchy$setPrevCloudColor(Vec3 value); + + @Accessor("prevCloudsType") + CloudStatus antarchy$getPrevCloudsType(); + + @Accessor("prevCloudsType") + void antarchy$setPrevCloudsType(CloudStatus value); + + @Accessor("rainSoundTime") + int antarchy$getRainSoundTime(); + + @Accessor("rainSoundTime") + void antarchy$setRainSoundTime(int value); + +} diff --git a/common-1.21.1/src/main/java/com/craisinlord/antarchy/mixins/client/LevelRendererPortalViewAreaMixin.java b/common-1.21.1/src/main/java/com/craisinlord/antarchy/mixins/client/LevelRendererPortalViewAreaMixin.java new file mode 100644 index 00000000..f150fc06 --- /dev/null +++ b/common-1.21.1/src/main/java/com/craisinlord/antarchy/mixins/client/LevelRendererPortalViewAreaMixin.java @@ -0,0 +1,63 @@ +package com.craisinlord.antarchy.mixins.client; + +import com.craisinlord.antarchy.content.client.renderer.PortalGunPortalViewAreaManager; +import com.craisinlord.antarchy.content.client.PortalGunPortalRenderState; +import com.llamalad7.mixinextras.injector.ModifyExpressionValue; +import net.minecraft.client.multiplayer.ClientLevel; +import net.minecraft.client.renderer.LevelRenderer; +import net.minecraft.client.renderer.chunk.SectionRenderDispatcher; +import net.minecraft.core.BlockPos; +import net.minecraft.sounds.SoundEvent; +import net.minecraft.sounds.SoundSource; +import net.minecraft.world.level.ChunkPos; +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.Redirect; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; + +@Mixin(LevelRenderer.class) +public abstract class LevelRendererPortalViewAreaMixin { + @Redirect(method = "renderSnowAndRain", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/multiplayer/ClientLevel;playLocalSound(Lnet/minecraft/core/BlockPos;Lnet/minecraft/sounds/SoundEvent;Lnet/minecraft/sounds/SoundSource;FFZ)V"), require = 0) + private void antarchy$suppressPortalWeatherSound(ClientLevel level, BlockPos pos, SoundEvent sound, SoundSource source, float volume, float pitch, boolean distanceDelay) { + if (PortalGunPortalRenderState.getContext() == null) { + level.playLocalSound(pos, sound, source, volume, pitch, distanceDelay); + } + } + + @ModifyExpressionValue(method = "setupRender", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/player/LocalPlayer;getX()D"), require = 0) + private double antarchy$usePortalCameraX(double original) { + PortalGunPortalRenderState.PortalRenderContext context = PortalGunPortalRenderState.getContext(); + return context == null ? original : context.cameraPos().x; + } + + @ModifyExpressionValue(method = "setupRender", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/player/LocalPlayer;getY()D"), require = 0) + private double antarchy$usePortalCameraY(double original) { + PortalGunPortalRenderState.PortalRenderContext context = PortalGunPortalRenderState.getContext(); + return context == null ? original : context.cameraPos().y; + } + + @ModifyExpressionValue(method = "setupRender", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/player/LocalPlayer;getZ()D"), require = 0) + private double antarchy$usePortalCameraZ(double original) { + PortalGunPortalRenderState.PortalRenderContext context = PortalGunPortalRenderState.getContext(); + return context == null ? original : context.cameraPos().z; + } + + @Inject(method = "addRecentlyCompiledSection", at = @At("HEAD"), cancellable = true) + private void antarchy$routePortalSectionCompile(SectionRenderDispatcher.RenderSection section, CallbackInfo ci) { + if (PortalGunPortalViewAreaManager.routeSectionCompiled(section)) { + ci.cancel(); + } + } + + @Inject(method = "setSectionDirty(IIIZ)V", at = @At("TAIL")) + private void antarchy$dirtyPortalSection(int sectionX, int sectionY, int sectionZ, boolean playerChanged, CallbackInfo ci) { + PortalGunPortalViewAreaManager.onSectionDirty(sectionX, sectionY, sectionZ, playerChanged); + } + + @Inject(method = "onChunkLoaded", at = @At("TAIL")) + private void antarchy$updatePortalChunkGraph(ChunkPos pos, CallbackInfo ci) { + PortalGunPortalViewAreaManager.onChunkLoaded(pos); + } + +} diff --git a/common-1.21.1/src/main/java/com/craisinlord/antarchy/mixins/client/LivingEntityPortalLerpAccessor.java b/common-1.21.1/src/main/java/com/craisinlord/antarchy/mixins/client/LivingEntityPortalLerpAccessor.java new file mode 100644 index 00000000..433c9cf5 --- /dev/null +++ b/common-1.21.1/src/main/java/com/craisinlord/antarchy/mixins/client/LivingEntityPortalLerpAccessor.java @@ -0,0 +1,17 @@ +package com.craisinlord.antarchy.mixins.client; + +import net.minecraft.world.entity.LivingEntity; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Mutable; +import org.spongepowered.asm.mixin.gen.Accessor; + +@Mixin(LivingEntity.class) +public interface LivingEntityPortalLerpAccessor { + @Mutable + @Accessor("lerpSteps") + void antarchy$setLerpSteps(int steps); + + @Mutable + @Accessor("lerpHeadSteps") + void antarchy$setLerpHeadSteps(int steps); +} diff --git a/common-1.21.1/src/main/java/com/craisinlord/antarchy/mixins/client/MinecraftMainRenderTargetAccessor.java b/common-1.21.1/src/main/java/com/craisinlord/antarchy/mixins/client/MinecraftMainRenderTargetAccessor.java new file mode 100644 index 00000000..0fd7d763 --- /dev/null +++ b/common-1.21.1/src/main/java/com/craisinlord/antarchy/mixins/client/MinecraftMainRenderTargetAccessor.java @@ -0,0 +1,14 @@ +package com.craisinlord.antarchy.mixins.client; + +import com.mojang.blaze3d.pipeline.RenderTarget; +import net.minecraft.client.Minecraft; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Mutable; +import org.spongepowered.asm.mixin.gen.Accessor; + +@Mixin(Minecraft.class) +public interface MinecraftMainRenderTargetAccessor { + @Mutable + @Accessor("mainRenderTarget") + void antarchy$setMainRenderTarget(RenderTarget renderTarget); +} diff --git a/common-1.21.1/src/main/java/com/craisinlord/antarchy/mixins/client/PortalGunEntityTeleportPacketMixin.java b/common-1.21.1/src/main/java/com/craisinlord/antarchy/mixins/client/PortalGunEntityTeleportPacketMixin.java new file mode 100644 index 00000000..4a96a9f9 --- /dev/null +++ b/common-1.21.1/src/main/java/com/craisinlord/antarchy/mixins/client/PortalGunEntityTeleportPacketMixin.java @@ -0,0 +1,68 @@ +package com.craisinlord.antarchy.mixins.client; + +import com.craisinlord.antarchy.content.portalgun.PortalGunPortalEntity; +import com.craisinlord.antarchy.content.portalgun.PortalGunWorldPortalShape; +import net.minecraft.client.Minecraft; +import net.minecraft.client.multiplayer.ClientPacketListener; +import net.minecraft.network.protocol.game.ClientboundTeleportEntityPacket; +import net.minecraft.world.entity.Entity; +import net.minecraft.world.entity.LivingEntity; +import net.minecraft.world.phys.AABB; +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.callback.CallbackInfo; + +@Mixin(ClientPacketListener.class) +public abstract class PortalGunEntityTeleportPacketMixin { + @Inject(method = "handleTeleportEntity", at = @At("TAIL")) + private void antarchy$snapPortalTransitEntity(ClientboundTeleportEntityPacket packet, CallbackInfo ci) { + Minecraft minecraft = Minecraft.getInstance(); + if (minecraft.level == null) { + return; + } + Entity entity = minecraft.level.getEntity(packet.getId()); + Vec3 destination = new Vec3(packet.getX(), packet.getY(), packet.getZ()); + if (entity == null) { + return; + } + antarchy$snapToPortalExit(entity, destination); + } + + private static void antarchy$snapToPortalExit(Entity entity, Vec3 destination) { + if (!isNearPortalExit(entity, destination)) { + return; + } + entity.lerpTo(destination.x, destination.y, destination.z, entity.getYRot(), entity.getXRot(), 0); + entity.setPortalCooldown(PortalGunPortalEntity.TELEPORT_COOLDOWN_TICKS); + entity.setPos(destination.x, destination.y, destination.z); + entity.xo = destination.x; + entity.yo = destination.y; + entity.zo = destination.z; + entity.yRotO = entity.getYRot(); + entity.xRotO = entity.getXRot(); + if (entity instanceof LivingEntity livingEntity) { + LivingEntityPortalLerpAccessor accessor = (LivingEntityPortalLerpAccessor) livingEntity; + accessor.antarchy$setLerpHeadSteps(0); + } + } + + private static boolean isNearPortalExit(Entity entity, Vec3 destination) { + Vec3 offset = destination.subtract(entity.position()); + double entityExtent = Math.max(entity.getBbWidth(), entity.getBbHeight()); + double edgeAllowance = entityExtent * 0.5D; + AABB search = entity.getBoundingBox().move(offset).inflate(entityExtent + PortalGunWorldPortalShape.DEFAULT_SCAN_DISTANCE); + Vec3 probe = destination.add(0.0D, entity.getEyeHeight(), 0.0D); + for (PortalGunPortalEntity portal : entity.level().getEntitiesOfClass(PortalGunPortalEntity.class, search, Entity::isAlive)) { + PortalGunWorldPortalShape.PortalLocalCoords coords = portal.getWorldPortalShape().localCoords(probe); + if (coords.depth() >= -entityExtent - 0.5D + && coords.depth() <= PortalGunWorldPortalShape.DEFAULT_SCAN_DISTANCE + 0.5D + && Math.abs(coords.horizontal()) <= portal.getWorldPortalShape().halfWidth() + edgeAllowance + && Math.abs(coords.vertical()) <= portal.getWorldPortalShape().halfHeight() + edgeAllowance) { + return true; + } + } + return false; + } +} diff --git a/common-1.21.1/src/main/java/com/craisinlord/antarchy/mixins/client/PortalGunZoomFovMixin.java b/common-1.21.1/src/main/java/com/craisinlord/antarchy/mixins/client/PortalGunZoomFovMixin.java new file mode 100644 index 00000000..b3b6b4b2 --- /dev/null +++ b/common-1.21.1/src/main/java/com/craisinlord/antarchy/mixins/client/PortalGunZoomFovMixin.java @@ -0,0 +1,18 @@ +package com.craisinlord.antarchy.mixins.client; + +import com.craisinlord.antarchy.content.client.PortalGunZoomClientState; +import net.minecraft.client.Camera; +import net.minecraft.client.Minecraft; +import net.minecraft.client.renderer.GameRenderer; +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(GameRenderer.class) +public abstract class PortalGunZoomFovMixin { + @Inject(method = "getFov", at = @At("RETURN"), cancellable = true) + private void antarchy$applyPortalGunZoom(Camera camera, float partialTick, boolean useFovSetting, CallbackInfoReturnable cir) { + cir.setReturnValue(PortalGunZoomClientState.applyFov(cir.getReturnValue(), partialTick, Minecraft.getInstance().options)); + } +} diff --git a/common-1.21.1/src/main/java/com/craisinlord/antarchy/mixins/client/PortalViewChunkCacheMixin.java b/common-1.21.1/src/main/java/com/craisinlord/antarchy/mixins/client/PortalViewChunkCacheMixin.java new file mode 100644 index 00000000..a75caffa --- /dev/null +++ b/common-1.21.1/src/main/java/com/craisinlord/antarchy/mixins/client/PortalViewChunkCacheMixin.java @@ -0,0 +1,17 @@ +package com.craisinlord.antarchy.mixins.client; + +import com.craisinlord.antarchy.content.client.renderer.PortalGunPortalViewAreaManager; +import net.minecraft.client.multiplayer.ClientChunkCache; +import net.minecraft.world.level.ChunkPos; +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(ClientChunkCache.class) +public abstract class PortalViewChunkCacheMixin { + @Inject(method = "drop", at = @At("TAIL")) + private void antarchy$invalidatePortalChunk(ChunkPos pos, CallbackInfo ci) { + PortalGunPortalViewAreaManager.onChunkDropped(pos); + } +} diff --git a/common-1.21.1/src/main/java/com/craisinlord/antarchy/mixins/client/PortalViewSectionSortingMixin.java b/common-1.21.1/src/main/java/com/craisinlord/antarchy/mixins/client/PortalViewSectionSortingMixin.java new file mode 100644 index 00000000..6c808236 --- /dev/null +++ b/common-1.21.1/src/main/java/com/craisinlord/antarchy/mixins/client/PortalViewSectionSortingMixin.java @@ -0,0 +1,42 @@ +package com.craisinlord.antarchy.mixins.client; + +import com.craisinlord.antarchy.content.client.renderer.PortalGunPortalViewAreaManager; +import com.craisinlord.antarchy.content.client.renderer.PortalViewSectionMarker; +import com.llamalad7.mixinextras.injector.ModifyExpressionValue; +import org.spongepowered.asm.mixin.Unique; +import java.util.Collection; +import net.minecraft.client.renderer.LevelRenderer; +import net.minecraft.client.renderer.chunk.SectionRenderDispatcher; +import net.minecraft.world.level.block.entity.BlockEntity; +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.Redirect; + +@Mixin(SectionRenderDispatcher.RenderSection.class) +public abstract class PortalViewSectionSortingMixin implements PortalViewSectionMarker { + @Unique + private boolean antarchy$portalViewSection; + + @Override + public void antarchy$markPortalViewSection() { + this.antarchy$portalViewSection = true; + } + + @Override + public boolean antarchy$isPortalViewSection() { + return this.antarchy$portalViewSection; + } + + @ModifyExpressionValue(method = "createVertexSorting", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/renderer/chunk/SectionRenderDispatcher;getCameraPosition()Lnet/minecraft/world/phys/Vec3;")) + private Vec3 antarchy$sortPortalGeometryFromVirtualCamera(Vec3 original) { + return PortalGunPortalViewAreaManager.cameraForSection((SectionRenderDispatcher.RenderSection) (Object) this, original); + } + + @Redirect(method = "updateGlobalBlockEntities", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/renderer/LevelRenderer;updateGlobalBlockEntities(Ljava/util/Collection;Ljava/util/Collection;)V")) + private void antarchy$routePortalGlobalBlockEntities(LevelRenderer renderer, Collection removed, Collection added) { + if (!PortalGunPortalViewAreaManager.updateGlobalBlockEntities((SectionRenderDispatcher.RenderSection) (Object) this, removed, added)) { + renderer.updateGlobalBlockEntities(removed, added); + } + } +} diff --git a/common-1.21.1/src/main/java/com/craisinlord/antarchy/mixins/client/PostChainTargetAccessor.java b/common-1.21.1/src/main/java/com/craisinlord/antarchy/mixins/client/PostChainTargetAccessor.java new file mode 100644 index 00000000..a9d56508 --- /dev/null +++ b/common-1.21.1/src/main/java/com/craisinlord/antarchy/mixins/client/PostChainTargetAccessor.java @@ -0,0 +1,17 @@ +package com.craisinlord.antarchy.mixins.client; + +import com.mojang.blaze3d.pipeline.RenderTarget; +import net.minecraft.client.renderer.PostChain; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Mutable; +import org.spongepowered.asm.mixin.gen.Accessor; + +@Mixin(PostChain.class) +public interface PostChainTargetAccessor { + @Accessor("screenTarget") + RenderTarget antarchy$getScreenTarget(); + + @Mutable + @Accessor("screenTarget") + void antarchy$setScreenTarget(RenderTarget renderTarget); +} diff --git a/common-1.21.1/src/main/java/com/craisinlord/antarchy/mixins/client/SodiumWorldRendererMixin.java b/common-1.21.1/src/main/java/com/craisinlord/antarchy/mixins/client/SodiumWorldRendererMixin.java new file mode 100644 index 00000000..851afae1 --- /dev/null +++ b/common-1.21.1/src/main/java/com/craisinlord/antarchy/mixins/client/SodiumWorldRendererMixin.java @@ -0,0 +1,19 @@ +package com.craisinlord.antarchy.mixins.client; + +import com.craisinlord.antarchy.content.client.renderer.SodiumCompat; +import net.minecraft.client.Camera; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Pseudo; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Coerce; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; + +@Pseudo +@Mixin(targets = "net.caffeinemc.mods.sodium.client.render.SodiumWorldRenderer", remap = false) +public abstract class SodiumWorldRendererMixin { + @Inject(method = "setupTerrain", at = @At("HEAD"), remap = false) + private void antarchy$captureTerrainSetup(Camera camera, @Coerce Object viewport, boolean spectator, boolean updateChunks, CallbackInfo ci) { + SodiumCompat.captureTerrainSetup(camera, viewport, spectator, updateChunks); + } +} diff --git a/common-1.21.1/src/main/java/com/craisinlord/antarchy/mixins/compat/scguns/ScgunsProjectileEntityAccessMixin.java b/common-1.21.1/src/main/java/com/craisinlord/antarchy/mixins/compat/scguns/ScgunsProjectileEntityAccessMixin.java new file mode 100644 index 00000000..a5c3efd2 --- /dev/null +++ b/common-1.21.1/src/main/java/com/craisinlord/antarchy/mixins/compat/scguns/ScgunsProjectileEntityAccessMixin.java @@ -0,0 +1,29 @@ +package com.craisinlord.antarchy.mixins.compat.scguns; + +import com.craisinlord.antarchy.content.gravity.ScgunsProjectileAccess; +import net.minecraft.world.entity.LivingEntity; +import org.jetbrains.annotations.Nullable; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Pseudo; +import org.spongepowered.asm.mixin.Shadow; + +/* + * Shadows must live on the class that declares the field, so the subclasses + * reach these through ScgunsProjectileAccess instead. + */ +@Mixin(targets = "top.ribs.scguns.entity.projectile.ProjectileEntity") +@Pseudo +public abstract class ScgunsProjectileEntityAccessMixin implements ScgunsProjectileAccess { + @Shadow(remap = false) @Nullable protected LivingEntity shooter; + @Shadow(remap = false) protected double modifiedGravity; + + @Override + public @Nullable LivingEntity antarchy$getScgunsShooter() { + return this.shooter; + } + + @Override + public double antarchy$getScgunsModifiedGravity() { + return this.modifiedGravity; + } +} 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 index f5e1f051..c4b36b8c 100644 --- 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 @@ -4,14 +4,13 @@ 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.gravity.ScgunsProjectileAccess; 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.Pseudo; -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; @@ -28,18 +27,14 @@ import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; }) @Pseudo 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; + LivingEntity shooter = ((ScgunsProjectileAccess) self).antarchy$getScgunsShooter(); boolean inverted = ThoraxisUndersideManager.isInUnderside(self) - || (this.shooter != null && AntarchyGravityApi.isGravityInverted(this.shooter)); + || (shooter != null && AntarchyGravityApi.isGravityInverted(shooter)); if (!inverted) { if (AntarchyGravityApi.isGravityInverted(self) && !AntarchyGravityApi.isGravityForced(self)) { AntarchyGravityApi.setAirborneGravityDirection( @@ -59,9 +54,9 @@ public abstract class ScgunsProjectileEntityGravityMixin { AntarchyGravityTransition.INSTANT ); if (!this.antarchy$launchDirectionAdjusted && self.tickCount <= 1) { - Vec3 motion = this.getDeltaMovement(); + Vec3 motion = self.getDeltaMovement(); if (motion.lengthSqr() > 1.0E-8D) { - this.setDeltaMovement(AntarchyGravityRotationUtil.vecPlayerToWorld( + self.setDeltaMovement(AntarchyGravityRotationUtil.vecPlayerToWorld( motion, AntarchyGravityDirection.UP )); @@ -73,12 +68,13 @@ public abstract class ScgunsProjectileEntityGravityMixin { @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) { + double modifiedGravity = ((ScgunsProjectileAccess) self).antarchy$getScgunsModifiedGravity(); + if (!AntarchyGravityApi.isGravityInverted(self) || 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)); + self.setDeltaMovement(self.getDeltaMovement().add(0.0D, -2.0D * modifiedGravity, 0.0D)); } } diff --git a/common-1.21.1/src/main/java/com/craisinlord/antarchy/mixins/gravity/EntityProjectileGravityMixin.java b/common-1.21.1/src/main/java/com/craisinlord/antarchy/mixins/gravity/EntityProjectileGravityMixin.java index ec072ea7..aa39bafd 100644 --- a/common-1.21.1/src/main/java/com/craisinlord/antarchy/mixins/gravity/EntityProjectileGravityMixin.java +++ b/common-1.21.1/src/main/java/com/craisinlord/antarchy/mixins/gravity/EntityProjectileGravityMixin.java @@ -22,6 +22,9 @@ public abstract class EntityProjectileGravityMixin { if (!(self instanceof Projectile projectile)) { return; } + if (projectile.isNoGravity()) { + return; + } boolean inverted = AntarchyGravityApi.isGravityInverted(projectile); if (!inverted && projectile.getOwner() != null) { 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 7c694031..fe53214d 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 @@ -6,6 +6,9 @@ "mixins": [ "AbstractMinecartMixin", "AbstractArrowAccessor", + "PortalGunEntityCollisionMixin", + "PortalGunZombieMixin", + "FallingBlockEntityAccessor", "AccessorFireBlock", "AccessorRangedAttribute", "AmericanFoodConsumeMixin", @@ -96,6 +99,7 @@ "gravity.ThrowableProjectileGravityMixin", "gravity.TrackedEntityAccessor", "compat.biolith.MultiNoiseBiomeSourceNullGuardMixin", + "compat.scguns.ScgunsProjectileEntityAccessMixin", "compat.scguns.ScgunsProjectileEntityGravityMixin", "compat.scguns.ScgunsArrowGravityMixin", "compat.irons_spellbooks.IronsSpellbooksProjectileGravityMixin", @@ -135,6 +139,17 @@ "time.GeckoLibAnimationControllerTimeDilationMixin" ], "client": [ + "client.CameraBasisAccessor", + "client.LevelRendererPortalViewAreaAccessor", + "client.LevelRendererPortalViewAreaMixin", + "client.LevelRendererPortalChunkBudgetMixin", + "client.LevelRendererPortalLifecycleMixin", + "client.PortalViewChunkCacheMixin", + "client.PortalViewSectionSortingMixin", + "client.LevelRendererPortalTargetsAccessor", + "client.LevelRendererPortalSceneInvoker", + "client.MinecraftMainRenderTargetAccessor", + "client.PostChainTargetAccessor", "client.AbstractMinecartRendererMixin", "client.EntityLightProbeMixin", "client.CustomBossBarMixin", @@ -154,6 +169,9 @@ "client.MountedVehicleCameraMixin", "client.ParalyzedMouseTurnMixin", "client.LocalPlayerPortalMixin", + "client.PortalGunEntityTeleportPacketMixin", + "client.PortalGunZoomFovMixin", + "client.LivingEntityPortalLerpAccessor", "client.ScreenEffectRendererPortalMixin", "client.ScreenEffectRendererFireVisualMixin", "client.GlimmeringTranslucencyMixin", diff --git a/common-1.21.1/src/main/resources/antarchy-sodium.mixins.json b/common-1.21.1/src/main/resources/antarchy-sodium.mixins.json new file mode 100644 index 00000000..40552668 --- /dev/null +++ b/common-1.21.1/src/main/resources/antarchy-sodium.mixins.json @@ -0,0 +1,13 @@ +{ + "required": false, + "package": "com.craisinlord.antarchy.mixins", + "plugin": "com.craisinlord.antarchy.mixins.AntarchyMixinPlugin", + "compatibilityLevel": "JAVA_21", + "client": [ + "client.SodiumWorldRendererMixin" + ], + "injectors": { + "defaultRequire": 1 + }, + "minVersion": "0.8" +} diff --git a/common-1.21.1/src/main/resources/assets/antarchy/lang/de_de.json b/common-1.21.1/src/main/resources/assets/antarchy/lang/de_de.json index 68d01114..f59b20eb 100644 --- a/common-1.21.1/src/main/resources/assets/antarchy/lang/de_de.json +++ b/common-1.21.1/src/main/resources/assets/antarchy/lang/de_de.json @@ -411,11 +411,6 @@ "tooltip.antarchy.reverie_bottle.capture": "Rechtsklicke mit einer Glasflasche auf eine Reverie, um sie einzufangen.", "tooltip.antarchy.reverie_bottle.release": "Rechtsklick, um die Reverie freizulassen.", "item.antarchy.gravity_gun": "Gravitationskanone", - "item.antarchy.miners_dream": "Traum des Bergmanns", - "tooltip.antarchy.miners_dream.line1": "Rechtsklick, um eine gewundene Höhle zu graben.", - "tooltip.antarchy.miners_dream.line2": "Lässt wertvolle Blöcke unberührt.", - "message.antarchy.miners_dream_disabled": "Der Traum des Bergmanns ist deaktiviert.", - "message.antarchy.miners_dream_busy": "Dein vorheriger Traum entfaltet sich noch.", "item.antarchy.diamond_minecart": "Die Diamantlore", "item.antarchy.cogwheel_pet": "Zahnrad-Haustier", "item.antarchy.fire_dragon_pet": "Feuerdrachen-Haustier", @@ -967,4 +962,6 @@ "subtitles.antarchy.hoverboard_mount": "Hoverboard startet", "subtitles.antarchy.hoverboard_dismount": "Hoverboard stoppt", "subtitles.antarchy.hoverboard_idle": "Hoverboard summt" +, + "entity.antarchy.portal_gun_portal": "Portalgun-Portal" } 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 fda1f7b0..8e6426aa 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 @@ -671,11 +671,6 @@ "tooltip.antarchy.reverie_bottle.release": "Right click to release the Reverie.", "item.antarchy.gravity_gun": "Gravity Gun", "item.antarchy.portal_gun": "Portal Gun", - "item.antarchy.miners_dream": "Miner's Dream", - "tooltip.antarchy.miners_dream.line1": "Use to carve a winding cave.", - "tooltip.antarchy.miners_dream.line2": "Leaves valuable blocks untouched.", - "message.antarchy.miners_dream_disabled": "The Miner's Dream is disabled.", - "message.antarchy.miners_dream_busy": "Your previous dream is still unfolding.", "item.antarchy.diamond_minecart": "The Diamond Minecart", "item.antarchy.cogwheel_pet": "Cogwheel Pet", "item.antarchy.fire_dragon_pet": "Fire Dragon Pet", @@ -844,7 +839,11 @@ "tooltip.antarchy.gravity_gun.blacklist": "Does not affect blacklisted blocks or entities.", "tooltip.antarchy.portal_gun.primary": "Attack: fire a blue portal.", "tooltip.antarchy.portal_gun.secondary": "Use: fire an orange portal.", + "tooltip.antarchy.portal_gun.channel": "Channel: %s", + "tooltip.antarchy.portal_gun.owner": "Owner: %s", "tooltip.antarchy.portal_gun.reset": "Sneak + use: fizzle your active portals.", + "tooltip.antarchy.portal_gun.size": "Portal size: %1$s x %2$s", + "tooltip.antarchy.portal_gun.grab_strength": "Grab strength: %s/4", "tooltip.antarchy.antimetal_inversion": "Inverts entites directly below it", "tooltip.antarchy.diamond_minecart.forward": "Will be implemented later.", "tooltip.antarchy.diamond_minecart.rails": " ", @@ -1247,6 +1246,9 @@ "advancements.antarchy.duct_tape.title": "I sawed this boat in half!", "advancements.antarchy.duct_tape.description": "and fixed it with the power of duct tape!", "key.categories.antarchy": "Antarchy", + "key.antarchy.portal_gun_zoom": "Portal Gun Zoom", + "key.antarchy.portal_gun_grab": "Portal Gun Grab", + "key.antarchy.portal_gun_reset": "Portal Gun Reset", "key.antarchy.brutalfly_flap": "Brutalfly Elytra: Flap", "advancements.antarchy.shrink_ray_craft.title": "Honey, I Shrunk the Mobs", "advancements.antarchy.shrink_ray_craft.description": "Craft a Shrink Ray.", @@ -1596,6 +1598,7 @@ ,"guide.antarchy.entry.brutalfly.description": "An oversized Elythian butterfly. Unlike its smaller counterparts, it is incredibly aggressive once out of its cocoon." ,"guide.antarchy.entry.brutalfly.description2": "Like any butterfly, a Brutalfly begins in a cocoon. It is relatively safe to be near the cocoon as long as it remains undamaged." ,"guide.antarchy.entry.brutalfly.description3": "If the Brutalfly is on the assault, all you can do is dispose of it." + ,"guide.antarchy.entry.brutalfly.description4": "Brutaflies are found in Elythia's wooded areas. Look for their cocoons hanging beneath the canopy." ,"guide.antarchy.entry.easter_bunny.title": "Easter Bunny" ,"guide.antarchy.entry.easter_bunny.subtitle": "Entity record" ,"guide.antarchy.entry.easter_bunny.description": "The Easter Bunny lays spawn eggs for all sorts of creatures! Life is like an easter egg, you never know what you're gonna get." @@ -1639,4 +1642,6 @@ ,"subtitles.antarchy.hoverboard_mount": "Hoverboard starts" ,"subtitles.antarchy.hoverboard_dismount": "Hoverboard stops" ,"subtitles.antarchy.hoverboard_idle": "Hoverboard hums" +, + "entity.antarchy.portal_gun_portal": "Portal Gun Portal" } diff --git a/common-1.21.1/src/main/resources/assets/antarchy/lang/es_es.json b/common-1.21.1/src/main/resources/assets/antarchy/lang/es_es.json index dd76c4b6..3567985f 100644 --- a/common-1.21.1/src/main/resources/assets/antarchy/lang/es_es.json +++ b/common-1.21.1/src/main/resources/assets/antarchy/lang/es_es.json @@ -411,11 +411,6 @@ "tooltip.antarchy.reverie_bottle.capture": "Haz clic derecho en una Reverie con una botella de cristal para guardarla.", "tooltip.antarchy.reverie_bottle.release": "Clic derecho para liberar a la Reverie.", "item.antarchy.gravity_gun": "Pistola gravitatoria", - "item.antarchy.miners_dream": "Sueño del minero", - "tooltip.antarchy.miners_dream.line1": "Clic derecho para excavar una cueva sinuosa.", - "tooltip.antarchy.miners_dream.line2": "Deja intactos los bloques valiosos.", - "message.antarchy.miners_dream_disabled": "El Sueño del minero está desactivado.", - "message.antarchy.miners_dream_busy": "Tu sueño anterior todavía se está desarrollando.", "item.antarchy.diamond_minecart": "La vagoneta de diamante", "item.antarchy.cogwheel_pet": "Mascota engranaje", "item.antarchy.fire_dragon_pet": "Mascota de dragón de fuego", @@ -964,4 +959,6 @@ "advancements.antarchy.beetle_buddies.description": "Domestica un escarabajo hércules.", "advancements.antarchy.herculean_task.title": "Tarea hercúlea", "advancements.antarchy.herculean_task.description": "Abate a un escarabajo hércules." +, + "entity.antarchy.portal_gun_portal": "Portal del arma de portales" } diff --git a/common-1.21.1/src/main/resources/assets/antarchy/lang/fr_fr.json b/common-1.21.1/src/main/resources/assets/antarchy/lang/fr_fr.json index 1bb5b8ef..1da3b124 100644 --- a/common-1.21.1/src/main/resources/assets/antarchy/lang/fr_fr.json +++ b/common-1.21.1/src/main/resources/assets/antarchy/lang/fr_fr.json @@ -411,11 +411,6 @@ "tooltip.antarchy.reverie_bottle.capture": "Faites un clic droit sur une Reverie avec une fiole en verre pour la capturer.", "tooltip.antarchy.reverie_bottle.release": "Clic droit pour libérer la Reverie.", "item.antarchy.gravity_gun": "Canon à gravité", - "item.antarchy.miners_dream": "Rêve du mineur", - "tooltip.antarchy.miners_dream.line1": "Clic droit pour creuser une grotte sinueuse.", - "tooltip.antarchy.miners_dream.line2": "Laisse les blocs précieux intacts.", - "message.antarchy.miners_dream_disabled": "Le Rêve du mineur est désactivé.", - "message.antarchy.miners_dream_busy": "Votre rêve précédent est encore en train de se déployer.", "item.antarchy.diamond_minecart": "Le wagonnet de diamant", "item.antarchy.cogwheel_pet": "Familier engrenage", "item.antarchy.fire_dragon_pet": "Familier dragon de feu", @@ -964,4 +959,6 @@ "advancements.antarchy.beetle_buddies.description": "Apprivoisez un scarabée Hercule.", "advancements.antarchy.herculean_task.title": "Travail herculéen", "advancements.antarchy.herculean_task.description": "Terrassez un scarabée Hercule." +, + "entity.antarchy.portal_gun_portal": "Portail du pistolet à portails" } diff --git a/common-1.21.1/src/main/resources/assets/antarchy/lang/it_it.json b/common-1.21.1/src/main/resources/assets/antarchy/lang/it_it.json index e5ceab2e..e3497a4c 100644 --- a/common-1.21.1/src/main/resources/assets/antarchy/lang/it_it.json +++ b/common-1.21.1/src/main/resources/assets/antarchy/lang/it_it.json @@ -411,11 +411,6 @@ "tooltip.antarchy.reverie_bottle.capture": "Fai clic destro su una Reverie con una bottiglia di vetro per catturarla.", "tooltip.antarchy.reverie_bottle.release": "Fai clic destro per liberare la Reverie.", "item.antarchy.gravity_gun": "Cannone gravitazionale", - "item.antarchy.miners_dream": "Sogno del minatore", - "tooltip.antarchy.miners_dream.line1": "Fai clic destro per scavare una grotta tortuosa.", - "tooltip.antarchy.miners_dream.line2": "Lascia intatti i blocchi preziosi.", - "message.antarchy.miners_dream_disabled": "Il Sogno del minatore è disattivato.", - "message.antarchy.miners_dream_busy": "Il tuo sogno precedente si sta ancora dispiegando.", "item.antarchy.diamond_minecart": "Il carrello di diamante", "item.antarchy.cogwheel_pet": "Famiglio ingranaggio", "item.antarchy.fire_dragon_pet": "Famiglio drago di fuoco", @@ -964,4 +959,6 @@ "advancements.antarchy.beetle_buddies.description": "Addomestica uno Scarabeo Ercole.", "advancements.antarchy.herculean_task.title": "Impresa erculea", "advancements.antarchy.herculean_task.description": "Uccidi uno Scarabeo Ercole." +, + "entity.antarchy.portal_gun_portal": "Portale della Portal Gun" } diff --git a/common-1.21.1/src/main/resources/assets/antarchy/lang/ja_jp.json b/common-1.21.1/src/main/resources/assets/antarchy/lang/ja_jp.json index 664b7767..6d81f97f 100644 --- a/common-1.21.1/src/main/resources/assets/antarchy/lang/ja_jp.json +++ b/common-1.21.1/src/main/resources/assets/antarchy/lang/ja_jp.json @@ -411,11 +411,6 @@ "tooltip.antarchy.reverie_bottle.capture": "ガラス瓶を持ってレヴェリーを右クリックすると収納できます。", "tooltip.antarchy.reverie_bottle.release": "右クリックでレヴェリーを解放します。", "item.antarchy.gravity_gun": "重力銃", - "item.antarchy.miners_dream": "採掘者の夢", - "tooltip.antarchy.miners_dream.line1": "右クリックで曲がりくねった洞窟を掘ります。", - "tooltip.antarchy.miners_dream.line2": "貴重なブロックはそのまま残します。", - "message.antarchy.miners_dream_disabled": "採掘者の夢は無効になっています。", - "message.antarchy.miners_dream_busy": "前の夢はまだ展開中です。", "item.antarchy.diamond_minecart": "ダイヤモンドのトロッコ", "item.antarchy.cogwheel_pet": "歯車ペット", "item.antarchy.fire_dragon_pet": "ファイアドラゴンのペット", @@ -964,4 +959,6 @@ "advancements.antarchy.beetle_buddies.description": "ヘラクレスオオカブトを手懐ける。", "advancements.antarchy.herculean_task.title": "ヘラクレス級の難題", "advancements.antarchy.herculean_task.description": "ヘラクレスオオカブトを討伐する。" +, + "entity.antarchy.portal_gun_portal": "ポータルガンのポータル" } diff --git a/common-1.21.1/src/main/resources/assets/antarchy/lang/ko_kr.json b/common-1.21.1/src/main/resources/assets/antarchy/lang/ko_kr.json index 4d74cf03..3a9f171b 100644 --- a/common-1.21.1/src/main/resources/assets/antarchy/lang/ko_kr.json +++ b/common-1.21.1/src/main/resources/assets/antarchy/lang/ko_kr.json @@ -411,11 +411,6 @@ "tooltip.antarchy.reverie_bottle.capture": "유리병으로 레버리를 우클릭하여 담습니다.", "tooltip.antarchy.reverie_bottle.release": "우클릭하여 레버리를 풀어줍니다.", "item.antarchy.gravity_gun": "중력총", - "item.antarchy.miners_dream": "광부의 꿈", - "tooltip.antarchy.miners_dream.line1": "우클릭하여 구불구불한 동굴을 뚫습니다.", - "tooltip.antarchy.miners_dream.line2": "귀중한 블록은 건드리지 않습니다.", - "message.antarchy.miners_dream_disabled": "광부의 꿈이 비활성화되어 있습니다.", - "message.antarchy.miners_dream_busy": "이전 꿈이 아직 펼쳐지고 있습니다.", "item.antarchy.diamond_minecart": "다이아몬드 광산 수레", "item.antarchy.cogwheel_pet": "톱니바퀴 펫", "item.antarchy.fire_dragon_pet": "화염 드래곤 펫", @@ -964,4 +959,6 @@ "advancements.antarchy.beetle_buddies.description": "헤라클레스장수풍뎅이를 길들이세요.", "advancements.antarchy.herculean_task.title": "헤라클레스의 과업", "advancements.antarchy.herculean_task.description": "헤라클레스장수풍뎅이를 처치하세요." +, + "entity.antarchy.portal_gun_portal": "포털 건 포털" } diff --git a/common-1.21.1/src/main/resources/assets/antarchy/lang/pt_br.json b/common-1.21.1/src/main/resources/assets/antarchy/lang/pt_br.json index 55cf70c9..d418f422 100644 --- a/common-1.21.1/src/main/resources/assets/antarchy/lang/pt_br.json +++ b/common-1.21.1/src/main/resources/assets/antarchy/lang/pt_br.json @@ -411,11 +411,6 @@ "tooltip.antarchy.reverie_bottle.capture": "Clique com o botão direito em uma Reverie com um frasco de vidro para guardá-la.", "tooltip.antarchy.reverie_bottle.release": "Clique com o botão direito para soltar a Reverie.", "item.antarchy.gravity_gun": "Arma Gravitacional", - "item.antarchy.miners_dream": "Sonho do Minerador", - "tooltip.antarchy.miners_dream.line1": "Clique com o botão direito para escavar uma caverna sinuosa.", - "tooltip.antarchy.miners_dream.line2": "Deixa blocos valiosos intactos.", - "message.antarchy.miners_dream_disabled": "O Sonho do Minerador está desativado.", - "message.antarchy.miners_dream_busy": "Seu sonho anterior ainda está se desenrolando.", "item.antarchy.diamond_minecart": "O Carrinho de Diamante", "item.antarchy.cogwheel_pet": "Mascote de Engrenagem", "item.antarchy.fire_dragon_pet": "Mascote de Dragão de Fogo", @@ -964,4 +959,6 @@ "advancements.antarchy.beetle_buddies.description": "Domestique um besouro-hércules.", "advancements.antarchy.herculean_task.title": "Tarefa Hercúlea", "advancements.antarchy.herculean_task.description": "Abata um besouro-hércules." +, + "entity.antarchy.portal_gun_portal": "Portal da arma de portais" } diff --git a/common-1.21.1/src/main/resources/assets/antarchy/lang/ru_ru.json b/common-1.21.1/src/main/resources/assets/antarchy/lang/ru_ru.json index 1bc3167c..e05f5a01 100644 --- a/common-1.21.1/src/main/resources/assets/antarchy/lang/ru_ru.json +++ b/common-1.21.1/src/main/resources/assets/antarchy/lang/ru_ru.json @@ -387,11 +387,6 @@ "tooltip.antarchy.reverie_bottle.capture": "Нажмите ПКМ стеклянной бутылочкой по грёзе, чтобы поймать её.", "tooltip.antarchy.reverie_bottle.release": "Нажмите ПКМ, чтобы выпустить грёзу.", "item.antarchy.gravity_gun": "Гравитационная пушка", - "item.antarchy.miners_dream": "Мечта шахтёра", - "tooltip.antarchy.miners_dream.line1": "Нажмите ПКМ, чтобы вырезать извилистую пещеру.", - "tooltip.antarchy.miners_dream.line2": "Оставляет ценные блоки нетронутыми.", - "message.antarchy.miners_dream_disabled": "Мечта шахтёра отключена.", - "message.antarchy.miners_dream_busy": "Ваша предыдущая мечта всё ещё воплощается.", "item.antarchy.diamond_minecart": "Алмазная вагонетка", "item.antarchy.cogwheel_pet": "Шестерёнка-питомец", "item.antarchy.fire_dragon_pet": "Огненный дракон-питомец", @@ -842,4 +837,6 @@ "advancements.antarchy.squidzooka_craft.description": "Создайте сквидзуку.", "advancements.antarchy.gravity_gun_craft.title": "Мастер притяжения", "advancements.antarchy.gravity_gun_craft.description": "Создайте гравитационную пушку." +, + "entity.antarchy.portal_gun_portal": "Портал портальной пушки" } diff --git a/common-1.21.1/src/main/resources/assets/antarchy/lang/zh_cn.json b/common-1.21.1/src/main/resources/assets/antarchy/lang/zh_cn.json index bde52c13..7c4c5d58 100644 --- a/common-1.21.1/src/main/resources/assets/antarchy/lang/zh_cn.json +++ b/common-1.21.1/src/main/resources/assets/antarchy/lang/zh_cn.json @@ -411,11 +411,6 @@ "tooltip.antarchy.reverie_bottle.capture": "手持玻璃瓶右键雷弗里即可将其收纳。", "tooltip.antarchy.reverie_bottle.release": "右键释放雷弗里。", "item.antarchy.gravity_gun": "重力枪", - "item.antarchy.miners_dream": "矿工之梦", - "tooltip.antarchy.miners_dream.line1": "右键挖出一条蜿蜒洞穴。", - "tooltip.antarchy.miners_dream.line2": "不会破坏贵重方块。", - "message.antarchy.miners_dream_disabled": "矿工之梦已禁用。", - "message.antarchy.miners_dream_busy": "你之前的梦仍在展开。", "item.antarchy.diamond_minecart": "钻石矿车", "item.antarchy.cogwheel_pet": "齿轮宠物", "item.antarchy.fire_dragon_pet": "火龙宠物", @@ -964,4 +959,6 @@ "advancements.antarchy.beetle_buddies.description": "驯服一只赫拉克勒斯甲虫。", "advancements.antarchy.herculean_task.title": "赫拉克勒斯之业", "advancements.antarchy.herculean_task.description": "斩杀一只赫拉克勒斯甲虫。" +, + "entity.antarchy.portal_gun_portal": "传送门枪传送门" } diff --git a/common-1.21.1/src/main/resources/assets/antarchy/lang/zh_tw.json b/common-1.21.1/src/main/resources/assets/antarchy/lang/zh_tw.json index 2c3a6435..88424f7c 100644 --- a/common-1.21.1/src/main/resources/assets/antarchy/lang/zh_tw.json +++ b/common-1.21.1/src/main/resources/assets/antarchy/lang/zh_tw.json @@ -411,11 +411,6 @@ "tooltip.antarchy.reverie_bottle.capture": "手持玻璃瓶右鍵雷弗裡即可將其收納。", "tooltip.antarchy.reverie_bottle.release": "右鍵釋放雷弗裡。", "item.antarchy.gravity_gun": "重力槍", - "item.antarchy.miners_dream": "礦工之夢", - "tooltip.antarchy.miners_dream.line1": "右鍵挖出一條蜿蜒洞穴。", - "tooltip.antarchy.miners_dream.line2": "不會破壞貴重方塊。", - "message.antarchy.miners_dream_disabled": "礦工之夢已禁用。", - "message.antarchy.miners_dream_busy": "你之前的夢仍在展開。", "item.antarchy.diamond_minecart": "鑽石礦車", "item.antarchy.cogwheel_pet": "齒輪寵物", "item.antarchy.fire_dragon_pet": "火龍寵物", @@ -964,4 +959,6 @@ "advancements.antarchy.beetle_buddies.description": "馴服一隻赫拉克勒斯甲蟲。", "advancements.antarchy.herculean_task.title": "赫拉克勒斯之業", "advancements.antarchy.herculean_task.description": "斬殺一隻赫拉克勒斯甲蟲。" +, + "entity.antarchy.portal_gun_portal": "傳送門槍傳送門" } diff --git a/common-1.21.1/src/main/resources/assets/antarchy/licenses/aperture_innovations_mit.txt b/common-1.21.1/src/main/resources/assets/antarchy/licenses/aperture_innovations_mit.txt new file mode 100644 index 00000000..bf7da1e1 --- /dev/null +++ b/common-1.21.1/src/main/resources/assets/antarchy/licenses/aperture_innovations_mit.txt @@ -0,0 +1,25 @@ +Crosshair texture, portal vortex textures, and portal audio adapted from Aperture Innovations by MisterSecret312. +Source: https://github.com/mistersecret312/ApertureInnovations +The Aperture Innovations mod metadata declares the MIT License. + +MIT License + +Copyright (c) MisterSecret312 + +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/models/item/miners_dream.json b/common-1.21.1/src/main/resources/assets/antarchy/models/item/miners_dream.json deleted file mode 100644 index c7a0f166..00000000 --- a/common-1.21.1/src/main/resources/assets/antarchy/models/item/miners_dream.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "parent": "minecraft:item/generated", - "textures": { - "layer0": "antarchy:item/miners_dream" - } -} diff --git a/common-1.21.1/src/main/resources/assets/antarchy/shaders/core/portal_view.fsh b/common-1.21.1/src/main/resources/assets/antarchy/shaders/core/portal_view.fsh new file mode 100644 index 00000000..79b13029 --- /dev/null +++ b/common-1.21.1/src/main/resources/assets/antarchy/shaders/core/portal_view.fsh @@ -0,0 +1,12 @@ +#version 150 + +uniform sampler2D Sampler0; +uniform vec4 ColorModulator; + +noperspective in vec2 portalUv; +out vec4 fragColor; + +void main() { + vec4 color = texture(Sampler0, portalUv); + fragColor = vec4(color.rgb, 1.0) * ColorModulator; +} diff --git a/common-1.21.1/src/main/resources/assets/antarchy/shaders/core/portal_view.json b/common-1.21.1/src/main/resources/assets/antarchy/shaders/core/portal_view.json new file mode 100644 index 00000000..8ff4de2b --- /dev/null +++ b/common-1.21.1/src/main/resources/assets/antarchy/shaders/core/portal_view.json @@ -0,0 +1,14 @@ +{ + "vertex": "antarchy:portal_view", + "fragment": "antarchy:portal_view", + "samplers": [ + { "name": "Sampler0" } + ], + "attributes": [ + "Position", + "UV0" + ], + "uniforms": [ + { "name": "ColorModulator", "type": "float", "count": 4, "values": [ 1.0, 1.0, 1.0, 1.0 ] } + ] +} diff --git a/common-1.21.1/src/main/resources/assets/antarchy/shaders/core/portal_view.vsh b/common-1.21.1/src/main/resources/assets/antarchy/shaders/core/portal_view.vsh new file mode 100644 index 00000000..b6de02e7 --- /dev/null +++ b/common-1.21.1/src/main/resources/assets/antarchy/shaders/core/portal_view.vsh @@ -0,0 +1,11 @@ +#version 150 + +in vec3 Position; +in vec2 UV0; + +noperspective out vec2 portalUv; + +void main() { + gl_Position = vec4(Position, 1.0); + portalUv = UV0; +} diff --git a/common-1.21.1/src/main/resources/assets/antarchy/sounds.json b/common-1.21.1/src/main/resources/assets/antarchy/sounds.json index f17ed18b..c10b45f8 100644 --- a/common-1.21.1/src/main/resources/assets/antarchy/sounds.json +++ b/common-1.21.1/src/main/resources/assets/antarchy/sounds.json @@ -1738,13 +1738,13 @@ "subtitle": "subtitles.antarchy.portal_gun_fire_blue", "sounds": [ { - "name": "antarchy:item/portalgun/wpn_portal_gun_fire_blue_01" + "name": "antarchy:portal_aperture/portal_gun_fire_primary_1" }, { - "name": "antarchy:item/portalgun/wpn_portal_gun_fire_blue_02" + "name": "antarchy:portal_aperture/portal_gun_fire_primary_2" }, { - "name": "antarchy:item/portalgun/wpn_portal_gun_fire_blue_03" + "name": "antarchy:portal_aperture/portal_gun_fire_primary_3" } ] }, @@ -1752,13 +1752,13 @@ "subtitle": "subtitles.antarchy.portal_gun_fire_orange", "sounds": [ { - "name": "antarchy:item/portalgun/wpn_portal_gun_fire_red_01" + "name": "antarchy:portal_aperture/portal_gun_fire_secondary_1" }, { - "name": "antarchy:item/portalgun/wpn_portal_gun_fire_red_02" + "name": "antarchy:portal_aperture/portal_gun_fire_secondary_2" }, { - "name": "antarchy:item/portalgun/wpn_portal_gun_fire_red_03" + "name": "antarchy:portal_aperture/portal_gun_fire_secondary_3" } ] }, @@ -1766,19 +1766,16 @@ "subtitle": "subtitles.antarchy.portal_gun_invalid_surface", "sounds": [ { - "name": "antarchy:item/portalgun/portal_invalid_surface_swt_01" + "name": "antarchy:portal_aperture/portal_invalid_surface_1" }, { - "name": "antarchy:portal/portal_invalid_surface_01" + "name": "antarchy:portal_aperture/portal_invalid_surface_2" }, { - "name": "antarchy:portal/portal_invalid_surface_02" + "name": "antarchy:portal_aperture/portal_invalid_surface_3" }, { - "name": "antarchy:portal/portal_invalid_surface_03" - }, - { - "name": "antarchy:portal/portal_invalid_surface_04" + "name": "antarchy:portal_aperture/portal_invalid_surface_4" } ] }, @@ -1786,7 +1783,7 @@ "subtitle": "subtitles.antarchy.portal_open_blue", "sounds": [ { - "name": "antarchy:portal/portal_open_blue_01" + "name": "antarchy:portal_aperture/portal_open_primary_1" } ] }, @@ -1794,7 +1791,10 @@ "subtitle": "subtitles.antarchy.portal_open_orange", "sounds": [ { - "name": "antarchy:portal/portal_open_red_01" + "name": "antarchy:portal_aperture/portal_open_secondary_1" + }, + { + "name": "antarchy:portal_aperture/portal_open_secondary_2" } ] }, @@ -1802,10 +1802,10 @@ "subtitle": "subtitles.antarchy.portal_fizzle", "sounds": [ { - "name": "antarchy:portal/portal_fizzle_01" + "name": "antarchy:portal_aperture/portal_fizzle_1" }, { - "name": "antarchy:portal/portal_fizzle_02" + "name": "antarchy:portal_aperture/portal_fizzle_2" } ] }, @@ -1813,13 +1813,13 @@ "subtitle": "subtitles.antarchy.portal_enter", "sounds": [ { - "name": "antarchy:portal/portal_enter_01" + "name": "antarchy:portal_aperture/portal_enter_1" }, { - "name": "antarchy:portal/portal_enter_02" + "name": "antarchy:portal_aperture/portal_enter_2" }, { - "name": "antarchy:portal/portal_enter_03" + "name": "antarchy:portal_aperture/portal_enter_3" } ] }, @@ -1827,10 +1827,13 @@ "subtitle": "subtitles.antarchy.portal_exit", "sounds": [ { - "name": "antarchy:portal/portal_exit_01" + "name": "antarchy:portal_aperture/portal_enter_1" }, { - "name": "antarchy:portal/portal_exit_02" + "name": "antarchy:portal_aperture/portal_enter_2" + }, + { + "name": "antarchy:portal_aperture/portal_enter_3" } ] }, @@ -1838,7 +1841,7 @@ "subtitle": "subtitles.antarchy.portal_ambient", "sounds": [ { - "name": "antarchy:portal/wpn_portal_ambient_lp_01" + "name": "antarchy:portal_aperture/portal_ambient" } ] }, diff --git a/common-1.21.1/src/main/resources/assets/antarchy/sounds/portal_aperture/portal_ambient.ogg b/common-1.21.1/src/main/resources/assets/antarchy/sounds/portal_aperture/portal_ambient.ogg new file mode 100644 index 0000000000000000000000000000000000000000..fad34aee3ba9a9bbaf971c0b044b217598d285f7 GIT binary patch literal 37697 zcmagFc|26n8$W*TY<7)(*JK@A*|(&z?-^V68boBt8Y#M#kZj2o$`G;@LTOP`36(6# zQnIxOskEw;erNRge7@iBU%&gh#+h@@J@=gFJj?re&NKG=_d5X$_|LWAbXtm8bliPN z6Olv?9gOr1iegqErcKO$0HC_a#{W*lp84eeF6NWW7NxL*Gu#1x|F0_v`)?Rd*lO!@ zC_r0%tM*nkRShisz8U`chxr}c=O4KPw)w#}H9gpxGpDPxn}2%rEU6D6!@UK1slN)$Dwio6mx z9td6*F}CGN7FW0Rd?Xu{x22kHoZ(4M8p?A4%m>nq;`mcDjaav&%IPm$jNOBs5w zE{Iu2H2&|0nO-1lHDZ-WeWj|)nes;Whj^-)o*NO?Es#<-8F!YZH0i<4GFq0@6oTG@ zc>O6HQgrW8Iue(%dHi@&KS$ku$5v>_pMa?4S<-BE^BfH$wcuDe(ub;vB48 z?s&LcfED($%&fM|d2N|5t@!t zW!3~{zRk#CO-*aU02BZme2u1wlYvEn>7cXA_?6Jf|LgWF@)y~79MX{ZBq*SskfDkd zA#CgeoCeC1soIVHm&mBFHFp);MZKBwS9EvoQtMVdiE_)1!}(>I*Yw_&M-4ofFIO9Y zrK)mw&QjR#ZFx7{o&8@)zV+}>sUov3VqlS0&eH=+au*Nhm1cLq68>UPPN_{RELmPV z>{w>Qtczj}wehiZe^I=gkNLH(>snFS(0y34?^&EKC;qR?M!yXr!}U^#Y1uw66j-0U z4J4WK_=>788m%sULGxL-~1oH*{inlJ_LA2B{|MgH#p&;4)dP!*6Mv;SL&LXU7%zutmO z0r9`fgc}ON`7Jj1&FZp@%@di&Pn7MyXxj7baNsu^(R3LW@OID%bRJ@)c_n7D*ne_0Vdnq3y;(Mv$P0jezO+KVltTV1-5eP!N?@NNpb(f6nfa2O^GY{g z#wuSnD1ZOqqPjDsi)YR$Hf(eOL}nM}m025?Ss#~K7nie;kso-bsII0#~i^-J&m z|89;A2A2UKTE-$$#==p?%n=qgsEcwksVja@O~x!z`Oy>2e`_4(+W`sc8vRdc{AU9I z;C#@+|7cj;j#qZnQN_hcWz^AZ)XC-l@3I@wwBv(>Wn&8LkVjPi#o`>Jg{I;@ay5N? zm)?B!ct1J0{4G(Bz2dyuf!r z;ccGdL%n%%-%Py=<$eQt|8qh2Q@>VIlD{=x{O}&_xzsVS`;&-VfT#r6}HB}uijTer;7hKl*_oY%cM4J z)pCgTc5v}`ha(^Ib{}_f9ZP^Ck2={s6L4`Ua(OoD?@|=AH`--txA(Jg7x%G*$+6>m z#}e+=HCzvSc zVQ-B8vvJem7&Dhbf95L-{k>!TU9ZANLR}6e%tU*87sq(J1$eF=ABl<(O z_wF5wnOU_Rxnegy8Z+V&Gcy+R=1l115BOM&OVRPYu;2Lcxx-;oLgyS@Vx|hb_rk}7 z!uIMszZGWC9O^e|c)3}c)8bZ(viGF+^;>o&F;fd-CUZJznj`I@aJ;E#@4dw_Q-SZU zQ=MJBUHzY}y10H%fRmW}o-iZqGQJ8)o;F%^)@L=L<^xBe%a!AB25(D3z1cE2QwTysC)Iz+a{&C^!zif~j8^X2jzFR$rvbPO(xa)d+qb6YFIp{%Xd z;H=qnOD5%jxs zu|w2E;or+g1^q5|F<&m*lW(2Pe0laYs1A3vzA5!+x^{meIP}`L(x{>O4i6{WYs^_) z!@Ea)=t>!aE5A>*&brv);hCkE{-2j;cEF*6Tff5wIFyaG))nF49IfxA9*wXe_(1D? zY1Bux`3a|Bt*gSp@Esm-sQEEJh^VmKP(8>MBtf~yFy$V%#afL}X_TS%wpx=_ZN9u9 zQ|&FO+EHy@Jk?SCEvX>iky&zNuc^*=46ZD2bUbho$~}fD_hOo|f-F#MWkDW0poNP+ z`fT8!>GJH5@`Ep?!edoKVh zI2J(1i0!t}RS@M+-k22xZg6jnEVrZ(p((8)I$A^BC>#!9iE1S$zvyLNkO6s>^%OLb z*#%5H$t=|r@cs{*{U4O}|CI3HhrKTNv2%j1(5g$R+@ge_a8QPDO-UrDKAVD8mL8$9 zpa4?JCrgdBruwOeO<@$#+N!=|!ndhBh3aU1@xG`ZoZXgVn}7qCR#NkNGxfy?v2FyxY$2cj?;q*&`nnu6P7L-tmK!N^KqWh+1KS z$&+fUFH%+@!1T(}dn=i`a)-qSfY=l)Fipq1r)Hj)={5%f9F zCM1b&@Gk`b{_fpQC(au6tL?Af!m3tRZ)~lQre^YxRIA{`zUBCqsLU;x%KN)*?nA>) z!~s@F0#yoC6$?1IiH0mj@|5s)h;EJdrCDk!GRr?nA$#7f<9F7B^4pAy}mMvRFbi z46i=GU;&IR7*kWt^i!3scZTOaA-oWz{O7{qa2qAahX9BB=L1nL{cB|UK+O7;(c|GN zTk49=&?`zyN{b6i%BxPFC@MLfT~&6rtiB?*ptPdm)G2ydPHuf=QE7Qae!l6{7wU&q z&5zTR6dZUOam!1w*s!Ym=Mw*`)}Fb>^Dg+(XWk5p2DX$Z_VyNJB>;58@gAK#w9TsA z6mZer2RXSct-t_aWYIiO)n6ia+C_*M2rs=x!46F8wO@+i+)6 z`ju6O<1YdAEAME7YbKVM^zM_*^O{SDssX3-=bML4`^|o^BOwqd8?Hq(tVB!hz;g|7 zsXnk}WeXlSQ%FYgsCy@cQ4NsPj0e-d5=QoKDfQq^xVpnT)p|Do6BQA-`EpEn(z#EV zWpud8-Uw9VWbBv>)`I87J$-t8t8>28>j7R=s?FV5d}rgJ_Cg1?2p_feb~zEGfSSYCgaCyEY=4dzHPaMFUT-fU9}vDc3!p8Q5Vn@s#|EpoaQQ`c1T@z0xetj6iY zQn#shJ!0b-n?yW6Un7n41T6h6ZG(zGqt_N1y1bP~n-6^eRRHJNecz|BNZt?Dz=c~d?cV1tNydi!@igXAtshSU9yi1bq;whzf4!K_e<(DU%=irqxX5k(xU{>yIP7r(l0lehiGP4hE<+H*BXy)THFeFGAU8g-*A1*e*LJNTXsZj_P zRw-Ax_UeE*u+E;yh<$IPl3o)wNS;U%0N~e=B*p=`Xu<+UTN@cNRw05=4l79AOOz7a zp^B#41)JG*U*y@1f8NGH{~%mLu>oD%Hmkhv=dY_PxHPA)SNT-Y<UaC1PsAobN9uE0hdm%U=#H!uXI^>5vA zjgX8;P@4MwoSf<04~1X>(?x$4NW*&2K%h$D5a3afffg^$zI{1avtygf0Za6r>f2HA zvThYwDlTyU7Qk5UxKdrMPbu~>w@iQb>xOag)y|G{p?LJ2O4eL^`ouzQ)o(sv(8AvK zc;|UDh^obpcgt31=n>KJ4} zUwKN-Inn_Y9{W^fb!_ze(I;()sfgnJ=2ow3V{hIxANAZRKRS0qg63D3{urbD$`3&k zcYV}}1L?D;iD`r%jcnjG#Fk2Q>|PGAM{J$mJY5`baPrcO;nTBv>+8z#uix`w#A7Yw z#t2l5h9H%p27o0HW5~6z@iW=%%~H|&=GyVIXwJdd*J5WsOIFo6IrF`LiZkD4(VlOL zwjE-@lNoqjZ+<2x)tAne=A%D3cGaxD11@zTMe-maqzehf6C%TqFYiFyG~*{-0kZ~z72 zKn;jvz%UPwGZf+zkL`$@!CWhx2e`fpKgGWoy5k>~e)o<Pzl27bYJ)OKtoA-;pPI%9ra*gPv!1t_QFBRN?c=E2=ou)Rm&#}y86qH0gV4gEVGd@2 zK4u{l5c5DXG@q1gUbNA*<>^_*sk4$2nt=i?nZ0}I)ww6G7xan{7rCL|KpZWz;R#c} z%EG~(N>h(d$eyXuaH(BAHJ~RoxMVXLB6P2TVk>8|R(j|YD0kulvtK?P|F{yu5{E}| zrf_qBq)p-@i^{8Rw_hiEay2ypFsn&T>Oe#@L^MdW9s(f1m@kauiW0$aSvg6?W;9g& zeo%N#yOeQEySQ&XfhR8B6;z%ldaVf!?N)wP7yChassB&qZol&*{dh`nvvS$)krX{qcZQ zmtvUo0-y%GZVU{X9R+m3eT+JwP%y#@kT_3i*iP*$vY|w8D-Sb|6B&PBhM!e`oLn6I zoe-`MGK(m|%VSy%tXv2n^LX!`3vX!J)WJ>R4moAGPkL=NJ7PvJrbrr_CV85hM)(a$ zIY2$9Vt3HvJ~g8)QHOdVqQF5zyjuS&^yTq{}Q{5NKoZ`7Z~wNvQ&TrQY*<@7WBeY zCneB*Hv3y#BdVe_4xM)UPQbRW^Q8N~FlpZ@MvMz{4>&gR3Tb-p>^*6kx_{&px^Rv_ zkJ?T~`8EmP-{7R}iLvOq4z!FC-3`_SNRxOX!$+k>v&$*_a;Zc@ZZW2 zK&VVtF5g6WdBhLd{gEX;eM{xe5CD{y#o&lw(Ra(eAl11$pFM#&;*6US3mJgd|I7@Ms95DnJ4(u#UHsU@Utfd8XT6AzfEI60k+>DqcsF) z156C}ssNP5t_z@1=_c`Q>@h@SnQfm5y8WL7UEb%H3hYdm1(*#ekQJ z;R08_i2#r1Y#5pYCLeB!ftv^L%A0j#M4YwafBie$rHyn+QPAQB7CjF-!vv7ynAbPe z-upUU1;^iioR$onuIM`Kd`hG35zp4W<~Ie|<;O0tfA%GDMQSD?rt>Kfv|>Txd(J3? z!gZmRu}7!Rng^w@%<{gdmlHv`!`bBEJHff7sh<^9x0hlzwN{MuNs?kqDI+bAb=O8=FoSA+F9I;`9k8v> z$V=Piqo9nAj3R}{in~WS3GCzpimI0+LUsykUrGFe}|e^6NbKA2ZSZLw3|U#u#k}m}WOT@zJ>0W5(%2?WV`c&XHr9rqkWW?rN_H zQE8z7E3OfSc`6%n69+uLVu6&b6ck5djyhq>3D@qk0q#-ke^i}09jZb6Runvsa-LPj zuZC}~tLnr)d7OK)_cAcXU`3)pMhxN%(7)%@Au&|nJ>Jil<2=Ma{56<>KxG|ZFNWL-e~J>p#5 z9&9`2xQt~a2L_NdygQ;x%R1g?6-xOA@yoG`2&YdYE9=LE0mEvEdl3+i+u~IJ{5j!2 z)r+Q`e26)YW52}?_9)$Y)m$~>+m$|Kw|QDh!v?Ml6``>CzQP=0bZkX{CW@WlCY9rW z41|jttOv2ABxpAi$}1V7uO|n1Ry%K|+>B2@Nk`FFL?LJpLESwkHL0W8e}j}$0L}=7 ztaBrx>*?X|uVc)joXYLL6!G;mJk}|UtdSyRM&A9xNj_|Jj04b2&vr)p?Af2=omp2=?6!XOfV2i ziZeV&2CS$z-!OdZ_+Q<=Bc+KSOs!<613P?By#Ylu&fdd8NN_y)aI*@JaaO=~Sp)+Z zde9#O^y$O8+>!UL)Wz`7o0AZA{qHizIz?xDF*l7*y;U&Zdn280vbFUU44Z*E!o#>^;>XAEs4z-&IelC4Ua8mk`nn*Syot1T&I0w_06a z?jF}!c>L33tpx*07RxAXde}On5F8L+g{BR#w5S;MvH|6jfB-@Iw23uHzZl^Bd(t|) z2%&t4X4sdUv`MEN{4o1^9!25qY{vj@&?&5uMRhR4y91Qt^HtBtAl`(I+)sCIN5ss1 z>6DLE`{sTOVzE*goVIjxUt3g1#VQH781O3?u~OTaGaPvD%N)-nzX@1`=rj3FC@2PV|p5nk#oNfCR+- zqIu}$XqLFw2Qo>T0AyT0yX%C_9<8NXDy`>igwCP(haCuo*Gd@tnq)GMtVjm8Dg4j4 z$WBN%Rv3{S$8vIl<2e124cQON`1$pUd`X{<07?t?_i*2WU?5005_V86u;mlup~h#rdym?zv52H1@b@3|Az9ICNu}fz_6Rj8(?A zpaT}>jxu3C-D+?|xo~-6tWRs#w%6P|yQ69_ns*f#f0D!u;;hP+B=3yaCK)5_XrsB) z9`;k)q`5$x#2Ja-&J0&k&LJELZ9d2}Kkb~vq{A2*0E$U~yv%OD&m(=Od@zFkjs;)| zaZY}LgPm?cgxG9+q4V5Rg9~p5a$kjxK3CnmNkaMCF>8JrGQ<{-plAe8LKOeY_Os=S zygiZ8&Esy4IWLAo9dg_Fk*^Ilb1X0Th5SzuZ#p9st>50i?#udks6-&Y`Bpi1PXuwr zUIPn4%?OY7jq)@r4S;)`2hW`ue1s2kh&Om>D2Kf z&a=%cR_bz)B%Q2zak55-RRHxo;}E3K_Vw_O846=|O(Ri4ca@nKc1c8b2ua6*35f5H z$k4HXiyH?u0+k&`pTTH)9N)p`UHlGOt+;UU7nN?-2yg{Myw|F(_1skN=eZ;*KP-@=&z2 z_?;v3?xx+bhyK@IPN0f74@)4xPz-vxW8$s0U!(qHvCiu@jG83~mS6@3X-NUnfR3L6 z+Gyddi5Sy0I=ZELahKWcbDS5%tm2LK3b<1qeXV;w$%{k51;`B?z<5zP{bF6^1A_~b zUSeZ=++8+{rT-empiLlnlMeSHo-^%wliyv>vN{BH{f(^^0a!rVWaohiL}rwiZwEP{ z_IwO+@MMf)2+0JdZPufkpNO|kcPEBb9E$p_=ASxQ#JxNB7@XP&V#*js01XKNyZBfI zrQk-QQSYq=AC>O^xL2*-=R$uB{t0WWiOaPF0_%tIq$)t+$nJu{Ubv(Yw(F)r;N2?C zuCVdNDeZ6P%0swG*JV2x@KKqTh2T0%f7LNMM*M?8g2l4$Ye< zB<~}=o1Lz{o%q7i|M^GEa`GO8!lpvSPCyG}z5(_bAKwKP?hw5BD*vKU^V8b`S*oVU z(0v+$1|Wb6eBX=@yG2v^JEhL{-@KW^epsg4w)XgNdE(fprJ<=`mMUn8`rJwwAHL2> zBKZdYQUVlNgDe6z_gRaT2+E?`$|MeqPPDn6vEDKL`sUTfJ+;HPEhe`|$v|C=oY;Y8 ziv+fO0u00wHNb+~wah@%ld;9%TRR4e$=!H1=LDTPmE<1KX*V3HW95`IHoldWDnSK| z(+7+m3Jf-G>%jwAmV|fl`4jWbRDoeNw&Q_(7;TWJRt!wW=An)`ivD$-=^@hK*=w^9 z^q=PrE8H)dIac$A^X#!ieegz>y6Jm5oTV-f7;@Y?IS3{4S-XF8#Z-Re_r{`av1%7~ zO*}ZAyq~vm^VBQ`Sdv+WEWj0Wz?z-QbOH8$pXh7W*v=!h9d|PQf@)GrG*8UBj8UZk z2y~Bu=lxE*@#JU0WUDUhP^GAWy=7cW(hYu=lc^bP7ki`{DRL=>(5UNv`HMz^mI>kO zp(HlUqW7u0^64}_L6XnUeJ3;Mxn1mp&}uk`oW<)Eh`Px^Uz!)Q%dO zmh(x*XvUT0MMAP!FN9kF>IBGc*J9h=`|j{ue*0a-uy!t1>+-?-2Fd_vptx2CIHw6{ zFrx`H7QmW^LZVU5Z&hnrUv}u-hI#_$FJs~_6;ZFHFtZg8ZxCZ1ZFCbJF;Ktn=UFH@ zTvtFtNco4RBF<#&SVaP5ipDr514_KWicY35q4f`p3cUEzzyUfh7%?`h;m%XBKW_Wt zrg6A`786DiGh*9RK6U20z5Qji69#1gcwCEkQxd>g5P<XsY8tPdL21*>X>BR;-hId z^qmoEwk}wP%402Qr93U_$6_16DfjmuehW+t{&vlIj}On!JfcsImb|WK@Cl$tF)&&y z(+4?Y0|jK}hLnAlecosw(C zPsz5bj8|4{8hKA+KCjBYQAF9nZ+5T}L;~HoXH>CDbr|}MHK#YfziN^I+Y4o`uom+Vkg802`RRSjb06(zIBzpUR-jt$o=sR5} zfaXLQ)@vjLER-*{e)gnUcU~XUHGBWw@(}Si3ln8{$xev&2evS995-3|-GDB75s4=NDQ0Ma z{xWuq+dZdwknNI>ZR7ZP@?*z9%#uy??0K06ZFyTI;QmX>xqcSTeIfL0eq^Dekr!B~ zJGjD@cCa%@KGFkv6&&dK?^CJXwGXhXvj`VS2d)Ng)w+5vr%hb`T35mVZ=6lC@@zLx~TerX;sT(ibsC@;Xa`M?ZWH&Obgh^2De z5T7ziiRA>hl%_dj2Llui#vi${NeQ5D-c`u%qI5<8p^^xGNC_6Y0HDZ_ASKXzJy1fl z;JvX~s+`c=*TJ8l*q!#QDTzumr{p)`GBbmX@k3gEFg&y z!7ssV?r;1CyTz?W9C7qH2&urGqS$w$fm7-?-?a2K8WMTG@K9w+6=Z5zn@saU3HI$a zo9?M1&3=G0W9u)~Z>|942UJP>acwO%_e^{b4CJ>F$sz5uAIQgk9#w?*9LG9&vmnLf z;uqSburF4%b6+&m%cVvj>=E25S0?(sh7%2^m8Bryc8W~L2r3nz2$RnrEod9eMfTE%Qq+1 zg4z?GsNLy`{BrvUAK<6HKl#b7EboN7@in8v?0c{{7(Ln}mbi8mp?u}@xumC-{byBX zP(=*-O?CM_n6pFTDCoN(ja!kB3St^Zik7d3{hF7qJe&cVS_LWCND) zaX*1=cQBNF%g@a^Lp`rXRORfCspRckVbv%xVJ8tktTgvW;RU%Q*4aVGOAQ1_Zr&mc-$A==_VLH348Kg4gCpG-QNTYxiWw)r+Qm1@h0Zcr$E71Of&&={0vQ7< zTAWcd^_Z%x3l1kf+$&A}b@>73Q>+N_AVkD%SJKjJ8(qaQ82O%wp>&iT)lk_l3f`<@ zAAWrK!Nn(*gX>r>0;;#fA`HoHTh-dTsSFrnsxI#}ogVgbIW*68`0O?8XTL~>J(+*y zCfsBij(L`s_tC4UW!SE9OaBIRn$|_PR^oXnCofha$$m0+OzY8t$AT~u%vVl*j^82{ zB(SQF0k|2ik=NXS3df=NX+%lhdU$^_Q=+!?i}-~2i6=WJCzcgtN;rRdr7h>(`*=g= zl8SRkAB=PYTa8oafXr+eSea{NL*m<=8Vg0hl)p?R>3wk!anhpwPX{`zttO8Wpqa?e z&GJ6e-T8KuH`5K8tk;|BGPSXmEgDNW@4NhA?4Z4m&j_2rN`T4;=1mad%by*!JtuJ8 zR)I<^Ha?0EBDl66>jE~9Bq^0LRk)xa#Yhfdp?K@Xo$)&Eev!0HSA*8e+2sAf{9_L{h5EK9^D^t%>hJQ&)|4Jit@ocg-VQy>5s>T>xP z*_TAH6Ns2Sf(j}^g1wPus_0}p@L|d3FlWW_tv?dOE7u>^WaN1`u~QkZC)b?SY5*}h zQDxG*@z3G%mwT?=DNYK-Jj>#puMRoC684-$6ql+Yv+`^nx}yl=(Pk6jmHHo=zBqt= zzlETG-tD>(0zPx_0y#9m7zQSxh7?P@9J}>;Bzfw4^ehvdudBZU zC}vqz+2!z`R->d31ib*t%FPQ{M$4tK#wdf8CVXFO`TDIX%&rpsSeW*U{+SK5#Ex>H>Og<*Nk=7TVe9T^K zhx^R}GPMN40kW%T-1tmAY-0!HP=(BI4kygziwU+0toYY*xccnQ)2XGbU(VUE{rrLA zWmuLucm0UB1vDb~diz)f7D)G;OBN{d`_M!Zo4Iv2y*XT6MWk*?fLxcNb%yDcW(<0H z-c4vX*s(ajymLSYdf$8{_-^1iP*q%<-x!qb9QJG^u2zT0!NY)l^%F9$-{G;#pt ztf+VP$h?RD~6p)sd;XG#>oqTXL$fE zoi(dTPCm)YUdQTm>6O@_s`&DxDfw9?umUqY_|ck)J_So+1j-n@?N#h+6DTp;WwvQ$ z2=dT6t=TJj&Z=o-B&p=p-#FP`kvpfAZ_Q0c57~k^>xnoFP~is@OA1Uhpkzn=x$ArD z_1J+z`mN;#LFWRDjtcIe>rs5*KtpndW6qGGP6Qb+&Qj@zy`%E$^p=l=w!ijcZ8KpU zaihZ^Hdr!Id)fKY;R8EWanwj%H|Dpb%B3po%`baqg((8fM(`M)Jux%(iiK`!;-!il zOXz0|@dfS0D7S#R{mLscS%UEFZer*z8zdTF;MuS*qjaRVAaPyvQp$P8&v5t4yDche z!bNxMFYy98?1Q|-zqizC{g@zYCLk1F+}%-^FmybL6IovN}Qw3eSS(16%#J z!qP`|WxzJz0RqYoiQxpchT)fICh>s|aT515(^Qc}F^FBWES^0Li$COA>wYYD^|4CS zJOTb<3fqDWxCsGL6X>J>87dA7&@&+sr?0ejzQEot2h1&KeO{7qJ%j}(Nf+-RwKejcU<~j|6q=!=$7rXZZgNo(8?XLu za32QCfiw9?nVKnn4YK~lT%ZX8ufJc^gY$qGW!D~ zn=5x|ii1aJ`;>!&QZG8!6G>id&Uyj(h7!rSpZQXUk-{BXvtZ5y)&05=gi#s zoR8s!m#H86-E?YRP5{Mk1Tyy?eEHky%7-h(#HV<{$54lPVc;SN(;3? z75;NIrH(y76GWaT@P?FQVN42qjRqtcCZMb}1tkx!QCGD&b~)uzX(_HNMqSGtR4I4h zoM>7J$_ap|ZY303mY6Pfmmjd4y`A~>`ldH8dLy02ovPN}jXd^8IT5;MI)dK&$V$eZXQ24s-mlgk8SuR2J8Peaq%5v`xE2ZxZ4BqrI>n(o z%Kz}0yQWWii9CrKlk`oFglSu`HZ6|N?M-Tt0F2=wx!v<|=l95?V=yOjMU)jJt|eR@ z^on{Tm%3=R6P`xf6yrbQ19Nba0L7XEj0}l*v^lh-WPFu*KteuonJuw&({XP>I6T?N&s?fV0!IONX512 zH|f65H^p7*ef#k1RcoPShR&}Gi}cN?3Ib$*sI*?-WurWyG7r1FO!k!h{Ji6|hVh5^ zwehWDtWPoq1GjV~a!^@8b`#~}6?vV?85~(+KAkmOi=)5oLVkpBI?MiyitW-9{FKP} zbUX@L>NoXbeiOi<3V%W_{f2S%R1n9f(QYxuHZ$cPMkvC26rSU#GH0e-aZPcKUt!?S zs{_h!-%QH9`cVHqx!69rOr$>!z^q+fKutad^uYl@e4xFHl}W{@pWOSKziQlMJ$*xY ztd)c4&)%cx93pEf9cM#szuLq-gpj6d03Jmr!el#-z@&XGAwXp6vb8WNjV$q>88-Sq zO_Q1&u&k)pyfcaeM%!x!GoQd`3)*>Y zGYxKT$psZ6ZCeb677~J)1u_&Y;K|=O#I(ACBbuBRc_2042z*h+48RQ$u6ie2~njpEWmx*d*5j{>-@t{RF0L3WVi5i<%L(1^N#|seBj*LpUjUTF4s;59K4!n zd;Zw?shPVxf-o&YL=~$7%AkXao7fI0;fBoU%I_BGgim{~o9ZD#f-8~ItarALnN=49 zYAigX0A3Q?E_Ut<>EuUA`_|GrWiF3!C218++Mf!!Z?a<3lTzIiCR#Ea;DC`qAG9|$ zuCNpRElE@OmPuM{MvJiDtd=y4h}rhk0nRDF&H;5CNaR=#-)vpzK{LZFHy7Ol({ut8*~rJinnw*UDI!>_Mj?wisZoib}8ceL{J6RZmoFKl6N@*9Xp-_JPxo=kE1$x!) z*-oO*Wp>cWPA^1gvKn912Lj%1CwHfq7z~?YXan1jrtX?UatbT8Y(p>nnv?KfzqLph zY-3LTme{)WM=#bnJEf89L#i-Bb-&r#vw$v+=;FZ@RL~8;lvEHRc1m*tlL;B&)dh2?3Zjvy-_P)d;N%fYJ-$6PRC($CZ@hI7#O57K z8V-afO-I0Yen4z#WkUrCKsn!A9?1W zd*R(--ltkIa@4m*yQWH~*!v?#&5=#~U|MPV>o4o+Liy#NL27U_+n;-HS(33}r#-g%=T#<{>R&74Ab@^~G(ep+_=59lSM}aw zC1$%*(p=@Cn&ntm91JN_cerpdR~GhWWD(ff0jsbWiuoVZ=U4UsVITwL7_QKc*ha;p z*>Gp6^$q$k#~x6syAJ4bbji8a=^&Pe{R1C zs5jA2H>oKRxwG&NgXBFwcR8u^y#tClx}${^P}ycG_1v$xK5oHcvU5TO*;#~<4H&fE zKTl5g-PWj@{bSMYX7BPA1U&V7g`n8*e89m=Br#9RXZxd4SG}86i$SLt*rvfkg2)f3 zp8hc9vx~mj219W(zW?{)wikgJqC|aadwf39{jV-$h~edhI4r* z!a&cJLal1Zb};Cy(Aw45G&GpL$gn*+^}n+mZa6f&m}famy?$X7w^_L~w;-=D=VZy5 zQ>RW8=4PKN%srKtdn&s$r|fKT5&e8cer-irVL@?GNls?*Espi&3XxjX%%R6`&VP8= zuDUL`tb8yA3#fxYvku(_AmNEyUZyf%qWoSoAYR96%@#vOz@JjAuOu1&v4&TQ~%Tdy0f9|{D%Wz8OQ8kJv z7IZ<(6ws~VhTS|=GDy|uN8%N{70r&{1B}6JT*v;Q^O<0)<}reX(o}pti(cz3)RDgZVzk-!#3wRYV0LANlMP57Siq2stkS04%U-Q-kKO}Xt3IHe)i{W^Y8CxmD6O}YtIHRN$n;ph;RUrHjMRn-5rp}IdH7h9SF;f|6I);hM9_5 zc`x<)z_!~;ID5xZn~x>Jn!0uAgc8l8SmK6XwX)gDB96O$;HMLgY@cv;$JtH2Zj5gd z(|pAc4F+fqA!1P&8g@dDc}&Q(5$NL5@M-O%*=pu>!b*n%PTUhJvqT!Alz4e-I0_YD zawJdeabss206u-oVAq5Xv}C#XeA$}wcqA?mG=VJ_?;d@FS~N_bM{R86qHa9EHI=c#v`Aj6}z@ z^N~M1%sqQdYxL7DokfV?G-%=ZXE;$&@Y1MP$kc8^8>7*!yFEyGZoO*nw-Fu2>k zq5HS10Y{r(Z{|PCa^XON^^e?97j~Y?h*k*mn*6a>!@Ab4LZPeqF8609442g zVXr~rObK=N#@{>$&c$w_^{*tMEs$dEb?bvq2 zjjWo$b=zFInBf`s2T{jn3ZbM$;@Et8l5qh*pXLW$x)!!set^SBZ5;YQzlARK z%V70~gj6!l?YeHHu#vC{eMJ#c{r8b_x^s)U^wP^OvgNAD)W5EoH0a5l2FMkB0OZu zTof5kl6j_58ID3Kg~||-5K5*7Nrvy%`}tkJ_n)roy$+tUp1s#z>t6SL?}GN0VgkfU zJGKwW1#OPLwK=!jsD1pYW7^#P`@uG@$EA9soV!Izm^oxsXoKmwzw}qVM0*}-_{QBp^!I#T^ z{-n8VZdHFlMbm@k?`|o5q#b=a;P?KS%xYX;4iF;Jl`Ne)f~D)D&G?Uz?Q&qJI$hbk zL6dFbXV9wZ-=dodn#ZVnPfnw+JP0V2O(-jTdsvF(|0ah*E7?vFzYP{`pBc0t{_>GY zF@NWmh0@7Pt6JHi1dIjD50q~-K#b1-0Y*buo0SlB;oA2r4w*wMe2N;4lgl(QJX$pk zSg&ms@q9>nhdu*C1c!KAhFnnKtuF&YxuwpDtNd@j2Evk0#uWPZt5WW9dFa|ro-uW@o@zZwB zi2EBxk8_94;^cMgsfC`dSXC%b`aSQ!s}IDPEfG$TKcy@`18DMxvrlf_zx`{>*#fv> zN{kRTI=Jrk*3a+b7ptBXh=xbpxE#9r_{H2`mJ_%Etr?S)ULIIGgDeo94v0e6;i#KAZ;YDX6`I@!mhA6Y>$yH+SC&BO5*hf*=8V5w)FiUi z@Z|X69-RnkRvwAqOdU&>vsnvj4e-TW#-n*Uua)GaI%Np-kks^#(4&1Ia@hWSNbolM zx6fyu+?YAf`zcQ~23P8O>DC3eC?WXpj{K=rW*Tj zd{bg*ooh_ez?C9H<~2sCO4yZ@fOezYmthYvvipuw&Skn?_^x@R#)pHo{F#rrcioON zMbmHY9muW4eq!7J?W~cQ1)|DcRk$P7o;~6ovbMoGy-sfr+v{)J`QD=LO@oxo`h$Sn zZ2`dG6DcT})zfz*7qq2EKe8*~S?FM2=CH>th%o6PI8zS`f}|102RE)7o$t#J_5VoU zZ6 zdsSF(SrXbU1cUJ;#IL+tf{{O1Uq%v7Q)!#UwZX76AG_1WXKr*=OzHlEc|L(1-d}?L zN3O9{%`>!2+5H$wyiRXZ_d6?r=0g=9v}$5^x1Tw%D}aPuGy{YdHyEV7hicCYc`7Rq z6|LTOgrBm9h_5WLX8XmkhEjPuLpC)tus~4b|0r0=c#k$)ta@#X*uY2@>5mUcO2j&ZoB;XWKFB@qD4Bc==C3OVn=F6K3&;qdadN--2IU;BE&3R?F=lZ2l{ASvAOVJ z!WjYIKfPp53__{c;?*vSb0(>9px7YLCwwc(==uVtpUSV!pg~KlV=Z%~8;6_4F+Ocac$L4eBr}S0wcS8w1-n zCivEX0Gz4x$FrvIf22CHKcHSMvhqzhyLE>brP+j`iHC%J1=*J}m#G0pe-GdiTHBdP zoS2Jfw-tQX&Y5N>cyD9^`Eqcea;nj>lKD1@31kmq1>;dyn@~$C{#-k$z@6kA?32ajZf0JfeJ4iGjpxh}+j|~3lq>n|?)$4H!QFR1 zN1WPZvbKfLmT1BG^gaP_zl?!5>6VQ-jSrx8iLn+}Z#)<8+j00RkR}3~ zed?gTo~!hbQ`1R|It4fK51DNzv|kN5;Crt<&)wHvRCP!BKotLCA!x@>kZ*nAX)($1 z#{rBLK+*qdGZKx>o%6iI**i4{{PLB%({-L)*Xze!`73jlR26K@_Kby8kP+{ghqVgb z`|X);qxSB=FLA(CD@kf~;XI-4IJ!gji_R$56a)>beyL9Y(Ae%qdQZ5B^r4b_4>IWBRkz1px{C6P?qX zSmRj4s65Zpo4GD+qUaud0@8kW-lZSoUkiQP{V*aPohb`2 zig15@j`;ODwSWR?KZV^8g(7ntOp0Qz)W#$<@!#mu^?ksvai>=dXOlM=o;h$RnaVKT zeevnFY-r%B$E{R(;WvIGPdN#WoG`3kL=#ny=uypAG1XsrWs>ILp`2d94v6A$SKe=CH5VnW~XZ)!zxR0LY=K+%Q!v8hl){^iT`r#}>~rhNoe zyMBT?$@m+5(L0+Fi}rWSAw9Z9>&xfRx}7{UGhp6-d7f0 zQNoBJNdREAQFh3~_cR+fGEiquk9Cj0aC2*76VH-hQu-h){c}~wX4xTB#fUP27I;P< z@KOgmUn5%j4YM3q#Nw9Nt~|S9T_dfYY)vEWR1;%9kb=LzN9z@%Kt7Ne6kx#vzWnFT z)sFH9$N-l3VrB{aHP^hVbZ~|qKseb@%$nXi-{2?LnD$QX4iwYV+me#vcTm8td3DpQKA_4`mPy&xpv#{`fUr|-yPQ1 zC6AA)$dIVNFDXHf5-3Gshoce>8rqtmD{ zxGO&ZRugr7=2!>>bc;SB@g?4NR9)+NdO3FR{pH4H|W)N-iVc zl}>Jmo#FF8Kgdco^_Use^*j?OS{+;SZuJrl#X((5`&e6Y|IxfK&8AlRgyCoJKiX{i z-PkAKs?(XtJ?Kpjat47P(7?WlBWYw% z8Vq2vC%@*#3HcdZOT(z{DIiF;%zt-9>+Ae28e04gbc*9#KR14A+?kYs0c{7&t(2xQ zD4D#d5^eQ7^eJ!mU3u(E1zz>yq<792oRy$7D16b@kZyRmx?eivDfKDoXCoQM&s-KZ z<+M44Jx`TEIf$hOu56A(z)!(a0BRWly%k-vf2SX`q5B2-?$#SQrykU z>kajYdxZNX+&CAan*wO}9|cz?b+P!4NvsGUVZr3rh)#ROtiyeQ@0tGOZ~w;q>KN_} zXIQU|?)GT8MiJYStie(s0)zIr{Wq4X(%SME3cVMmb^HB`lTX_C!lnm$b+7)utF?D1 z^svGK#vDD`^7~RHFFR0#^$@uE;R7R~-^uN~DqpK16}dHmUXL~^YL8UBarZjd5?PSEK%0;ciVv{zHen*3CO_ zl6@6d>2nTZz*8Zsy=H3g@2(G;VQt&>Oh&Arj5P^yQ3&`T>ZtDW(|a#XAeN>Ig0P`A)= z2)(68U4Zi19+2v?7*W5{cw=FORG=L^6S`{WEjf2*mAn>HrvHqds4K7!&Z|dbd3d1u zTpUp~%^=@Q{fA+Yd^~$uPyZHaZk}n2yXmx+VmBmRKqKqZ_@p4#$Am-fxFiZd4YfV< zkUbeqgoV$DwzrW|@pF4CD3XB5re3Z4N86a}N+q04ZGc%4My=0P7<+*=g+D#jrdC3u zU51`wIWOE>6+fgm2m}@*2Kx=;912aoC8*dOC+d8v2#tR}bNrh0a;AB8HKPCLJt_apr^eQrCEjz*)UH$mGEwj9saEYCIyso|(V9&@rpA8G)WIGX_Ee;w zwMA=6nxf!0@w3=-xrqsx(W|!9D_ob6^n5UVYv7Vg7!#*cBW>$p$cS>q8YV9S+Qe9? z|7Pn3oC-eNUs&2O!ACxa*qyu)c5`uJ+Tz}A&`1N!Sn%_ya*1KIpnPu!zo0o?HcW~T zl@*SmwHyGE_>+9|8lvn~DUDWcaiE2XP>C`iBzRSugnCvfzJwh)F^P`iEl z(C5D|Wkb$XXN(bcsz@9fcF*FL)%9OafTtXi-;PnL1W@!aONb`pLa+?Fe(+~>cxM79 zPk_;U|COwz+}T4nXzJM zSFJ%?%9PN+?{v2+c9YNY&Cg4s)k@E-kbPC?BlWzG0s6 zbgJ(UKPyh2v>V7u{;Wd2m|sKt;bRkZeiND}PLR?}6oeZAO6ISm_nZhyV4eHAVY6yb#BugQB#3cxIw7M7+x#_3(H26)J|o3a~9>=D$FhpwuxC z3kj;iSc(k7e{^O~f``5-0pAP-)pzVzj}Kuq*=Iiljh{U$)~91(jGDB2h4uI%oX)Xd zXiI?ecXsY>J=)Wu0a+jf(Lijc_aImSzQ`E+#S=39w^vF%|K!BWPNs9{z(N-&o`$dx z9XNp%vEB2U`c})zcSQSSMg^u8ko=ka;KBsXkIou@I_mIa(Cn1C&XdjfB~?+s@V+gX zH(!?hpLpw}nh>J=Iw|WT9>A6BtqI-aZj>&j&)UhU!_vsr@46}-#X4~n{d0CUg%uQh z6a^pY#K2R^!M)%7j}LhDwZObgSDR*d?|di6iTA;2j#PY`mK=%@T6DV;ec)3lj1i;+ zV_OJ-nH)#P0OHHdbCKH%y9I5XeO*6kZ%Dbx>E4ZQ|9IzcOkUx-VbDH|SCuya)bCj8 z@q4>7*XgRE`fsk|5-4HgtfF^Ov=bdzPwef~s{!DKI`UJ?V*Sja?YDO^+H4jmXkUiv zL-ls4^v3K^%`Z>ZzdfWt6TtU0$}CR?o2^X$hkUr|nfU0*^!uvMY90+~d>y@Gp1}&} zX~tevbI(W}T+H!%$cSi_?+@bss}{7-hP(heyH_iYe)`2ti8>6vKYR|JAI?Li!TqR} zcS21JAlB%;bZ(O8IYl@Xe0rjd`_mA?0cEVl=))iZ=;`rvo?UcUZs=!o-VFSd#-&KC zh#cPX^eEu$>R}+BYMvd2PS5&#`M%$NSFK`}E6<&#CS~mUtkh*IGv>=}7+t@yn=$qD zsFcD3932L@)q8e@;jRbbQO|MOpKA9`t)e*g_@cHK04gxIwzjXj7sX-03ev?RwtSRg zt-Jv8LW5zwBh}8l|ANz3^#|^9`^WH_p3Z-jytO>`DgN1Y!%B0yvd$i)_5x^ZsvMx9 zzz+Y|-jX8MCAjgmwVjVYvYS?$W3$HOOX`fMO=FTc-=Gucm|m*NlBAgHU}GP>E87PkQvPol{ZE_I z{{QDWFy(<;dDB(uinYZ%q-YKsrEw3QoEi1ZHaJ#Uu3;PXgV+O!FeYvKLt^zvAm0X`+-sTg&LdKUm*fQw0m zE0LTLQibPPEdGd|*h;vbhONgLv?dvWx^#zxfdgUZ8Lh&Y5%L{#1Xyb7VM$>rs=%)# zZ7EsvX&nL-#Hk&YG($Ci-$d|en8fJUxXWv+E_SF2aFF)e$b~~3srEbe<2M}(ZTi9^ z4X3`(y{KV5$aOXo>i3ac$yQ+hqz5`y;r_Mb;s+x{{&Ei=b6i-L*Lt%l{w~orYuMUT z0@JO@2Z%}bpG8`^VrFEc+{A?D7;8uGa~+q(dAe6ktchB)>JQqc3?|@^%8@WVuLp#@C%R@6R(Z3 z?hL=7dHG(70aWhb+}t#d9@?cLR!&G7DaQ20JVB^ifK{!=mw_y<9h)`-F3?Q}0D9zY zYOjP^L}B~qcK+vtO5kUJ%sdRK#L9~ZpANs2-SkQ2l9_~Uxiz{>Bh!K@+-!T&f8Vl! z|IwLHi~XKASB!KfuaIpb@pSusd9W?8=5n3@D5%<-rqdUWJsocpb9ze^A^1%%0NjLt5TYt(tOkLJr>baH?!Nu@dO5px@Rh5W2g*%dM*VlrP{ z>7+d%bA;fk6Rdpg+64}|GgJZgX13uK@lTXpqtt(|_N*!TV99tffX^{CD*O z3Z=2;etp2m3^C5&L*CIU7{aN=f-!0Xh|T_b@MnPS<#ku*XOgi&Ln{93P zx$1ydp+c3F9B)9(gLB70))NFh>dfDL=8<&q6csoJD=aZK0%OLv=J8#JCcmQkWPzW_WUE%Y3}m>9v@@SY_Z+Jp+f3-O!0Bd>zpQQH{YI zjr4f?K?W5ki)<&EB)&^K&*T>WXzH1UToI2x3>U?h=>oEP^|1na|H^l^b1CTwf}g1V zFznp^a0gAcr-O7B?v?rum_Z?)r2V1|?U#ghgK8?4M(!38MUqOsM_ASGlpJYh0KJmZ zexiLj2-zTuNSR_5-&83+`@7md%HL<#^lzTLSGaXX9?K?b zU3}-(HCoyF1_T9P!vV85o%qGQ_nNzVxb5e}10{yIjg<165t1T2^P){^YY!y^N*v># z_$MsKhK{1b;B_nfY-H!u;FA2;VxIixzYbb7tH-wZ#UQVlaDXTu?sS?qWWn@L5jNFT z?e9{=z$nMW`Qcr`d#pRp53k&=Rr*=2z*G8(9=KF$UF|Qu=@8kt;WbV-!h34+?fK+3 zxswS3WM6SpcSV~-T-yLYNt_gkLoF}t^`Z#b$DZ%i^=B=GE;(JRM`^6%#s!0#42B|D zNWjl49Fz8XO}{u4Q5=&(;f&2=wa?QqbNubKGs3bl1#F-GN*SKK;s(#*!O6?Hroi<> z#e-H@XP{%7q>3$x4=an$c=lg4HUBP7ne!Bd$Z#-?=6+_9}CNAwTHt` zG8?Mwv+6P^tB)>>pz8|dw0eCua+3*2;zOfjupjmgq^^yR%qZgC_+$gzV^t7x*T>fj z4MvNB#*IHOBWOMS)_amJcCKr@szPsypTDQI#P>LV{dK2i3U24U(_xwC`|N{2Vc1?k zgCA&K$xTQvp5jViOD_yxyxPpL>H8dep>G4Dej6i=a`FNZUf}oSDn$MPsZP}H<~i{M zfn$EHH3NmVrvv&3e{1p4Ne0yZBAFkce_>}zGamG0HRU+Ze5(7` zIt~)}!`r95{H;)%4*j(TG71ep(pQ1IGr@MFuB`gf`a3N~ewWo1$Fizvb|U5+Bc&Il z_g;Iak?;!{OSk-Efmm^!-#6aqR4QEAp>AhA3+dDz;;imZ{GpXQaD=8ou*_>tDp`V0 z3LI3&BBL^Y-M;|6rQW6C|7jC3)FLl0f+swv#x? zK>FhdFjF+&PQq7GxJs>7e)Kpk#Z2K4El3NW@$72~LGAIjQg5Fu(X>46GTs!+K)I=6 z&$@XYs*Up%i>#brElRcY-##o`DBIZ?vN%|woyG?L#_wpQz2cLeS=8GbDqqPe(fY9X ziDQGa9|)glR390e`(CZ~I3R4PLNT01w1klQ;tyujB!v1ZTqR(i!;MKgqRJ+!cF;n2&Ok}SHybi5Pv8AVSUGQ1;C zf_Y?)QJdch0oN;K+-nO`TXg z8@@hD5tW#{PR~^fnPQVkTAlWDHJb|eYA+d@+<3+&#};sy$q9Qh8n1v-wF#1F)2N^Y zM6(|wT3X=-bF9PDDxyN3(-(T zVLEEENJ_{1X#zMg$!(@*audnPJ}$%nl)O58Z+(OHGKu{hmP8#OM4N)hhl@T`m7O4n zY2Drt151Py=_B)W8^4U;+L8v7kJ<)S8fBU^2bnByT6I1zbNkiyk(w!$&HHMaM^AP< z?>{o?MXFT{isC^2tA7wR>5K_mc7p@k-Q4!_0EeYUjz&ZrjBO=}O`? zRriHSqBK4+w=!*$wU*<4v0Dt-Q-d*AkL>46fa2|SuQy2c(Rb~(>xhJCv@DP7lQR2r z%||qChG@oRPvP(9k&c*u6M$XzbinfkFu*Uz$29Zq zS?kDrd_lG^($ymo(|UGW&n$Gu6&^&?K;UIWPTiZ3djgw2p%(TQ>0bjrhUZbYXeg6j zmq+8D!Tku4MW`$TBr@mqQn;4^c{f1ko}4K6?=t(Rx7aeg-kqVC1W~+)Y7HLSbL3t$ zWp^e6_&Jh+jy^%|dC}--5ms>-D(}GnqyB!SQiEdW;DTw+&Jt1T_k<0WZcq5pX;$st zSDp`l*|$j_m@hJVjj_;}Y@q&~e6;eM`p%;WxJ`oZ^M8M%`FK1+?CTeet@oUpoC_Ea z{tt#2h8`pl{FDTV&7ClSFMRJ5-6QtQchtA2oURBw+ciX78o~_?sk;!s3l7Y{Y~j3) zA}2T}S9rDi#CX)Xo=5CKgCQ;;);0v?Dvexyu zJHjL_%UCK@hl7nW!AU0UT!wuJQ5$@W4j|PBl!q+MM}4GIKV6F85_8!Qa)}{w`k}|DHL*vRQ-% z?I%4!Lv7n-}odotagRQOmunt6$CoLK07K4_Q^ z&?*Q(02DLD0TOFF;nqd#7Mq@j$YYDM7CIO|pQq};EZ*W8O3FHbPdob6MX?fq3VGez zv~#|P{lMjFLBSm_2SyE@of92AeA~hxmi60`hGl?vZ^f#qE0Td#{pBm>Se8&^;Q!J- zznc`sm5EB%o8b zy{(sSa2OOz5nvn>F+yk9?h%lTP9C^+Gc;jCZ~y$2&|0lPb2=;2sN0L*2#SzIPKHq% zle>65D}j>+$3R7<@VBCerl!MmL12LqM<^*N+QO*pCrE-J zK`nnRREh!OJ)XUulZt=|b*wf$P8~BX;3b-ir}L^H3|R4Zaa3}_Ll?vqzPCe^#LOgd zq*ucY)FvDea|!Fklb?t89p}PKJ*YOFsu+mo%Mg7)&3oQKoB^WB^i#1HC~sZFpXP8a zrRa`e=dh_UzObc)GVh)E--k?VLf*Xpaq48(!%gOqqKLM7N{B;fAHb;cwg50b(7eC~ zP|_Z4i0CwOru0nZ#a%m1k;@*GhZh~{;|yINvfMUMJ!h3zN&r1z;1`y%*sKnHFiPhJ zruSc>tT4nwN0>i`tM0C#A-64#Vx=9FO-pvdZcuR+;u7)QfCBNL7u!$z8!H|a22Ira zN<;23qgFljEIZ1o3Q6P?Dn&k02&U7g9tKWitQL9WGT$V+$llF;yXx)xwgItJ^|yLA zj#^82a)Lv!Wn+$4N; zg&v4fr0zuG4}h4pFuvELSQY)9Vdf>fsfSPQfyRh~OhIb_lI*K5so{o(TjB3Ew0yl8 zcKJ%X#eR=yr0lQR;>!2e1cqdrf*If z9B`vCRZ_80zij|DvH{+~WLN&!Wr?W*Zdg1_5xF~qO=Ycf)Pp;!aB7JBZURqlSv*Ft zhjO-20z@Cw0|UCsET)uN zrUJf~j`venSFkzKF->plBISJP2QNjdt6Wvw?(DpdplhD$J$Dq`k)%T1n>f8Mi5XA5 z1_GpYkBYelxBI}E$@qlX&s$d=cN6`?-Z<3C-OzmSX!E$pLJ=#rN}Dr-f>DmX3|Pqm zfGCm32Hz$iZT)z-k-w{z_LIT52NudBK*rBZl7fk2atu`;t`1`;V!)1ls;hJkW; znnV~X_I$(YODa!~zI>;Wl&xdlY5Jo&VU~VX4DQTU&jp@({$HiDs!@;Ed`E;Xo!gjx zq{lpZEHx&;p6F^03LkH?^m{x!zMD9)nzGfUru;s8=d1%oVuA8?Pf`@hn#(|bi7U3> z8*WxNl;!5EE?wX{;!ymvE6hCD^VLRo1jf?_`6>l2P?G_eXJ{3Ccn55V*m&|jZ*Wk$ zV8DI&xx*?Z)?^j#VvTsmcL-Trq6r$mQvsIAppBgJo*RGlMqeINd8!$dID#Zb$__Bd zsy?9!y+jRwE7V6XZnLD{k9AlU%euhl=lsm3QRjDP5GHUSdbk;AD)lkLJn;>egP>hR z3u72Ow5W1X;AvWJb8N0yEz=gm3obf3FolOI2NG8X1}KRW9fwlh-i2;=um`f@0)E-WxSTJ6&L-I&zA5!+L&RliDuvWSy*_2xZ#`NM-}+6)mKu2g=g_BkTt8OgYM13LM;&X93%(OIYC zQVFMW(#>qu-w%@oBE4n&N;O`z4fJ1s(r~~kng+ZQg^8^9$5p9P`(syZD=>j+(6EBp zim?t_pcZ%hDSSeYqR)KCix9;G=hPVArVFwu@2@uNyf>JMPqwZ_#Q)%n{H4 z?Z?~iic)FrYo_xjdG<3-25L<#ihbxo7$4TPQ@2?MWKnoCbmP#A~xb#3X za;$&iXu{e!{osgdBUg1zYNTVqt&J}OA~>t~u~%G^TTf@?-_xoE9go&3NEE_}s4qgmwhXCP zXN{%`g>$;LrWG+$&B+Ay`}@aIEMI@&Z!G(z0j6cG++G33jAuA94&dHV*DIl{KtFB| zPjlU#-_1vE*~fT! zGkbySfK}71CcAHf=zU&Eyv64QLk9_WFW@XQ_(g;h5aA@sFqm@MkIEaGaIWzdG1HAsmfD_0oee;^UMt}Xk87hN}4yDmyv^?UWsv} zTYQdBV8ZxCsS^(--yx#miAq=ETnxd)bKK93XJ%bJ+P)fw$5@SjTnDR1;M7b@9ia3y zoqwFhT}V@Z$iRw6J#;?6;o-q_5q`T*k`fa~S1-YE5s^q}3fO=DMoZ*<{?had(S-`W z4V&kGbYUXcE-U3=Aez;Gk}`mheBxCTc&Y<9U}Nf+HIuTa-h@FgeO zMpV=}L2|YHPn(OSn%A`mDN66bm@^`sm`Hd*K!0gm=obH}zNPa&zR#yfD6d=mkW?Cd zOzkAx2MEm17zXgbFPet|$jlKSB!`kAa;If^qES4m=7zJ^yVpY14_LfwF=)L6MVB07 zeESqJ2E|MTW3s4e0jijrBY)Zv1*D%9hRa=xM_o4Bpyt7mTS1NQ|ccmJB zEb$W6z&_NpmFgddyM3^~$4+x6{!pnWQ>aBgm(ZkbJ5y@U?@ZZ&Qji~`$*Bsn6q_FFXQ4{^-00x3N-`?QpWGDx_DG_&h3GkKiN^C3 z3wrNoN}f%rVxBzYOYN0qQsnvyyw=^H=ZXN+Q7Zt8MkEXa68~KcQ>>&DKFYssMn1CZ zY3mp5FHWGe>3(u4xvqPuWu6U}14}cERSlM4ZOsn4?wAUql8Z6ypfBvju{2&|L>~sC z6;dbMjbxf8#3&&-`$*G^@c%(a6W6-NM`01^S@+WwRs-j%y#C(bbaJ3XT*a{gCO>Cd zWdt=}pdLV`AW3o0_X^F3_Z-`ep)q|>%E0rrs>`r%S(U;%#u0)2bwH4J<`T)Io;|ZP^qF)K5CW{yWdWPWRdPGODkRy|Z zs&}bQ$A(^&Hg!N3)BAezZ5>_9F{q6WgHQLr8JN%BxaL1i6BYcowlU94IyoafEg>U2 zE4%PketuR~Zg#=-YX$kxq3&kN)oa-|Q*%m-ZWfa7<`mp0xCVW}fAlw6-iu83w%p$o z_qi|oeh2G*C%J{^F~WO`4=Av1obzHNV@Uw8bQ;6$Jwg>Yrg>3AiTbe3@}syCzjPBO z31&T&xXI#f10~`{McRX6(0L%{K*66F_~oAW8lS&=Qg@d*TzSJ^`ZXh%eRB_zZ^S0l z5CU_k+3xAHQ@mbi3fyD#0veGl=|5$Mi;7={@8lfTp5JcEvc9nO#gE*8w{$&?ab^8! z2;`K^KJ9Tpp+7j#$5lb$zt;HtT(5nA`my;#o?&tG#t)8_;4&gpZGI8?KRK2U-%V2A zIut#WyXNT0z~3G1hj%-DLcU=%*#PMuL!Nv@ zxF_nlcc1mo50}fF$7^gEM5%DOH?J_Z4p2>Y22o=GO&NS;9LEV=CiA5)5K@5Lp%)8U z5iT7o;PmlNKe?^cMF@Br%3J;sqQ4yI!!by(|L3#{Dd;S9-voy}*~iyExeN&VJhN+D znR#|QkAbt4Eb9rV>#cF2CfwLTJ=lv-6nPcH+~3EZd|4@=?*B*Vdmat@&*L0|VpeK* zHk8EiRs3I6gD@CMkr-3TFIq$@h{$HS$Up*WRNUVef-I9xY@YB|P~b(*O0>|EFo~7| zZ%$e5<2bLV3m^x5Z^3CFXlGk#JyHG9Aa&g6aT0yYgx1~N)iS?N$2rGa6rmx@Nm_+- zz-y2R(oy$3dw&Q!Z~y!pzi+iBkq+`nc14U68C@<;Td_J=!RpJ`o{m74n3ay-6vLfi zlBV}{_K(3FnKC;>Wdc{m>Z0V4e7tfjqZNwz*{znKYcc?Ez2vlesenwFFtei1bnQ?5o~`lO*qPtq;u6SXp-gH*OIJEY z4DhyRE)J(-{)io{UxP z4rr(H=EuWnB{lj5)JxMgkhRv66w&RY3|goAkj$c>8jv6RS1T*PHU~=BqI+ z9B-xw1ACqe;t~c~pZR0prBLJFtJVDk9K7ScW#}_N33yyDDHm-R!n?^II&3&)Ki}Dm zSCn9yN`ehD8M~*XT3vZm+7h|&6&f+Uswgtq{52{2lYG1dbDoNMkF6_yil3AMf@pz| zqiYFV{*OM|IQge1oHbQxG|djZPkMZe-5P&wdAjkaskFc2b!CX9)z4>Nn5WvLN3m9m z2l8g`C}Km;)!6lyePHgbr>7%YW&q$rTMWap`dxhg43eY1;^dj zR@{mzqt@Lf3l+Ll`|Txv`2M-4?%;hrdW@bJeNY&tbaf1w5hUXN**=dxwA~2XWYjtK z$+)$&*Zdf{44*92^)>PD$%-&X8bE-G93^pHku0nG&Q_o-`{GhJ_HPH%g&Wf!b1abk z2yjtQUQ>ZnrR%-ifE$pAECJ77T~FYog3eyDve zYHKpe+hK*-$_`-`%6_+yca&1LjCyNvILf$#wW1Xiv=zt?3w+yU5$1=u3KT zKa-{}`I9`Fuf`MyxfWLL|Ex!4b=$0C5c_!L3n=3Jp4SmgD8RY$>Uh^38{VhFAOsJX zDW)-ow!lj&2aHrRFCrTr=o;mh=s)0^NtjWv&QFH63h)O2rTL`WR{q03jWN$AJ?wX`UN1$1?rzHRmmWHCB=LFg zS5zZX7eS~2A+SodU;zGr->dxJj=B^K>TQuB8}5CcD)YQ?^M*;TA3gEg9j=hWcSd`p z>19$^kAG^z92rMh#(ii5`6Vb&`t`SMX2tEIzgES>&;00~_nwtBACtxf2XBk#TJ(At zV)xbiBNQqkh<4qxA|w5%tT3N@=#$bNb>qI=j{d%b8;3HPkF>Gc`iJ<^h#G^V!0Ob) zhFN`E@xv*qt3$12?=zEU{fR$y9My#v#HDD^FRjg&E25)uHk`YxxvqoM@8^}eA92nU zvfqiMQ8kqwc#B^+g4oqVfDSIV%{KB%v)HhC-`haCpnR*;1t#qThWaRm`K!a;m$8P( zX@ruGU>sEiVG;$sEoy(3eHv64{F0tw()45LHms2hswp0j{UW#Y>wp6M2<@HD&&fX8 zQJ^#?9G3$RV2@U$TNELc_?aqd67PkVzo6k;d!Z^gLep--{rJBkG<5aa<)VC*UabBmQL1L^4S z$<_QMW+NjYZH=I|G2Lx?#v{@CMKW}3L~V}80BDu)Y? zFY~QZGI0^A{utpY*8I^SFlA=#lM?gUI(nvqf5X&qO|sgj*^Gi=zx>aFSKx2k+q!DW z;mj|ka*t`|^zFZVlgvD-@+wt?O;N$rRk;2dOsXVIpD2*l<1pFV$l%G(TPf##)!*4% z{-u)h)r6~Me|L6hNGm6u#NdTHSkfs3n2oU{F0d%bAe3k^6yBuy@LtuSQ~s*=L}Cj+ zY`oAXc{;1JFta8UEeMp||l);X;<@W`&&N&P`Mj zHR!trYtLG%(j;Lhtfx&rC6>$!$#OtXdVbabfi%qfT}AgzS0R!Npqx&ly0QiD3G=(| zMNcVI3CglOQm>q8js4h&h)ofQLA-d)4wxt0;i6xRc+>yR_GNfR**s@M{-62YH|LEm zU^_YZ5fAi&>kNi{00R^3Iy;3HFpnAM;$Xyx>)9Stf8T^L&VSKz7yRSL`F4QS^%}Py z=hY>88ov{M5@bgL;}pP~TH3N_2{>8%O99(sBnam_sA*Ir%|8aj{z|jy#IR#P*p>Si z{iFYZ^`o(ovy}j z&Z`i?0Y7>$`v5ODP!~e>YKQH=Kl<+Rd)z3vBI_7VUBSu{iIW|W6|HS#|6MJWJEqT! zv0A!F0_*teABPE-6+mX*X;<^n<7a9xU~A2m&p0yws|y8eyjLwnGf&sOEKSeyDSR$7 z@F#>uzAhSUc<=2j?Z4Y4bv@aR?X#*&Mv5bAZ?!KzdFFUHV81u2jjhMWBHsYKfagCi z9l0Hi&8S@ejgIoVC%K%lBd5F`%30HmS(9b|RD_>RJBH900w6)5CoePFFhZ3iFoW|xJ#RjLe2G%FVz2y!s1SDP`000|Rimv*&H361B4(@RKQ zE`{W@2IYPlO^c3fOI%%(epW`{U7$n2X zf(cteaw~S03#jRm)MIC!~zH*juFF#A+kN1=g|iLZ76 z?|UhmfzY4vH=jMJFx`-BjXg?!a-vw$29!T?gefIQ8DE_p&GVHn=i&vcDD|l*qY~Dy z{ccbIydGFP7_#7ULO~)lO!EMm`Jq~DNMStq4`gx9-}^p#mj9?pI^^>F-Dd3<)06SN zsj4O1!tWH!u914I9y^%8t68)cWB{}9 z9+7%5Nra`KRDApH(YMXK`(N3|iPt1Tg|*Qei*I^YIn(s~fL93L)!Os|iH0D@@+Z!c zzjBi?A-*;=w|f>)@pf9!mx#+zYoN>W>p8~p8z-Ut);2P3Q1E~bHPssQ#1Vax=OVui zLL(vWNFVuk+kGYAqaAd&`SRSX>wVqxVc%~@_frm(V?6)^Kb<$C2p|+BPO=0*@PAal z8JC}5tG0En+B8hsK2~?Xrh8EnoT0Y3pjrNy=EAipGNfc4GGqpROCxcZ%_D;@5RAlPLWTTWaQcdrS^VE?+9c9`~yRl&j+00ncKY*E%ovw zmG3xd%OO9;V%+8##-o0 zI~t5k@gzjf`kd%3V&P}rH>~f6*>hhi_D7&3-k*p&R`3j+DpDHfAr1#V z5i=pj*e0@h<2`XVsm=ji6|V6xxUP%GV3ggU+e%CC2LEZ1PPWv?0ZZqb1Z% zjVcGyo?KPrWn*^py>|G!r;OY9$`ZYf8mkC~SN=lGHstA0K;-yD^YH*q%$h+3fhyEfE7hil67IH_=YR^}n|c7--?JQWJ?1RW54t%X$$c4X)nxnlXmHjDXUTB<&GR}q(pUg|jM?#1 zV^oQUUukNmsOjD@7&yfM?ka20uJ@N4TrL&+qi{wSaRt_wg;@QV*Mn8W=tBs8`d+)r zYF(Oq=%qC3f!*q-&d;LtV*2yo*Ea+0e<$dFzX-s;Ql7^ytKR3GBmC`@^()duat|_2 zX@W)^_y936&u1=Rcp^Ev-az8z> zJogi@A&?;)12*aU_YgetuUD+Ro3Z-3;Ppr;e?EA8ZnwqV%z z-;m$TPWf(L;i-hH8lJq)zb{2x9ap4MCix;(u;3I17cpQ#8dgvPV`o1K30&v<8d|C1 z8ZLWu=ops>hV*#v2!Cjoh&d8s1*F{l_jmpxcr)tIul!M~m4BVdt%*g(-M z>%q1=?xDAqpJi5gMZ9@@vA(4qGW3bM`<3buX)?A4X$C(L5VTh?7+-Hq{L(+HV{TUd z{*TXen|8)iGcM{DMnC)*3*H|3*|C8aI*2+@T(==<`K9to;8)L{;ZET`rX{fs=||@r zGVi!W;5^QgwCHEjAr9ZCzus{yLHy-vXxToM>Q)?j!(Me=)b(c!a3pq6ku zk>=qpz0qPd5H_)Pu(Ndf*-Ffa$7%KroRRagk0{=4M#XM!p_gDr|dIdqW7H-mj?RNl)8#R9bB= zTA#cj0`Er|*knPefckAUKs$v4`bJ%}duvPL;di4G*BkJcj@^t*vexvRN{->JVr-Db z*jj_emA^6t-49?fFQ6YvAHIzK6%Ih#vZFeY3Un%~eIw(5h8TTh^TVz| zO4=oxnCB51))G$VO)xa8FIfP&$l?D2Fb2>0uQffu&xZ{FfSCpWzS{l7k490CKWZHo zKQ1|I(~niJB!GLoCq#2h01E(+uyF(c4&VX^H2_E?0ATw_ngjp<0O+e3Y0EFE; zkUL>w$IL%lMhfv}fFFPX06jDC3wD-;&y5jvaUV7w8JvDAkl;Ca0t3L6E;tR903Hke z!M8GmTE6sQuh+SP+g7dBU&p9Lvbe$fV)Y{d2}nS~2%t}30V3K+3IG6DQ-daFJl2Zo zMwYOY3f>P?$9!+&o2c!%D8ZHW<&QDG+rUl?Vnh3;WfM=#W$`HN*j)wUr2>>K| z!py6L0U!gjOB@AOBVz#o0H6t%+GUWnDTwU%D>qeuzov5FCrALFBLDyZ0R9U80FQF2 zdi)U6w)Jv$t}6De+TO*! z6Y>EVgVG#V>U5bn%btB2(ML=idbxD)_40D#XHL23~*Wz&I_ z5R?-Z089s=Mg^UWc1(J!_S_6MOdUB)#wtK2`#GioU=PlK0W<;tJ{$g|&mu}(PjWXs zYI`~T@6q=1?O_u13AO?&JWc@s00A4D0BE5S03eAzf(ift?N_JX@I+00=rX3cl_E08eLUQvd|vdIJCe0002!Q$PXNHn5$H(v0uc%T00OahPz2VKQ>6rO00b9T z0H6c_001XC&{nf`r>;{Tv9N7zB3pA|Bi(oO*9mSjswoqaYXJakpjZZu0{p<)mfe!xq{f3XagjnB;z8?;+fy?plJ5c(^tQ#oUC(v?Z0P}!x01}|T0f0b# zs|o->)p)<#KRw1#{}NQAcS~Ekiu+TD8bGxD9{>OV8nY^}NoQ|?N`Q(ErU1DG=J4Js zar)p*iIgw}0G=BFDPBiNNHIVKc%B{@fC0uWF0cSMtNg%rg1N$8bf2(E+E@bs`1i3& z0Lb+Ko>dy`8vp=cNC2LSeWG{mF8Vvr1+P`A$VI=?U(;^KWG{OHY%l|TV+J;$Ie_g1 zxD5b;@v({kAidQQuNBR!R;zF&ZqFS%S|gUD*-%n0M|ThafK?3?!Vx~bhB83O-stHL z?*TCTl3Wih9NHk54#2#GGU9+w8vxRf%AXq|Y~a`*3l=W+dKd=$C>zY5P~{~kFxO=V z0E5~OO#%a_fdU%yjA{@71F)a~ngCvwyl~h2zqsyTJDy>#vHr>5Jk%PRkQntUbZeck$)Jbk+TqN$g%@1?YF7WZ^7jk{+% za(phX#R0nYdA?nD`{|A?N}Qub@yqW*U$~YdfIAc=p8oYG-t61Y_Qvs5!k!~s&OiqM D`_F6_ literal 0 HcmV?d00001 diff --git a/common-1.21.1/src/main/resources/assets/antarchy/sounds/portal_aperture/portal_enter_1.ogg b/common-1.21.1/src/main/resources/assets/antarchy/sounds/portal_aperture/portal_enter_1.ogg new file mode 100644 index 0000000000000000000000000000000000000000..51a32878e7ca95877792b3ad69fba7625d44b278 GIT binary patch literal 14251 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 KKp5<12LB7POr%Nx literal 0 HcmV?d00001 diff --git a/common-1.21.1/src/main/resources/assets/antarchy/sounds/portal_aperture/portal_fizzle_1.ogg b/common-1.21.1/src/main/resources/assets/antarchy/sounds/portal_aperture/portal_fizzle_1.ogg new file mode 100644 index 0000000000000000000000000000000000000000..f4d1ccd43fb8fc5bc0c7b31b7f89f69bd866c998 GIT binary patch literal 23488 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 h=iLEq%dqqgz5%U3ROxO+q(MSDzoXCdzW3h0 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 literal 0 HcmV?d00001 diff --git a/common-1.21.1/src/main/resources/assets/antarchy/sounds/portal_aperture/portal_gun_fire_primary_1.ogg b/common-1.21.1/src/main/resources/assets/antarchy/sounds/portal_aperture/portal_gun_fire_primary_1.ogg new file mode 100644 index 0000000000000000000000000000000000000000..ca20531e79f32a5a3e58a6461356bf3c2647caf2 GIT binary patch literal 24949 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 literal 0 HcmV?d00001 diff --git a/common-1.21.1/src/main/resources/assets/antarchy/sounds/portal_aperture/portal_gun_fire_primary_2.ogg b/common-1.21.1/src/main/resources/assets/antarchy/sounds/portal_aperture/portal_gun_fire_primary_2.ogg new file mode 100644 index 0000000000000000000000000000000000000000..afc71c1ae4d8e1fa0796d42b5f5f07b5ce31d145 GIT binary patch literal 29578 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 Ux%S=EH_PJL*0SLt_kINYFHJkPssI20 literal 0 HcmV?d00001 diff --git a/common-1.21.1/src/main/resources/assets/antarchy/sounds/portal_aperture/portal_gun_fire_primary_3.ogg b/common-1.21.1/src/main/resources/assets/antarchy/sounds/portal_aperture/portal_gun_fire_primary_3.ogg new file mode 100644 index 0000000000000000000000000000000000000000..3866132ba710ecc4976fe7402b68b687e81dfc50 GIT binary patch literal 28222 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 L|JDZp00000FRkz< literal 0 HcmV?d00001 diff --git a/common-1.21.1/src/main/resources/assets/antarchy/sounds/portal_aperture/portal_gun_fire_secondary_1.ogg b/common-1.21.1/src/main/resources/assets/antarchy/sounds/portal_aperture/portal_gun_fire_secondary_1.ogg new file mode 100644 index 0000000000000000000000000000000000000000..d3e7f87d78aba4800d9c2007435beb78f2a1295e GIT binary patch literal 39870 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

RX{ZxHLl?-sFEZ)^W9g@tvU`h#l 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 N-|LTMDux|k{{xL+@dW?? literal 0 HcmV?d00001 diff --git a/common-1.21.1/src/main/resources/assets/antarchy/sounds/portal_aperture/portal_gun_fire_secondary_3.ogg b/common-1.21.1/src/main/resources/assets/antarchy/sounds/portal_aperture/portal_gun_fire_secondary_3.ogg new file mode 100644 index 0000000000000000000000000000000000000000..8bd4cbce984d1fba1d0e11e0dede300e89590782 GIT binary patch literal 26615 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 UaXkF+!w)}vBKPY6j)xzf1>aLHbN~PV literal 0 HcmV?d00001 diff --git a/common-1.21.1/src/main/resources/assets/antarchy/sounds/portal_aperture/portal_invalid_surface_1.ogg b/common-1.21.1/src/main/resources/assets/antarchy/sounds/portal_aperture/portal_invalid_surface_1.ogg new file mode 100644 index 0000000000000000000000000000000000000000..21cf038edafad82d0a1e27053bb805c82a22dc48 GIT binary patch literal 23665 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 W>E>p)Gv2U1s}NM61pne4!T$lxSr|J2 literal 0 HcmV?d00001 diff --git a/common-1.21.1/src/main/resources/assets/antarchy/sounds/portal_aperture/portal_invalid_surface_2.ogg b/common-1.21.1/src/main/resources/assets/antarchy/sounds/portal_aperture/portal_invalid_surface_2.ogg new file mode 100644 index 0000000000000000000000000000000000000000..e35ea76236b8b5f4e4da5b3a7afae3e30dfd5c43 GIT binary patch literal 21731 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 KsM!L9fd2=BIKa&S literal 0 HcmV?d00001 diff --git a/common-1.21.1/src/main/resources/assets/antarchy/sounds/portal_aperture/portal_invalid_surface_4.ogg b/common-1.21.1/src/main/resources/assets/antarchy/sounds/portal_aperture/portal_invalid_surface_4.ogg new file mode 100644 index 0000000000000000000000000000000000000000..e54bab20cfffba240353aa803abd414d47f42018 GIT binary patch literal 13370 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 XOFCnn{U_5FPI>OjEIDSie8B$$)wD0* literal 0 HcmV?d00001 diff --git a/common-1.21.1/src/main/resources/assets/antarchy/sounds/portal_aperture/portal_open_primary_1.ogg b/common-1.21.1/src/main/resources/assets/antarchy/sounds/portal_aperture/portal_open_primary_1.ogg new file mode 100644 index 0000000000000000000000000000000000000000..86af3d84977146d7fafca567219dae08758431dd GIT binary patch literal 27822 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 S{XCOe)~gYVJ!-AW;Qs*A<4))R literal 0 HcmV?d00001 diff --git a/common-1.21.1/src/main/resources/assets/antarchy/sounds/portal_aperture/portal_open_secondary_1.ogg b/common-1.21.1/src/main/resources/assets/antarchy/sounds/portal_aperture/portal_open_secondary_1.ogg new file mode 100644 index 0000000000000000000000000000000000000000..5c46e7ce5bfcdf0cafe24df9930217329915483e GIT binary patch literal 24406 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 #`<7ZJ_W1{W%-ml zrEM1Di6gaR|7IeqwF9r%7MQ4TzaJmU^M~DH939*0{PpTi2+P2)bTQ3m&zuLd@4G*a zhQ@|Nb7=Y^)!PhwYZC|4!6$)pqzpn9zwM}8;Fr| zXU>>iU%my18So1LOimYIBFnwVt8u=|);;I@d~wAg;OU#JJ08w|k*#A6M4W7F9|nw` z-Lok_m2ii|EStmrm*!Eemz~_1W|CHPhi!6xQelFQty$o)m2Rvv4JcqdN7?>Ziov?{Lr4Gw^*S^NCd-UyYKgeqTM+u&(yroNTsoj`owP2wC004xxOssz! z^Oc!pt<5roX4s+rhB2yRw>hEBk{UUk+V#*h?NVZFjd<=vCGWcHzu$JWQVOSUrWr@PdRN6E{A}7LvQr`RKCU4{{Qg5bKY2a!nBzKpT_PhUuE|vFoC&Iq=?sjm;Tw#K=?e-Xol` zqGkM}%mcP-(7}oOdFS8ZMD>U6QZ4v4Kb7}8g2{d`Qv-;qdsECrYXOV{0Q7_C0RR910Dy15Qw9F0yv1$B zw1HiEq$c#c006OX`u8PhCUzN8`6YZuxU6DKXgFi4^R5>3-$-ChpW2+9#j}JJTHZ4B zG<_Uq#CB|8992KOi5&7n*7N!15q0?0htzRm;${w3+!D1_r^e-0Oy|k1#aZgZw2rY*$=n8mnJkFXD+pS_tJ9-YsaN`_pAQe=jmeZ4(~bY9&X8#Ia+6_v6X^4&UwT!az;#NZ8ZLP3L7F=Ix$r7z+APhyH#j zwo|J(%l?yJH4Wd7_E;x#`T1E&8jN`^Yfz)BvD@6L3*J7)?vaY~$DJeZ`s4DknM>x5=7EJ004&nof4g*)|BpQOnt4Fwp|#RkRRt7%I36<#KgP1 z*6yv9!B*P=^C%d0m(g(jV#{x=zCXV|mUwcX`uNKKda`7;(nt1;TgXv(`M#e$zU3$8 z@vT}<-D1!^I&!)k7(Ia<+(&kxk=Jp3anfcP1SYl22DCn4FG(&|(kGFk|Z5Y2P(Hj;SNZYVYVd+wU~d?9V&|9^2VnMJ_+cuFuA<)xo}Je{+DZU+d@ZH~q^UtW0v@_n67)d#_dB4?Uc` zm8}!%_Iv8UQ78!e7+W2~PRU#MhVDCY`|94mnLJ)b+pB$S&&vg?0RY{%Ipq0t3hpN(H?DL;EPJmhGrp1HVu*)FYCdOhb`X9unW^mq**R zen?8Nj^ElGkGn?}p~H>tq;WBH+$UZa@V30wB4M#Bp$Xry<#*$()pN}%=dSFsJ+f_< z$L=>Q>F)NpU;z((keDZ>U$6f(;Jzn`=JG(rs41H&3xVIYVKfblkVKiM9NqGcTI=Lyh(@y)#0#CXD!uFk3r*MS-wu3}hF{ zrEyj--(@-;-PIiL%(c<6QiWV_?4_`2b0nt`5G^Ymn}L&Z=ysQbkF30-^(abOn*w)k zH%{-l$AELSKB!m*=bF659iJr6T+P&MVARuo>`%eLrC#?ZE5V~KefkbhWrH!%RhaKp ze^%jMPP|_6*56Jpgv1on&#R|4e~(+w+WCm~VNQ*5H3ERREQ6m;DXrM$4HE!Zt=k_P zU~CD?l|b7ee3kfomd^@ro{9W3UkXTHr1(Y3KL7ysx5!9{bp6wMTwVT;**1Ioi}a_X z(DHWN)^L9l?VD*%bhBWb9Zk?09GSy3$65UDV;+mJ?!=d9r#3n_@`z}6*{Mau$!q7q zV0-C)QOseXZ@E>6hA!|+H$$c|RSSpCs^k0Sr$wu;Ys72$Dw;EQ+ev*`gtWy-?R*|e zeA7yyhtZAUBVl~!X1F|z@7xxSaTpiMV4wRVALr!9Gu8o1ZHkkVA-K~5DT$(>$xtEE zwOVnECnL{%QO5j+(*gdL3@C1UP5|Va-F~-|n2;nl(hhvIZl{~Nn9ILp+FTrFrTZTJ zp(S`}Fp3pCl1#N3c0&@$htHuJLn|I8zS~r_EDRt3PiJRS00hW~00000005|W000I6 z001*7uhk3xq5r)9zW?R_?(m+8+9FtHRahCMF+R#{v zTQ$<3er3F9Tggq=#7p;cbQBi>l4Pb=F}73u*xh1D8nc8!waL~q`s47lPHB3cx2dV) z)1JBQw&ywJY$J2pw!%x)yp($W%~7I2>m)Uemx+&z`(y}rWe7oipvITo`OvqeUPYg> zg&T)Fn)XA2GM2+NPp^OKL-gi_6=Od!!tuCdycr~GPJ0A^z8wGnAi0@W007Ad0Km%; z0H8xFibubs%kLcPqEdFc`N|%fd+$W^BMY(2li=LI`lvNwd`wyoB?{LQ+SSrXPor?DRbUNi8U4FUez;G zigM*LZM)95g#v_6tsw00>&w4D9p2_15{$-cU$nIrzRec;_$7%OU3e3A)A6_j03c|7 zBk5EsFhRtUOo-=6NDC7X55dDmo;?{USdBVwA0ojH%jk<*^YtyNmyLsMRR_Ff$JS5) zD--8oU5@R?-(l3oAhdfiAOikK{I^~g$=!(YiOL@Uz%dEdl@txFi_`aJK@$LYPw+b9|E3-tC*o?v^A&dA(7mQY=36wDZWm)_F_! zthJnqz2U+_iT;J}>~LhtxrP z0KdRv`;{qZ@9aHy&E8U-j!Q!AcqK$DtzFsV9*=&$pUPATOdasvh@Y${_1c+-!>@0h z5NH`-{Xc(i<%6JO#;Y*Q$%OB9_lMU--Ep_LX8S{}(q1ORl!*bKB(QULn)QP$#E>B8-WAAV@#zL*;r0&^eKAcSu!&=fE znGZ6;fRCf;=}jJjWQ&)YPNdAgWx_B6jt-VD_6;oR?~PSfo=qEUI)DmNF4Lcs@zvW5O#yK2Hc!MU zZ8x^g;gl*uhP5pih5@ZI1AvbMU;(L8F-wncN1`%Jd!N5#G&!_d+15s2ZlFL6JfyVZ zrP>^LIJ*nyDHdM{&YfZ`l7Uw}#(Se_)xagy8@wTQ_y$^-8BOB#czUfX=fhfJSd(0c zzQ6-j?n9XiQ6o-@xqj0Y#+|raaEbslrk7@a#%Dty&OybSSD9d3Evs#+(MmLaxY*<# zZn56A_cwAt#FyU|LxGAMrjvGY*;Lc{dn-(EQLCnH9%RhhY~E-rYCk+ymNBOf%UmpI z3_8Fzbc^vX_Gvxw33E!rhN+0kJh9oE_GOdGN8tDSI1u#?fhtmvA!|J;j2z(PLE#J8 zU1WymCp63U+7hJ8@_w24y^NI1b^yM&``vF#V5Wc_a0U1GD_2C2I|b^7@$aj_7$f|x z^Rd8TZq{Qfo(XOXhv&Y{zd3Cs+^;t$sSoLVbV9@VO$tkMG;b9fG7r+EmTaepBQfps zRM$QGcW>2Kr(0vsxlSmHpj2#$xzkFGG@3J#!;93^{b`OrkI2sC)N-bjmRR*YTuth`fW)iO!pBSi4-DoL2_5fh`9Swz*SD z?UiA0t;5`m%6P-#(~;R>X7B3QF?foCri0GOLlkqmO{%Yy(PTfKuHp{Kb+YXzbA($N z>Buf$*+ypfD-N`_DlVqIQ=fbfsH8%5UxdzDK6F*6g&9Xnp@M@MwQ z;~Q)1Zo$lwN1C}z|AR>?53qrx0bO4Jv0bsNtW^!iFq#Mj3;>>o`=4pMft#|VI02l$ zZ^r;>3T&VBH<~cE9_pv*khOxXqm%T=&An@KD&(hwe4N^D3LHYzp=TVs-rgrcSlLRP zPRtqNRtxu?JHmOopDw3H?h+dDagOe2wK?x|8V`#**&6dtmmWhRB83N&Qb2cd8tl0{BT;leWS4udB3SP%j%h*+>k~9 z$pAE7&0Sc15-Jm~BGv97V0#GNG>q$i1Hz#>NSBnhDu>$c)tVJ~z2Is4$yy-RXCmRK(S5m(A);p`6+hna-EKT`Un)Nao*Ig z&Zb+Up@TjNR5BrlNnWci&l=?OEB?oJ;EHpM&E{F$RgHaC9Ua=jEO5w=tMMU$a9;k$ zjK1wNzj~k;bwvQ4Wcyxw90wF|K3iiCXC;n|=yY;_y1$u7?H`Q0YZH+p|BSJ%j(Vbu;g)7Meee|0+g5$)W0&k1~dnLKez8*Y5Cd zww=rQ9G_pGDxVjX<9$;3xprj3%^m)}o#XqQqTQe3ZK@9qST=R*K27}!&Cie=gDho= uVl)4Ad;gMU#ff&2)oM5PS(4qt!#^q)JEuOmQ}Nr0c&^>4XDC_tga`l>*fzuf literal 0 HcmV?d00001 diff --git a/common-1.21.1/src/main/resources/assets/antarchy/sounds/portal_aperture/portal_open_secondary_2.ogg b/common-1.21.1/src/main/resources/assets/antarchy/sounds/portal_aperture/portal_open_secondary_2.ogg new file mode 100644 index 0000000000000000000000000000000000000000..85136fada6086d78241cc4f995a5751c98b9606c GIT binary patch literal 26902 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 literal 0 HcmV?d00001 diff --git a/common-1.21.1/src/main/resources/assets/antarchy/textures/entity/portal_gun/portal_blue_vortex.png b/common-1.21.1/src/main/resources/assets/antarchy/textures/entity/portal_gun/portal_blue_vortex.png new file mode 100644 index 0000000000000000000000000000000000000000..aaef912a782ca310cf0b1c4613cdf96aec90fc67 GIT binary patch literal 1192 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 N002ovPDHLkV1mqUJE#Bv literal 0 HcmV?d00001 diff --git a/common-1.21.1/src/main/resources/assets/antarchy/textures/gui/portal_gun_crosshair.png b/common-1.21.1/src/main/resources/assets/antarchy/textures/gui/portal_gun_crosshair.png new file mode 100644 index 0000000000000000000000000000000000000000..1adf142c8305ae800503704ba3b99e9911438f95 GIT binary patch literal 341 zcmeAS@N?(olHy`uVBq!ia0vp^NNn{1`ISV`@iy0XB4ude`@%$AjK*3j@E{-7)?oX!~avgFIa4i$LbL+&lND#l*MF?|aa))A#ugdV8cq zl+?doYq0L`J^z`L6>fWnZ}{`?I;Z_t>mZK}(u!;6Uw9{0YI`!;u&bo~>E?A=Iul-; zUmc}VrNbcGc;bR+s2xXY%1?z%7ajFg3xqnRytbOzp|wJxm*dk!Z@;76)A6e6<5uP2;fu^k=mdKI;Vst0KB4wLI3~& literal 0 HcmV?d00001 diff --git a/common-1.21.1/src/main/resources/assets/antarchy/textures/item/miners_dream.png b/common-1.21.1/src/main/resources/assets/antarchy/textures/item/miners_dream.png deleted file mode 100644 index d8371b7bf30b5fda9ce81b5fc1864e5bbbbc19f8..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 425 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!63?wyl`GbKJV{wqX6T`Z5GB1G~mUKs7M+SzC z{oH>NS%Li60G|+7qdIyEN zISXjRt$ER%KuW$O$S?RmUchixvJB*U&H|6fVg?3oVGw3ym^DWNC@4|l8c`CQpH@G`{S%?doM4-T<(-_TT0n*8x! zf94Ov$Z2cE&syFO<^GxdK%ZgD4YT9}KNqKI^zu4!Xs&ihQ`RzkIdfj9!sb***DbHq zSbL5-icZ|qyzzxuRQjWR&4twgOY$vxZZm1kl6qpX%T}VhD~`8_=b=G*RrUMdOF#N( i9{W-F>uz0bHDhmx$h0#rOFsc!#Ng@b=d#Wzp$P!~ait~z diff --git a/common-1.21.1/src/main/resources/data/antarchy/computer/entry/brutalfly.json b/common-1.21.1/src/main/resources/data/antarchy/computer/entry/brutalfly.json index 6fc1ecbf..66756563 100644 --- a/common-1.21.1/src/main/resources/data/antarchy/computer/entry/brutalfly.json +++ b/common-1.21.1/src/main/resources/data/antarchy/computer/entry/brutalfly.json @@ -3,9 +3,11 @@ "category": "elythia", "title": "guide.antarchy.entry.brutalfly.title", "subtitle": "guide.antarchy.entry.brutalfly.subtitle", - "description": ["guide.antarchy.entry.brutalfly.description", "guide.antarchy.entry.brutalfly.description2", "guide.antarchy.entry.brutalfly.description3"], + "description": ["guide.antarchy.entry.brutalfly.description", "guide.antarchy.entry.brutalfly.description2", "guide.antarchy.entry.brutalfly.description3", "guide.antarchy.entry.brutalfly.description4"], "entity": "antarchy:brutalfly", "cover_entity": "antarchy:brutalfly", + "locator": "antarchy:brutalfly", + "dimension": "antarchy:elythia", "rotation": 45, "render_scale": 0.6 } diff --git a/common-1.21.1/src/main/resources/data/antarchy/loot_table/chests/temple_of_technoblade/treasure.json b/common-1.21.1/src/main/resources/data/antarchy/loot_table/chests/temple_of_technoblade/treasure.json index f8b30a15..c1695f46 100644 --- a/common-1.21.1/src/main/resources/data/antarchy/loot_table/chests/temple_of_technoblade/treasure.json +++ b/common-1.21.1/src/main/resources/data/antarchy/loot_table/chests/temple_of_technoblade/treasure.json @@ -127,7 +127,7 @@ { "type": "minecraft:item", "name": "antarchy:blood_gods_blade", - "weight": 1 + "weight": 2 }, { "type": "minecraft:item", diff --git a/common-1.21.1/src/main/resources/data/antarchy/recipe/big_bertha_blade.json b/common-1.21.1/src/main/resources/data/antarchy/recipe/big_bertha_blade.json index 9593727f..13957e4b 100644 --- a/common-1.21.1/src/main/resources/data/antarchy/recipe/big_bertha_blade.json +++ b/common-1.21.1/src/main/resources/data/antarchy/recipe/big_bertha_blade.json @@ -33,4 +33,4 @@ "id": "antarchy:big_bertha_blade", "count": 1 } -} +} \ No newline at end of file diff --git a/common-1.21.1/src/main/resources/data/antarchy/recipe/big_bertha_handle.json b/common-1.21.1/src/main/resources/data/antarchy/recipe/big_bertha_handle.json index aded4e9b..3d16262f 100644 --- a/common-1.21.1/src/main/resources/data/antarchy/recipe/big_bertha_handle.json +++ b/common-1.21.1/src/main/resources/data/antarchy/recipe/big_bertha_handle.json @@ -27,4 +27,4 @@ "id": "antarchy:big_bertha_handle", "count": 1 } -} +} \ No newline at end of file diff --git a/common-1.21.1/src/main/resources/data/antarchy/recipe/big_bertha_hilt.json b/common-1.21.1/src/main/resources/data/antarchy/recipe/big_bertha_hilt.json index eb198b6e..28d51611 100644 --- a/common-1.21.1/src/main/resources/data/antarchy/recipe/big_bertha_hilt.json +++ b/common-1.21.1/src/main/resources/data/antarchy/recipe/big_bertha_hilt.json @@ -36,4 +36,4 @@ "id": "antarchy:big_bertha_hilt", "count": 1 } -} +} \ No newline at end of file diff --git a/common-1.21.1/src/main/resources/data/antarchy/recipe/miners_dream.json.disabled b/common-1.21.1/src/main/resources/data/antarchy/recipe/miners_dream.json.disabled deleted file mode 100644 index ac875084..00000000 --- a/common-1.21.1/src/main/resources/data/antarchy/recipe/miners_dream.json.disabled +++ /dev/null @@ -1,19 +0,0 @@ -{ - "type": "minecraft:crafting_shaped", - "category": "misc", - "pattern": [ - "GGG", - "RDR", - "TTT" - ], - "key": { - "G": { "item": "antarchy:flytrap_goo" }, - "R": { "item": "minecraft:redstone_block" }, - "D": { "item": "minecraft:diamond_pickaxe" }, - "T": { "item": "minecraft:tnt" } - }, - "result": { - "id": "antarchy:miners_dream", - "count": 1 - } -} diff --git a/common-1.21.1/src/main/resources/data/antarchy/tags/block/miners_dream_carvable.json b/common-1.21.1/src/main/resources/data/antarchy/tags/block/miners_dream_carvable.json deleted file mode 100644 index 6f7d26ee..00000000 --- a/common-1.21.1/src/main/resources/data/antarchy/tags/block/miners_dream_carvable.json +++ /dev/null @@ -1,36 +0,0 @@ -{ - "replace": false, - "values": [ - "#minecraft:base_stone_overworld", - "#minecraft:base_stone_nether", - "minecraft:stone", - "minecraft:deepslate", - "minecraft:granite", - "minecraft:diorite", - "minecraft:andesite", - "minecraft:tuff", - "minecraft:calcite", - "minecraft:netherrack", - "minecraft:blackstone", - "minecraft:basalt", - "minecraft:smooth_basalt", - "minecraft:end_stone", - "minecraft:dirt", - "minecraft:coarse_dirt", - "minecraft:rooted_dirt", - "minecraft:podzol", - "minecraft:mycelium", - "minecraft:gravel", - "minecraft:sand", - "minecraft:red_sand", - "minecraft:sandstone", - "minecraft:red_sandstone", - "minecraft:clay", - "minecraft:soul_sand", - "minecraft:soul_soil", - "antarchy:nyxite", - "antarchy:pale_nyxite", - "antarchy:dream_sand", - "antarchy:dream_sandstone" - ] -} diff --git a/common-1.21.1/src/main/resources/data/antarchy/tags/item/big_bertha_ingredient.json b/common-1.21.1/src/main/resources/data/antarchy/tags/item/big_bertha_ingredient.json index 935bc10b..a7b62f62 100644 --- a/common-1.21.1/src/main/resources/data/antarchy/tags/item/big_bertha_ingredient.json +++ b/common-1.21.1/src/main/resources/data/antarchy/tags/item/big_bertha_ingredient.json @@ -25,4 +25,4 @@ "antarchy:hercules_horn", "antarchy:flytrap_goo" ] -} +} \ No newline at end of file diff --git a/common-1.21.1/src/main/resources/data/antarchy/weapon_attributes/blood_gods_blade.json b/common-1.21.1/src/main/resources/data/antarchy/weapon_attributes/blood_gods_blade.json new file mode 100644 index 00000000..d9bee8f9 --- /dev/null +++ b/common-1.21.1/src/main/resources/data/antarchy/weapon_attributes/blood_gods_blade.json @@ -0,0 +1,39 @@ +{ + "attributes": { + "attack_range": 3.0, + "category": "sword", + "attacks": [ + { + "hitbox": "HORIZONTAL_PLANE", + "damage_multiplier": 1, + "angle": 120, + "upswing": 0.5, + "animation": "bettercombat:one_handed_slash_horizontal_right", + "swing_sound": { + "id": "bettercombat:sword_slash" + } + }, + { + "hitbox": "HORIZONTAL_PLANE", + "damage_multiplier": 1, + "angle": 120, + "upswing": 0.5, + "animation": "bettercombat:one_handed_slash_horizontal_left", + "swing_sound": { + "id": "bettercombat:sword_slash" + } + }, + { + "hitbox": "FORWARD_BOX", + "damage_multiplier": 1.4, + "angle": 0, + "upswing": 0.5, + "animation": "bettercombat:one_handed_stab", + "swing_sound": { + "id": "bettercombat:sword_slash", + "pitch": 1.2 + } + } + ] + } +} diff --git a/fabric-1.20.1/src/main/java/com/craisinlord/antarchy/fabric/AntarchyConfigModuleFabric.java b/fabric-1.20.1/src/main/java/com/craisinlord/antarchy/fabric/AntarchyConfigModuleFabric.java index df2b329e..112fb35e 100644 --- a/fabric-1.20.1/src/main/java/com/craisinlord/antarchy/fabric/AntarchyConfigModuleFabric.java +++ b/fabric-1.20.1/src/main/java/com/craisinlord/antarchy/fabric/AntarchyConfigModuleFabric.java @@ -211,7 +211,6 @@ public final class AntarchyConfigModuleFabric { || name.equals("invertProjectilesFromInvertedPlayers") || name.startsWith("gravityGun") || name.startsWith("critterCage") - || name.startsWith("minersDream") || name.equals("potentNyxiteInvertedDurationSeconds") || name.equals("corneaEarNightVisionSeconds") || name.startsWith("american") diff --git a/fabric-1.20.1/src/main/java/com/craisinlord/antarchy/fabric/AntarchyFabricEvents.java b/fabric-1.20.1/src/main/java/com/craisinlord/antarchy/fabric/AntarchyFabricEvents.java index 356796b5..53860bf2 100644 --- a/fabric-1.20.1/src/main/java/com/craisinlord/antarchy/fabric/AntarchyFabricEvents.java +++ b/fabric-1.20.1/src/main/java/com/craisinlord/antarchy/fabric/AntarchyFabricEvents.java @@ -23,7 +23,6 @@ import com.craisinlord.antarchy.content.command.GravityCommand; import com.craisinlord.antarchy.content.command.QueenLocateCommand; import com.craisinlord.antarchy.content.time.TimeDilationCommand; import com.craisinlord.antarchy.content.time.TimeDilationManager; -import com.craisinlord.antarchy.content.item.MinersDreamExcavationManager; import net.fabricmc.fabric.api.command.v2.CommandRegistrationCallback; import net.fabricmc.fabric.api.entity.event.v1.ServerLivingEntityEvents; import net.fabricmc.fabric.api.event.lifecycle.v1.ServerTickEvents; @@ -164,7 +163,6 @@ public final class AntarchyFabricEvents { tickIchorPlayers(level); tickBloodglassRecharge(level); AttitudeAdjusterSlamManager.tick(level); - MinersDreamExcavationManager.tick(level); } TimeDilationManager.tickServer(server); invertedPlayers.retainAll(activeThisTick); diff --git a/fabric-1.20.1/src/main/java/com/craisinlord/antarchy/fabric/registry/AntarchyFabricItems.java b/fabric-1.20.1/src/main/java/com/craisinlord/antarchy/fabric/registry/AntarchyFabricItems.java index 8e926f20..cb234124 100644 --- a/fabric-1.20.1/src/main/java/com/craisinlord/antarchy/fabric/registry/AntarchyFabricItems.java +++ b/fabric-1.20.1/src/main/java/com/craisinlord/antarchy/fabric/registry/AntarchyFabricItems.java @@ -1257,9 +1257,6 @@ public final class AntarchyFabricItems { () -> new GravityGunItem(new Item.Properties().stacksTo(1).durability(512).rarity(Rarity.RARE).fireResistant())); - // Temporarily disabled: keeping the code but not registering the item for now. - // public static final DeferredItem MINERS_DREAM = ITEMS.register("miners_dream", - // () -> new MinersDreamItem(new Item.Properties().stacksTo(1).rarity(Rarity.RARE))); public static final DeferredItem SQUIDZOOKA = ITEMS.register("squidzooka", diff --git a/fabric-1.20.1/src/main/resources/fabric.mod.json b/fabric-1.20.1/src/main/resources/fabric.mod.json index 9c129c96..80f382e2 100644 --- a/fabric-1.20.1/src/main/resources/fabric.mod.json +++ b/fabric-1.20.1/src/main/resources/fabric.mod.json @@ -32,5 +32,8 @@ "minecraft": "~${minecraft_version}", "java": ">=17", "fabric-api": "*" + }, + "breaks": { + "apple_cows": "*" } } diff --git a/fabric-1.21.1/build.gradle b/fabric-1.21.1/build.gradle index 71079725..eb353483 100644 --- a/fabric-1.21.1/build.gradle +++ b/fabric-1.21.1/build.gradle @@ -37,8 +37,12 @@ dependencies { modImplementation "net.fabricmc:fabric-loader:${fabric_loader_version_1_21_1}" modApi "net.fabricmc.fabric-api:fabric-api:${fabric_api_version_1_21_1}" + modLocalRuntime "maven.modrinth:midnightlib:${midnightlib}" modImplementation "software.bernie.geckolib:geckolib-fabric-1.21.1:${geckolib_version}" modImplementation(files("$rootDir/infinite_dimensions_jars/antos-fabric-1.21.1-${antos_version}.jar")) + if (providers.gradleProperty('antarchySodiumCompatTest').orNull == 'true') { + modLocalRuntime(files("$rootDir/infinite_dimensions_jars/sodium-fabric-0.8.13+mc1.21.1.jar")) + } modCompileOnly("curse.maven:intapi-${intapi_curseforge_id}:${intapi_curseforge_file_id_fabric}") modLocalRuntime("curse.maven:intapi-${intapi_curseforge_id}:${intapi_curseforge_file_id_fabric}") modLocalRuntime("dev.architectury:architectury-fabric:${architectury_api_version}") @@ -46,8 +50,8 @@ dependencies { compileOnly "mezz.jei:jei-1.21.1-fabric-api:${jei_version}" modLocalRuntime "mezz.jei:jei-1.21.1-fabric:${jei_version}" + modLocalRuntime(files("$rootDir/infinite_dimensions_jars/infinity-2.7.3+1.21.1-fabric.jar")) compileOnly(files("$rootDir/infinite_dimensions_jars/infinity-2.7.3+1.21.1-neoforge.jar")) - runtimeOnly(files("$rootDir/infinite_dimensions_jars/infinity-2.7.3+1.21.1-fabric.jar")) compileOnly(files("$rootDir/infinite_dimensions_jars/gravity-api-1.0.7-1.20.1.jar")) compileOnly(files("$rootDir/infinite_dimensions_jars/sable-neoforge-1.21.1-2.0.3.jar")) @@ -71,7 +75,7 @@ dependencies { // space) but load the real Fabric jar at runtime — sizeable_foliage's Fabric jar ships // intermediary-mapped, which this compileOnly classpath can't resolve directly. compileOnly(files("$rootDir/sizeable_foliage_jars/sizeable_foliage-neoforge-1.21.1-1.0.0.jar")) - runtimeOnly(files("$rootDir/sizeable_foliage_jars/sizeable_foliage-fabric-1.21.1-1.0.0.jar")) + modLocalRuntime(files("$rootDir/sizeable_foliage_jars/sizeable_foliage-fabric-1.21.1-1.0.0.jar")) annotationProcessor "org.spongepowered:mixin:0.8.7:processor" implementation(annotationProcessor("io.github.llamalad7:mixinextras-common:${mixin_extras_1_21_1}")) @@ -111,10 +115,12 @@ tasks.named('processResources', ProcessResources).configure { // does — inject the refmap key into fabric's own copy of the shared file after copying. tasks.named('processResources', ProcessResources).configure { doLast { - var commonMixinConfig = new File(destinationDir, 'antarchy-common.mixins.json') - var json = new groovy.json.JsonSlurper().parse(commonMixinConfig) - json.put('refmap', 'antarchy.mixins.refmap.json') - commonMixinConfig.text = groovy.json.JsonOutput.prettyPrint(groovy.json.JsonOutput.toJson(json)) + ['antarchy-common.mixins.json', 'antarchy-sodium.mixins.json'].each { configName -> + var mixinConfig = new File(destinationDir, configName) + var json = new groovy.json.JsonSlurper().parse(mixinConfig) + json.put('refmap', 'antarchy.mixins.refmap.json') + mixinConfig.text = groovy.json.JsonOutput.prettyPrint(groovy.json.JsonOutput.toJson(json)) + } } } diff --git a/fabric-1.21.1/src/main/java/com/craisinlord/antarchy/fabric/AntarchyFabricEvents.java b/fabric-1.21.1/src/main/java/com/craisinlord/antarchy/fabric/AntarchyFabricEvents.java index b5d2ce5f..5c23159e 100644 --- a/fabric-1.21.1/src/main/java/com/craisinlord/antarchy/fabric/AntarchyFabricEvents.java +++ b/fabric-1.21.1/src/main/java/com/craisinlord/antarchy/fabric/AntarchyFabricEvents.java @@ -18,6 +18,8 @@ import com.craisinlord.antarchy.content.gravity.AntarchyGravityDirection; import com.craisinlord.antarchy.content.gravity.AntarchyGravityTransition; import com.craisinlord.antarchy.content.horde.CavarynHordeManager; import com.craisinlord.antarchy.content.portal.PermanentPortalManager; +import com.craisinlord.antarchy.content.portalgun.PortalGunProjectileEntity; +import com.craisinlord.antarchy.content.portalgun.PortalGunGrabManager; import com.craisinlord.antarchy.content.command.CavarynCommand; import com.craisinlord.antarchy.content.command.CaterpillarCommand; import com.craisinlord.antarchy.content.command.DimensionalTearCommand; @@ -27,7 +29,6 @@ import com.craisinlord.antarchy.content.command.RoyalCommand; import com.craisinlord.antarchy.content.time.TimeDilationCommand; import com.craisinlord.antarchy.content.time.TimeDilationManager; import com.craisinlord.antarchy.content.time.ChronosphereManager; -import com.craisinlord.antarchy.content.item.MinersDreamExcavationManager; import com.craisinlord.antarchy.content.item.WormHookTetherManager; import com.craisinlord.antarchy.content.entity.trades.ComputerScientistTradeManager; import com.craisinlord.antarchy.content.worldgen.thoraxis.ThoraxisUndersideManager; @@ -36,6 +37,7 @@ import net.fabricmc.fabric.api.entity.event.v1.ServerLivingEntityEvents; import net.fabricmc.fabric.api.entity.event.v1.ServerEntityWorldChangeEvents; import net.fabricmc.fabric.api.entity.event.v1.ServerPlayerEvents; import net.fabricmc.fabric.api.event.lifecycle.v1.ServerTickEvents; +import net.fabricmc.fabric.api.event.lifecycle.v1.ServerLifecycleEvents; import net.fabricmc.fabric.api.networking.v1.ServerPlayConnectionEvents; import net.fabricmc.fabric.api.object.builder.v1.trade.TradeOfferHelper; import net.fabricmc.fabric.api.event.player.AttackEntityCallback; @@ -75,6 +77,9 @@ public final class AntarchyFabricEvents { } public static void register() { + ServerLifecycleEvents.SERVER_STOPPING.register(server -> PortalGunProjectileEntity.releaseAllProjectileTickets()); + ServerLifecycleEvents.SERVER_STOPPING.register(PortalGunGrabManager::clear); + ServerLifecycleEvents.SERVER_STOPPING.register(com.craisinlord.antarchy.content.portalgun.PortalGunResetManager::clear); ServerPlayConnectionEvents.JOIN.register((handler, sender, server) -> { ChronosphereManager.refresh(handler.player); TimeDilationManager.resyncPersistentEffects(handler.player); @@ -183,13 +188,16 @@ public final class AntarchyFabricEvents { : net.minecraft.world.InteractionResultHolder.fail(held); }); AttackEntityCallback.EVENT.register((player, world, hand, entity, hitResult) -> { + if (player.getItemInHand(hand).getItem() instanceof com.craisinlord.antarchy.content.item.PortalGunItem) { + return InteractionResult.FAIL; + } if (!world.isClientSide && player.getItemInHand(hand).getItem() instanceof AttitudeAdjusterItem && player.getAttackStrengthScale(0.5F) >= 0.95F) { AttitudeAdjusterSlamManager.markSpecialHit(player); } return InteractionResult.PASS; }); net.fabricmc.fabric.api.event.player.AttackBlockCallback.EVENT.register((player, world, hand, pos, direction) -> { - if (player.getMainHandItem().getItem() instanceof com.craisinlord.antarchy.content.item.PortalGunItem) { + if (player.getItemInHand(hand).getItem() instanceof com.craisinlord.antarchy.content.item.PortalGunItem) { return InteractionResult.FAIL; } return InteractionResult.PASS; @@ -201,6 +209,7 @@ public final class AntarchyFabricEvents { }); ServerTickEvents.END_SERVER_TICK.register(server -> { + PortalGunGrabManager.tick(server); Set activeThisTick = new HashSet<>(); for (ServerLevel level : server.getAllLevels()) { CavarynHordeManager.tick(level); @@ -211,7 +220,6 @@ public final class AntarchyFabricEvents { tickIchorPlayers(level); tickBloodglassRecharge(level); AttitudeAdjusterSlamManager.tick(level); - MinersDreamExcavationManager.tick(level); for (ServerPlayer player : level.players()) { WormHookTetherManager.tick(player); } 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 77a7f1e8..59ddeac1 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 @@ -5,6 +5,7 @@ import com.craisinlord.antarchy.fabric.registry.AntarchyFabricItems; import com.craisinlord.antarchy.fabric.registry.AntarchyFabricMisc; import com.craisinlord.antarchy.Antarchy; +import com.craisinlord.antarchy.content.client.PortalGunCrosshairRenderer; import com.craisinlord.antarchy.content.client.CameraShakeClientState; import com.craisinlord.antarchy.content.client.AntarchyArchivePreviews; import com.craisinlord.antarchy.content.client.HerculesBeetleImpactShakeClientState; @@ -28,6 +29,7 @@ import net.fabricmc.fabric.api.client.render.fluid.v1.FluidRenderHandlerRegistry import net.fabricmc.fabric.api.client.particle.v1.ParticleFactoryRegistry; import net.fabricmc.fabric.api.client.rendering.v1.BlockEntityRendererRegistry; import net.fabricmc.fabric.api.client.rendering.v1.ColorProviderRegistry; +import net.fabricmc.fabric.api.client.rendering.v1.CoreShaderRegistrationCallback; import net.fabricmc.fabric.api.client.rendering.v1.EntityModelLayerRegistry; import net.fabricmc.fabric.api.client.rendering.v1.EntityRendererRegistry; import net.fabricmc.fabric.api.client.rendering.v1.HudRenderCallback; @@ -37,6 +39,7 @@ import net.minecraft.client.model.ChestBoatModel; import net.minecraft.client.gui.screens.MenuScreens; import net.minecraft.client.renderer.BiomeColors; import net.minecraft.client.renderer.RenderType; +import com.mojang.blaze3d.vertex.DefaultVertexFormat; import net.minecraft.client.renderer.texture.TextureAtlas; import net.minecraft.client.renderer.blockentity.BlockEntityRendererProvider; import net.minecraft.client.renderer.blockentity.CampfireRenderer; @@ -66,6 +69,18 @@ public final class AntarchyFabricClientBootstrap { } public static void register() { + CoreShaderRegistrationCallback.EVENT.register(context -> { + PortalGunPortalViewRenderer.clearPortalViewShader(); + try { + context.register( + ResourceLocation.fromNamespaceAndPath(Antarchy.MODID, "portal_view"), + DefaultVertexFormat.POSITION_TEX, + PortalGunPortalViewRenderer::setPortalViewShader + ); + } catch (java.io.IOException | RuntimeException exception) { + Antarchy.LOGGER.error("Failed to register portal view shader", exception); + } + }); AntarchyArchivePreviews.register(); AntarchyKeyBindings.register(); MenuScreens.register(AntarchyFabricMisc.DORRIE_INVENTORY_MENU.get(), com.craisinlord.antarchy.content.client.screen.DorrieInventoryScreen::new); @@ -611,14 +626,15 @@ public final class AntarchyFabricClientBootstrap { DorrieJumpHudRenderer.render(guiGraphics); HerculesBeetleChargeHudRenderer.render(guiGraphics); com.craisinlord.antarchy.content.client.KingJudgmentFlashHudRenderer.render(guiGraphics); + PortalGunCrosshairRenderer.render(guiGraphics); }); - WorldRenderEvents.AFTER_TRANSLUCENT.register(context -> { + WorldRenderEvents.LAST.register(context -> { BloodCrystalKatanaTrailHandler.render(context); ScorpionWhipTetherRenderHandler.render(context); WormHookTetherRenderHandler.render(context); if (context.matrixStack() != null && com.craisinlord.antarchy.content.client.renderer.PortalGunPortalViewRenderer.isEnabled()) { - com.craisinlord.antarchy.content.client.renderer.PortalGunPortalViewRenderer.render(context.camera(), context.matrixStack().last().pose(), context.tickCounter()); + com.craisinlord.antarchy.content.client.renderer.PortalGunPortalViewRenderer.render(context.camera(), context.matrixStack().last().pose(), context.projectionMatrix(), context.tickCounter()); } }); } 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 a986d1b0..6eb99e73 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 @@ -49,6 +49,24 @@ public final class AntarchyKeyBindings { GLFW.GLFW_KEY_B, CATEGORY ); + public static final KeyMapping PORTAL_GUN_ZOOM = new KeyMapping( + "key.antarchy.portal_gun_zoom", + InputConstants.Type.MOUSE, + GLFW.GLFW_MOUSE_BUTTON_MIDDLE, + CATEGORY + ); + public static final KeyMapping PORTAL_GUN_GRAB = new KeyMapping( + "key.antarchy.portal_gun_grab", + InputConstants.Type.KEYSYM, + GLFW.GLFW_KEY_G, + CATEGORY + ); + public static final KeyMapping PORTAL_GUN_RESET = new KeyMapping( + "key.antarchy.portal_gun_reset", + InputConstants.Type.KEYSYM, + GLFW.GLFW_KEY_R, + CATEGORY + ); private AntarchyKeyBindings() {} @@ -59,6 +77,9 @@ public final class AntarchyKeyBindings { KeyBindingHelper.registerKeyBinding(TIGERS_EYE_CAMOUFLAGE); KeyBindingHelper.registerKeyBinding(ROYAL_INVERSION_TOGGLE); KeyBindingHelper.registerKeyBinding(ROYAL_BOUNDARY); + KeyBindingHelper.registerKeyBinding(PORTAL_GUN_ZOOM); + KeyBindingHelper.registerKeyBinding(PORTAL_GUN_GRAB); + KeyBindingHelper.registerKeyBinding(PORTAL_GUN_RESET); } public static boolean isBrutalflyFlapPressed() { @@ -84,4 +105,20 @@ public final class AntarchyKeyBindings { public static boolean consumeRoyalBoundaryPressed() { return Minecraft.getInstance().screen == null && ROYAL_BOUNDARY.consumeClick(); } + + public static boolean consumePortalGunZoomPressed() { + return Minecraft.getInstance().screen == null && PORTAL_GUN_ZOOM.consumeClick(); + } + + public static boolean consumePortalGunGrabPressed() { + return Minecraft.getInstance().screen == null && PORTAL_GUN_GRAB.consumeClick(); + } + + public static boolean consumePortalGunResetPressed() { + return Minecraft.getInstance().screen == null && PORTAL_GUN_RESET.consumeClick(); + } + + public static boolean isPortalGunResetDown() { + return Minecraft.getInstance().screen == null && (PORTAL_GUN_RESET.isDown() || PORTAL_GUN_RESET.consumeClick()); + } } diff --git a/fabric-1.21.1/src/main/java/com/craisinlord/antarchy/fabric/client/PortalGunClientHandler.java b/fabric-1.21.1/src/main/java/com/craisinlord/antarchy/fabric/client/PortalGunClientHandler.java index 25883904..59be6aba 100644 --- a/fabric-1.21.1/src/main/java/com/craisinlord/antarchy/fabric/client/PortalGunClientHandler.java +++ b/fabric-1.21.1/src/main/java/com/craisinlord/antarchy/fabric/client/PortalGunClientHandler.java @@ -1,7 +1,9 @@ package com.craisinlord.antarchy.fabric.client; import com.craisinlord.antarchy.content.item.PortalGunItem; +import com.craisinlord.antarchy.content.client.PortalGunZoomClientState; import com.craisinlord.antarchy.content.network.PortalGunPrimaryPayload; +import com.craisinlord.antarchy.content.network.PortalGunGrabPayload; import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientTickEvents; import net.fabricmc.fabric.api.client.networking.v1.ClientPlayNetworking; import net.minecraft.client.Minecraft; @@ -17,19 +19,39 @@ public final class PortalGunClientHandler { } private static void tickMouse(Minecraft mc) { + if (mc.player == null) { + com.craisinlord.antarchy.content.client.PortalGunGrabClientState.clear(); + com.craisinlord.antarchy.content.client.PortalGunResetClientState.clear(); + } + boolean hasPortalGun = mc.player != null && (mc.player.getMainHandItem().getItem() instanceof PortalGunItem || mc.player.getOffhandItem().getItem() instanceof PortalGunItem); + boolean gameplayInput = mc.screen == null; + boolean held = hasPortalGun && gameplayInput; + boolean zoomPressed = AntarchyKeyBindings.consumePortalGunZoomPressed(); + PortalGunZoomClientState.tick(mc, hasPortalGun, zoomPressed && gameplayInput); + boolean resetActive = com.craisinlord.antarchy.content.client.PortalGunResetClientState.tick( + mc, + held, + AntarchyKeyBindings.isPortalGunResetDown(), + ClientPlayNetworking::send + ); + if (held && AntarchyKeyBindings.consumePortalGunGrabPressed()) { + ClientPlayNetworking.send(new PortalGunGrabPayload()); + } if (mc.player == null || mc.level == null || mc.screen != null) { lastAttackDown = false; return; } - if (!(mc.player.getMainHandItem().getItem() instanceof PortalGunItem)) { + boolean offhand = !(mc.player.getMainHandItem().getItem() instanceof PortalGunItem) + && mc.player.getOffhandItem().getItem() instanceof PortalGunItem; + if (!offhand && !(mc.player.getMainHandItem().getItem() instanceof PortalGunItem)) { lastAttackDown = false; return; } boolean attackDown = mc.options.keyAttack.isDown(); - if (attackDown && !lastAttackDown) { - ClientPlayNetworking.send(new PortalGunPrimaryPayload()); + if (attackDown && !lastAttackDown && !resetActive) { + ClientPlayNetworking.send(new PortalGunPrimaryPayload(offhand)); } lastAttackDown = attackDown; } 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 47ecf2e3..53f1cba8 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 @@ -42,6 +42,11 @@ public final class AntarchyFabricClientNetworking { ClientPlayNetworking.registerGlobalReceiver(AntarchyGameResultPayload.TYPE, (payload, context) -> context.client().execute(() -> com.craisinlord.antarchy.content.client.AntarchyGameClientState.update(payload))); com.craisinlord.antarchy.content.network.AntarchyGameNetworking.setSender(ClientPlayNetworking::send); + com.craisinlord.antarchy.content.client.PortalGunIndicatorClientState.setRequestSender(ClientPlayNetworking::send); + ClientPlayNetworking.registerGlobalReceiver(PortalGunIndicatorPayload.TYPE, (payload, context) -> + context.client().execute(() -> com.craisinlord.antarchy.content.client.PortalGunIndicatorClientState.update(payload))); + ClientPlayNetworking.registerGlobalReceiver(PortalGunGrabStatePayload.TYPE, (payload, context) -> + context.client().execute(() -> com.craisinlord.antarchy.content.client.PortalGunGrabClientState.update(payload.active()))); ClientPlayNetworking.registerGlobalReceiver(GravityStatePayload.TYPE, (payload, context) -> context.client().execute(() -> AntarchyFabricNetworking.handleGravityState(context.player(), payload))); ClientPlayNetworking.registerGlobalReceiver(BloodglassStatePayload.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 051efdca..6291f2c4 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 @@ -49,6 +49,8 @@ public final class AntarchyFabricNetworking { registerPayloadTypes(); registerServerReceivers(); com.craisinlord.antarchy.content.network.AntarchyGameNetworkHandler.setResultSender(ServerPlayNetworking::send); + com.craisinlord.antarchy.content.portalgun.PortalGunIndicatorSync.setSender(ServerPlayNetworking::send); + com.craisinlord.antarchy.content.portalgun.PortalGunGrabManager.setStateSender(ServerPlayNetworking::send); } public static void bootstrapMultipartCommon() { @@ -73,6 +75,8 @@ public final class AntarchyFabricNetworking { private static void registerPayloadTypes() { PayloadTypeRegistry.playS2C().register(AntarchyGameResultPayload.TYPE, AntarchyGameResultPayload.STREAM_CODEC); + PayloadTypeRegistry.playS2C().register(PortalGunIndicatorPayload.TYPE, PortalGunIndicatorPayload.STREAM_CODEC); + PayloadTypeRegistry.playS2C().register(PortalGunGrabStatePayload.TYPE, PortalGunGrabStatePayload.STREAM_CODEC); PayloadTypeRegistry.playS2C().register(GravityStatePayload.TYPE, GravityStatePayload.STREAM_CODEC); PayloadTypeRegistry.playS2C().register(BloodglassStatePayload.TYPE, BloodglassStatePayload.STREAM_CODEC); PayloadTypeRegistry.playS2C().register(TigerEyeCamouflageStatePayload.TYPE, TigerEyeCamouflageStatePayload.STREAM_CODEC); @@ -88,6 +92,9 @@ public final class AntarchyFabricNetworking { PayloadTypeRegistry.playC2S().register(GravityGunPrimaryPayload.TYPE, GravityGunPrimaryPayload.STREAM_CODEC); PayloadTypeRegistry.playC2S().register(PortalGunPrimaryPayload.TYPE, PortalGunPrimaryPayload.STREAM_CODEC); + PayloadTypeRegistry.playC2S().register(PortalGunIndicatorRequestPayload.TYPE, PortalGunIndicatorRequestPayload.STREAM_CODEC); + PayloadTypeRegistry.playC2S().register(PortalGunGrabPayload.TYPE, PortalGunGrabPayload.STREAM_CODEC); + PayloadTypeRegistry.playC2S().register(PortalGunResetPayload.TYPE, PortalGunResetPayload.STREAM_CODEC); PayloadTypeRegistry.playC2S().register(EyeOfStormPrimaryPayload.TYPE, EyeOfStormPrimaryPayload.STREAM_CODEC); PayloadTypeRegistry.playC2S().register(OpenGiantFryingPanPayload.TYPE, OpenGiantFryingPanPayload.STREAM_CODEC); PayloadTypeRegistry.playC2S().register(RoyalMountActionPayload.TYPE, RoyalMountActionPayload.STREAM_CODEC); @@ -121,6 +128,12 @@ public final class AntarchyFabricNetworking { context.server().execute(() -> handleGravityGunPrimary(context.player(), payload))); ServerPlayNetworking.registerGlobalReceiver(PortalGunPrimaryPayload.TYPE, (payload, context) -> context.server().execute(() -> handlePortalGunPrimary(context.player(), payload))); + ServerPlayNetworking.registerGlobalReceiver(PortalGunIndicatorRequestPayload.TYPE, (payload, context) -> + context.server().execute(() -> com.craisinlord.antarchy.content.portalgun.PortalGunIndicatorSync.handleRequest(context.player(), payload))); + ServerPlayNetworking.registerGlobalReceiver(PortalGunGrabPayload.TYPE, (payload, context) -> + context.server().execute(() -> com.craisinlord.antarchy.content.portalgun.PortalGunGrabManager.toggle(context.player()))); + ServerPlayNetworking.registerGlobalReceiver(PortalGunResetPayload.TYPE, (payload, context) -> + context.server().execute(() -> com.craisinlord.antarchy.content.portalgun.PortalGunResetManager.handleInput(context.player(), payload.action()))); ServerPlayNetworking.registerGlobalReceiver(EyeOfStormPrimaryPayload.TYPE, (payload, context) -> context.server().execute(() -> handleEyeOfStormPrimary(context.player(), payload))); ServerPlayNetworking.registerGlobalReceiver(OpenGiantFryingPanPayload.TYPE, (payload, context) -> @@ -260,10 +273,11 @@ public final class AntarchyFabricNetworking { } private static void handlePortalGunPrimary(ServerPlayer player, PortalGunPrimaryPayload payload) { - if (!(player.getMainHandItem().getItem() instanceof PortalGunItem portalGunItem)) { + ItemStack stack = payload.offhand() ? player.getOffhandItem() : player.getMainHandItem(); + if (!(stack.getItem() instanceof PortalGunItem portalGunItem)) { return; } - portalGunItem.firePrimary(player.serverLevel(), player, player.getMainHandItem()); + portalGunItem.firePrimary(player.serverLevel(), player, stack, payload.offhand()); } private static void handleRoyalMountAction(ServerPlayer player, RoyalMountActionPayload payload) { diff --git a/fabric-1.21.1/src/main/java/com/craisinlord/antarchy/fabric/registry/AntarchyFabricSounds.java b/fabric-1.21.1/src/main/java/com/craisinlord/antarchy/fabric/registry/AntarchyFabricSounds.java index 9b9ab0bd..d133177e 100644 --- a/fabric-1.21.1/src/main/java/com/craisinlord/antarchy/fabric/registry/AntarchyFabricSounds.java +++ b/fabric-1.21.1/src/main/java/com/craisinlord/antarchy/fabric/registry/AntarchyFabricSounds.java @@ -344,6 +344,9 @@ public final class AntarchyFabricSounds { public static final DeferredHolder PORTAL_FIZZLE = registerSoundEvent("portal_fizzle"); + public static final DeferredHolder PORTAL_ENTER = registerSoundEvent("portal_enter"); + public static final DeferredHolder PORTAL_EXIT = registerSoundEvent("portal_exit"); + public static final DeferredHolder PORTAL_AMBIENT = registerSoundEvent("portal_ambient"); public static final DeferredHolder BED_BUG_AMBIENT = registerSoundEvent("bed_bug_ambient"); diff --git a/fabric-1.21.1/src/main/java/com/craisinlord/antarchy/fabric/registry/AntarchyFabricSpawnPlacements.java b/fabric-1.21.1/src/main/java/com/craisinlord/antarchy/fabric/registry/AntarchyFabricSpawnPlacements.java index 30b004b2..ad609970 100644 --- a/fabric-1.21.1/src/main/java/com/craisinlord/antarchy/fabric/registry/AntarchyFabricSpawnPlacements.java +++ b/fabric-1.21.1/src/main/java/com/craisinlord/antarchy/fabric/registry/AntarchyFabricSpawnPlacements.java @@ -67,7 +67,7 @@ public final class AntarchyFabricSpawnPlacements { SpawnPlacements.register(AntarchyFabricEntities.VORTEX.get(), SpawnPlacementTypes.NO_RESTRICTIONS, Heightmap.Types.MOTION_BLOCKING_NO_LEAVES, com.craisinlord.antarchy.content.entity.vortex.VortexEntity::canSpawn); SpawnPlacements.register(AntarchyFabricEntities.SCORPION.get(), SpawnPlacementTypes.ON_GROUND, Heightmap.Types.MOTION_BLOCKING_NO_LEAVES, ScorpionEntity::canSpawn); SpawnPlacements.register(AntarchyFabricEntities.BASILISK.get(), SpawnPlacementTypes.ON_GROUND, Heightmap.Types.MOTION_BLOCKING_NO_LEAVES, BasiliskEntity::canSpawn); - SpawnPlacements.register(AntarchyFabricEntities.EMPEROR_SCORPION.get(), SpawnPlacementTypes.ON_GROUND, Heightmap.Types.MOTION_BLOCKING_NO_LEAVES, EmperorScorpionEntity::canSpawn); + SpawnPlacements.register(AntarchyFabricEntities.EMPEROR_SCORPION.get(), SpawnPlacementTypes.NO_RESTRICTIONS, Heightmap.Types.MOTION_BLOCKING_NO_LEAVES, EmperorScorpionEntity::canSpawn); SpawnPlacements.register(AntarchyFabricEntities.TORETERROR.get(), SpawnPlacementTypes.ON_GROUND, Heightmap.Types.MOTION_BLOCKING_NO_LEAVES, ToreterrorEntity::canSpawn); SpawnPlacements.register(AntarchyFabricEntities.CRAWLING_BLIGHT.get(), SpawnPlacementTypes.ON_GROUND, Heightmap.Types.MOTION_BLOCKING_NO_LEAVES, com.craisinlord.antarchy.content.entity.CrawlingBlightEntity::canSpawn); SpawnPlacements.register(AntarchyFabricEntities.SKULKING_FRIGHT.get(), SpawnPlacementTypes.ON_GROUND, Heightmap.Types.MOTION_BLOCKING_NO_LEAVES, com.craisinlord.antarchy.content.entity.SkulkingFrightEntity::canSpawn); diff --git a/fabric-1.21.1/src/main/resources/fabric.mod.json b/fabric-1.21.1/src/main/resources/fabric.mod.json index b2fd1d08..93e915c7 100644 --- a/fabric-1.21.1/src/main/resources/fabric.mod.json +++ b/fabric-1.21.1/src/main/resources/fabric.mod.json @@ -28,6 +28,7 @@ }, "mixins": [ "antarchy-common.mixins.json", + "antarchy-sodium.mixins.json", "antarchy-fabric.mixins.json" ], "depends": { @@ -42,7 +43,9 @@ "sizeable_foliage": "*" }, "breaks": { - "infinity": "<2.7.3" + "infinity": "<2.7.3", + "apple_cows": "*", + "hoverboards": "*" }, "suggests": { "modmenu": "*", diff --git a/forge-1.20.1/src/main/java/com/craisinlord/antarchy/forge/AntarchyConfigModuleForge.java b/forge-1.20.1/src/main/java/com/craisinlord/antarchy/forge/AntarchyConfigModuleForge.java index 6d802296..1058d06d 100644 --- a/forge-1.20.1/src/main/java/com/craisinlord/antarchy/forge/AntarchyConfigModuleForge.java +++ b/forge-1.20.1/src/main/java/com/craisinlord/antarchy/forge/AntarchyConfigModuleForge.java @@ -447,11 +447,6 @@ public final class AntarchyConfigModuleForge { AntarchySettings.setCritterCageMaxCapturableWidth(AntarchyToolsConfig.critterCageMaxCapturableWidth()); AntarchySettings.setCritterCageMaxCapturableHeight(AntarchyToolsConfig.critterCageMaxCapturableHeight()); - AntarchySettings.setMinersDreamEnabled(AntarchyToolsConfig.minersDreamEnabled()); - AntarchySettings.setMinersDreamMinimumRange(AntarchyToolsConfig.minersDreamMinimumRange()); - AntarchySettings.setMinersDreamMaximumRange(AntarchyToolsConfig.minersDreamMaximumRange()); - AntarchySettings.setMinersDreamTorchSpacing(AntarchyToolsConfig.minersDreamTorchSpacing()); - AntarchySettings.setMinersDreamBlocksPerTick(AntarchyToolsConfig.minersDreamBlocksPerTick()); AntarchySettings.setDuctTapeRepairPercentPerUse(AntarchyToolsConfig.ductTapeRepairPercentPerUse()); AntarchySettings.setPotentNyxiteInvertedDurationSeconds(AntarchyToolsConfig.potentNyxiteInvertedDurationSeconds()); diff --git a/forge-1.20.1/src/main/java/com/craisinlord/antarchy/forge/AntarchyToolsConfig.java b/forge-1.20.1/src/main/java/com/craisinlord/antarchy/forge/AntarchyToolsConfig.java index 01126de9..da9e84af 100644 --- a/forge-1.20.1/src/main/java/com/craisinlord/antarchy/forge/AntarchyToolsConfig.java +++ b/forge-1.20.1/src/main/java/com/craisinlord/antarchy/forge/AntarchyToolsConfig.java @@ -187,11 +187,6 @@ public final class AntarchyToolsConfig { // Miner's Dream - private static final ForgeConfigSpec.BooleanValue MINERS_DREAM_ENABLED; - private static final ForgeConfigSpec.IntValue MINERS_DREAM_MINIMUM_RANGE; - private static final ForgeConfigSpec.IntValue MINERS_DREAM_MAXIMUM_RANGE; - private static final ForgeConfigSpec.IntValue MINERS_DREAM_TORCH_SPACING; - private static final ForgeConfigSpec.IntValue MINERS_DREAM_BLOCKS_PER_TICK; // Miscellaneous items / consumables @@ -472,16 +467,6 @@ public final class AntarchyToolsConfig { b.pop(); - // Miner's Dream - - b.push("minersDream"); - MINERS_DREAM_ENABLED = b.comment("Master toggle. If false, the item remains craftable but cannot be used.").define("enabled", true); - MINERS_DREAM_MINIMUM_RANGE = b.comment("Minimum cave length in blocks. Clamped to [8, 256].") .defineInRange("minimumRange", 48, 8, 256); - MINERS_DREAM_MAXIMUM_RANGE = b.comment("Maximum cave length in blocks. Clamped to [8, 256].") .defineInRange("maximumRange", 96, 8, 256); - MINERS_DREAM_TORCH_SPACING = b.comment("Approximate blocks between torch placements along the cave.") .defineInRange("torchSpacing", 10, 4, 64); - MINERS_DREAM_BLOCKS_PER_TICK = b.comment("Maximum blocks removed per server tick while excavating.") .defineInRange("blocksPerTick", 150, 1, 2000); - b.pop(); - // Miscellaneous items / consumables @@ -662,11 +647,6 @@ public final class AntarchyToolsConfig { static double critterCageMaxCapturableWidth() { return CRITTER_CAGE_MAX_CAPTURABLE_WIDTH.get(); } static double critterCageMaxCapturableHeight() { return CRITTER_CAGE_MAX_CAPTURABLE_HEIGHT.get(); } - static boolean minersDreamEnabled() { return MINERS_DREAM_ENABLED.get(); } - static int minersDreamMinimumRange() { return MINERS_DREAM_MINIMUM_RANGE.get(); } - static int minersDreamMaximumRange() { return MINERS_DREAM_MAXIMUM_RANGE.get(); } - static int minersDreamTorchSpacing() { return MINERS_DREAM_TORCH_SPACING.get(); } - static int minersDreamBlocksPerTick() { return MINERS_DREAM_BLOCKS_PER_TICK.get(); } static double ductTapeRepairPercentPerUse() { return DUCT_TAPE_REPAIR_PERCENT_PER_USE.get(); } static double potentNyxiteInvertedDurationSeconds() { return POTENT_NYXITE_INVERTED_DURATION_SECONDS.get(); } diff --git a/forge-1.20.1/src/main/java/com/craisinlord/antarchy/forge/registry/AntarchyForgeEvents.java b/forge-1.20.1/src/main/java/com/craisinlord/antarchy/forge/registry/AntarchyForgeEvents.java index 18e6eef4..37b5f587 100644 --- a/forge-1.20.1/src/main/java/com/craisinlord/antarchy/forge/registry/AntarchyForgeEvents.java +++ b/forge-1.20.1/src/main/java/com/craisinlord/antarchy/forge/registry/AntarchyForgeEvents.java @@ -70,7 +70,6 @@ import net.minecraftforge.event.entity.living.LivingEvent; import net.minecraftforge.event.TickEvent; import net.minecraftforge.event.village.VillagerTradesEvent; import net.minecraftforge.event.village.WandererTradesEvent; -import com.craisinlord.antarchy.content.item.MinersDreamExcavationManager; import net.minecraftforge.fluids.FluidInteractionRegistry; import com.craisinlord.antarchy.forge.network.AntarchyForgeNetworkCore; import com.craisinlord.antarchy.content.time.TimeDilationManager; @@ -106,7 +105,7 @@ public final class AntarchyForgeEvents { MinecraftForge.EVENT_BUS.addListener(AntarchyForgeEvents::tickDreadAndIchor); MinecraftForge.EVENT_BUS.addListener(AntarchyForgeEvents::tickScorpionWhips); MinecraftForge.EVENT_BUS.addListener(AntarchyForgeEvents::tickDreamSandLowGravity); - MinecraftForge.EVENT_BUS.addListener(AntarchyForgeEvents::tickMinersDreamExcavations); + MinecraftForge.EVENT_BUS.addListener(AntarchyForgeEvents::tickAttitudeAdjusterSlams); MinecraftForge.EVENT_BUS.addListener(AntarchyForgeEvents::tickTimeDilation); MinecraftForge.EVENT_BUS.addListener(AntarchyForgeEvents::handleAntiwaterDamage); MinecraftForge.EVENT_BUS.addListener(AntarchyForgeEvents::handleAntiwaterFall); @@ -1007,11 +1006,10 @@ public final class AntarchyForgeEvents { } } - static void tickMinersDreamExcavations(TickEvent.ServerTickEvent event) { + static void tickAttitudeAdjusterSlams(TickEvent.ServerTickEvent event) { if (event.phase != TickEvent.Phase.END) return; for (ServerLevel level : event.getServer().getAllLevels()) { AttitudeAdjusterSlamManager.tick(level); - MinersDreamExcavationManager.tick(level); } } diff --git a/forge-1.20.1/src/main/java/com/craisinlord/antarchy/forge/registry/AntarchyForgeItems.java b/forge-1.20.1/src/main/java/com/craisinlord/antarchy/forge/registry/AntarchyForgeItems.java index 2ca9f565..9bcf022b 100644 --- a/forge-1.20.1/src/main/java/com/craisinlord/antarchy/forge/registry/AntarchyForgeItems.java +++ b/forge-1.20.1/src/main/java/com/craisinlord/antarchy/forge/registry/AntarchyForgeItems.java @@ -779,9 +779,6 @@ public final class AntarchyForgeItems { consumer.accept(AntarchyGeoItemExtensions.crossbowHold(this)); } }); - // Temporarily disabled: keeping the code but not registering the item for now. - // public static final RegistryObject MINERS_DREAM = ITEMS.register("miners_dream", - // () -> new MinersDreamItem(new Item.Properties().stacksTo(1).rarity(Rarity.RARE))); public static final RegistryObject SQUIDZOOKA = ITEMS.register("squidzooka", () -> new SquidzookaItem(new Item.Properties().stacksTo(1).durability(384).rarity(Rarity.RARE)) { @Override diff --git a/forge-1.20.1/src/main/resources/META-INF/mods.toml b/forge-1.20.1/src/main/resources/META-INF/mods.toml index 4dba59d6..7f5f4117 100644 --- a/forge-1.20.1/src/main/resources/META-INF/mods.toml +++ b/forge-1.20.1/src/main/resources/META-INF/mods.toml @@ -22,6 +22,15 @@ displayTest="MATCH_VERSION" versionRange="${minecraft_version_range}" ordering="NONE" side="BOTH" +# Antarchy already includes Apple Cows. Forge 1.20.1 has no "incompatible" dependency type, +# so this optional dependency uses a version range no release can satisfy: if the standalone +# Apple Cows mod is installed, Forge refuses to load instead of running both copies. +[[dependencies.${mod_id}]] + modId="apple_cows" + mandatory=false + versionRange="(,0)" + ordering="NONE" + side="BOTH" [[mixins]] config="antarchy-common.mixins.json" diff --git a/gradle.properties b/gradle.properties index 700d0cbc..02846585 100644 --- a/gradle.properties +++ b/gradle.properties @@ -6,7 +6,7 @@ org.gradle.configureondemand=true minecraft_version_1_21_1=1.21.1 modid=antarchy mod_id=antarchy -mod_version=2.1.1 +mod_version=2.2.0 maven_group=com.craisinlord mixin_version=0.15.4+mixin.0.8.7 @@ -54,8 +54,8 @@ jei_version=19.27.0.336 intapi_curseforge_id = 817709 -intapi_curseforge_file_id_neoforge = 7921681 -intapi_curseforge_file_id_fabric = 7921674 +intapi_curseforge_file_id_neoforge = 8868053 +intapi_curseforge_file_id_fabric = 8868049 intapi_curseforge_file_id_common = 5178461 antos_version = 1.2.1 diff --git a/neoforge-1.21.1/build.gradle b/neoforge-1.21.1/build.gradle index f9b7acfc..5bbc0fe4 100644 --- a/neoforge-1.21.1/build.gradle +++ b/neoforge-1.21.1/build.gradle @@ -46,6 +46,9 @@ neoForge { } dependencies { + if (providers.gradleProperty('antarchySodiumCompatTest').orNull == 'true') { + runtimeOnly(files(rootProject.file("infinite_dimensions_jars/sodium-neoforge-0.8.13+mc1.21.1.jar"))) + } compileOnly(files(rootProject.file("infinite_dimensions_jars/antos-neoforge-1.21.1-${antos_version}.jar"))) runtimeOnly(files(rootProject.file("infinite_dimensions_jars/antos-neoforge-1.21.1-${antos_version}.jar"))) compileOnly("curse.maven:intapi-${intapi_curseforge_id}:${intapi_curseforge_file_id_neoforge}") 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 a9baa126..37e8c373 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 @@ -630,11 +630,6 @@ public final class AntarchyConfigModuleNeoforge { AntarchySettings.setCritterCageMaxCapturableWidth(AntarchyToolsConfig.critterCageMaxCapturableWidth()); AntarchySettings.setCritterCageMaxCapturableHeight(AntarchyToolsConfig.critterCageMaxCapturableHeight()); - AntarchySettings.setMinersDreamEnabled(AntarchyToolsConfig.minersDreamEnabled()); - AntarchySettings.setMinersDreamMinimumRange(AntarchyToolsConfig.minersDreamMinimumRange()); - AntarchySettings.setMinersDreamMaximumRange(AntarchyToolsConfig.minersDreamMaximumRange()); - AntarchySettings.setMinersDreamTorchSpacing(AntarchyToolsConfig.minersDreamTorchSpacing()); - AntarchySettings.setMinersDreamBlocksPerTick(AntarchyToolsConfig.minersDreamBlocksPerTick()); AntarchySettings.setDuctTapeRepairPercentPerUse(AntarchyToolsConfig.ductTapeRepairPercentPerUse()); AntarchySettings.setPotentNyxiteInvertedDurationSeconds(AntarchyToolsConfig.potentNyxiteInvertedDurationSeconds()); @@ -669,6 +664,15 @@ public final class AntarchyConfigModuleNeoforge { AntarchySettings.setElythiaPortalEnabled(AntarchyMiscConfig.elythiaPortalEnabled()); AntarchySettings.setThoraxisPortalEnabled(AntarchyMiscConfig.thoraxisPortalEnabled()); AntarchySettings.setCavarynPortalEnabled(AntarchyMiscConfig.cavarynPortalEnabled()); + AntarchySettings.setPortalGunCanFireThroughGlass(AntarchyMiscConfig.portalGunCanFireThroughGlass()); + AntarchySettings.setPortalGunCanFireThroughLiquid(AntarchyMiscConfig.portalGunCanFireThroughLiquid()); + AntarchySettings.setPortalGunCanPortalsResizeWhenCreated(AntarchyMiscConfig.portalGunCanPortalsResizeWhenCreated()); + AntarchySettings.setPortalGunCanPortalProjectilesChunkload(AntarchyMiscConfig.portalGunCanPortalProjectilesChunkload()); + AntarchySettings.setPortalGunSeeThroughPortals(AntarchyMiscConfig.portalGunSeeThroughPortals()); + AntarchySettings.setPortalGunFancyPortals(AntarchyMiscConfig.portalGunFancyPortals()); + AntarchySettings.setPortalGunMaxShootDistance(AntarchyMiscConfig.portalGunMaxShootDistance()); + AntarchySettings.setPortalGunEntityGrabWeightBase(AntarchyMiscConfig.portalGunEntityGrabWeightBase()); + AntarchySettings.setPortalGunIndicatorSize(AntarchyMiscConfig.portalGunIndicatorSize()); // Misc diff --git a/neoforge-1.21.1/src/main/java/com/craisinlord/antarchy/neoforge/AntarchyMiscConfig.java b/neoforge-1.21.1/src/main/java/com/craisinlord/antarchy/neoforge/AntarchyMiscConfig.java index ebbf6d9d..8e9b2e29 100644 --- a/neoforge-1.21.1/src/main/java/com/craisinlord/antarchy/neoforge/AntarchyMiscConfig.java +++ b/neoforge-1.21.1/src/main/java/com/craisinlord/antarchy/neoforge/AntarchyMiscConfig.java @@ -32,6 +32,15 @@ public final class AntarchyMiscConfig { private static final ModConfigSpec.BooleanValue ELYTHIA_PORTAL_ENABLED; private static final ModConfigSpec.BooleanValue THORAXIS_PORTAL_ENABLED; private static final ModConfigSpec.BooleanValue CAVARYN_PORTAL_ENABLED; + private static final ModConfigSpec.BooleanValue PORTAL_GUN_CAN_FIRE_THROUGH_GLASS; + private static final ModConfigSpec.BooleanValue PORTAL_GUN_CAN_FIRE_THROUGH_LIQUID; + private static final ModConfigSpec.BooleanValue PORTAL_GUN_CAN_PORTALS_RESIZE_WHEN_CREATED; + private static final ModConfigSpec.BooleanValue PORTAL_GUN_CAN_PORTAL_PROJECTILES_CHUNKLOAD; + private static final ModConfigSpec.BooleanValue PORTAL_GUN_SEE_THROUGH_PORTALS; + private static final ModConfigSpec.BooleanValue PORTAL_GUN_FANCY_PORTALS; + private static final ModConfigSpec.IntValue PORTAL_GUN_MAX_SHOOT_DISTANCE; + private static final ModConfigSpec.IntValue PORTAL_GUN_ENTITY_GRAB_WEIGHT_BASE; + private static final ModConfigSpec.IntValue PORTAL_GUN_INDICATOR_SIZE; // Diamond Minecart private static final ModConfigSpec.BooleanValue DIAMOND_MINECART_ENABLED; @@ -129,6 +138,18 @@ public final class AntarchyMiscConfig { CAVARYN_PORTAL_ENABLED = b.comment("Whether Myrmite portals to Cavaryn can activate and function.").define("cavarynPortalEnabled", true); b.pop(); + b.push("portalGun"); + PORTAL_GUN_CAN_FIRE_THROUGH_GLASS = b.define("canFireThroughGlass", false); + PORTAL_GUN_CAN_FIRE_THROUGH_LIQUID = b.define("canFireThroughLiquid", false); + PORTAL_GUN_CAN_PORTALS_RESIZE_WHEN_CREATED = b.define("canPortalsResizeWhenCreated", true); + PORTAL_GUN_CAN_PORTAL_PROJECTILES_CHUNKLOAD = b.define("canPortalProjectilesChunkload", true); + PORTAL_GUN_SEE_THROUGH_PORTALS = b.define("seeThroughPortals", true); + PORTAL_GUN_FANCY_PORTALS = b.define("fancyPortals", true); + PORTAL_GUN_MAX_SHOOT_DISTANCE = b.defineInRange("maxShootDistance", 10000, 1, Integer.MAX_VALUE); + PORTAL_GUN_ENTITY_GRAB_WEIGHT_BASE = b.defineInRange("entityGrabWeightBase", 100, 0, 1000); + PORTAL_GUN_INDICATOR_SIZE = b.defineInRange("indicatorSize", 30, 0, 100); + b.pop(); + // Diamond Minecart @@ -196,6 +217,15 @@ public final class AntarchyMiscConfig { public static boolean elythiaPortalEnabled() { return ELYTHIA_PORTAL_ENABLED.get(); } public static boolean thoraxisPortalEnabled() { return THORAXIS_PORTAL_ENABLED.get(); } public static boolean cavarynPortalEnabled() { return CAVARYN_PORTAL_ENABLED.get(); } + public static boolean portalGunCanFireThroughGlass() { return PORTAL_GUN_CAN_FIRE_THROUGH_GLASS.get(); } + public static boolean portalGunCanFireThroughLiquid() { return PORTAL_GUN_CAN_FIRE_THROUGH_LIQUID.get(); } + public static boolean portalGunCanPortalsResizeWhenCreated() { return PORTAL_GUN_CAN_PORTALS_RESIZE_WHEN_CREATED.get(); } + public static boolean portalGunCanPortalProjectilesChunkload() { return PORTAL_GUN_CAN_PORTAL_PROJECTILES_CHUNKLOAD.get(); } + public static boolean portalGunSeeThroughPortals() { return PORTAL_GUN_SEE_THROUGH_PORTALS.get(); } + public static boolean portalGunFancyPortals() { return PORTAL_GUN_FANCY_PORTALS.get(); } + public static int portalGunMaxShootDistance() { return PORTAL_GUN_MAX_SHOOT_DISTANCE.get(); } + public static int portalGunEntityGrabWeightBase() { return PORTAL_GUN_ENTITY_GRAB_WEIGHT_BASE.get(); } + public static int portalGunIndicatorSize() { return PORTAL_GUN_INDICATOR_SIZE.get(); } static boolean diamondMinecartEnabled() { return DIAMOND_MINECART_ENABLED.get(); } static boolean diamondMinecartPlacesRails() { return DIAMOND_MINECART_PLACES_RAILS.get(); } diff --git a/neoforge-1.21.1/src/main/java/com/craisinlord/antarchy/neoforge/AntarchyNeoforgeEvents.java b/neoforge-1.21.1/src/main/java/com/craisinlord/antarchy/neoforge/AntarchyNeoforgeEvents.java index 089dad02..3df0b55f 100644 --- a/neoforge-1.21.1/src/main/java/com/craisinlord/antarchy/neoforge/AntarchyNeoforgeEvents.java +++ b/neoforge-1.21.1/src/main/java/com/craisinlord/antarchy/neoforge/AntarchyNeoforgeEvents.java @@ -11,6 +11,7 @@ import com.craisinlord.antarchy.content.time.TimeDilationCommand; import net.neoforged.bus.api.SubscribeEvent; import net.neoforged.fml.common.EventBusSubscriber; import net.neoforged.neoforge.event.RegisterCommandsEvent; +import net.neoforged.neoforge.event.server.ServerStoppingEvent; import net.minecraft.commands.Commands; import net.minecraft.network.chat.Component; @@ -29,4 +30,9 @@ public final class AntarchyNeoforgeEvents { TimeDilationCommand.register(event.getDispatcher()); RoyalCommand.register(event.getDispatcher()); } + + @SubscribeEvent + public static void onServerStopping(ServerStoppingEvent event) { + com.craisinlord.antarchy.content.portalgun.PortalGunProjectileEntity.releaseAllProjectileTickets(); + } } 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 419e1df7..eac2a651 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 @@ -258,11 +258,6 @@ public final class AntarchyToolsConfig { // Miner's Dream - private static final ModConfigSpec.BooleanValue MINERS_DREAM_ENABLED; - private static final ModConfigSpec.IntValue MINERS_DREAM_MINIMUM_RANGE; - private static final ModConfigSpec.IntValue MINERS_DREAM_MAXIMUM_RANGE; - private static final ModConfigSpec.IntValue MINERS_DREAM_TORCH_SPACING; - private static final ModConfigSpec.IntValue MINERS_DREAM_BLOCKS_PER_TICK; // Miscellaneous items / consumables @@ -635,16 +630,6 @@ public final class AntarchyToolsConfig { b.pop(); - // Miner's Dream - - b.push("minersDream"); - MINERS_DREAM_ENABLED = b.comment("Master toggle. If false, the item remains craftable but cannot be used.").define("enabled", true); - MINERS_DREAM_MINIMUM_RANGE = b.comment("Minimum cave length in blocks. Clamped to [8, 256].") .defineInRange("minimumRange", 48, 8, 256); - MINERS_DREAM_MAXIMUM_RANGE = b.comment("Maximum cave length in blocks. Clamped to [8, 256].") .defineInRange("maximumRange", 96, 8, 256); - MINERS_DREAM_TORCH_SPACING = b.comment("Approximate blocks between torch placements along the cave.") .defineInRange("torchSpacing", 10, 4, 64); - MINERS_DREAM_BLOCKS_PER_TICK = b.comment("Maximum blocks removed per server tick while excavating.") .defineInRange("blocksPerTick", 150, 1, 2000); - b.pop(); - // Miscellaneous items / consumables @@ -833,11 +818,6 @@ public final class AntarchyToolsConfig { static double critterCageMaxCapturableWidth() { return CRITTER_CAGE_MAX_CAPTURABLE_WIDTH.get(); } static double critterCageMaxCapturableHeight() { return CRITTER_CAGE_MAX_CAPTURABLE_HEIGHT.get(); } - static boolean minersDreamEnabled() { return MINERS_DREAM_ENABLED.get(); } - static int minersDreamMinimumRange() { return MINERS_DREAM_MINIMUM_RANGE.get(); } - static int minersDreamMaximumRange() { return MINERS_DREAM_MAXIMUM_RANGE.get(); } - static int minersDreamTorchSpacing() { return MINERS_DREAM_TORCH_SPACING.get(); } - static int minersDreamBlocksPerTick() { return MINERS_DREAM_BLOCKS_PER_TICK.get(); } static double ductTapeRepairPercentPerUse() { return DUCT_TAPE_REPAIR_PERCENT_PER_USE.get(); } static double potentNyxiteInvertedDurationSeconds() { return POTENT_NYXITE_INVERTED_DURATION_SECONDS.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 2a2757bb..86a23f83 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 @@ -58,6 +58,27 @@ public final class AntarchyKeyBindings { InputConstants.KEY_B, CATEGORY ); + public static final KeyMapping PORTAL_GUN_ZOOM = new KeyMapping( + "key.antarchy.portal_gun_zoom", + KeyConflictContext.IN_GAME, + InputConstants.Type.MOUSE, + InputConstants.MOUSE_BUTTON_MIDDLE, + CATEGORY + ); + public static final KeyMapping PORTAL_GUN_GRAB = new KeyMapping( + "key.antarchy.portal_gun_grab", + KeyConflictContext.IN_GAME, + InputConstants.Type.KEYSYM, + InputConstants.KEY_G, + CATEGORY + ); + public static final KeyMapping PORTAL_GUN_RESET = new KeyMapping( + "key.antarchy.portal_gun_reset", + KeyConflictContext.IN_GAME, + InputConstants.Type.KEYSYM, + InputConstants.KEY_R, + CATEGORY + ); private AntarchyKeyBindings() {} @@ -69,6 +90,9 @@ public final class AntarchyKeyBindings { event.register(TIGERS_EYE_CAMOUFLAGE); event.register(ROYAL_INVERSION_TOGGLE); event.register(ROYAL_BOUNDARY); + event.register(PORTAL_GUN_ZOOM); + event.register(PORTAL_GUN_GRAB); + event.register(PORTAL_GUN_RESET); } public static boolean isMountFlightTogglePressed() { @@ -90,4 +114,20 @@ public final class AntarchyKeyBindings { public static boolean consumeRoyalBoundaryPressed() { return ROYAL_BOUNDARY.consumeClick(); } + + public static boolean consumePortalGunZoomPressed() { + return PORTAL_GUN_ZOOM.consumeClick(); + } + + public static boolean consumePortalGunGrabPressed() { + return PORTAL_GUN_GRAB.consumeClick(); + } + + public static boolean consumePortalGunResetPressed() { + return PORTAL_GUN_RESET.consumeClick(); + } + + public static boolean isPortalGunResetDown() { + return PORTAL_GUN_RESET.isDown() || PORTAL_GUN_RESET.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 6bc5d737..23d39194 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 @@ -2,6 +2,7 @@ package com.craisinlord.antarchy.neoforge.client; import com.craisinlord.antarchy.Antarchy; import com.craisinlord.antarchy.content.client.GoopedHudRenderer; +import com.craisinlord.antarchy.content.client.PortalGunCrosshairRenderer; import com.craisinlord.antarchy.content.client.AntarchyArchivePreviews; import com.craisinlord.antarchy.content.client.game.AntarchyComputerGames; import com.craisinlord.antarchy.content.client.HordeHudRenderer; @@ -16,6 +17,7 @@ import com.craisinlord.antarchy.neoforge.registry.AntarchyNeoforgeItems; import com.craisinlord.antarchy.neoforge.registry.AntarchyNeoforgeMisc; import com.mojang.blaze3d.shaders.FogShape; import com.mojang.blaze3d.systems.RenderSystem; +import com.mojang.blaze3d.vertex.DefaultVertexFormat; import com.mojang.blaze3d.vertex.VertexConsumer; import net.minecraft.client.model.BoatModel; import net.minecraft.client.model.ArmorStandModel; @@ -38,6 +40,7 @@ import net.minecraft.client.renderer.BiomeColors; import net.minecraft.client.renderer.BlockEntityWithoutLevelRenderer; import net.minecraft.client.renderer.ItemBlockRenderTypes; import net.minecraft.client.renderer.RenderType; +import net.minecraft.client.renderer.ShaderInstance; import net.minecraft.client.renderer.blockentity.CampfireRenderer; import net.minecraft.client.renderer.entity.LivingEntityRenderer; import net.minecraft.client.renderer.item.ItemProperties; @@ -56,6 +59,7 @@ import net.neoforged.bus.api.SubscribeEvent; import net.neoforged.fml.common.EventBusSubscriber; import net.neoforged.fml.event.lifecycle.FMLClientSetupEvent; import net.neoforged.neoforge.client.event.RegisterParticleProvidersEvent; +import net.neoforged.neoforge.client.event.RegisterShadersEvent; import net.neoforged.neoforge.client.event.RegisterColorHandlersEvent; import net.neoforged.neoforge.client.event.RegisterMenuScreensEvent; import net.neoforged.neoforge.client.event.EntityRenderersEvent; @@ -98,6 +102,19 @@ public final class AntarchyNeoForgeClient { private AntarchyNeoForgeClient() { } + @SubscribeEvent + public static void registerPortalViewShader(RegisterShadersEvent event) { + PortalGunPortalViewRenderer.clearPortalViewShader(); + try { + event.registerShader( + new ShaderInstance(event.getResourceProvider(), ResourceLocation.fromNamespaceAndPath(Antarchy.MODID, "portal_view"), DefaultVertexFormat.POSITION_TEX), + PortalGunPortalViewRenderer::setPortalViewShader + ); + } catch (java.io.IOException | RuntimeException exception) { + Antarchy.LOGGER.error("Failed to register portal view shader", exception); + } + } + @SubscribeEvent public static void clientSetup(FMLClientSetupEvent event) { Antarchy.physicalClient = true; @@ -368,6 +385,11 @@ public final class AntarchyNeoForgeClient { @SubscribeEvent public static void registerGuiLayers(RegisterGuiLayersEvent event) { + event.registerAbove( + VanillaGuiLayers.CROSSHAIR, + ResourceLocation.fromNamespaceAndPath(Antarchy.MODID, "portal_gun_crosshair"), + (guiGraphics, partialTick) -> PortalGunCrosshairRenderer.render(guiGraphics) + ); event.registerAboveAll( ResourceLocation.fromNamespaceAndPath(Antarchy.MODID, "dread_darkness"), (guiGraphics, partialTick) -> DreadHudRenderer.render(guiGraphics) diff --git a/neoforge-1.21.1/src/main/java/com/craisinlord/antarchy/neoforge/client/PortalGunClientHandler.java b/neoforge-1.21.1/src/main/java/com/craisinlord/antarchy/neoforge/client/PortalGunClientHandler.java index d43e82f1..3bc0efee 100644 --- a/neoforge-1.21.1/src/main/java/com/craisinlord/antarchy/neoforge/client/PortalGunClientHandler.java +++ b/neoforge-1.21.1/src/main/java/com/craisinlord/antarchy/neoforge/client/PortalGunClientHandler.java @@ -2,7 +2,9 @@ package com.craisinlord.antarchy.neoforge.client; import com.craisinlord.antarchy.Antarchy; import com.craisinlord.antarchy.content.item.PortalGunItem; +import com.craisinlord.antarchy.content.client.PortalGunZoomClientState; import com.craisinlord.antarchy.content.network.PortalGunPrimaryPayload; +import com.craisinlord.antarchy.content.network.PortalGunGrabPayload; import net.minecraft.client.Minecraft; import net.neoforged.api.distmarker.Dist; import net.neoforged.bus.api.SubscribeEvent; @@ -20,19 +22,40 @@ public final class PortalGunClientHandler { @SubscribeEvent public static void onClientTick(ClientTickEvent.Post event) { Minecraft mc = Minecraft.getInstance(); + if (mc.player == null) { + com.craisinlord.antarchy.content.client.PortalGunGrabClientState.clear(); + com.craisinlord.antarchy.content.client.PortalGunResetClientState.clear(); + } + com.craisinlord.antarchy.content.client.PortalGunIndicatorClientState.setRequestSender(net.neoforged.neoforge.network.PacketDistributor::sendToServer); + boolean hasPortalGun = mc.player != null && (mc.player.getMainHandItem().getItem() instanceof PortalGunItem || mc.player.getOffhandItem().getItem() instanceof PortalGunItem); + boolean gameplayInput = mc.screen == null; + boolean held = hasPortalGun && gameplayInput; + boolean zoomPressed = AntarchyKeyBindings.consumePortalGunZoomPressed(); + PortalGunZoomClientState.tick(mc, hasPortalGun, zoomPressed && gameplayInput); + boolean resetActive = com.craisinlord.antarchy.content.client.PortalGunResetClientState.tick( + mc, + held, + AntarchyKeyBindings.isPortalGunResetDown(), + net.neoforged.neoforge.network.PacketDistributor::sendToServer + ); + if (held && AntarchyKeyBindings.consumePortalGunGrabPressed()) { + PacketDistributor.sendToServer(new PortalGunGrabPayload()); + } if (mc.player == null || mc.level == null || mc.screen != null) { lastAttackDown = false; return; } - if (!(mc.player.getMainHandItem().getItem() instanceof PortalGunItem)) { + boolean offhand = !(mc.player.getMainHandItem().getItem() instanceof PortalGunItem) + && mc.player.getOffhandItem().getItem() instanceof PortalGunItem; + if (!offhand && !(mc.player.getMainHandItem().getItem() instanceof PortalGunItem)) { lastAttackDown = false; return; } boolean attackDown = mc.options.keyAttack.isDown(); - if (attackDown && !lastAttackDown) { - PacketDistributor.sendToServer(new PortalGunPrimaryPayload()); + if (attackDown && !lastAttackDown && !resetActive) { + PacketDistributor.sendToServer(new PortalGunPrimaryPayload(offhand)); } lastAttackDown = attackDown; } diff --git a/neoforge-1.21.1/src/main/java/com/craisinlord/antarchy/neoforge/client/PortalGunPortalViewRenderHandler.java b/neoforge-1.21.1/src/main/java/com/craisinlord/antarchy/neoforge/client/PortalGunPortalViewRenderHandler.java index ef70aea2..cc38226c 100644 --- a/neoforge-1.21.1/src/main/java/com/craisinlord/antarchy/neoforge/client/PortalGunPortalViewRenderHandler.java +++ b/neoforge-1.21.1/src/main/java/com/craisinlord/antarchy/neoforge/client/PortalGunPortalViewRenderHandler.java @@ -2,26 +2,24 @@ package com.craisinlord.antarchy.neoforge.client; import com.craisinlord.antarchy.Antarchy; import com.craisinlord.antarchy.content.client.renderer.PortalGunPortalViewRenderer; +import net.neoforged.api.distmarker.Dist; +import net.neoforged.bus.api.SubscribeEvent; +import net.neoforged.fml.common.EventBusSubscriber; import net.neoforged.neoforge.client.event.RenderLevelStageEvent; +@EventBusSubscriber(modid = Antarchy.MODID, value = Dist.CLIENT) public final class PortalGunPortalViewRenderHandler { - private static long lastStageLogNanos; - private PortalGunPortalViewRenderHandler() { } + @SubscribeEvent public static void onRenderLevelStage(RenderLevelStageEvent event) { if (!PortalGunPortalViewRenderer.isEnabled()) { return; } - if (event.getStage() != RenderLevelStageEvent.Stage.AFTER_PARTICLES) { + if (event.getStage() != RenderLevelStageEvent.Stage.AFTER_BLOCK_ENTITIES) { return; } - long now = System.nanoTime(); - if (now - lastStageLogNanos > 5_000_000_000L) { - lastStageLogNanos = now; - Antarchy.LOGGER.info("Portal gun NeoForge render stage fired stage={} partial={}", event.getStage(), event.getPartialTick().getGameTimeDeltaPartialTick(false)); - } - PortalGunPortalViewRenderer.render(event.getCamera(), event.getPoseStack().last().pose(), event.getPartialTick()); + PortalGunPortalViewRenderer.render(event.getCamera(), event.getModelViewMatrix(), event.getProjectionMatrix(), event.getPartialTick()); } } diff --git a/neoforge-1.21.1/src/main/java/com/craisinlord/antarchy/neoforge/network/AntarchyGravityNetworking.java b/neoforge-1.21.1/src/main/java/com/craisinlord/antarchy/neoforge/network/AntarchyGravityNetworking.java index 4332c90a..1b165d81 100644 --- a/neoforge-1.21.1/src/main/java/com/craisinlord/antarchy/neoforge/network/AntarchyGravityNetworking.java +++ b/neoforge-1.21.1/src/main/java/com/craisinlord/antarchy/neoforge/network/AntarchyGravityNetworking.java @@ -19,6 +19,7 @@ import com.craisinlord.antarchy.content.network.PortalGunPrimaryPayload; import com.craisinlord.antarchy.content.client.CameraShakeClientState; import net.minecraft.world.entity.Entity; import net.minecraft.server.level.ServerPlayer; +import net.minecraft.world.item.ItemStack; import net.minecraft.world.phys.Vec3; import net.neoforged.neoforge.network.PacketDistributor; import net.neoforged.neoforge.network.handling.IPayloadContext; @@ -29,7 +30,21 @@ public final class AntarchyGravityNetworking { } public static void register(PayloadRegistrar registrar) { + com.craisinlord.antarchy.content.portalgun.PortalGunIndicatorSync.setSender(PacketDistributor::sendToPlayer); + com.craisinlord.antarchy.content.portalgun.PortalGunGrabManager.setStateSender(PacketDistributor::sendToPlayer); registrar.playToClient( + com.craisinlord.antarchy.content.network.PortalGunIndicatorPayload.TYPE, + com.craisinlord.antarchy.content.network.PortalGunIndicatorPayload.STREAM_CODEC, + (payload, context) -> context.enqueueWork(() -> com.craisinlord.antarchy.content.client.PortalGunIndicatorClientState.update(payload)) + ).playToServer( + com.craisinlord.antarchy.content.network.PortalGunIndicatorRequestPayload.TYPE, + com.craisinlord.antarchy.content.network.PortalGunIndicatorRequestPayload.STREAM_CODEC, + AntarchyGravityNetworking::handlePortalGunIndicatorRequest + ).playToClient( + com.craisinlord.antarchy.content.network.PortalGunGrabStatePayload.TYPE, + com.craisinlord.antarchy.content.network.PortalGunGrabStatePayload.STREAM_CODEC, + (payload, context) -> context.enqueueWork(() -> com.craisinlord.antarchy.content.client.PortalGunGrabClientState.update(payload.active())) + ).playToClient( GravityStatePayload.TYPE, GravityStatePayload.STREAM_CODEC, AntarchyGravityNetworking::handleGravityState @@ -45,6 +60,18 @@ public final class AntarchyGravityNetworking { PortalGunPrimaryPayload.TYPE, PortalGunPrimaryPayload.STREAM_CODEC, AntarchyGravityNetworking::handlePortalGunPrimary + ).playToServer( + com.craisinlord.antarchy.content.network.PortalGunGrabPayload.TYPE, + com.craisinlord.antarchy.content.network.PortalGunGrabPayload.STREAM_CODEC, + AntarchyGravityNetworking::handlePortalGunGrab + ).playToServer( + com.craisinlord.antarchy.content.network.PortalGunResetPayload.TYPE, + com.craisinlord.antarchy.content.network.PortalGunResetPayload.STREAM_CODEC, + (payload, context) -> context.enqueueWork(() -> { + if (context.player() instanceof ServerPlayer serverPlayer) { + com.craisinlord.antarchy.content.portalgun.PortalGunResetManager.handleInput(serverPlayer, payload.action()); + } + }) ).playToServer( EyeOfStormPrimaryPayload.TYPE, EyeOfStormPrimaryPayload.STREAM_CODEC, @@ -203,11 +230,28 @@ public final class AntarchyGravityNetworking { return; } - if (!(serverPlayer.getMainHandItem().getItem() instanceof PortalGunItem portalGunItem)) { + ItemStack stack = payload.offhand() ? serverPlayer.getOffhandItem() : serverPlayer.getMainHandItem(); + if (!(stack.getItem() instanceof PortalGunItem portalGunItem)) { return; } - portalGunItem.firePrimary(serverPlayer.serverLevel(), serverPlayer, serverPlayer.getMainHandItem()); + portalGunItem.firePrimary(serverPlayer.serverLevel(), serverPlayer, stack, payload.offhand()); + }); + } + + private static void handlePortalGunGrab(com.craisinlord.antarchy.content.network.PortalGunGrabPayload payload, IPayloadContext context) { + context.enqueueWork(() -> { + if (context.player() instanceof ServerPlayer serverPlayer) { + com.craisinlord.antarchy.content.portalgun.PortalGunGrabManager.toggle(serverPlayer); + } + }); + } + + private static void handlePortalGunIndicatorRequest(com.craisinlord.antarchy.content.network.PortalGunIndicatorRequestPayload payload, IPayloadContext context) { + context.enqueueWork(() -> { + if (context.player() instanceof ServerPlayer serverPlayer) { + com.craisinlord.antarchy.content.portalgun.PortalGunIndicatorSync.handleRequest(serverPlayer, payload); + } }); } 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 978cc30d..2fdcba3f 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 @@ -88,29 +88,18 @@ import net.neoforged.neoforge.event.village.VillagerTradesEvent; import net.neoforged.neoforge.event.village.WandererTradesEvent; import net.neoforged.neoforge.event.tick.PlayerTickEvent; import net.neoforged.neoforge.event.tick.ServerTickEvent; -import com.craisinlord.antarchy.content.item.MinersDreamExcavationManager; import net.neoforged.neoforge.fluids.FluidInteractionRegistry; import net.neoforged.neoforge.network.PacketDistributor; public final class AntarchyNeoForgeEvents { - private static final long SERVER_HANG_WARN_NANOS = 5_000_000_000L; - private static final long SERVER_IDLE_BUSY_WARN_NANOS = 30_000_000_000L; - private static final long SERVER_HANG_REPEAT_NANOS = 30_000_000_000L; private static final double OVERHEAD_INVERSION_WARN_MS = 10.0D; private static final long OVERHEAD_INVERSION_WARN_INTERVAL_TICKS = 100L; - private static volatile boolean serverHangWatchdogStarted; - private static volatile long serverTickStartNanos; - private static volatile long serverTickEndNanos; - private static volatile long serverHangLastDumpNanos; - private static volatile Thread serverTickThread; private static long lastOverheadInversionWarnTick = Long.MIN_VALUE / 2L; private AntarchyNeoForgeEvents() {} public static void register(IEventBus modEventBus) { modEventBus.addListener(AntarchyNeoForgeEvents::onCommonSetup); - NeoForge.EVENT_BUS.addListener(AntarchyNeoForgeEvents::beginServerTickWatchdog); - NeoForge.EVENT_BUS.addListener(AntarchyNeoForgeEvents::endServerTickWatchdog); NeoForge.EVENT_BUS.addListener(AntarchyNeoForgeEvents::onMissileSquidDeath); NeoForge.EVENT_BUS.addListener(AntarchyNeoForgeEvents::onPermanentPortalSacrifice); NeoForge.EVENT_BUS.addListener(AntarchyNeoForgeEvents::onLivingBreathe); @@ -126,6 +115,7 @@ public final class AntarchyNeoForgeEvents { NeoForge.EVENT_BUS.addListener(AntarchyNeoForgeEvents::handleBossMagicBurstMitigation); NeoForge.EVENT_BUS.addListener(AntarchyNeoForgeEvents::handleScorpionWhipAttackEntity); NeoForge.EVENT_BUS.addListener(AntarchyNeoForgeEvents::handleAttitudeAdjusterAttackEntity); + NeoForge.EVENT_BUS.addListener(AntarchyNeoForgeEvents::handlePortalGunAttackEntity); NeoForge.EVENT_BUS.addListener(AntarchyNeoForgeEvents::handleScorpionWhipLeftClickBlock); NeoForge.EVENT_BUS.addListener(AntarchyNeoForgeEvents::handlePortalGunLeftClickBlock); NeoForge.EVENT_BUS.addListener(AntarchyNeoForgeEvents::handleScorpionWhipRightClickBlock); @@ -140,7 +130,7 @@ public final class AntarchyNeoForgeEvents { NeoForge.EVENT_BUS.addListener(AntarchyNeoForgeEvents::tickDreadAndIchor); NeoForge.EVENT_BUS.addListener(AntarchyNeoForgeEvents::tickScorpionWhips); NeoForge.EVENT_BUS.addListener(AntarchyNeoForgeEvents::tickWormHooks); - NeoForge.EVENT_BUS.addListener(AntarchyNeoForgeEvents::tickMinersDreamExcavations); + NeoForge.EVENT_BUS.addListener(AntarchyNeoForgeEvents::tickAttitudeAdjusterSlams); NeoForge.EVENT_BUS.addListener(AntarchyNeoForgeEvents::handleAntiwaterDamage); NeoForge.EVENT_BUS.addListener(AntarchyNeoForgeEvents::handleAntiwaterFall); NeoForge.EVENT_BUS.addListener(AntarchyNeoForgeEvents::handleBloodCrystalBootsFall); @@ -178,92 +168,6 @@ public final class AntarchyNeoForgeEvents { NeoForge.EVENT_BUS.addListener(AntarchyNeoForgeEvents::onWandererTrades); } - static void beginServerTickWatchdog(ServerTickEvent.Pre event) { - ensureServerHangWatchdogStarted(); - serverTickThread = Thread.currentThread(); - serverTickStartNanos = System.nanoTime(); - } - - static void endServerTickWatchdog(ServerTickEvent.Post event) { - serverTickEndNanos = System.nanoTime(); - serverTickStartNanos = 0L; - } - - private static synchronized void ensureServerHangWatchdogStarted() { - if (serverHangWatchdogStarted) { - return; - } - serverHangWatchdogStarted = true; - Thread watchdog = new Thread(() -> { - while (true) { - try { - Thread.sleep(2_000L); - } catch (InterruptedException ignored) { - Thread.currentThread().interrupt(); - return; - } - - long tickStart = serverTickStartNanos; - long tickEnd = serverTickEndNanos; - Thread tickThread = serverTickThread; - long now = System.nanoTime(); - if (tickThread == null || now - serverHangLastDumpNanos < SERVER_HANG_REPEAT_NANOS) { - continue; - } - - if (tickStart > 0L && now - tickStart >= SERVER_HANG_WARN_NANOS) { - antarchy$dumpServerThreadWatchdog( - "server thread appears hung", - (now - tickStart) / 1_000_000.0D, - tickThread - ); - serverHangLastDumpNanos = now; - continue; - } - - Thread.State state = tickThread.getState(); - boolean idleBusy = tickStart <= 0L - && tickEnd > 0L - && now - tickEnd >= SERVER_IDLE_BUSY_WARN_NANOS - && state != Thread.State.WAITING - && state != Thread.State.TIMED_WAITING - && !antarchy$isServerThreadIdleWait(tickThread); - if (idleBusy) { - antarchy$dumpServerThreadWatchdog( - "server thread busy outside active tick", - (now - tickEnd) / 1_000_000.0D, - tickThread - ); - serverHangLastDumpNanos = now; - } - } - }, "Antarchy Server Hang Watchdog"); - watchdog.setDaemon(true); - watchdog.start(); - } - - private static void antarchy$dumpServerThreadWatchdog(String message, double elapsedMs, Thread tickThread) { - Antarchy.LOGGER.error( - "[antarchy-watchdog] {} elapsedMs={} thread={} state={}", - message, elapsedMs, tickThread.getName(), tickThread.getState() - ); - for (StackTraceElement element : tickThread.getStackTrace()) { - Antarchy.LOGGER.error("[antarchy-watchdog] at {}", element); - } - } - - private static boolean antarchy$isServerThreadIdleWait(Thread tickThread) { - for (StackTraceElement element : tickThread.getStackTrace()) { - String method = element.getMethodName(); - if (method.equals("waitUntilNextTick") - || method.equals("waitForTasks") - || method.equals("managedBlock")) { - return true; - } - } - return false; - } - static void onVillagerTrades(VillagerTradesEvent event) { if (event.getType() == AntarchyNeoforgeMisc.COMPUTER_SCIENTIST.get()) { int[] slots = ComputerScientistTradeManager.slotsPerLevel(); @@ -1093,6 +997,13 @@ public final class AntarchyNeoForgeEvents { AttitudeAdjusterSlamManager.markSpecialHit(player); } + static void handlePortalGunAttackEntity(AttackEntityEvent event) { + if (event.getEntity().getMainHandItem().getItem() instanceof com.craisinlord.antarchy.content.item.PortalGunItem + || event.getEntity().getOffhandItem().getItem() instanceof com.craisinlord.antarchy.content.item.PortalGunItem) { + event.setCanceled(true); + } + } + static void handleScorpionWhipLeftClickBlock(PlayerInteractEvent.LeftClickBlock event) { if (!(event.getEntity() instanceof ServerPlayer player)) { return; @@ -1216,10 +1127,9 @@ public final class AntarchyNeoForgeEvents { } } - static void tickMinersDreamExcavations(ServerTickEvent.Post event) { + static void tickAttitudeAdjusterSlams(ServerTickEvent.Post event) { for (ServerLevel level : event.getServer().getAllLevels()) { AttitudeAdjusterSlamManager.tick(level); - MinersDreamExcavationManager.tick(level); } } diff --git a/neoforge-1.21.1/src/main/java/com/craisinlord/antarchy/neoforge/registry/AntarchyNeoforgeSounds.java b/neoforge-1.21.1/src/main/java/com/craisinlord/antarchy/neoforge/registry/AntarchyNeoforgeSounds.java index a127f2b0..3b5bb707 100644 --- a/neoforge-1.21.1/src/main/java/com/craisinlord/antarchy/neoforge/registry/AntarchyNeoforgeSounds.java +++ b/neoforge-1.21.1/src/main/java/com/craisinlord/antarchy/neoforge/registry/AntarchyNeoforgeSounds.java @@ -127,6 +127,9 @@ public final class AntarchyNeoforgeSounds { public static final DeferredHolder PORTAL_OPEN_BLUE = registerSoundEvent("portal_open_blue"); public static final DeferredHolder PORTAL_OPEN_ORANGE = registerSoundEvent("portal_open_orange"); public static final DeferredHolder PORTAL_FIZZLE = registerSoundEvent("portal_fizzle"); + public static final DeferredHolder PORTAL_ENTER = registerSoundEvent("portal_enter"); + public static final DeferredHolder PORTAL_EXIT = registerSoundEvent("portal_exit"); + public static final DeferredHolder PORTAL_AMBIENT = registerSoundEvent("portal_ambient"); public static final DeferredHolder BED_BUG_AMBIENT = registerSoundEvent("bed_bug_ambient"); public static final DeferredHolder BED_BUG_HURT = registerSoundEvent("bed_bug_hurt"); public static final DeferredHolder BED_BUG_ATTACK = registerSoundEvent("bed_bug_attack"); diff --git a/neoforge-1.21.1/src/main/java/com/craisinlord/antarchy/neoforge/registry/AntarchyNeoforgeSpawnPlacements.java b/neoforge-1.21.1/src/main/java/com/craisinlord/antarchy/neoforge/registry/AntarchyNeoforgeSpawnPlacements.java index 8fc65ac0..da0dbd2a 100644 --- a/neoforge-1.21.1/src/main/java/com/craisinlord/antarchy/neoforge/registry/AntarchyNeoforgeSpawnPlacements.java +++ b/neoforge-1.21.1/src/main/java/com/craisinlord/antarchy/neoforge/registry/AntarchyNeoforgeSpawnPlacements.java @@ -51,7 +51,7 @@ public class AntarchyNeoforgeSpawnPlacements { event.register(AntarchyNeoforgeEntites.VORTEX.get(), SpawnPlacementTypes.NO_RESTRICTIONS, Heightmap.Types.MOTION_BLOCKING_NO_LEAVES, com.craisinlord.antarchy.content.entity.vortex.VortexEntity::canSpawn, RegisterSpawnPlacementsEvent.Operation.OR); event.register(AntarchyNeoforgeEntites.SCORPION.get(), SpawnPlacementTypes.ON_GROUND, Heightmap.Types.MOTION_BLOCKING_NO_LEAVES, ScorpionEntity::canSpawn, RegisterSpawnPlacementsEvent.Operation.OR); event.register(AntarchyNeoforgeEntites.BASILISK.get(), SpawnPlacementTypes.ON_GROUND, Heightmap.Types.MOTION_BLOCKING_NO_LEAVES, BasiliskEntity::canSpawn, RegisterSpawnPlacementsEvent.Operation.OR); - event.register(AntarchyNeoforgeEntites.EMPEROR_SCORPION.get(), SpawnPlacementTypes.ON_GROUND, Heightmap.Types.MOTION_BLOCKING_NO_LEAVES, EmperorScorpionEntity::canSpawn, RegisterSpawnPlacementsEvent.Operation.OR); + event.register(AntarchyNeoforgeEntites.EMPEROR_SCORPION.get(), SpawnPlacementTypes.NO_RESTRICTIONS, Heightmap.Types.MOTION_BLOCKING_NO_LEAVES, EmperorScorpionEntity::canSpawn, RegisterSpawnPlacementsEvent.Operation.OR); event.register(AntarchyNeoforgeEntites.CRAWLING_BLIGHT.get(), SpawnPlacementTypes.ON_GROUND, Heightmap.Types.MOTION_BLOCKING_NO_LEAVES, CrawlingBlightEntity::canSpawn, RegisterSpawnPlacementsEvent.Operation.OR); event.register(AntarchyNeoforgeEntites.SKULKING_FRIGHT.get(), SpawnPlacementTypes.ON_GROUND, Heightmap.Types.MOTION_BLOCKING_NO_LEAVES, SkulkingFrightEntity::canSpawn, RegisterSpawnPlacementsEvent.Operation.OR); event.register(AntarchyNeoforgeEntites.MANTICORE.get(), SpawnPlacementTypes.NO_RESTRICTIONS, Heightmap.Types.MOTION_BLOCKING_NO_LEAVES, com.craisinlord.antarchy.content.entity.ManticoreEntity::canSpawn, RegisterSpawnPlacementsEvent.Operation.OR); diff --git a/neoforge-1.21.1/src/main/java/com/craisinlord/antarchy/neoforge/registry/PortalGunGrabEvents.java b/neoforge-1.21.1/src/main/java/com/craisinlord/antarchy/neoforge/registry/PortalGunGrabEvents.java new file mode 100644 index 00000000..c9cd49f4 --- /dev/null +++ b/neoforge-1.21.1/src/main/java/com/craisinlord/antarchy/neoforge/registry/PortalGunGrabEvents.java @@ -0,0 +1,25 @@ +package com.craisinlord.antarchy.neoforge.registry; + +import com.craisinlord.antarchy.Antarchy; +import com.craisinlord.antarchy.content.portalgun.PortalGunGrabManager; +import net.neoforged.bus.api.SubscribeEvent; +import net.neoforged.fml.common.EventBusSubscriber; +import net.neoforged.neoforge.event.server.ServerStoppingEvent; +import net.neoforged.neoforge.event.tick.ServerTickEvent; + +@EventBusSubscriber(modid = Antarchy.MODID) +public final class PortalGunGrabEvents { + private PortalGunGrabEvents() { + } + + @SubscribeEvent + public static void onServerTick(ServerTickEvent.Post event) { + PortalGunGrabManager.tick(event.getServer()); + } + + @SubscribeEvent + public static void onServerStopping(ServerStoppingEvent event) { + PortalGunGrabManager.clear(event.getServer()); + com.craisinlord.antarchy.content.portalgun.PortalGunResetManager.clear(event.getServer()); + } +} diff --git a/neoforge-1.21.1/src/main/resources/META-INF/neoforge.mods.toml b/neoforge-1.21.1/src/main/resources/META-INF/neoforge.mods.toml index c05d200d..54dcfd04 100644 --- a/neoforge-1.21.1/src/main/resources/META-INF/neoforge.mods.toml +++ b/neoforge-1.21.1/src/main/resources/META-INF/neoforge.mods.toml @@ -17,6 +17,9 @@ displayURL="https://www.curseforge.com/minecraft/mc-mods/antarchy" [[mixins]] config = "antarchy-common.mixins.json" +[[mixins]] + config = "antarchy-sodium.mixins.json" + [[mixins]] config = "antarchy-neoforge.mixins.json" @@ -68,6 +71,22 @@ modId="antos" side="BOTH" reason="Antarchy requires Infinite Dimensions 2.7.3 or newer" +[[dependencies.antarchy]] + modId="apple_cows" + type="incompatible" + versionRange="*" + ordering="NONE" + side="BOTH" + reason="Antarchy already includes Apple Cows. Remove the standalone Apple Cows mod." + +[[dependencies.antarchy]] + modId="hoverboards" + type="incompatible" + versionRange="*" + ordering="NONE" + side="BOTH" + reason="Antarchy already includes Hoverboards. Remove the standalone Hoverboards mod." + [[dependencies.antarchy]] modId="configured" type="optional" diff --git a/settings.gradle b/settings.gradle index 4e2803e8..e5ba5dc9 100644 --- a/settings.gradle +++ b/settings.gradle @@ -8,7 +8,7 @@ pluginManagement { gradlePluginPortal() } plugins { - id 'net.fabricmc.fabric-loom-remap' version '1.15.5' + id 'net.fabricmc.fabric-loom-remap' version '1.16.3' } }