diff --git a/build.gradle b/build.gradle index e08739c1..d4825f1b 100644 --- a/build.gradle +++ b/build.gradle @@ -56,8 +56,8 @@ sourceSets.main.resources { dependencies { implementation "net.neoforged:neoforge:${project.neoforge_version}" -// implementation "com.aetherteam.aether:aether:${project.aether_version}" - implementation "local:aether:${project.aether_version}" + implementation "com.aetherteam.aether:aether:${project.aether_version}" +// implementation "local:aether:${project.aether_version}" implementation "com.aetherteam.nitrogen:nitrogen_internals:${project.nitrogen_version}" implementation "com.aetherteam.cumulus:cumulus_menus:${project.cumulus_version}" diff --git a/gradle.properties b/gradle.properties index bef52ddf..cea590fe 100644 --- a/gradle.properties +++ b/gradle.properties @@ -15,7 +15,7 @@ neoforge_version=20.4.200 mappings=2023.12.31 # Dependencies -aether_version=1.20.4-1.5.0-neoforge +aether_version=1.20.4-1.5.1-neoforge nitrogen_version=1.20.4-1.1.9-neoforge cumulus_version=1.20.4-1.0.2-neoforge curios_version=7.3.4 diff --git a/libs/aether-1.20.4-1.5.0-neoforge.jar b/libs/aether-1.20.4-1.5.0-neoforge.jar index e732fa00..5e226a64 100644 Binary files a/libs/aether-1.20.4-1.5.0-neoforge.jar and b/libs/aether-1.20.4-1.5.0-neoforge.jar differ diff --git a/src/generated/resources/.cache/735031f3addf80804addae5e3f53249900116f1e b/src/generated/resources/.cache/735031f3addf80804addae5e3f53249900116f1e index b3dfe2f5..187a9b25 100644 --- a/src/generated/resources/.cache/735031f3addf80804addae5e3f53249900116f1e +++ b/src/generated/resources/.cache/735031f3addf80804addae5e3f53249900116f1e @@ -1,2 +1,2 @@ -// 1.20.4 2024-10-08T19:15:20.1292942 Sound Definitions -df30d82357e727e30abbd87c8196ed3bb33821af assets/aether_genesis/sounds.json +// 1.20.4 2024-11-06T10:17:38.1217774 Sound Definitions +96e0a61c71f62470ce732d690f8e8ffdf81697b9 assets/aether_genesis/sounds.json diff --git a/src/generated/resources/assets/aether_genesis/sounds.json b/src/generated/resources/assets/aether_genesis/sounds.json index b2b93415..3a19eb16 100644 --- a/src/generated/resources/assets/aether_genesis/sounds.json +++ b/src/generated/resources/assets/aether_genesis/sounds.json @@ -36,6 +36,12 @@ ], "subtitle": "subtitles.aether_genesis.entity.cog.break" }, + "entity.host_eye.collide": { + "sounds": [ + "aether:entity/slider/collide" + ], + "subtitle": "subtitles.aether_genesis.entity.host_eye.collide" + }, "entity.labyrinth_eye.cog_loss": { "sounds": [ "aether_genesis:entity/labyrinth_eye/cog_loss" @@ -79,6 +85,65 @@ ], "subtitle": "subtitles.aether_genesis.entity.sentry_guardian.spawn" }, + "entity.slider_host_mimic.ambient": { + "sounds": [ + "ambient/cave/cave1", + "ambient/cave/cave2", + "ambient/cave/cave3", + "ambient/cave/cave4", + "ambient/cave/cave5", + "ambient/cave/cave6", + "ambient/cave/cave7", + "ambient/cave/cave8", + "ambient/cave/cave9", + "ambient/cave/cave10", + "ambient/cave/cave11", + "ambient/cave/cave12", + "ambient/cave/cave13", + "ambient/cave/cave14", + "ambient/cave/cave15", + "ambient/cave/cave16", + "ambient/cave/cave17", + "ambient/cave/cave18", + "ambient/cave/cave19" + ], + "subtitle": "subtitles.aether_genesis.entity.slider_host_mimic.ambient" + }, + "entity.slider_host_mimic.awaken": { + "sounds": [ + "aether:entity/slider/awaken" + ], + "subtitle": "subtitles.aether_genesis.entity.slider_host_mimic.awaken" + }, + "entity.slider_host_mimic.death": { + "sounds": [ + "aether:entity/slider/death" + ], + "subtitle": "subtitles.aether_genesis.entity.slider_host_mimic.death" + }, + "entity.slider_host_mimic.hurt": { + "sounds": [ + "step/stone1", + "step/stone2", + "step/stone3", + "step/stone4", + "step/stone5", + "step/stone6" + ], + "subtitle": "subtitles.aether_genesis.entity.slider_host_mimic.hurt" + }, + "entity.slider_host_mimic.scare": { + "sounds": [ + "aether_genesis:entity/tracking_golem/creepy_seen" + ], + "subtitle": "subtitles.aether_genesis.entity.slider_host_mimic.scare" + }, + "entity.slider_host_mimic.shoot": { + "sounds": [ + "aether:entity/slider/awaken" + ], + "subtitle": "subtitles.aether_genesis.entity.slider_host_mimic.shoot" + }, "entity.tempest.ambient": { "sounds": [ "aether:entity/zephyr/call" diff --git a/src/main/java/com/aetherteam/genesis/client/GenesisSoundEvents.java b/src/main/java/com/aetherteam/genesis/client/GenesisSoundEvents.java index f86932fb..54b0f18f 100644 --- a/src/main/java/com/aetherteam/genesis/client/GenesisSoundEvents.java +++ b/src/main/java/com/aetherteam/genesis/client/GenesisSoundEvents.java @@ -32,6 +32,13 @@ public class GenesisSoundEvents { public static final DeferredHolder ENTITY_TRACKING_GOLEM_SAY = register("entity.tracking_golem.say"); public static final DeferredHolder ENTITY_TRACKING_GOLEM_CREEPY_SEEN = register("entity.tracking_golem.creepy_seen"); + public static final DeferredHolder ENTITY_SLIDER_HOST_MIMIC_AWAKEN = register("entity.slider_host_mimic.awaken"); + public static final DeferredHolder ENTITY_SLIDER_HOST_MIMIC_AMBIENT = register("entity.slider_host_mimic.ambient"); + public static final DeferredHolder ENTITY_SLIDER_HOST_MIMIC_SHOOT = register("entity.slider_host_mimic.shoot"); + public static final DeferredHolder ENTITY_SLIDER_HOST_MIMIC_SCARE = register("entity.slider_host_mimic.scare"); + public static final DeferredHolder ENTITY_SLIDER_HOST_MIMIC_HURT = register("entity.slider_host_mimic.hurt"); + public static final DeferredHolder ENTITY_SLIDER_HOST_MIMIC_DEATH = register("entity.slider_host_mimic.death"); + public static final DeferredHolder ENTITY_SENTRY_GUARDIAN_DEATH = register("entity.sentry_guardian.death"); public static final DeferredHolder ENTITY_SENTRY_GUARDIAN_SUMMON = register("entity.sentry_guardian.summon"); public static final DeferredHolder ENTITY_SENTRY_GUARDIAN_HIT = register("entity.sentry_guardian.hit"); @@ -39,9 +46,6 @@ public class GenesisSoundEvents { public static final DeferredHolder ENTITY_LABYRINTH_EYE_DEATH = register("entity.labyrinth_eye.death"); public static final DeferredHolder ENTITY_LABYRINTH_EYE_COG_LOSS = register("entity.labyrinth_eye.cog_loss"); - public static final DeferredHolder ENTITY_LABYRINTH_EYE_MOVE = register("entity.labyrinth_eye.move"); - - public static final DeferredHolder ENTITY_COG_BREAK = register("entity.cog.break"); public static final DeferredHolder ENTITY_TEMPEST_AMBIENT = register("entity.tempest.ambient"); public static final DeferredHolder ENTITY_TEMPEST_HURT = register("entity.tempest.hurt"); @@ -52,6 +56,10 @@ public class GenesisSoundEvents { public static final DeferredHolder ENTITY_ZEPHYROO_HURT = register("entity.zephyroo.hurt"); public static final DeferredHolder ENTITY_ZEPHYROO_DEATH = register("entity.zephyroo.death"); public static final DeferredHolder ENTITY_ZEPHYROO_JUMP = register("entity.zephyroo.jump"); + public static final DeferredHolder ENTITY_LABYRINTH_EYE_MOVE = register("entity.labyrinth_eye.move"); + + public static final DeferredHolder ENTITY_HOST_EYE_COLLIDE = register("entity.host_eye.collide"); + public static final DeferredHolder ENTITY_COG_BREAK = register("entity.cog.break"); public static final DeferredHolder MUSIC_AETHER_NIGHT = register("music.aether_night"); public static final DeferredHolder MUSIC_MINIBOSS = register("music.miniboss"); diff --git a/src/main/java/com/aetherteam/genesis/client/renderer/entity/CogProjectileRenderer.java b/src/main/java/com/aetherteam/genesis/client/renderer/entity/CogProjectileRenderer.java index 85f1ce6d..53095cc4 100644 --- a/src/main/java/com/aetherteam/genesis/client/renderer/entity/CogProjectileRenderer.java +++ b/src/main/java/com/aetherteam/genesis/client/renderer/entity/CogProjectileRenderer.java @@ -28,9 +28,9 @@ public void render(CogProjectile cog, float entityYaw, float partialTicks, PoseS poseStack.pushPose(); float yRot = Mth.rotLerp(partialTicks, cog.yRotO, cog.getYRot()); float xRot = Mth.lerp(partialTicks, cog.xRotO, cog.getXRot()); - poseStack.translate(1.0, 1.5, 1.0); - if (!cog.isLarge()) { - poseStack.scale(0.25F, 0.25F, 0.25F); + poseStack.translate(0.0, -0.5, 0.0); + if (cog.isLarge()) { + poseStack.scale(1.5F, 1.5F, 1.5F); } VertexConsumer vertexConsumer = buffer.getBuffer(RenderType.entityTranslucent(this.getTextureLocation(cog))); this.cog.setupAnim(cog, 0.0F, 0.0F, cog.tickCount, yRot, xRot); diff --git a/src/main/java/com/aetherteam/genesis/client/renderer/entity/HostEyeProjectileRenderer.java b/src/main/java/com/aetherteam/genesis/client/renderer/entity/HostEyeProjectileRenderer.java index c3316c82..6e2b6b61 100644 --- a/src/main/java/com/aetherteam/genesis/client/renderer/entity/HostEyeProjectileRenderer.java +++ b/src/main/java/com/aetherteam/genesis/client/renderer/entity/HostEyeProjectileRenderer.java @@ -4,15 +4,29 @@ import com.aetherteam.genesis.client.renderer.GenesisModelLayers; import com.aetherteam.genesis.client.renderer.entity.model.HostEyeProjectileModel; import com.aetherteam.genesis.entity.projectile.HostEyeProjectile; +import com.mojang.blaze3d.vertex.PoseStack; +import com.mojang.blaze3d.vertex.VertexConsumer; +import net.minecraft.client.renderer.MultiBufferSource; +import net.minecraft.client.renderer.RenderType; +import net.minecraft.client.renderer.entity.EntityRenderer; import net.minecraft.client.renderer.entity.EntityRendererProvider; -import net.minecraft.client.renderer.entity.MobRenderer; +import net.minecraft.client.renderer.texture.OverlayTexture; import net.minecraft.resources.ResourceLocation; -public class HostEyeProjectileRenderer extends MobRenderer { +public class HostEyeProjectileRenderer extends EntityRenderer { private static final ResourceLocation HOST_EYE_PROJECTILE_TEXTURE = new ResourceLocation(AetherGenesis.MODID, "textures/entity/projectile/host_eye.png"); + private final HostEyeProjectileModel eyeModel; public HostEyeProjectileRenderer(EntityRendererProvider.Context context) { - super(context, new HostEyeProjectileModel(context.bakeLayer(GenesisModelLayers.HOST_EYE_PROJECTILE)), 0.5F); + super(context); + this.eyeModel = new HostEyeProjectileModel(context.bakeLayer(GenesisModelLayers.HOST_EYE_PROJECTILE)); + } + + @Override + public void render(HostEyeProjectile hostEye, float entityYaw, float partialTicks, PoseStack poseStack, MultiBufferSource buffer, int packedLight) { + VertexConsumer vertexConsumer = buffer.getBuffer(RenderType.entityTranslucent(this.getTextureLocation(hostEye))); + this.eyeModel.renderToBuffer(poseStack, vertexConsumer, packedLight, OverlayTexture.NO_OVERLAY, 1.0F, 1.0F, 1.0F, 1.0F); + super.render(hostEye, entityYaw, partialTicks, poseStack, buffer, packedLight); } @Override diff --git a/src/main/java/com/aetherteam/genesis/client/renderer/entity/HostMimicRenderer.java b/src/main/java/com/aetherteam/genesis/client/renderer/entity/HostMimicRenderer.java index 9b229cfd..7302a1be 100644 --- a/src/main/java/com/aetherteam/genesis/client/renderer/entity/HostMimicRenderer.java +++ b/src/main/java/com/aetherteam/genesis/client/renderer/entity/HostMimicRenderer.java @@ -2,7 +2,7 @@ import com.aetherteam.genesis.AetherGenesis; import com.aetherteam.genesis.client.renderer.GenesisModelLayers; -import com.aetherteam.genesis.client.renderer.entity.layers.HostMimicLayer; +import com.aetherteam.genesis.client.renderer.entity.layers.HostMimicGlowLayer; import com.aetherteam.genesis.client.renderer.entity.model.SliderHostMimicModel; import com.aetherteam.genesis.entity.monster.dungeon.boss.SliderHostMimic; import net.minecraft.client.renderer.entity.EntityRendererProvider; @@ -11,11 +11,11 @@ public class HostMimicRenderer extends MobRenderer { private static final ResourceLocation HOST_MIMIC_ASLEEP_TEXTURE = new ResourceLocation(AetherGenesis.MODID, "textures/entity/mobs/slider_host_mimic/slider_host_mimic_asleep.png"); - private static final ResourceLocation HOST_MIMIC_AWAKE_TEXTURE = new ResourceLocation(AetherGenesis.MODID, "textures/entity/mobs/slider_host_mimic/slider_host_mimic_critical.png"); + private static final ResourceLocation HOST_MIMIC_AWAKE_TEXTURE = new ResourceLocation(AetherGenesis.MODID, "textures/entity/mobs/slider_host_mimic/slider_host_mimic.png"); public HostMimicRenderer(EntityRendererProvider.Context context) { - super(context, new SliderHostMimicModel(context.bakeLayer(GenesisModelLayers.SLIDER_HOST_MIMIC)), 0.5F); - this.addLayer(new HostMimicLayer(this)); + super(context, new SliderHostMimicModel(context.bakeLayer(GenesisModelLayers.SLIDER_HOST_MIMIC)), 1.25F); + this.addLayer(new HostMimicGlowLayer(this)); } @Override diff --git a/src/main/java/com/aetherteam/genesis/client/renderer/entity/LabyrinthEyeRenderer.java b/src/main/java/com/aetherteam/genesis/client/renderer/entity/LabyrinthEyeRenderer.java index 7be7256a..489f2e73 100644 --- a/src/main/java/com/aetherteam/genesis/client/renderer/entity/LabyrinthEyeRenderer.java +++ b/src/main/java/com/aetherteam/genesis/client/renderer/entity/LabyrinthEyeRenderer.java @@ -2,7 +2,7 @@ import com.aetherteam.genesis.AetherGenesis; import com.aetherteam.genesis.client.renderer.GenesisModelLayers; -import com.aetherteam.genesis.client.renderer.entity.layers.LabyrinthEyeLayer; +import com.aetherteam.genesis.client.renderer.entity.layers.LabyrinthEyeGlowLayer; import com.aetherteam.genesis.client.renderer.entity.model.LabyrinthEyeModel; import com.aetherteam.genesis.entity.monster.dungeon.boss.LabyrinthEye; import net.minecraft.client.renderer.entity.EntityRendererProvider; @@ -15,7 +15,7 @@ public class LabyrinthEyeRenderer extends MobRenderer { private static final ResourceLocation SENTRY_GUARDIAN_TEXTURE = new ResourceLocation(AetherGenesis.MODID, "textures/entity/mobs/sentry_guardian/sentry_guardian.png"); - private static final ResourceLocation SENTRY_GUARDIAN_CRITICAL_GLOW = new ResourceLocation(AetherGenesis.MODID, "textures/entity/mobs/sentry_guardian/sentry_guardian_critical.png"); + private static final ResourceLocation SENTRY_GUARDIAN_AWAKE_TEXTURE = new ResourceLocation(AetherGenesis.MODID, "textures/entity/mobs/sentry_guardian/sentry_guardian_critical.png"); public SentryGuardianRenderer(EntityRendererProvider.Context context) { super(context, new SentryGuardianModel(context.bakeLayer(GenesisModelLayers.SENTRY_GUARDIAN)), 0.5F); - this.addLayer(new SentryGuardianLayer(this)); + this.addLayer(new SentryGuardianGlowLayer(this)); } @Override public ResourceLocation getTextureLocation(SentryGuardian sentryGuardian) { - return sentryGuardian.isAwake() ? SENTRY_GUARDIAN_CRITICAL_GLOW : SENTRY_GUARDIAN_TEXTURE; + return sentryGuardian.isAwake() ? SENTRY_GUARDIAN_AWAKE_TEXTURE : SENTRY_GUARDIAN_TEXTURE; } } diff --git a/src/main/java/com/aetherteam/genesis/client/renderer/entity/layers/HostMimicLayer.java b/src/main/java/com/aetherteam/genesis/client/renderer/entity/layers/HostMimicGlowLayer.java similarity index 85% rename from src/main/java/com/aetherteam/genesis/client/renderer/entity/layers/HostMimicLayer.java rename to src/main/java/com/aetherteam/genesis/client/renderer/entity/layers/HostMimicGlowLayer.java index 7335d926..4f866cfa 100644 --- a/src/main/java/com/aetherteam/genesis/client/renderer/entity/layers/HostMimicLayer.java +++ b/src/main/java/com/aetherteam/genesis/client/renderer/entity/layers/HostMimicGlowLayer.java @@ -13,10 +13,10 @@ import net.minecraft.client.renderer.texture.OverlayTexture; import net.minecraft.resources.ResourceLocation; -public class HostMimicLayer extends EyesLayer { - private static final RenderType HOST_MIMIC_CRITICAL_GLOW = RenderType.eyes(new ResourceLocation(AetherGenesis.MODID, "textures/entity/mobs/slider_host_mimic/slider_host_mimic_critical_glow.png")); +public class HostMimicGlowLayer extends EyesLayer { + private static final RenderType HOST_MIMIC_CRITICAL_GLOW = RenderType.eyes(new ResourceLocation(AetherGenesis.MODID, "textures/entity/mobs/slider_host_mimic/slider_host_mimic_glow.png")); - public HostMimicLayer(RenderLayerParent entityRenderer) { + public HostMimicGlowLayer(RenderLayerParent entityRenderer) { super(entityRenderer); } diff --git a/src/main/java/com/aetherteam/genesis/client/renderer/entity/layers/LabyrinthEyeLayer.java b/src/main/java/com/aetherteam/genesis/client/renderer/entity/layers/LabyrinthEyeGlowLayer.java similarity index 90% rename from src/main/java/com/aetherteam/genesis/client/renderer/entity/layers/LabyrinthEyeLayer.java rename to src/main/java/com/aetherteam/genesis/client/renderer/entity/layers/LabyrinthEyeGlowLayer.java index 363d7029..d2ffc726 100644 --- a/src/main/java/com/aetherteam/genesis/client/renderer/entity/layers/LabyrinthEyeLayer.java +++ b/src/main/java/com/aetherteam/genesis/client/renderer/entity/layers/LabyrinthEyeGlowLayer.java @@ -13,11 +13,11 @@ import net.minecraft.client.renderer.texture.OverlayTexture; import net.minecraft.resources.ResourceLocation; -public class LabyrinthEyeLayer extends EyesLayer { +public class LabyrinthEyeGlowLayer extends EyesLayer { private static final RenderType LABYRINTH_EYE_ASLEEP_GLOW = RenderType.eyes(new ResourceLocation(AetherGenesis.MODID, "textures/entity/mobs/labyrinth_eye/labyrinth_eye_sleep_glow.png")); private static final RenderType LABYRINTH_EYE_AWAKE_GLOW = RenderType.eyes(new ResourceLocation(AetherGenesis.MODID, "textures/entity/mobs/labyrinth_eye/labyrinth_eye_awake_glow.png")); - public LabyrinthEyeLayer(RenderLayerParent entityRenderer) { + public LabyrinthEyeGlowLayer(RenderLayerParent entityRenderer) { super(entityRenderer); } diff --git a/src/main/java/com/aetherteam/genesis/client/renderer/entity/layers/SentryGuardianLayer.java b/src/main/java/com/aetherteam/genesis/client/renderer/entity/layers/SentryGuardianGlowLayer.java similarity index 90% rename from src/main/java/com/aetherteam/genesis/client/renderer/entity/layers/SentryGuardianLayer.java rename to src/main/java/com/aetherteam/genesis/client/renderer/entity/layers/SentryGuardianGlowLayer.java index db5357fb..8ca2f52e 100644 --- a/src/main/java/com/aetherteam/genesis/client/renderer/entity/layers/SentryGuardianLayer.java +++ b/src/main/java/com/aetherteam/genesis/client/renderer/entity/layers/SentryGuardianGlowLayer.java @@ -13,11 +13,11 @@ import net.minecraft.client.renderer.texture.OverlayTexture; import net.minecraft.resources.ResourceLocation; -public class SentryGuardianLayer extends EyesLayer { +public class SentryGuardianGlowLayer extends EyesLayer { private static final RenderType SENTRY_GUARDIAN_GLOW = RenderType.eyes(new ResourceLocation(AetherGenesis.MODID, "textures/entity/mobs/sentry_guardian/sentry_guardian_glow.png")); private static final RenderType SENTRY_GUARDIAN_CRITICAL_GLOW = RenderType.eyes(new ResourceLocation(AetherGenesis.MODID, "textures/entity/mobs/sentry_guardian/sentry_guardian_critical_glow.png")); - public SentryGuardianLayer(RenderLayerParent entityRenderer) { + public SentryGuardianGlowLayer(RenderLayerParent entityRenderer) { super(entityRenderer); } diff --git a/src/main/java/com/aetherteam/genesis/client/renderer/entity/model/CogProtectileModel.java b/src/main/java/com/aetherteam/genesis/client/renderer/entity/model/CogProtectileModel.java index 62f8a98e..3282de58 100644 --- a/src/main/java/com/aetherteam/genesis/client/renderer/entity/model/CogProtectileModel.java +++ b/src/main/java/com/aetherteam/genesis/client/renderer/entity/model/CogProtectileModel.java @@ -54,7 +54,7 @@ public static LayerDefinition createBodyLayer() { @Override public void setupAnim(CogProjectile cog, float limbSwing, float limbSwingAmount, float ageInTicks, float netHeadYaw, float headPitch) { - this.cog.xRot += 0.15F; + this.cog.xRot += 0.01F; this.cog.yRot = this.cog.xRot; this.cog.zRot = this.cog.xRot; } diff --git a/src/main/java/com/aetherteam/genesis/client/renderer/entity/model/HostEyeProjectileModel.java b/src/main/java/com/aetherteam/genesis/client/renderer/entity/model/HostEyeProjectileModel.java index 534ffb19..acf0d1ea 100644 --- a/src/main/java/com/aetherteam/genesis/client/renderer/entity/model/HostEyeProjectileModel.java +++ b/src/main/java/com/aetherteam/genesis/client/renderer/entity/model/HostEyeProjectileModel.java @@ -19,7 +19,7 @@ public static LayerDefinition createBodyLayer() { MeshDefinition meshDefinition = new MeshDefinition(); PartDefinition partDefinition = meshDefinition.getRoot(); - partDefinition.addOrReplaceChild("eye", CubeListBuilder.create().texOffs(0, 0).addBox(-5.5F, -11.0F, -5.5F, 11.0F, 11.0F, 11.0F, new CubeDeformation(0.0F)), PartPose.offset(0.0F, 24.0F, 0.0F)); + partDefinition.addOrReplaceChild("eye", CubeListBuilder.create().texOffs(0, 0).addBox(-5.5F, -5.5F, -5.5F, 11.0F, 11.0F, 11.0F, new CubeDeformation(0.0F)), PartPose.offset(0.0F, 5.5F, 0.0F)); return LayerDefinition.create(meshDefinition, 64, 32); } diff --git a/src/main/java/com/aetherteam/genesis/client/renderer/entity/model/LabyrinthEyeModel.java b/src/main/java/com/aetherteam/genesis/client/renderer/entity/model/LabyrinthEyeModel.java index 36f1d570..ada19fe4 100644 --- a/src/main/java/com/aetherteam/genesis/client/renderer/entity/model/LabyrinthEyeModel.java +++ b/src/main/java/com/aetherteam/genesis/client/renderer/entity/model/LabyrinthEyeModel.java @@ -8,6 +8,8 @@ import net.minecraft.client.model.geom.PartPose; import net.minecraft.client.model.geom.builders.*; +import java.util.List; + public class LabyrinthEyeModel extends EntityModel { private final ModelPart labyrinthEye; private final ModelPart cogGroup; @@ -472,23 +474,32 @@ public static LayerDefinition createBodyLayer() { @Override public void setupAnim(LabyrinthEye eye, float limbSwing, float limbSwingAmount, float ageInTicks, float netHeadYaw, float headPitch) { - this.cogGroup.zRot = ageInTicks * 4000; - this.cog.visible = eye.getHealth() >= 212.0F; - this.cog2.visible = eye.getHealth() >= 174.0F; - this.cog3.visible = eye.getHealth() >= 117.0F; - this.cog4.visible = eye.getHealth() >= 79.0F; - this.cog5.visible = eye.getHealth() >= 41.0F; - this.leftCog.visible = eye.getHealth() >= 231.0F; - this.cog6.visible = eye.getHealth() >= 60.0F; - this.cog7.visible = eye.getHealth() >= 155.0F; - this.cog8.visible = eye.getHealth() >= 193.0F; - this.cog9.visible = eye.getHealth() >= 98.0F; - this.cog10.visible = eye.getHealth() >= 22.0F; - this.rightCog.visible = eye.getHealth() >= 136.0F; + this.cogGroup.zRot = ageInTicks * 0.2F; + for (int i = 1; i < 13; i++) { + ModelPart part = this.cogParts().get(i - 1); + part.visible = i >= eye.getStage() && eye.isAlive(); + } } @Override public void renderToBuffer(PoseStack poseStack, VertexConsumer vertexConsumer, int packedLight, int packedOverlay, float red, float green, float blue, float alpha) { this.labyrinthEye.render(poseStack, vertexConsumer, packedLight, packedOverlay, red, green, blue, alpha); } + + public List cogParts() { + return List.of( + this.rightCog, + this.cog4, + this.cog3, + this.cog, + this.cog2, + this.cog5, + this.leftCog, + this.cog9, + this.cog8, + this.cog6, + this.cog7, + this.cog10 + ); + } } \ No newline at end of file diff --git a/src/main/java/com/aetherteam/genesis/client/renderer/entity/model/SentryGuardianModel.java b/src/main/java/com/aetherteam/genesis/client/renderer/entity/model/SentryGuardianModel.java index 7cbb37fd..d73a96b8 100644 --- a/src/main/java/com/aetherteam/genesis/client/renderer/entity/model/SentryGuardianModel.java +++ b/src/main/java/com/aetherteam/genesis/client/renderer/entity/model/SentryGuardianModel.java @@ -43,8 +43,20 @@ public static LayerDefinition createBodyLayer() { @Override public void setupAnim(SentryGuardian guardian, float limbSwing, float limbSwingAmount, float ageInTicks, float netHeadYaw, float headPitch) { - this.leftArm.xRot = -Mth.cos(limbSwing * 0.6662F) * 1.4F * limbSwingAmount; - this.rightArm.xRot = -Mth.cos(limbSwing * 0.6662F) * 1.4F * limbSwingAmount; + int i = guardian.getAttackAnimationTick(); + if (i <= 0) { + this.leftArm.xRot = 0.25F - Mth.cos(limbSwing * 0.3662F) * 0.3F * limbSwingAmount; + this.rightArm.xRot = 0.25F - Mth.cos(limbSwing * 0.3662F) * 0.3F * limbSwingAmount; + } + } + + @Override + public void prepareMobModel(SentryGuardian guardian, float limbSwing, float limbSwingAmount, float partialTicks) { + int i = guardian.getAttackAnimationTick(); + if (i > 0) { + this.rightArm.xRot = -0.25F + Mth.triangleWave((float) i - partialTicks, 10.0F); + this.leftArm.xRot = -0.25F + Mth.triangleWave((float) i - partialTicks, 10.0F); + } } @Override diff --git a/src/main/java/com/aetherteam/genesis/client/renderer/entity/model/SliderHostMimicModel.java b/src/main/java/com/aetherteam/genesis/client/renderer/entity/model/SliderHostMimicModel.java index 54a29c8b..812fa258 100644 --- a/src/main/java/com/aetherteam/genesis/client/renderer/entity/model/SliderHostMimicModel.java +++ b/src/main/java/com/aetherteam/genesis/client/renderer/entity/model/SliderHostMimicModel.java @@ -7,14 +7,23 @@ import net.minecraft.client.model.geom.ModelPart; import net.minecraft.client.model.geom.PartPose; import net.minecraft.client.model.geom.builders.*; +import net.minecraft.util.Mth; public class SliderHostMimicModel extends EntityModel { private final ModelPart mimic; private final ModelPart body; + private final ModelPart frontLeftLeg; + private final ModelPart frontRightLeg; + private final ModelPart backRightLeg; + private final ModelPart backLeftLeg; public SliderHostMimicModel(ModelPart root) { this.mimic = root.getChild("mimic"); this.body = root.getChild("body"); + this.frontLeftLeg = this.body.getChild("leg_front_left"); + this.frontRightLeg = this.body.getChild("leg_front_right"); + this.backRightLeg = this.body.getChild("leg_back_right"); + this.backLeftLeg = this.body.getChild("leg_back_left"); } public static LayerDefinition createBodyLayer() { @@ -41,6 +50,10 @@ public static LayerDefinition createBodyLayer() { @Override public void setupAnim(SliderHostMimic hostMimic, float limbSwing, float limbSwingAmount, float ageInTicks, float netHeadYaw, float headPitch) { if (hostMimic.isAwake()) { + this.frontRightLeg.xRot = Mth.cos(limbSwing * 0.6662F + Mth.PI) * 0.6F * limbSwingAmount; + this.backRightLeg.xRot = Mth.cos(limbSwing * 0.6662F) * 0.6F * limbSwingAmount; + this.frontLeftLeg.xRot = Mth.cos(limbSwing * 0.6662F) * 0.6F * limbSwingAmount; + this.backLeftLeg.xRot = Mth.cos(limbSwing * 0.6662F + Mth.PI) * 0.6F * limbSwingAmount; this.body.visible = true; this.mimic.visible = false; } else { diff --git a/src/main/java/com/aetherteam/genesis/data/generators/GenesisSoundData.java b/src/main/java/com/aetherteam/genesis/data/generators/GenesisSoundData.java index 3dbc3210..1a9df862 100644 --- a/src/main/java/com/aetherteam/genesis/data/generators/GenesisSoundData.java +++ b/src/main/java/com/aetherteam/genesis/data/generators/GenesisSoundData.java @@ -1,5 +1,6 @@ package com.aetherteam.genesis.data.generators; +import com.aetherteam.aether.client.AetherSoundEvents; import com.aetherteam.genesis.AetherGenesis; import com.aetherteam.genesis.client.GenesisSoundEvents; import net.minecraft.data.PackOutput; @@ -117,6 +118,54 @@ public void registerSounds() { .subtitle("subtitles.aether_genesis.entity.tracking_golem.seen_enemy") ); + this.add(GenesisSoundEvents.ENTITY_SLIDER_HOST_MIMIC_AWAKEN, + definition().with(sound("aether:entity/slider/awaken")) + .subtitle("subtitles.aether_genesis.entity.slider_host_mimic.awaken") + ); + this.add(GenesisSoundEvents.ENTITY_SLIDER_HOST_MIMIC_AMBIENT, + definition().with(sound("minecraft:ambient/cave/cave1"), + sound("minecraft:ambient/cave/cave2"), + sound("minecraft:ambient/cave/cave3"), + sound("minecraft:ambient/cave/cave4"), + sound("minecraft:ambient/cave/cave5"), + sound("minecraft:ambient/cave/cave6"), + sound("minecraft:ambient/cave/cave7"), + sound("minecraft:ambient/cave/cave8"), + sound("minecraft:ambient/cave/cave9"), + sound("minecraft:ambient/cave/cave10"), + sound("minecraft:ambient/cave/cave11"), + sound("minecraft:ambient/cave/cave12"), + sound("minecraft:ambient/cave/cave13"), + sound("minecraft:ambient/cave/cave14"), + sound("minecraft:ambient/cave/cave15"), + sound("minecraft:ambient/cave/cave16"), + sound("minecraft:ambient/cave/cave17"), + sound("minecraft:ambient/cave/cave18"), + sound("minecraft:ambient/cave/cave19")) + .subtitle("subtitles.aether_genesis.entity.slider_host_mimic.ambient") + ); + this.add(GenesisSoundEvents.ENTITY_SLIDER_HOST_MIMIC_SHOOT, + definition().with(sound("aether:entity/slider/awaken")) + .subtitle("subtitles.aether_genesis.entity.slider_host_mimic.shoot") + ); + this.add(GenesisSoundEvents.ENTITY_SLIDER_HOST_MIMIC_SCARE, + definition().with(sound("aether_genesis:entity/tracking_golem/creepy_seen")) + .subtitle("subtitles.aether_genesis.entity.slider_host_mimic.scare") + ); + this.add(GenesisSoundEvents.ENTITY_SLIDER_HOST_MIMIC_HURT, + definition().with(sound("minecraft:step/stone1"), + sound("minecraft:step/stone2"), + sound("minecraft:step/stone3"), + sound("minecraft:step/stone4"), + sound("minecraft:step/stone5"), + sound("minecraft:step/stone6")) + .subtitle("subtitles.aether_genesis.entity.slider_host_mimic.hurt") + ); + this.add(GenesisSoundEvents.ENTITY_SLIDER_HOST_MIMIC_DEATH, + definition().with(sound("aether:entity/slider/death")) + .subtitle("subtitles.aether_genesis.entity.slider_host_mimic.death") + ); + this.add(GenesisSoundEvents.ENTITY_SENTRY_GUARDIAN_DEATH, definition().with(sound("aether_genesis:entity/sentry_guardian/death")) .subtitle("subtitles.aether_genesis.entity.tracking_golem.creepy_seen") @@ -147,6 +196,11 @@ public void registerSounds() { sound("aether_genesis:entity/labyrinth_eye/move_2")) .subtitle("subtitles.aether_genesis.entity.labyrinth_eye.move") ); + + this.add(GenesisSoundEvents.ENTITY_HOST_EYE_COLLIDE, + definition().with(sound("aether:entity/slider/collide")) + .subtitle("subtitles.aether_genesis.entity.host_eye.collide") + ); this.add(GenesisSoundEvents.ENTITY_COG_BREAK, definition().with(sound("aether_genesis:entity/cog/wall_final")) .subtitle("subtitles.aether_genesis.entity.cog.break") diff --git a/src/main/java/com/aetherteam/genesis/entity/GenesisEntityTypes.java b/src/main/java/com/aetherteam/genesis/entity/GenesisEntityTypes.java index c8413b26..785c91c1 100644 --- a/src/main/java/com/aetherteam/genesis/entity/GenesisEntityTypes.java +++ b/src/main/java/com/aetherteam/genesis/entity/GenesisEntityTypes.java @@ -50,7 +50,7 @@ public class GenesisEntityTypes { public static final DeferredHolder, EntityType> SENTRY_GUARDIAN = ENTITY_TYPES.register("sentry_guardian", () -> EntityType.Builder.of(SentryGuardian::new, MobCategory.MONSTER).sized(2.25F, 2.5F).fireImmune().clientTrackingRange(10).build("sentry_guardian")); public static final DeferredHolder, EntityType> SLIDER_HOST_MIMIC = ENTITY_TYPES.register("slider_host_mimic", - () -> EntityType.Builder.of(SliderHostMimic::new, MobCategory.MONSTER).sized(2, 2.5F).clientTrackingRange(10).build("slider_host_mimic")); + () -> EntityType.Builder.of(SliderHostMimic::new, MobCategory.MONSTER).sized(2.1F, 2.5F).clientTrackingRange(10).build("slider_host_mimic")); public static final DeferredHolder, EntityType> LABYRINTH_EYE = ENTITY_TYPES.register("labyrinth_eye", () -> EntityType.Builder.of(LabyrinthEye::new, MobCategory.MONSTER).sized(2, 2).clientTrackingRange(8).build("labyrinth_eye")); @@ -98,9 +98,9 @@ public class GenesisEntityTypes { public static final DeferredHolder, EntityType> DETONATION_PROJECTILE = ENTITY_TYPES.register("detonation_projectile", () -> EntityType.Builder.of(DetonationProjectile::new, MobCategory.MISC).clientTrackingRange(4).updateInterval(10).sized(0.9F, 0.9F).fireImmune().build("detonation_projectile")); public static final DeferredHolder, EntityType> HOST_EYE = ENTITY_TYPES.register("host_eye", - () -> EntityType.Builder.of(HostEyeProjectile::new, MobCategory.MISC).sized(0.4F, 0.4F).clientTrackingRange(10).fireImmune().build("host_eye")); + () -> EntityType.Builder.of(HostEyeProjectile::new, MobCategory.MISC).sized(0.8F, 0.8F).clientTrackingRange(10).fireImmune().build("host_eye")); public static final DeferredHolder, EntityType> FLYING_COG = ENTITY_TYPES.register("flying_cog", - () -> EntityType.Builder.of(CogProjectile::new, MobCategory.MISC).clientTrackingRange(4).updateInterval(10).sized(1.5F, 1.5F).fireImmune().build("flying_Cog")); + () -> EntityType.Builder.of(CogProjectile::new, MobCategory.MISC).clientTrackingRange(4).updateInterval(10).sized(0.75F, 0.75F).fireImmune().build("flying_Cog")); @SubscribeEvent public static void registerSpawnPlacements(SpawnPlacementRegisterEvent event) { @@ -121,9 +121,8 @@ public static void registerEntityAttributes(EntityAttributeCreationEvent event) event.put(GenesisEntityTypes.TRACKING_GOLEM.get(), TrackingGolem.createMobAttributes().build()); event.put(GenesisEntityTypes.SKYROOT_MIMIC.get(), Mimic.createMobAttributes().build()); event.put(GenesisEntityTypes.SENTRY_GUARDIAN.get(), SentryGuardian.createMobAttributes().build()); - event.put(GenesisEntityTypes.SLIDER_HOST_MIMIC.get(), SliderHostMimic.createHostAttributes().build()); + event.put(GenesisEntityTypes.SLIDER_HOST_MIMIC.get(), SliderHostMimic.createMobAttributes().build()); event.put(GenesisEntityTypes.LABYRINTH_EYE.get(), LabyrinthEye.createMobAttributes().build()); - event.put(GenesisEntityTypes.HOST_EYE.get(), HostEyeProjectile.createMobAttributes().build()); event.put(GenesisEntityTypes.FANGRIN.get(), Fangrin.createAttributes().build()); event.put(GenesisEntityTypes.KRAISITH.get(), Kraisith.createAttributes().build()); event.put(GenesisEntityTypes.FLEETING_WISP.get(), CompanionMob.createAttributes().build()); diff --git a/src/main/java/com/aetherteam/genesis/entity/monster/dungeon/BattleSentry.java b/src/main/java/com/aetherteam/genesis/entity/monster/dungeon/BattleSentry.java index 6d7bfe0a..539b2fb2 100644 --- a/src/main/java/com/aetherteam/genesis/entity/monster/dungeon/BattleSentry.java +++ b/src/main/java/com/aetherteam/genesis/entity/monster/dungeon/BattleSentry.java @@ -5,10 +5,12 @@ import net.minecraft.core.particles.BlockParticleOption; import net.minecraft.core.particles.ParticleOptions; import net.minecraft.core.particles.ParticleTypes; +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.sounds.SoundEvent; +import net.minecraft.world.DifficultyInstance; import net.minecraft.world.damagesource.DamageSource; import net.minecraft.world.entity.*; import net.minecraft.world.entity.ai.attributes.AttributeSupplier; @@ -17,7 +19,9 @@ import net.minecraft.world.entity.monster.Slime; import net.minecraft.world.entity.player.Player; import net.minecraft.world.level.Level; +import net.minecraft.world.level.ServerLevelAccessor; import net.minecraft.world.phys.Vec3; +import org.jetbrains.annotations.Nullable; public class BattleSentry extends Slime { public static final EntityDataAccessor DATA_AWAKE_ID = SynchedEntityData.defineId(BattleSentry.class, EntityDataSerializers.BOOLEAN); @@ -29,12 +33,11 @@ public BattleSentry(EntityType pEntityType, Level pLevel super(pEntityType, pLevel); } + @Nullable @Override - protected void registerGoals() { - this.goalSelector.addGoal(1, new BattleSentry.FloatGoal(this)); - this.goalSelector.addGoal(2, new BattleSentry.AttackGoal(this)); - this.goalSelector.addGoal(5, new BattleSentry.KeepOnJumpingGoal(this)); - this.targetSelector.addGoal(1, new NearestAttackableTargetGoal<>(this, Player.class, 10, true, false, (entity) -> Math.abs(entity.getY() - this.getY()) <= 4.0)); + public SpawnGroupData finalizeSpawn(ServerLevelAccessor pLevel, DifficultyInstance pDifficulty, MobSpawnType pReason, @Nullable SpawnGroupData pSpawnData, @Nullable CompoundTag pDataTag) { + this.setAwake(false); + return super.finalizeSpawn(pLevel, pDifficulty, pReason, pSpawnData, pDataTag); } public static AttributeSupplier.Builder createMobAttributes() { @@ -44,10 +47,18 @@ public static AttributeSupplier.Builder createMobAttributes() { .add(Attributes.ATTACK_DAMAGE, 4.0); } + @Override + protected void registerGoals() { + this.goalSelector.addGoal(1, new BattleSentry.FloatGoal(this)); + this.goalSelector.addGoal(2, new BattleSentry.AttackGoal(this)); + this.goalSelector.addGoal(5, new BattleSentry.KeepOnJumpingGoal(this)); + this.targetSelector.addGoal(1, new NearestAttackableTargetGoal<>(this, Player.class, 10, true, false, (entity) -> Math.abs(entity.getY() - this.getY()) <= 4.0)); + } + @Override protected void defineSynchedData() { super.defineSynchedData(); - this.entityData.define(DATA_AWAKE_ID, false); + this.entityData.define(DATA_AWAKE_ID, true); this.entityData.define(DATA_STALKING_ID, true); } diff --git a/src/main/java/com/aetherteam/genesis/entity/monster/dungeon/boss/LabyrinthEye.java b/src/main/java/com/aetherteam/genesis/entity/monster/dungeon/boss/LabyrinthEye.java index 0d72e081..3bd7e0a6 100644 --- a/src/main/java/com/aetherteam/genesis/entity/monster/dungeon/boss/LabyrinthEye.java +++ b/src/main/java/com/aetherteam/genesis/entity/monster/dungeon/boss/LabyrinthEye.java @@ -3,6 +3,7 @@ import com.aetherteam.aether.Aether; import com.aetherteam.aether.block.AetherBlocks; import com.aetherteam.aether.entity.AetherBossMob; +import com.aetherteam.aether.entity.ai.goal.MostDamageTargetGoal; import com.aetherteam.aether.entity.monster.dungeon.boss.BossNameGenerator; import com.aetherteam.aether.event.AetherEventDispatch; import com.aetherteam.aether.network.packet.clientbound.BossInfoPacket; @@ -10,10 +11,11 @@ import com.aetherteam.genesis.entity.projectile.CogProjectile; import com.aetherteam.nitrogen.entity.BossRoomTracker; import com.aetherteam.nitrogen.network.PacketRelay; +import net.minecraft.core.BlockPos; +import net.minecraft.core.particles.ParticleTypes; import net.minecraft.nbt.CompoundTag; import net.minecraft.network.FriendlyByteBuf; import net.minecraft.network.chat.Component; -import net.minecraft.network.chat.MutableComponent; import net.minecraft.network.syncher.EntityDataAccessor; import net.minecraft.network.syncher.EntityDataSerializers; import net.minecraft.network.syncher.SynchedEntityData; @@ -24,174 +26,251 @@ import net.minecraft.sounds.Music; import net.minecraft.sounds.SoundEvent; import net.minecraft.sounds.SoundEvents; -import net.minecraft.tags.DamageTypeTags; import net.minecraft.util.Mth; import net.minecraft.world.BossEvent; +import net.minecraft.world.Difficulty; import net.minecraft.world.DifficultyInstance; import net.minecraft.world.damagesource.DamageSource; import net.minecraft.world.entity.*; +import net.minecraft.world.entity.ai.attributes.AttributeInstance; import net.minecraft.world.entity.ai.attributes.AttributeSupplier; import net.minecraft.world.entity.ai.attributes.Attributes; import net.minecraft.world.entity.ai.goal.Goal; -import net.minecraft.world.entity.ai.goal.LookAtPlayerGoal; import net.minecraft.world.entity.ai.goal.RandomLookAroundGoal; -import net.minecraft.world.entity.ai.goal.target.HurtByTargetGoal; +import net.minecraft.world.entity.ai.goal.WaterAvoidingRandomStrollGoal; import net.minecraft.world.entity.ai.goal.target.NearestAttackableTargetGoal; import net.minecraft.world.entity.monster.Enemy; import net.minecraft.world.entity.monster.Monster; import net.minecraft.world.entity.player.Player; -import net.minecraft.world.item.Items; +import net.minecraft.world.entity.projectile.Projectile; import net.minecraft.world.level.Level; import net.minecraft.world.level.ServerLevelAccessor; import net.minecraft.world.level.block.Block; import net.minecraft.world.level.block.Blocks; import net.minecraft.world.level.block.HorizontalDirectionalBlock; import net.minecraft.world.level.block.state.BlockState; +import net.minecraft.world.level.pathfinder.Path; import net.minecraft.world.phys.Vec3; +import net.neoforged.neoforge.common.NeoForgeMod; import net.neoforged.neoforge.entity.IEntityWithComplexSpawn; +import org.apache.commons.lang3.tuple.Pair; import org.jetbrains.annotations.Nullable; -import java.util.EnumSet; -import java.util.Map; +import java.util.*; import java.util.function.Function; +import java.util.stream.IntStream; public class LabyrinthEye extends PathfinderMob implements AetherBossMob, Enemy, IEntityWithComplexSpawn { public static final EntityDataAccessor DATA_AWAKE_ID = SynchedEntityData.defineId(LabyrinthEye.class, EntityDataSerializers.BOOLEAN); public static final EntityDataAccessor DATA_BOSS_NAME_ID = SynchedEntityData.defineId(LabyrinthEye.class, EntityDataSerializers.COMPONENT); public static final EntityDataAccessor DATA_BOSS_STAGE = SynchedEntityData.defineId(LabyrinthEye.class, EntityDataSerializers.INT); private static final Music MINIBOSS_MUSIC = new Music(GenesisSoundEvents.MUSIC_MINIBOSS, 0, 0, true); - public static final Map> DUNGEON_BLOCK_CONVERSIONS = Map.ofEntries( + public static final Map> DUNGEON_BLOCK_CONVERSIONS = new HashMap<>(Map.ofEntries( Map.entry(AetherBlocks.LOCKED_CARVED_STONE.get(), (blockState) -> AetherBlocks.CARVED_STONE.get().defaultBlockState()), Map.entry(AetherBlocks.LOCKED_SENTRY_STONE.get(), (blockState) -> AetherBlocks.SENTRY_STONE.get().defaultBlockState()), Map.entry(AetherBlocks.BOSS_DOORWAY_CARVED_STONE.get(), (blockState) -> Blocks.AIR.defaultBlockState()), Map.entry(AetherBlocks.TREASURE_DOORWAY_CARVED_STONE.get(), (blockState) -> AetherBlocks.SKYROOT_TRAPDOOR.get().defaultBlockState().setValue(HorizontalDirectionalBlock.FACING, blockState.getValue(HorizontalDirectionalBlock.FACING))) - ); + )); - public int chatTime; - private final boolean[] stageDone = new boolean[13]; -// private int cappedAmount; + /** + * Goal for targeting in groups of entities + */ + private MostDamageTargetGoal mostDamageTargetGoal; private final ServerBossEvent bossFight; - private BossRoomTracker bronzeDungeon; + private final boolean[] stageDone = new boolean[13]; + public int chatCooldown; + + public LabyrinthEye(EntityType entityType, Level level) { + super(entityType, level); + this.bossFight = new ServerBossEvent(this.getBossName(), BossEvent.BossBarColor.RED, BossEvent.BossBarOverlay.PROGRESS); + this.bossFight.setVisible(false); + this.xpReward = XP_REWARD_BOSS; + this.setPersistenceRequired(); + this.setMaxUpStep(1.0F); + for (int i = 0; i < 12; i++) { + this.stageDone[i] = false; + } + } + + @Override + public SpawnGroupData finalizeSpawn(ServerLevelAccessor level, DifficultyInstance difficulty, MobSpawnType reason, @Nullable SpawnGroupData spawnData, @Nullable CompoundTag dataTag) { + this.setBossName(BossNameGenerator.generateBossName(this.getRandom()).append(Component.translatable("gui.aether_genesis.labyrinth_eye.title"))); + this.moveTo(Mth.floor(this.getX()), this.getY(), Mth.floor(this.getZ())); + return spawnData; + } public static AttributeSupplier.Builder createMobAttributes() { return Monster.createMobAttributes() - .add(Attributes.MAX_HEALTH, 500) + .add(Attributes.MAX_HEALTH, 500.0) .add(Attributes.MOVEMENT_SPEED, 0.27) - .add(Attributes.ATTACK_DAMAGE, 3.0) .add(Attributes.KNOCKBACK_RESISTANCE, 0.75) - .add(Attributes.FOLLOW_RANGE, 64.0); + .add(Attributes.FOLLOW_RANGE, 4.0); } @Override protected void registerGoals() { - this.goalSelector.addGoal(0, new DoNothingGoal(this)); - this.targetSelector.addGoal(4, new ArrowAttackCogGoal(this)); - this.goalSelector.addGoal(6, new LookAtPlayerGoalBoss(this, Player.class, 8.0F)); - this.goalSelector.addGoal(7, new RandomLookAroundGoal(this)); - this.targetSelector.addGoal(1, new HurtByTargetGoal(this, LabyrinthEye.class)); + this.goalSelector.addGoal(0, new LabyrinthEye.TrackPlayerGoal(this)); + this.goalSelector.addGoal(1, new LabyrinthEye.LookAroundGoal(this)); + this.goalSelector.addGoal(2, new LabyrinthEye.StrollGoal(this, 1.0)); + this.targetSelector.addGoal(3, new LabyrinthEye.CogAttackGoal(this)); + this.goalSelector.addGoal(4, new LabyrinthEye.InactiveGoal(this)); + + this.mostDamageTargetGoal = new MostDamageTargetGoal(this); + this.targetSelector.addGoal(1, this.mostDamageTargetGoal); this.targetSelector.addGoal(2, new NearestAttackableTargetGoal<>(this, Player.class, 10, true, false, livingEntity -> this.isBossFight())); } - public LabyrinthEye(EntityType pEntityType, Level pLevel) { - super(pEntityType, pLevel); - this.bossFight = new ServerBossEvent(this.getBossName(), BossEvent.BossBarColor.RED, BossEvent.BossBarOverlay.PROGRESS); - this.bossFight.setVisible(false); - this.xpReward = XP_REWARD_BOSS; - this.setPersistenceRequired(); - for (int i = 0; i < 12; i++) - this.stageDone[i] = false; + @Override + public void defineSynchedData() { + super.defineSynchedData(); + this.entityData.define(DATA_AWAKE_ID, false); + this.entityData.define(DATA_BOSS_NAME_ID, Component.literal("Labyrinth's Eye")); + this.entityData.define(DATA_BOSS_STAGE, 1); } - private boolean isBossStage(int stage) { //TODO - return switch (stage) { - case 1 -> (getHealth() <= getMaxHealth() && getHealth() >= getMaxHealth() * 0.9); - case 2 -> (getHealth() < getMaxHealth() * 0.9 && getHealth() >= getMaxHealth() * 0.8); - case 3 -> (getHealth() < getMaxHealth() * 0.8 && getHealth() >= getMaxHealth() * 0.725); - case 4 -> (getHealth() < getMaxHealth() * 0.65 && getHealth() >= getMaxHealth() * 0.575); - case 5 -> (getHealth() < getMaxHealth() * 0.575 && getHealth() >= getMaxHealth() * 0.5); - case 6 -> (getHealth() < getMaxHealth() * 0.45 && getHealth() >= getMaxHealth() * 0.4); - case 7 -> (getHealth() < getMaxHealth() * 0.4 && getHealth() >= getMaxHealth() * 0.35); - case 8 -> (getHealth() < getMaxHealth() * 0.35 && getHealth() >= getMaxHealth() * 0.3); - case 9 -> (getHealth() < getMaxHealth() * 0.3 && getHealth() >= getMaxHealth() * 0.25); - case 10 -> (getHealth() < getMaxHealth() * 0.25 && getHealth() >= getMaxHealth() * 0.2); - case 11 -> (getHealth() < getMaxHealth() * 0.2 && getHealth() >= getMaxHealth() * 0.15); - case 12 -> (getHealth() < getMaxHealth() * 0.15 && getHealth() >= getMaxHealth() * 0.1); - case 13 -> (getHealth() < getMaxHealth() * 0.1); - default -> false; - }; + @Override + public void tick() { + super.tick(); + if (!this.isAwake() || (this.getTarget() instanceof Player player && (player.isCreative() || player.isSpectator()))) { + this.setTarget(null); + } + this.evaporate(); + if (this.getChatCooldown() > 0) { + this.chatCooldown--; + } + this.setXRot(0); + AttributeInstance gravity = this.getAttribute(NeoForgeMod.ENTITY_GRAVITY.value()); + if (gravity != null) { + double fallSpeed = Math.max(gravity.getValue() * -1.25, -0.1); // Entity isn't allowed to fall too slowly from gravity. + if (this.getDeltaMovement().y() < fallSpeed) { + this.setDeltaMovement(this.getDeltaMovement().x(), fallSpeed, this.getDeltaMovement().z()); + this.hasImpulse = true; + } + } } - private void setStage(int stage) { - this.entityData.set(DATA_BOSS_STAGE, stage); - this.entityData.get(DATA_BOSS_STAGE); + private void evaporate() { + Pair minMax = this.getDefaultBounds(this); + AetherBossMob.super.evaporate(this, minMax.getLeft(), minMax.getRight(), (blockState) -> true); } - public int getStage() { - return this.entityData.get(DATA_BOSS_STAGE); + @Override + public void customServerAiStep() { + super.customServerAiStep(); + this.bossFight.setProgress(this.getHealth() / this.getMaxHealth()); + this.trackDungeon(); } - @Override - protected SoundEvent getDeathSound() { - return GenesisSoundEvents.ENTITY_LABYRINTH_EYE_DEATH.get(); - } - @Override - protected SoundEvent getAmbientSound() { - return GenesisSoundEvents.ENTITY_LABYRINTH_EYE_MOVE.get(); + public boolean hurt(DamageSource source, float amount) { + Optional damageResult = this.canDamageLabyrinthEye(source); + if (damageResult.isPresent()) { + if (super.hurt(source, amount) && this.getHealth() > 0) { + if (!this.isBossFight()) { + this.start(); + } + if (!this.level().isClientSide() && source.getEntity() instanceof LivingEntity living) { + this.mostDamageTargetGoal.addAggro(living, amount); // AI goal for being hurt. + for (int stage = 0; stage < 13; stage++) { + if (this.isBossStage(stage) && !this.stageDone[stage]) { + this.setStage(stage); + this.spawnLargeCog(stage); + } + } + } + return true; + } + } + return false; } - public void die(DamageSource source) { - this.level().explode(this, this.position().x, this.position().y, this.position().z, 0.3F, false, Level.ExplosionInteraction.TNT); - if (this.level() instanceof ServerLevel) { - this.bossFight.setProgress(this.getHealth() / this.getMaxHealth()); - if (this.getDungeon() != null) { - this.getDungeon().grantAdvancements(source); - this.tearDownRoom(); + private Optional canDamageLabyrinthEye(DamageSource source) { + if (this.level().getDifficulty() != Difficulty.PEACEFUL) { + if (source.getDirectEntity() instanceof LivingEntity attacker) { + if (this.getDungeon() == null || this.getDungeon().isPlayerWithinRoomInterior(attacker)) { // Only allow damage within the boss room. + return Optional.of(attacker); + } else { + this.sendTooFarMessage(attacker); + } + } else if (source.getDirectEntity() instanceof Projectile projectile) { + if (projectile.getOwner() instanceof LivingEntity attacker) { + if (this.getDungeon() == null || this.getDungeon().isPlayerWithinRoomInterior(attacker)) { // Only allow damage within the boss room. + return Optional.of(attacker); + } else { + return this.sendTooFarMessage(attacker); + } + } } } - super.die(source); + return Optional.empty(); } - public void spawnLargeCog(Entity entityToAttack, int stage) { - if (this.stageDone[stage]) - return; - CogProjectile cog = new CogProjectile(this.level(), this, true); - cog.setYRot(this.getYRot()); - cog.setXRot(this.getXRot()); - double var3 = entityToAttack.position().x + entityToAttack.getMotionDirection().getStepX() - this.position().x; - double var5 = entityToAttack.position().y + -this.getMotionDirection().getStepY(); - double var7 = entityToAttack.position().z + entityToAttack.getMotionDirection().getStepZ() - this.position().z; - float var9 = (float) Math.sqrt(var3 * var3 + var7 * var7); - if (!this.level().isClientSide) { - float distance = var9 * 0.075F; - cog.shoot(var3, var5 + (var9 * 0.2F), var7, distance, 0.0F); - this.playSound(GenesisSoundEvents.ENTITY_LABYRINTH_EYE_COG_LOSS.get(), 2.0F, 1.0F); - this.playSound(SoundEvents.ITEM_BREAK, 0.8F, 0.8F + this.level().random.nextFloat() * 0.4F); - this.level().addFreshEntity(cog); + private Optional sendTooFarMessage(LivingEntity attacker) { + if (!this.level().isClientSide() && attacker instanceof Player player) { + if (this.getChatCooldown() <= 0) { + this.displayTooFarMessage(player); // Too far from Slider + this.setChatCooldown(15); + } + } + return Optional.empty(); + } + + private boolean isBossStage(int stage) { + float interval = this.getMaxHealth() / 13.0F; + return this.getHealth() <= (this.getMaxHealth() - ((stage - 1) * interval)) + && this.getHealth() >= (this.getMaxHealth() - (stage * interval)); + } + + private void spawnLargeCog(int stage) { + if (!this.stageDone[stage]) { + LivingEntity target = this.getTarget(); + if (target != null) { + CogProjectile cog = new CogProjectile(this.level(), this, true); + cog.setYRot(this.getYRot()); + cog.setXRot(this.getXRot()); + cog.setPos(this.getCogPosition()); + double x = target.position().x() - cog.getX(); + double y = target.position().y() - cog.getY(); + double z = target.position().z() - cog.getZ(); + float dist = (float) Math.sqrt(x * x + z * z); + if (!this.level().isClientSide()) { + float distance = dist * 0.075F; + cog.shoot(x, y + (dist * 0.2F), z, distance, 1.0F); + this.playSound(GenesisSoundEvents.ENTITY_LABYRINTH_EYE_COG_LOSS.get(), 2.0F, 1.0F); + this.playSound(SoundEvents.ITEM_BREAK, 0.8F, 0.8F + this.level().getRandom().nextFloat() * 0.4F); + this.level().addFreshEntity(cog); + } + this.stageDone[stage] = true; + } } - stageDone[stage] = true; } - @Override - public void tick() { - super.tick(); - if (!this.isAwake() || (this.getTarget() instanceof Player player && (player.isCreative() || player.isSpectator()))) { - this.setTarget(null); - } + public Vec3 getCogPosition() { + float randomRot = this.getRandom().nextInt(360) * Mth.DEG_TO_RAD; + Vec3 lookVec = this.getLookAngle(); + Vec3 upVec = new Vec3(0, 1, 0); + Vec3 sideVec = lookVec.cross(upVec); + Vec3 point = upVec.scale(Mth.cos(randomRot)).add(sideVec.scale(Mth.sin(randomRot))).scale(0.75); + return this.position().add(point).add(0, this.getEyeHeight(), 0); + } - if (this.chatTime > 0) { - this.chatTime--; + private void start() { + this.setHealth(this.getMaxHealth()); + this.setAwake(true); + this.setBossFight(true); + if (this.getDungeon() != null) { + this.closeRoom(); } + AetherEventDispatch.onBossFightStart(this, this.getDungeon()); } public void reset() { this.setAwake(false); this.setBossFight(false); this.setTarget(null); - this.setHealth(this.getMaxHealth()); if (this.getDungeon() != null) { this.setPos(this.getDungeon().originCoordinates()); this.openRoom(); @@ -200,67 +279,105 @@ public void reset() { } @Override - public void checkDespawn() {} + public void die(DamageSource source) { + this.explode(); + if (this.level() instanceof ServerLevel) { + this.bossFight.setProgress(this.getHealth() / this.getMaxHealth()); + if (this.getDungeon() != null) { + this.getDungeon().grantAdvancements(source); + this.tearDownRoom(); + } + } + super.die(source); + } - @Override - public void defineSynchedData() { - super.defineSynchedData(); - this.entityData.define(DATA_AWAKE_ID, false); - this.entityData.define(DATA_BOSS_STAGE, 13); - this.entityData.define(DATA_BOSS_NAME_ID, Component.literal("Labyrinth's Eye")); + private void explode() { + for (int i = 0; i < (this.getHealth() <= 0 ? 16 : 48); i++) { + double x = this.position().x() + (double) (this.getRandom().nextFloat() - this.getRandom().nextFloat()) * 1.5; + double y = this.getBoundingBox().minY + 1.75 + (double) (this.getRandom().nextFloat() - this.getRandom().nextFloat()) * 1.5; + double z = this.position().z() + (double) (this.getRandom().nextFloat() - this.getRandom().nextFloat()) * 1.5; + this.level().addParticle(ParticleTypes.POOF, x, y, z, 0.0, 0.0, 0.0); + } } - @Override - public void addAdditionalSaveData(CompoundTag tag) { - super.addAdditionalSaveData(tag); - this.addBossSaveData(tag); - tag.putBoolean("Awake", this.isAwake()); + public void checkDespawn() { } + + @Nullable + @Override + public BlockState convertBlock(BlockState state) { + return DUNGEON_BLOCK_CONVERSIONS.getOrDefault(state.getBlock(), (blockState) -> null).apply(state); } @Override - public void readAdditionalSaveData(CompoundTag tag) { - super.readAdditionalSaveData(tag); - this.readBossSaveData(tag); - if (tag.contains("Awake")) { - this.setAwake(tag.getBoolean("Awake")); + public void startSeenByPlayer(ServerPlayer player) { + super.startSeenByPlayer(player); + PacketRelay.sendToPlayer(new BossInfoPacket.Display(this.bossFight.getId(), this.getId()), player); + if (this.getDungeon() == null || this.getDungeon().isPlayerTracked(player)) { + this.bossFight.addPlayer(player); + AetherEventDispatch.onBossFightPlayerAdd(this, this.getDungeon(), player); } } - public boolean hurt(DamageSource source, float damage) { - Entity entity = source.getDirectEntity(); - Entity attacker = source.getEntity(); - if (entity != null && source.is(DamageTypeTags.IS_PROJECTILE)) { - if (!this.level().isClientSide && attacker instanceof Player && ((Player)attacker).getMainHandItem() != Items.AIR.getDefaultInstance()) { - this.chatTime = 60; - attacker.sendSystemMessage(Component.translatable("gui.aether_genesis.boss.message.projectile")); - } - return false; - } - if (!this.isBossFight()) { - this.setHealth(this.getMaxHealth()); - this.setAwake(true); - this.setBossFight(true); - if (this.getDungeon() != null) { - this.closeRoom(); - } - AetherEventDispatch.onBossFightStart(this, this.getDungeon()); + @Override + public void stopSeenByPlayer(ServerPlayer player) { + super.stopSeenByPlayer(player); + PacketRelay.sendToPlayer(new BossInfoPacket.Remove(this.bossFight.getId(), this.getId()), player); + this.bossFight.removePlayer(player); + AetherEventDispatch.onBossFightPlayerRemove(this, this.getDungeon(), player); + } + + @Override + public void onDungeonPlayerAdded(@javax.annotation.Nullable Player player) { + if (player instanceof ServerPlayer serverPlayer) { + this.bossFight.addPlayer(serverPlayer); + AetherEventDispatch.onBossFightPlayerAdd(this, this.getDungeon(), serverPlayer); } - for (int stage = 0; stage < 13; stage++) { - if (isBossStage(stage) && !this.stageDone[stage]) { - setStage(stage); - spawnLargeCog(this, stage); - } + } + + @Override + public void onDungeonPlayerRemoved(@javax.annotation.Nullable Player player) { + if (player instanceof ServerPlayer serverPlayer) { + this.bossFight.removePlayer(serverPlayer); + AetherEventDispatch.onBossFightPlayerRemove(this, this.getDungeon(), serverPlayer); } - return super.hurt(source, damage); } - protected float getJumpPower() { - return 0.0F; + public int getStage() { + return this.entityData.get(DATA_BOSS_STAGE); + } + + private void setStage(int stage) { + this.entityData.set(DATA_BOSS_STAGE, stage); } - public LabyrinthEye self(){ - return this; + public boolean isAwake() { + return this.entityData.get(DATA_AWAKE_ID); + } + + public void setAwake(boolean awake) { + this.entityData.set(DATA_AWAKE_ID, awake); + } + + @Override + public Component getBossName() { + return this.entityData.get(DATA_BOSS_NAME_ID); + } + + @Override + public void setBossName(Component component) { + this.entityData.set(DATA_BOSS_NAME_ID, component); + this.bossFight.setName(component); + } + + @Override + public BossRoomTracker getDungeon() { + return this.bronzeDungeon; + } + + @Override + public void setDungeon(BossRoomTracker bossRoomTracker) { + this.bronzeDungeon = bossRoomTracker; } @Override @@ -300,14 +417,20 @@ public Music getBossMusic() { return MINIBOSS_MUSIC; } - @Override - public BossRoomTracker getDungeon() { - return this.bronzeDungeon; + /** + * @return The {@link Integer} for the cooldown until another chat message can display. + */ + public int getChatCooldown() { + return this.chatCooldown; } - @Override - public void setDungeon(BossRoomTracker bossRoomTracker) { - this.bronzeDungeon = bossRoomTracker; + /** + * Sets the cooldown for when another chat message can display. + * + * @param cooldown The {@link Integer} cooldown. + */ + public void setChatCooldown(int cooldown) { + this.chatCooldown = cooldown; } @Override @@ -315,159 +438,254 @@ public int getDeathScore() { return this.deathScore; } - @Nullable @Override - public BlockState convertBlock(BlockState state) { - return DUNGEON_BLOCK_CONVERSIONS.getOrDefault(state.getBlock(), (blockState) -> null).apply(state); + public void setCustomName(@Nullable Component name) { + super.setCustomName(name); + this.setBossName(name); } @Override - public void writeSpawnData(FriendlyByteBuf buffer) { - CompoundTag tag = new CompoundTag(); - this.addBossSaveData(tag); - buffer.writeNbt(tag); + protected SoundEvent getAmbientSound() { + return GenesisSoundEvents.ENTITY_LABYRINTH_EYE_MOVE.get(); } @Override - public void readSpawnData(FriendlyByteBuf additionalData) { - CompoundTag tag = additionalData.readNbt(); - if (tag != null) { - this.readBossSaveData(tag); - } + protected SoundEvent getDeathSound() { + return GenesisSoundEvents.ENTITY_LABYRINTH_EYE_DEATH.get(); } @Override - public void startSeenByPlayer(ServerPlayer player) { - super.startSeenByPlayer(player); - PacketRelay.sendToPlayer(new BossInfoPacket.Display(this.bossFight.getId(), this.getId()), player); - if (this.getDungeon() == null || this.getDungeon().isPlayerTracked(player)) { - this.bossFight.addPlayer(player); - } + protected float getJumpPower() { + return 0.0F; } @Override - public void customServerAiStep() { - super.customServerAiStep(); - this.bossFight.setProgress(this.getHealth() / this.getMaxHealth()); - this.trackDungeon(); + public float getYRot() { + return !this.isAwake() ? 0 : super.getYRot(); } @Override - public void stopSeenByPlayer(ServerPlayer player) { - super.stopSeenByPlayer(player); - PacketRelay.sendToPlayer(new BossInfoPacket.Remove(this.bossFight.getId(), this.getId()), player); - this.bossFight.removePlayer(player); + public boolean isPushable() { + return false; } @Override - public void onDungeonPlayerAdded(@Nullable Player player) { - if (player instanceof ServerPlayer serverPlayer) { - this.bossFight.addPlayer(serverPlayer); - } + public boolean isNoGravity() { + return !this.isAwake(); } @Override - public void onDungeonPlayerRemoved(@Nullable Player player) { - if (player instanceof ServerPlayer serverPlayer) { - this.bossFight.removePlayer(serverPlayer); - } - } - - public boolean isAwake() { - return this.entityData.get(DATA_AWAKE_ID); - } - - public void setAwake(boolean ready) { - this.entityData.set(DATA_AWAKE_ID, ready); + public void addAdditionalSaveData(CompoundTag tag) { + super.addAdditionalSaveData(tag); + this.addBossSaveData(tag); + tag.putBoolean("Awake", this.isAwake()); + tag.putIntArray("Stage", IntStream.range(0, this.stageDone.length).mapToObj(i -> this.stageDone[i]).map(bool -> bool.compareTo(false)).toList()); } @Override - public Component getBossName() { - return this.entityData.get(DATA_BOSS_NAME_ID); + public void readAdditionalSaveData(CompoundTag tag) { + super.readAdditionalSaveData(tag); + this.readBossSaveData(tag); + if (tag.contains("Awake")) { + this.setAwake(tag.getBoolean("Awake")); + } + if (tag.contains("Stage")) { + int[] stages = tag.getIntArray("Stage"); + for (int i : stages) { + this.stageDone[i] = stages[i] != 0; + } + } } @Override - public boolean isPushable() { - return false; + public void writeSpawnData(FriendlyByteBuf buffer) { + CompoundTag tag = new CompoundTag(); + this.addBossSaveData(tag); + buffer.writeNbt(tag); } @Override - public void setBossName(Component component) { - this.entityData.set(DATA_BOSS_NAME_ID, component); - this.bossFight.setName(component); + public void readSpawnData(FriendlyByteBuf additionalData) { + CompoundTag tag = additionalData.readNbt(); + if (tag != null) { + this.readBossSaveData(tag); + } } - protected void alignSpawnPos() { - this.moveTo(Mth.floor(this.getX()), this.getY(), Mth.floor(this.getZ())); - } + public static class TrackPlayerGoal extends Goal { + private final LabyrinthEye labyrinthEye; + private final double speedModifier; + private final boolean followingTargetEvenIfNotSeen; + private Path path; + private double pathedTargetX; + private double pathedTargetY; + private double pathedTargetZ; + private int ticksUntilNextPathRecalculation; + private long lastCanUseCheck; + + public TrackPlayerGoal(LabyrinthEye labyrinthEye) { + this.labyrinthEye = labyrinthEye; + this.speedModifier = 1.0F; + this.followingTargetEvenIfNotSeen = true; + this.setFlags(EnumSet.of(Goal.Flag.MOVE, Goal.Flag.LOOK)); + } - public MutableComponent generateGuardianName() { - MutableComponent result = BossNameGenerator.generateBossName(this.getRandom()); - return result.append(Component.translatable("gui.aether_genesis.labyrinth_eye.title")); - } + @Override + public boolean canUse() { + if (this.labyrinthEye.isAwake()) { + long i = this.labyrinthEye.level().getGameTime(); + if (i - this.lastCanUseCheck < 20L) { + return false; + } else { + this.lastCanUseCheck = i; + LivingEntity target = this.labyrinthEye.getTarget(); + if (target == null) { + return false; + } else if (!target.isAlive()) { + return false; + } else { + this.path = this.labyrinthEye.getNavigation().createPath(target, 0); + return this.path != null; + } + } + } + return false; + } - @Override - public boolean isNoGravity() { - return !isAwake(); - } + @Override + public boolean canContinueToUse() { + if (this.labyrinthEye.isAwake()) { + LivingEntity target = this.labyrinthEye.getTarget(); + if (target == null) { + return false; + } else if (!target.isAlive()) { + return false; + } else if (!this.followingTargetEvenIfNotSeen) { + return !this.labyrinthEye.getNavigation().isDone(); + } else if (!this.labyrinthEye.isWithinRestriction(target.blockPosition())) { + return false; + } else { + return !(target instanceof Player player) || !target.isSpectator() && !player.isCreative(); + } + } + return false; + } - @Override - public float getYRot() { - return !isAwake() ? 0 : super.getYRot(); - } + @Override + public void start() { + this.labyrinthEye.getNavigation().moveTo(this.path, this.speedModifier); + this.labyrinthEye.setAggressive(true); + this.ticksUntilNextPathRecalculation = 0; + } - @Override - public SpawnGroupData finalizeSpawn(ServerLevelAccessor level, DifficultyInstance difficulty, MobSpawnType reason, @Nullable SpawnGroupData pSpawnData, @Nullable CompoundTag dataTag) { - this.alignSpawnPos(); - SpawnGroupData data = super.finalizeSpawn(level, difficulty, reason, pSpawnData, dataTag); - this.setBossName(generateGuardianName()); - return data; + @Override + public void stop() { + LivingEntity livingentity = this.labyrinthEye.getTarget(); + if (!EntitySelector.NO_CREATIVE_OR_SPECTATOR.test(livingentity)) { + this.labyrinthEye.setTarget(null); + } + this.labyrinthEye.setAggressive(false); + this.labyrinthEye.getNavigation().stop(); + } + + @Override + public boolean requiresUpdateEveryTick() { + return true; + } + + @Override + public void tick() { + LivingEntity livingentity = this.labyrinthEye.getTarget(); + if (livingentity != null) { + this.labyrinthEye.getLookControl().setLookAt(livingentity, 30.0F, 30.0F); + this.ticksUntilNextPathRecalculation = Math.max(this.ticksUntilNextPathRecalculation - 1, 0); + if ((this.followingTargetEvenIfNotSeen || this.labyrinthEye.getSensing().hasLineOfSight(livingentity)) + && this.ticksUntilNextPathRecalculation <= 0 + && (this.pathedTargetX == 0.0 && this.pathedTargetY == 0.0 && this.pathedTargetZ == 0.0 + || livingentity.distanceToSqr(this.pathedTargetX, this.pathedTargetY, this.pathedTargetZ) >= 1.0 + || this.labyrinthEye.getRandom().nextFloat() < 0.05F)) { + this.pathedTargetX = livingentity.getX(); + this.pathedTargetY = livingentity.getY(); + this.pathedTargetZ = livingentity.getZ(); + this.ticksUntilNextPathRecalculation = 4 + this.labyrinthEye.getRandom().nextInt(7); + double d0 = this.labyrinthEye.distanceToSqr(livingentity); + if (d0 > 1024.0) { + this.ticksUntilNextPathRecalculation += 10; + } else if (d0 > 256.0) { + this.ticksUntilNextPathRecalculation += 5; + } + if (!this.labyrinthEye.getNavigation().moveTo(livingentity, this.speedModifier)) { + this.ticksUntilNextPathRecalculation += 15; + } + this.ticksUntilNextPathRecalculation = this.adjustedTickDelay(this.ticksUntilNextPathRecalculation); + } + } + } } - public static class DoNothingGoal extends Goal { + public static class LookAroundGoal extends RandomLookAroundGoal { private final LabyrinthEye labyrinthEye; - public DoNothingGoal(LabyrinthEye labyrinthEye) { + + public LookAroundGoal(LabyrinthEye labyrinthEye) { + super(labyrinthEye); this.labyrinthEye = labyrinthEye; - this.setFlags(EnumSet.of(Flag.MOVE, Flag.JUMP)); } @Override public boolean canUse() { - return !this.labyrinthEye.isBossFight(); + return super.canUse() && this.labyrinthEye.isAwake(); } + } - @Override - public void start() { - this.labyrinthEye.setDeltaMovement(Vec3.ZERO); - this.labyrinthEye.setPos(this.labyrinthEye.position().x, - this.labyrinthEye.position().y, - this.labyrinthEye.position().z); + public static class StrollGoal extends WaterAvoidingRandomStrollGoal { + private final LabyrinthEye labyrinthEye; + + public StrollGoal(LabyrinthEye labyrinthEye, double speedModifier) { + super(labyrinthEye, speedModifier); + this.labyrinthEye = labyrinthEye; } - } - @Override - public void setCustomName(@Nullable Component name) { - super.setCustomName(name); - this.setBossName(name); + @Override + public boolean canUse() { + return super.canUse() && this.labyrinthEye.isAwake(); + } } - public static class ArrowAttackCogGoal extends Goal { + public static class CogAttackGoal extends Goal { private final LabyrinthEye labyrinthEye; - public ArrowAttackCogGoal(LabyrinthEye labyrinthEye) { + public CogAttackGoal(LabyrinthEye labyrinthEye) { this.labyrinthEye = labyrinthEye; } @Override public boolean canUse() { - return this.labyrinthEye.isBossFight() && this.labyrinthEye.random.nextInt(20) == 0; + return this.labyrinthEye.isAwake() && this.labyrinthEye.getTarget() != null && this.labyrinthEye.random.nextInt(20) == 0 ; + } + + @Override + public boolean canContinueToUse() { + return super.canContinueToUse() && this.labyrinthEye.isAwake() && this.labyrinthEye.getTarget() != null; } @Override public void start() { - Entity cog = new CogProjectile(this.labyrinthEye.level(), this.labyrinthEye, false); - this.labyrinthEye.level().addFreshEntity(cog); - cog.setPos(labyrinthEye.position()); + LivingEntity target = this.labyrinthEye.getTarget(); + if (target != null) { + CogProjectile cog = new CogProjectile(this.labyrinthEye.level(), this.labyrinthEye, false); + cog.setYRot(this.labyrinthEye.getYRot()); + cog.setXRot(this.labyrinthEye.getXRot()); + cog.setPos(this.labyrinthEye.getCogPosition()); + double x = target.position().x() - cog.getX(); + double y = target.position().y() - cog.getY(); + double z = target.position().z() - cog.getZ(); + float dist = (float) Math.sqrt(x * x + z * z); + float distance = dist * 0.075F; + cog.shoot(x, y + (dist * 0.2F), z, distance, 20.0F); + this.labyrinthEye.playSound(GenesisSoundEvents.ENTITY_LABYRINTH_EYE_COG_LOSS.get(), 2.0F, 1.0F); + this.labyrinthEye.playSound(SoundEvents.ITEM_BREAK, 0.8F, 0.8F + this.labyrinthEye.level().getRandom().nextFloat() * 0.4F); + this.labyrinthEye.level().addFreshEntity(cog); + } } @Override @@ -476,16 +694,24 @@ public boolean requiresUpdateEveryTick() { } } - public static class LookAtPlayerGoalBoss extends LookAtPlayerGoal{ + public static class InactiveGoal extends Goal { private final LabyrinthEye labyrinthEye; - public LookAtPlayerGoalBoss(LabyrinthEye pMob, Class pLookAtType, float pLookDistance) { - super(pMob, pLookAtType, pLookDistance); - this.labyrinthEye = pMob; + public InactiveGoal(LabyrinthEye labyrinthEye) { + this.labyrinthEye = labyrinthEye; + this.labyrinthEye.setRot(0, 0); + this.setFlags(EnumSet.of(Flag.MOVE, Flag.JUMP)); } + @Override public boolean canUse() { - return super.canUse() && this.labyrinthEye.isBossFight(); + return !this.labyrinthEye.isAwake(); + } + + @Override + public void start() { + this.labyrinthEye.setDeltaMovement(Vec3.ZERO); + this.labyrinthEye.setPos(this.labyrinthEye.position().x(), this.labyrinthEye.position().y(), this.labyrinthEye.position().z()); } } } \ No newline at end of file diff --git a/src/main/java/com/aetherteam/genesis/entity/monster/dungeon/boss/SentryGuardian.java b/src/main/java/com/aetherteam/genesis/entity/monster/dungeon/boss/SentryGuardian.java index 2a93ddcb..543f2fa2 100644 --- a/src/main/java/com/aetherteam/genesis/entity/monster/dungeon/boss/SentryGuardian.java +++ b/src/main/java/com/aetherteam/genesis/entity/monster/dungeon/boss/SentryGuardian.java @@ -3,7 +3,9 @@ import com.aetherteam.aether.Aether; import com.aetherteam.aether.block.AetherBlocks; import com.aetherteam.aether.entity.AetherBossMob; +import com.aetherteam.aether.entity.AetherEntityTypes; import com.aetherteam.aether.entity.ai.goal.ContinuousMeleeAttackGoal; +import com.aetherteam.aether.entity.ai.goal.MostDamageTargetGoal; import com.aetherteam.aether.entity.monster.dungeon.Sentry; import com.aetherteam.aether.entity.monster.dungeon.boss.BossNameGenerator; import com.aetherteam.aether.event.AetherEventDispatch; @@ -11,11 +13,12 @@ import com.aetherteam.genesis.client.GenesisSoundEvents; import com.aetherteam.nitrogen.entity.BossRoomTracker; import com.aetherteam.nitrogen.network.PacketRelay; +import net.minecraft.core.BlockPos; import net.minecraft.core.particles.DustParticleOptions; +import net.minecraft.core.particles.ParticleTypes; import net.minecraft.nbt.CompoundTag; import net.minecraft.network.FriendlyByteBuf; import net.minecraft.network.chat.Component; -import net.minecraft.network.chat.MutableComponent; import net.minecraft.network.syncher.EntityDataAccessor; import net.minecraft.network.syncher.EntityDataSerializers; import net.minecraft.network.syncher.SynchedEntityData; @@ -27,24 +30,23 @@ import net.minecraft.sounds.SoundEvent; import net.minecraft.sounds.SoundEvents; import net.minecraft.sounds.SoundSource; -import net.minecraft.tags.DamageTypeTags; import net.minecraft.util.Mth; import net.minecraft.world.BossEvent; import net.minecraft.world.Difficulty; import net.minecraft.world.DifficultyInstance; import net.minecraft.world.damagesource.DamageSource; import net.minecraft.world.entity.*; +import net.minecraft.world.entity.ai.attributes.AttributeInstance; import net.minecraft.world.entity.ai.attributes.AttributeSupplier; import net.minecraft.world.entity.ai.attributes.Attributes; import net.minecraft.world.entity.ai.goal.Goal; -import net.minecraft.world.entity.ai.goal.MoveTowardsRestrictionGoal; +import net.minecraft.world.entity.ai.goal.RandomLookAroundGoal; import net.minecraft.world.entity.ai.goal.WaterAvoidingRandomStrollGoal; -import net.minecraft.world.entity.ai.goal.target.HurtByTargetGoal; import net.minecraft.world.entity.ai.goal.target.NearestAttackableTargetGoal; import net.minecraft.world.entity.monster.Enemy; import net.minecraft.world.entity.monster.Monster; import net.minecraft.world.entity.player.Player; -import net.minecraft.world.item.Items; +import net.minecraft.world.entity.projectile.Projectile; import net.minecraft.world.level.Level; import net.minecraft.world.level.ServerLevelAccessor; import net.minecraft.world.level.block.Block; @@ -52,140 +54,233 @@ import net.minecraft.world.level.block.HorizontalDirectionalBlock; import net.minecraft.world.level.block.state.BlockState; import net.minecraft.world.phys.Vec3; +import net.neoforged.neoforge.common.NeoForgeMod; import net.neoforged.neoforge.entity.IEntityWithComplexSpawn; +import org.apache.commons.lang3.tuple.Pair; import org.jetbrains.annotations.Nullable; import java.util.EnumSet; +import java.util.HashMap; import java.util.Map; +import java.util.Optional; import java.util.function.Function; -import static com.aetherteam.aether.entity.AetherEntityTypes.SENTRY; - public class SentryGuardian extends PathfinderMob implements AetherBossMob, Enemy, IEntityWithComplexSpawn { public static final EntityDataAccessor DATA_AWAKE_ID = SynchedEntityData.defineId(SentryGuardian.class, EntityDataSerializers.BOOLEAN); public static final EntityDataAccessor DATA_BOSS_NAME_ID = SynchedEntityData.defineId(SentryGuardian.class, EntityDataSerializers.COMPONENT); private static final Music MINIBOSS_MUSIC = new Music(GenesisSoundEvents.MUSIC_MINIBOSS, 0, 0, true); - public static final Map> DUNGEON_BLOCK_CONVERSIONS = Map.ofEntries( + public static final Map> DUNGEON_BLOCK_CONVERSIONS = new HashMap<>(Map.ofEntries( Map.entry(AetherBlocks.LOCKED_CARVED_STONE.get(), (blockState) -> AetherBlocks.CARVED_STONE.get().defaultBlockState()), Map.entry(AetherBlocks.LOCKED_SENTRY_STONE.get(), (blockState) -> AetherBlocks.SENTRY_STONE.get().defaultBlockState()), Map.entry(AetherBlocks.BOSS_DOORWAY_CARVED_STONE.get(), (blockState) -> Blocks.AIR.defaultBlockState()), Map.entry(AetherBlocks.TREASURE_DOORWAY_CARVED_STONE.get(), (blockState) -> AetherBlocks.SKYROOT_TRAPDOOR.get().defaultBlockState().setValue(HorizontalDirectionalBlock.FACING, blockState.getValue(HorizontalDirectionalBlock.FACING))) - ); + )); - public int chatTime; - private int attackTime = 0; -// private int cappedAmount; + /** + * Goal for targeting in groups of entities + */ + private MostDamageTargetGoal mostDamageTargetGoal; private final ServerBossEvent bossFight; - private BossRoomTracker bronzeDungeon; - + public int chatCooldown; + private int attackAnimationTick = 0; + + public SentryGuardian(EntityType entityType, Level level) { + super(entityType, level); + this.bossFight = new ServerBossEvent(this.getBossName(), BossEvent.BossBarColor.RED, BossEvent.BossBarOverlay.PROGRESS); + this.bossFight.setVisible(false); + this.xpReward = XP_REWARD_BOSS; + this.setPersistenceRequired(); + this.setMaxUpStep(1.0F); + } + + @Override + public SpawnGroupData finalizeSpawn(ServerLevelAccessor level, DifficultyInstance difficulty, MobSpawnType reason, @Nullable SpawnGroupData spawnData, @Nullable CompoundTag dataTag) { + this.setBossName(BossNameGenerator.generateBossName(this.getRandom()).append(Component.translatable("gui.aether_genesis.sentry_guardian.title"))); + this.moveTo(Mth.floor(this.getX()), this.getY(), Mth.floor(this.getZ())); + return spawnData; + } + public static AttributeSupplier.Builder createMobAttributes() { return Monster.createMobAttributes() - .add(Attributes.MAX_HEALTH, 350) - .add(Attributes.MOVEMENT_SPEED, 0.265) + .add(Attributes.MAX_HEALTH, 350.0) + .add(Attributes.MOVEMENT_SPEED, 1.0) .add(Attributes.KNOCKBACK_RESISTANCE, 0.5) .add(Attributes.FOLLOW_RANGE, 64.0); } @Override protected void registerGoals() { - this.goalSelector.addGoal(0, new DoNothingGoal(this)); - this.targetSelector.addGoal(2, new ContinuousMeleeAttackGoal(this, 1.0, false)); - this.targetSelector.addGoal(3, new SummonSentryGoal(this)); - this.goalSelector.addGoal(5, new MoveTowardsRestrictionGoal(this, 1.0)); - this.goalSelector.addGoal(7, new WaterAvoidingRandomStrollGoal(this, 1.0)); - this.targetSelector.addGoal(1, new HurtByTargetGoal(this, SentryGuardian.class)); + this.goalSelector.addGoal(0, new SentryGuardian.AttackPlayerGoal(this)); + this.goalSelector.addGoal(1, new SentryGuardian.LookAroundGoal(this)); + this.goalSelector.addGoal(2, new SentryGuardian.StrollGoal(this, 1.0)); + this.goalSelector.addGoal(3, new SentryGuardian.SummonSentryGoal(this)); + this.goalSelector.addGoal(4, new SentryGuardian.InactiveGoal(this)); + + this.mostDamageTargetGoal = new MostDamageTargetGoal(this); + this.targetSelector.addGoal(1, this.mostDamageTargetGoal); this.targetSelector.addGoal(2, new NearestAttackableTargetGoal<>(this, Player.class, 10, true, false, livingEntity -> this.isBossFight())); } - public int getAttackAnimationTick() { - return this.attackTime; + @Override + public void defineSynchedData() { + super.defineSynchedData(); + this.entityData.define(DATA_AWAKE_ID, false); + this.entityData.define(DATA_BOSS_NAME_ID, Component.literal("Sentry Guardian")); } - public SentryGuardian(EntityType pEntityType, Level pLevel) { - super(pEntityType, pLevel); - this.bossFight = new ServerBossEvent(this.getBossName(), BossEvent.BossBarColor.RED, BossEvent.BossBarOverlay.PROGRESS); - this.bossFight.setVisible(false); - this.xpReward = XP_REWARD_BOSS; - this.setPersistenceRequired(); + @Override + public void handleEntityEvent(byte pId) { + if (pId == 4) { + this.attackAnimationTick = 10; + this.playSound(SoundEvents.IRON_GOLEM_ATTACK, 1.0F, 1.0F); + } else { + super.handleEntityEvent(pId); + } } - public void spawnSentry() { - // if (!this.level.isClientSide && this.cappedAmount < 5) { - if (!this.level().isClientSide) { - Sentry sentry = new Sentry(SENTRY.get(), this.level()); - sentry.setPos(this.position()); - this.level().addFreshEntity(sentry); - sentry.setDeltaMovement(0, 1, 0); - // sentry.fallDistance = -100.0F; - sentry.setTarget(this.getTarget()); - // this.cappedAmount++; - // sentry.setParent(this); - this.level().playSound(this, this.blockPosition(), GenesisSoundEvents.ENTITY_SENTRY_GUARDIAN_SUMMON.get(), SoundSource.AMBIENT, 2.0F, 1.0F); + @Override + public void tick() { + super.tick(); + if (!this.isAwake() || (this.getTarget() instanceof Player player && (player.isCreative() || player.isSpectator()))) { + this.setTarget(null); + } + this.evaporate(); + this.spawnParticles(); + if (this.getChatCooldown() > 0) { + this.chatCooldown--; + } + AttributeInstance gravity = this.getAttribute(NeoForgeMod.ENTITY_GRAVITY.value()); + if (gravity != null) { + double fallSpeed = Math.max(gravity.getValue() * -1.25, -0.1); // Entity isn't allowed to fall too slowly from gravity. + if (this.getDeltaMovement().y() < fallSpeed) { + this.setDeltaMovement(this.getDeltaMovement().x(), fallSpeed, this.getDeltaMovement().z()); + this.hasImpulse = true; + } } } @Override - protected SoundEvent getHurtSound( DamageSource damageSource) { - return GenesisSoundEvents.ENTITY_SENTRY_GUARDIAN_HIT.get(); + public void aiStep() { + super.aiStep(); + if (this.attackAnimationTick > 0) { + --this.attackAnimationTick; + } + } + + private void evaporate() { + Pair minMax = this.getDefaultBounds(this); + AetherBossMob.super.evaporate(this, minMax.getLeft(), minMax.getRight(), (blockState) -> true); + } + + private void spawnParticles() { + if (this.level().isClientSide()) { + if (this.getHealth() > 0.0F) { + double a = (this.getRandom().nextFloat() - 0.5F); + double b = this.getRandom().nextFloat(); + double c = (this.getRandom().nextFloat() - 0.5F); + double x = this.position().x() + a * b; + double y = this.getBoundingBox().minY + b - 0.30000001192092896D; + double z = this.position().z() + c * b; + if (!this.isAwake()) { + this.level().addParticle(new DustParticleOptions(Vec3.fromRGB24(4741535).toVector3f(), 1.0F), x, y, z, 0.29, 0.28, 0.48); + } else { + this.level().addParticle(new DustParticleOptions(Vec3.fromRGB24(10429253).toVector3f(), 1.0F), x, y, z, 0.43, 0.18, 0.28); + } + } + } } @Override - protected SoundEvent getAmbientSound() { - return GenesisSoundEvents.ENTITY_SENTRY_GUARDIAN_LIVING.get(); + public void customServerAiStep() { + super.customServerAiStep(); + this.bossFight.setProgress(this.getHealth() / this.getMaxHealth()); + this.trackDungeon(); } @Override - protected SoundEvent getDeathSound() { - return GenesisSoundEvents.ENTITY_SENTRY_GUARDIAN_DEATH.get(); + public boolean doHurtTarget(Entity entity) { + this.attackAnimationTick = 10; + this.level().broadcastEntityEvent(this, (byte) 4); + boolean flag = entity.hurt(this.damageSources().mobAttack(this), 5 + this.random.nextInt(3)); + if (flag) { + double d2; + if (entity instanceof LivingEntity living) { + d2 = living.getAttributeValue(Attributes.KNOCKBACK_RESISTANCE); + } else { + d2 = 0.0D; + } + double d0 = d2; + double d1 = Math.max(0.0, 1.0 - d0); + entity.setDeltaMovement(entity.getDeltaMovement().add(0.0, 0.4 * d1, 0.0)); + this.doEnchantDamageEffects(this, entity); + } + return flag; } - public void die(DamageSource source) { - this.setDeltaMovement(Vec3.ZERO); - this.level().explode(this, this.position().x, this.position().y, this.position().z, 0.3F, false, Level.ExplosionInteraction.TNT); - spawnSentry(); - if (this.level() instanceof ServerLevel) { - this.bossFight.setProgress(this.getHealth() / this.getMaxHealth()); - if (this.getDungeon() != null) { - this.getDungeon().grantAdvancements(source); - this.tearDownRoom(); + public boolean hurt(DamageSource source, float amount) { + Optional damageResult = this.canDamageSentryGuardian(source); + if (damageResult.isPresent()) { + if (super.hurt(source, amount) && this.getHealth() > 0) { + if (!this.isBossFight()) { + this.start(); + } + if (!this.level().isClientSide() && source.getEntity() instanceof LivingEntity living) { + this.mostDamageTargetGoal.addAggro(living, amount); // AI goal for being hurt. + } + return true; } } - super.die(source); + return false; } - public void tick() { - super.tick(); - if (this.attackTime > 0) - this.attackTime--; - if (!this.isAwake() || (this.getTarget() instanceof Player player && (player.isCreative() || player.isSpectator()))) { - this.setTarget(null); + private Optional canDamageSentryGuardian(DamageSource source) { + if (this.level().getDifficulty() != Difficulty.PEACEFUL) { + if (source.getDirectEntity() instanceof LivingEntity attacker) { + if (this.getDungeon() == null || this.getDungeon().isPlayerWithinRoomInterior(attacker)) { // Only allow damage within the boss room. + return Optional.of(attacker); + } else { + this.sendTooFarMessage(attacker); + } + } else if (source.getDirectEntity() instanceof Projectile projectile) { + if (projectile.getOwner() instanceof LivingEntity attacker) { + if (this.getDungeon() == null || this.getDungeon().isPlayerWithinRoomInterior(attacker)) { // Only allow damage within the boss room. + return Optional.of(attacker); + } else { + return this.sendTooFarMessage(attacker); + } + } + } } - this.extinguishFire(); - if (getHealth() > 0.0F) { - double a = (this.random.nextFloat() - 0.5F); - double b = this.random.nextFloat(); - double c = (this.random.nextFloat() - 0.5F); - double d = this.position().x + a * b; - double e = this.getBoundingBox().minY + b - 0.30000001192092896D; - double f = this.position().z + c * b; - if (!isAwake()) { - this.level().addParticle(new DustParticleOptions(Vec3.fromRGB24(10444703).toVector3f(), 1.0F), d, e, f, 0.28999999165534973D, 0.2800000011920929D, 0.47999998927116394D); - } else { - this.level().addParticle(new DustParticleOptions(Vec3.fromRGB24(9315170).toVector3f(), 1.0F), d, e, f, 0.4300000071525574D, 0.18000000715255737D, 0.2800000011920929D); + return Optional.empty(); + } + + private Optional sendTooFarMessage(LivingEntity attacker) { + if (!this.level().isClientSide() && attacker instanceof Player player) { + if (this.getChatCooldown() <= 0) { + this.displayTooFarMessage(player); // Too far from Slider + this.setChatCooldown(15); } } - if (this.chatTime > 0) - this.chatTime--; + return Optional.empty(); + } + + private void start() { + this.setHealth(this.getMaxHealth()); + this.setAwake(true); + this.setBossFight(true); + if (this.getDungeon() != null) { + this.closeRoom(); + } + AetherEventDispatch.onBossFightStart(this, this.getDungeon()); } public void reset() { this.setAwake(false); this.setBossFight(false); this.setTarget(null); - this.setHealth(this.getMaxHealth()); if (this.getDungeon() != null) { this.setPos(this.getDungeon().originCoordinates()); this.openRoom(); @@ -193,84 +288,98 @@ public void reset() { AetherEventDispatch.onBossFightStop(this, this.getDungeon()); } - public boolean doHurtTarget(Entity entity) { - this.attackTime = 4 + this.random.nextInt(4); - this.level().broadcastEntityEvent(this, (byte)4); - boolean flag = entity.hurt(this.damageSources().mobAttack(this), 5 + this.random.nextInt(3)); - if (flag) { - double d2; - if (entity instanceof LivingEntity) { - LivingEntity living = (LivingEntity)entity; - d2 = living.getAttributeValue(Attributes.KNOCKBACK_RESISTANCE); - } else { - d2 = 0.0D; + @Override + public void die(DamageSource source) { + this.explode(); + if (this.level() instanceof ServerLevel) { + this.bossFight.setProgress(this.getHealth() / this.getMaxHealth()); + if (this.getDungeon() != null) { + this.getDungeon().grantAdvancements(source); + this.tearDownRoom(); } - - double d0 = d2; - double d1 = Math.max(0.0D, 1.0D - d0); - entity.setDeltaMovement(entity.getDeltaMovement().add(0.0D, (double)0.4F * d1, 0.0D)); - this.doEnchantDamageEffects(this, entity); } + super.die(source); + } - this.playSound(SoundEvents.IRON_GOLEM_ATTACK, 1.0F, 1.0F); //TODO - return flag; + private void explode() { + for (int i = 0; i < (this.getHealth() <= 0 ? 16 : 48); i++) { + double x = this.position().x() + (double) (this.getRandom().nextFloat() - this.getRandom().nextFloat()) * 1.5; + double y = this.getBoundingBox().minY + 1.75 + (double) (this.getRandom().nextFloat() - this.getRandom().nextFloat()) * 1.5; + double z = this.position().z() + (double) (this.getRandom().nextFloat() - this.getRandom().nextFloat()) * 1.5; + this.level().addParticle(ParticleTypes.POOF, x, y, z, 0.0, 0.0, 0.0); + } } @Override - public void checkDespawn() {} + public void checkDespawn() { } + @Nullable @Override - public void defineSynchedData() { - super.defineSynchedData(); - this.entityData.define(DATA_AWAKE_ID, false); - this.entityData.define(DATA_BOSS_NAME_ID, Component.literal("Sentry Guardian")); + public BlockState convertBlock(BlockState state) { + return DUNGEON_BLOCK_CONVERSIONS.getOrDefault(state.getBlock(), (blockState) -> null).apply(state); } - @Override - public void addAdditionalSaveData( CompoundTag tag) { - super.addAdditionalSaveData(tag); - this.addBossSaveData(tag); - tag.putBoolean("Awake", this.isAwake()); + public void startSeenByPlayer(ServerPlayer player) { + super.startSeenByPlayer(player); + PacketRelay.sendToPlayer(new BossInfoPacket.Display(this.bossFight.getId(), this.getId()), player); + if (this.getDungeon() == null || this.getDungeon().isPlayerTracked(player)) { + this.bossFight.addPlayer(player); + AetherEventDispatch.onBossFightPlayerAdd(this, this.getDungeon(), player); + } } @Override - public void readAdditionalSaveData( CompoundTag tag) { - super.readAdditionalSaveData(tag); - this.readBossSaveData(tag); - if (tag.contains("Awake")) { - this.setAwake(tag.getBoolean("Awake")); - } + public void stopSeenByPlayer(ServerPlayer player) { + super.stopSeenByPlayer(player); + PacketRelay.sendToPlayer(new BossInfoPacket.Remove(this.bossFight.getId(), this.getId()), player); + this.bossFight.removePlayer(player); + AetherEventDispatch.onBossFightPlayerRemove(this, this.getDungeon(), player); } - public boolean hurt(DamageSource source, float damage) { - Entity entity = source.getDirectEntity(); - Entity attacker = source.getEntity(); - if (entity != null && source.is(DamageTypeTags.IS_PROJECTILE)) { - if (!this.level().isClientSide && attacker instanceof Player && ((Player)attacker).getMainHandItem() != Items.AIR.getDefaultInstance()) { - this.chatTime = 60; - attacker.sendSystemMessage(Component.translatable("gui.aether_genesis.boss.message.projectile")); - } - return false; + @Override + public void onDungeonPlayerAdded(@Nullable Player player) { + if (player instanceof ServerPlayer serverPlayer) { + this.bossFight.addPlayer(serverPlayer); + AetherEventDispatch.onBossFightPlayerAdd(this, this.getDungeon(), serverPlayer); } - if (!this.isBossFight()) { - this.setHealth(this.getMaxHealth()); - this.setAwake(true); - this.setBossFight(true); - if (this.getDungeon() != null) { - this.closeRoom(); - } - AetherEventDispatch.onBossFightStart(this, this.getDungeon()); + } + + @Override + public void onDungeonPlayerRemoved(@Nullable Player player) { + if (player instanceof ServerPlayer serverPlayer) { + this.bossFight.removePlayer(serverPlayer); + AetherEventDispatch.onBossFightPlayerRemove(this, this.getDungeon(), serverPlayer); } - return super.hurt(source, damage); } - protected float getJumpPower() { - return 0.0F; + public boolean isAwake() { + return this.entityData.get(DATA_AWAKE_ID); } - public SentryGuardian self(){ - return this; + public void setAwake(boolean awake) { + this.entityData.set(DATA_AWAKE_ID, awake); + } + + @Override + public Component getBossName() { + return this.entityData.get(DATA_BOSS_NAME_ID); + } + + @Override + public void setBossName(Component component) { + this.entityData.set(DATA_BOSS_NAME_ID, component); + this.bossFight.setName(component); + } + + @Override + public BossRoomTracker getDungeon() { + return this.bronzeDungeon; + } + + @Override + public void setDungeon(BossRoomTracker bossRoomTracker) { + this.bronzeDungeon = bossRoomTracker; } @Override @@ -310,14 +419,24 @@ public Music getBossMusic() { return MINIBOSS_MUSIC; } - @Override - public BossRoomTracker getDungeon() { - return this.bronzeDungeon; + /** + * @return The {@link Integer} for the cooldown until another chat message can display. + */ + public int getChatCooldown() { + return this.chatCooldown; } - @Override - public void setDungeon(BossRoomTracker bossRoomTracker) { - this.bronzeDungeon = bossRoomTracker; + /** + * Sets the cooldown for when another chat message can display. + * + * @param cooldown The {@link Integer} cooldown. + */ + public void setChatCooldown(int cooldown) { + this.chatCooldown = cooldown; + } + + public int getAttackAnimationTick() { + return this.attackAnimationTick; } @Override @@ -325,185 +444,172 @@ public int getDeathScore() { return this.deathScore; } - @Nullable @Override - public BlockState convertBlock(BlockState state) { - return DUNGEON_BLOCK_CONVERSIONS.getOrDefault(state.getBlock(), (blockState) -> null).apply(state); + public void setCustomName(@Nullable Component name) { + super.setCustomName(name); + this.setBossName(name); } @Override - public void writeSpawnData(FriendlyByteBuf buffer) { - CompoundTag tag = new CompoundTag(); - this.addBossSaveData(tag); - buffer.writeNbt(tag); + protected SoundEvent getAmbientSound() { + return GenesisSoundEvents.ENTITY_SENTRY_GUARDIAN_LIVING.get(); } @Override - public void readSpawnData(FriendlyByteBuf additionalData) { - CompoundTag tag = additionalData.readNbt(); - if (tag != null) { - this.readBossSaveData(tag); - } + protected SoundEvent getHurtSound( DamageSource damageSource) { + return GenesisSoundEvents.ENTITY_SENTRY_GUARDIAN_HIT.get(); } @Override - public void startSeenByPlayer( ServerPlayer player) { - super.startSeenByPlayer(player); - PacketRelay.sendToPlayer(new BossInfoPacket.Display(this.bossFight.getId(), this.getId()), player); - if (this.getDungeon() == null || this.getDungeon().isPlayerTracked(player)) { - this.bossFight.addPlayer(player); - } + protected SoundEvent getDeathSound() { + return GenesisSoundEvents.ENTITY_SENTRY_GUARDIAN_DEATH.get(); } @Override - public void customServerAiStep() { - super.customServerAiStep(); - this.bossFight.setProgress(this.getHealth() / this.getMaxHealth()); - this.trackDungeon(); + protected float getJumpPower() { + return 0.0F; } @Override - public void stopSeenByPlayer( ServerPlayer player) { - super.stopSeenByPlayer(player); - PacketRelay.sendToPlayer(new BossInfoPacket.Remove(this.bossFight.getId(), this.getId()), player); - this.bossFight.removePlayer(player); + public boolean isPushable() { + return false; } @Override - public void onDungeonPlayerAdded(@Nullable Player player) { - if (player instanceof ServerPlayer serverPlayer) { - this.bossFight.addPlayer(serverPlayer); - } + public void addAdditionalSaveData( CompoundTag tag) { + super.addAdditionalSaveData(tag); + this.addBossSaveData(tag); + tag.putBoolean("Awake", this.isAwake()); } @Override - public void onDungeonPlayerRemoved(@Nullable Player player) { - if (player instanceof ServerPlayer serverPlayer) { - this.bossFight.removePlayer(serverPlayer); + public void readAdditionalSaveData( CompoundTag tag) { + super.readAdditionalSaveData(tag); + this.readBossSaveData(tag); + if (tag.contains("Awake")) { + this.setAwake(tag.getBoolean("Awake")); } } - public boolean isAwake() { - return this.entityData.get(DATA_AWAKE_ID); - } - - public void setAwake(boolean ready) { - this.entityData.set(DATA_AWAKE_ID, ready); - } - - @Override - public Component getBossName() { - return this.entityData.get(DATA_BOSS_NAME_ID); - } - @Override - public boolean isPushable() { - return false; + public void writeSpawnData(FriendlyByteBuf buffer) { + CompoundTag tag = new CompoundTag(); + this.addBossSaveData(tag); + buffer.writeNbt(tag); } @Override - public void setBossName(Component component) { - this.entityData.set(DATA_BOSS_NAME_ID, component); - this.bossFight.setName(component); + public void readSpawnData(FriendlyByteBuf additionalData) { + CompoundTag tag = additionalData.readNbt(); + if (tag != null) { + this.readBossSaveData(tag); + } } - protected void alignSpawnPos() { - this.moveTo(Mth.floor(this.getX()), this.getY(), Mth.floor(this.getZ())); - } + public static class AttackPlayerGoal extends ContinuousMeleeAttackGoal { + private final SentryGuardian sentryGuardian; - public MutableComponent generateGuardianName() { - MutableComponent result = BossNameGenerator.generateBossName(this.getRandom()); - return result.append(Component.translatable("gui.aether_genesis.sentry_guardian.title")); - } + public AttackPlayerGoal(SentryGuardian sentryGuardian) { + super(sentryGuardian, 1.0, false); + this.sentryGuardian = sentryGuardian; + } - @Override - public SpawnGroupData finalizeSpawn( ServerLevelAccessor pLevel, DifficultyInstance pDifficulty, MobSpawnType pReason, @javax.annotation.Nullable SpawnGroupData pSpawnData, @javax.annotation.Nullable CompoundTag pDataTag) { - this.alignSpawnPos(); - SpawnGroupData data = super.finalizeSpawn(pLevel, pDifficulty, pReason, pSpawnData, pDataTag); - this.setBossName(generateGuardianName()); - return data; + @Override + public boolean canUse() { + return super.canUse() && this.sentryGuardian.isAwake(); + } } - public static class DoNothingGoal extends Goal { + public static class LookAroundGoal extends RandomLookAroundGoal { private final SentryGuardian sentryGuardian; - public DoNothingGoal(SentryGuardian sentryGuardian) { + + public LookAroundGoal(SentryGuardian sentryGuardian) { + super(sentryGuardian); this.sentryGuardian = sentryGuardian; - this.setFlags(EnumSet.of(Flag.MOVE, Flag.JUMP)); } @Override public boolean canUse() { - return !this.sentryGuardian.isBossFight(); + return super.canUse() && this.sentryGuardian.isAwake(); } + } - @Override - public void start() { - this.sentryGuardian.setDeltaMovement(Vec3.ZERO); - this.sentryGuardian.setPos(this.sentryGuardian.position().x, - this.sentryGuardian.position().y, - this.sentryGuardian.position().z); + public static class StrollGoal extends WaterAvoidingRandomStrollGoal { + private final SentryGuardian sentryGuardian; + + public StrollGoal(SentryGuardian sentryGuardian, double speedModifier) { + super(sentryGuardian, speedModifier); + this.sentryGuardian = sentryGuardian; } - } - @Override - public void setCustomName(@Nullable Component name) { - super.setCustomName(name); - this.setBossName(name); + @Override + public boolean canUse() { + return super.canUse() && this.sentryGuardian.isAwake(); + } } public static class SummonSentryGoal extends Goal { - private final Mob mob; - private int heightOffsetUpdateTime = 10; - private float heightOffset = 0.5F; + private final SentryGuardian sentryGuardian; + private int spawnDelay; - public SummonSentryGoal(Mob mob) { - this.mob = mob; + public SummonSentryGoal(SentryGuardian sentryGuardian) { + this.sentryGuardian = sentryGuardian; } - public void spawnSentry() { - // if (!this.level.isClientSide && this.cappedAmount < 5) { - if (!this.mob.level().isClientSide) { - Sentry sentry = new Sentry(SENTRY.get(), this.mob.level()); - sentry.setPos(this.mob.position()); - this.mob.level().addFreshEntity(sentry); - sentry.setDeltaMovement(0, 1, 0); - // sentry.fallDistance = -100.0F; - sentry.setTarget(this.mob.getTarget()); - // this.cappedAmount++; - // sentry.setParent(this); - this.mob.level().playSound(this.mob, this.mob.blockPosition(), GenesisSoundEvents.ENTITY_SENTRY_GUARDIAN_SUMMON.get(), SoundSource.AMBIENT, 2.0F, 1.0F); + @Override + public boolean canUse() { + if (this.sentryGuardian.isAwake()) { + LivingEntity target = this.sentryGuardian.getTarget(); + if (target != null && target.isAlive()) { + return this.sentryGuardian.level().getDifficulty() != Difficulty.PEACEFUL; + } } + return false; } + @Override public void tick() { - if (!this.mob.level().isClientSide) { - if (this.mob.level().random.nextInt(100) == 1 && this.mob.getTarget() != null) - spawnSentry(); - this.heightOffsetUpdateTime--; - if (this.heightOffsetUpdateTime <= 0) { - this.heightOffsetUpdateTime = 100; - this.heightOffset = 0.5F + (float)this.mob.level().random.nextGaussian() * 3.0F; - } - if (this.mob.getTarget() != null && (this.mob.getTarget()).position().y + this.mob.getTarget().getEyeHeight() > this.mob.position().y + this.mob.getEyeHeight() + this.heightOffset) - this.mob.setDeltaMovement(0, 0.700000011920929D * 0.700000011920929D, 0); + if (this.sentryGuardian.level().getRandom().nextInt(100) == 1 && this.sentryGuardian.getTarget() != null) { + this.spawnDelay = 10; + this.sentryGuardian.setDeltaMovement(0, 0.5, 0); + } + this.spawnDelay--; + if (this.spawnDelay == 0) { + this.spawnSentry(); + this.spawnDelay = -1; } - if (!this.mob.onGround() && this.mob.getMotionDirection().getStepY() < 0.0D) - this.mob.setDeltaMovement(0, 0.8D, 0); super.tick(); } + public void spawnSentry() { + Sentry sentry = new Sentry(AetherEntityTypes.SENTRY.get(), this.sentryGuardian.level()); + sentry.setPos(this.sentryGuardian.position()); + sentry.setDeltaMovement(0.0, 1.0, 0.0); + sentry.fallDistance = -100.0F; + sentry.setTarget(this.sentryGuardian.getTarget()); + this.sentryGuardian.level().addFreshEntity(sentry); + this.sentryGuardian.level().playSound(this.sentryGuardian, this.sentryGuardian.blockPosition(), GenesisSoundEvents.ENTITY_SENTRY_GUARDIAN_SUMMON.get(), SoundSource.AMBIENT, 2.0F, 1.0F); + } + } + + public static class InactiveGoal extends Goal { + private final SentryGuardian sentryGuardian; + + public InactiveGoal(SentryGuardian sentryGuardian) { + this.sentryGuardian = sentryGuardian; + this.sentryGuardian.setRot(0, 0); + this.setFlags(EnumSet.of(Flag.MOVE, Flag.JUMP)); + } + @Override public boolean canUse() { - LivingEntity target = this.mob.getTarget(); - if (target != null && target.isAlive()) { - return this.mob.level().getDifficulty() != Difficulty.PEACEFUL; - } else { - return false; - } + return !this.sentryGuardian.isAwake(); } - public boolean requiresUpdateEveryTick() { - return true; + @Override + public void start() { + this.sentryGuardian.setDeltaMovement(Vec3.ZERO); + this.sentryGuardian.setPos(this.sentryGuardian.position().x(), this.sentryGuardian.position().y(), this.sentryGuardian.position().z()); } } } \ No newline at end of file diff --git a/src/main/java/com/aetherteam/genesis/entity/monster/dungeon/boss/SliderHostMimic.java b/src/main/java/com/aetherteam/genesis/entity/monster/dungeon/boss/SliderHostMimic.java index 109b7061..143c83b0 100644 --- a/src/main/java/com/aetherteam/genesis/entity/monster/dungeon/boss/SliderHostMimic.java +++ b/src/main/java/com/aetherteam/genesis/entity/monster/dungeon/boss/SliderHostMimic.java @@ -1,22 +1,23 @@ package com.aetherteam.genesis.entity.monster.dungeon.boss; import com.aetherteam.aether.Aether; +import com.aetherteam.aether.AetherConfig; +import com.aetherteam.aether.AetherTags; import com.aetherteam.aether.block.AetherBlocks; -import com.aetherteam.aether.client.AetherSoundEvents; import com.aetherteam.aether.entity.AetherBossMob; +import com.aetherteam.aether.entity.ai.goal.MostDamageTargetGoal; import com.aetherteam.aether.entity.monster.dungeon.boss.BossNameGenerator; import com.aetherteam.aether.event.AetherEventDispatch; import com.aetherteam.aether.network.packet.clientbound.BossInfoPacket; import com.aetherteam.genesis.client.GenesisSoundEvents; -import com.aetherteam.genesis.entity.GenesisEntityTypes; import com.aetherteam.genesis.entity.projectile.HostEyeProjectile; import com.aetherteam.nitrogen.entity.BossRoomTracker; import com.aetherteam.nitrogen.network.PacketRelay; +import net.minecraft.core.BlockPos; import net.minecraft.core.particles.ParticleTypes; import net.minecraft.nbt.CompoundTag; import net.minecraft.network.FriendlyByteBuf; import net.minecraft.network.chat.Component; -import net.minecraft.network.chat.MutableComponent; import net.minecraft.network.syncher.EntityDataAccessor; import net.minecraft.network.syncher.EntityDataSerializers; import net.minecraft.network.syncher.SynchedEntityData; @@ -30,19 +31,19 @@ import net.minecraft.tags.DamageTypeTags; import net.minecraft.util.Mth; import net.minecraft.world.BossEvent; +import net.minecraft.world.Difficulty; import net.minecraft.world.DifficultyInstance; import net.minecraft.world.damagesource.DamageSource; import net.minecraft.world.entity.*; import net.minecraft.world.entity.ai.attributes.AttributeSupplier; import net.minecraft.world.entity.ai.attributes.Attributes; -import net.minecraft.world.entity.ai.goal.AvoidEntityGoal; import net.minecraft.world.entity.ai.goal.Goal; -import net.minecraft.world.entity.ai.goal.WaterAvoidingRandomStrollGoal; -import net.minecraft.world.entity.ai.goal.target.HurtByTargetGoal; import net.minecraft.world.entity.ai.goal.target.NearestAttackableTargetGoal; +import net.minecraft.world.entity.ai.util.DefaultRandomPos; import net.minecraft.world.entity.monster.Enemy; import net.minecraft.world.entity.player.Player; -import net.minecraft.world.item.Items; +import net.minecraft.world.entity.projectile.Projectile; +import net.minecraft.world.level.Explosion; import net.minecraft.world.level.Level; import net.minecraft.world.level.ServerLevelAccessor; import net.minecraft.world.level.block.Block; @@ -50,102 +51,73 @@ import net.minecraft.world.level.block.HorizontalDirectionalBlock; import net.minecraft.world.level.block.state.BlockState; import net.minecraft.world.phys.Vec3; +import net.neoforged.neoforge.common.ToolActions; import net.neoforged.neoforge.entity.IEntityWithComplexSpawn; +import org.apache.commons.lang3.tuple.Pair; import javax.annotation.Nullable; -import java.util.ArrayList; -import java.util.EnumSet; -import java.util.List; -import java.util.Map; +import java.util.*; import java.util.function.Function; public class SliderHostMimic extends PathfinderMob implements AetherBossMob, Enemy, IEntityWithComplexSpawn { public static final EntityDataAccessor DATA_AWAKE_ID = SynchedEntityData.defineId(SliderHostMimic.class, EntityDataSerializers.BOOLEAN); public static final EntityDataAccessor DATA_BOSS_NAME_ID = SynchedEntityData.defineId(SliderHostMimic.class, EntityDataSerializers.COMPONENT); - private static final Music MINIBOSS_MUSIC = new Music(GenesisSoundEvents.MUSIC_MINIBOSS, 0, 0, true); - public static final Map> DUNGEON_BLOCK_CONVERSIONS = Map.ofEntries( + private static final Music SLIDER_HOST_MIMIC_MUSIC = new Music(GenesisSoundEvents.MUSIC_MINIBOSS, 0, 0, true); + public static final Map> DUNGEON_BLOCK_CONVERSIONS = new HashMap<>(Map.ofEntries( Map.entry(AetherBlocks.LOCKED_CARVED_STONE.get(), (blockState) -> AetherBlocks.CARVED_STONE.get().defaultBlockState()), Map.entry(AetherBlocks.LOCKED_SENTRY_STONE.get(), (blockState) -> AetherBlocks.SENTRY_STONE.get().defaultBlockState()), Map.entry(AetherBlocks.BOSS_DOORWAY_CARVED_STONE.get(), (blockState) -> Blocks.AIR.defaultBlockState()), Map.entry(AetherBlocks.TREASURE_DOORWAY_CARVED_STONE.get(), (blockState) -> AetherBlocks.SKYROOT_TRAPDOOR.get().defaultBlockState().setValue(HorizontalDirectionalBlock.FACING, blockState.getValue(HorizontalDirectionalBlock.FACING))) - ); + )); + + /** + * Goal for targeting in groups of entities + */ + private MostDamageTargetGoal mostDamageTargetGoal; - private BossRoomTracker bronzeDungeon; private final ServerBossEvent bossFight; + private BossRoomTracker bronzeDungeon; + private final List eyeProjectiles = new ArrayList<>(); + private int spawnEyeCooldown; private int chatCooldown; - public int sendDelay = 15; - public int sendRespawnDelay = 10; - public int scareTime = 0; - - public List eyes = new ArrayList<>(); public SliderHostMimic(EntityType entityType, Level level) { super(entityType, level); this.bossFight = new ServerBossEvent(this.getBossName(), BossEvent.BossBarColor.BLUE, BossEvent.BossBarOverlay.PROGRESS); - this.bossFight.setVisible(false); + this.setBossFight(false); this.xpReward = XP_REWARD_BOSS; this.setPersistenceRequired(); + this.setMaxUpStep(1.0F); } - @Override - protected void registerGoals() { - this.goalSelector.addGoal(0, new DoNothingGoal(this)); - this.goalSelector.addGoal(2, new AvoidEntityGoal<>(this, Player.class, 3.0F, 1.25F, 2.0F)); - this.goalSelector.addGoal(6, new WaterAvoidingRandomStrollGoal(this, 1.0D)); - this.targetSelector.addGoal(1, new HurtByTargetGoal(this, SliderHostMimic.class)); - this.targetSelector.addGoal(2, new NearestAttackableTargetGoal<>(this, Player.class, 10, true, false, livingEntity -> this.isBossFight())); - } - - public SliderHostMimic self(){ - return this; - } - - /** - * Generates a name for the boss and adjusts its position. - */ @Override public SpawnGroupData finalizeSpawn(ServerLevelAccessor level, DifficultyInstance difficulty, MobSpawnType reason, @Nullable SpawnGroupData spawnData, @Nullable CompoundTag dataTag) { - this.alignSpawnPos(); - SpawnGroupData data = super.finalizeSpawn(level, difficulty, reason, spawnData, dataTag); - this.setBossName(this.generateHostName()); - return data; - } - - public void sendEye() { - while (this.eyes.size() > 4) (this.eyes.remove(0)).setHealth(0); - HostEyeProjectile eye = new HostEyeProjectile(GenesisEntityTypes.HOST_EYE.get(), level()); - this.level().addFreshEntity(eye); - eye.setPos(this.position()); - this.level().playSound(this, this.blockPosition(), AetherSoundEvents.ENTITY_SLIDER_AWAKEN.get(), SoundSource.HOSTILE, 2.5F, 1.0F / (this.random.nextFloat() * 0.2F + 0.9F)); - this.eyes.add(eye); - this.sendDelay = 30; - if (isDeadOrDying()) - killEyes(); - } - - public void killEyes() { - while (this.eyes.size() != 0) - this.eyes.remove(0).discard(); - } - - public MutableComponent generateHostName() { - MutableComponent result = BossNameGenerator.generateBossName(this.getRandom()); - return result.append(Component.translatable("gui.aether_genesis.host.title")); - } - - protected void alignSpawnPos() { + this.setBossName(BossNameGenerator.generateBossName(this.getRandom()).append(Component.translatable("gui.aether_genesis.host.title"))); this.moveTo(Mth.floor(this.getX()), this.getY(), Mth.floor(this.getZ())); + return spawnData; } - public static AttributeSupplier.Builder createHostAttributes() { + public static AttributeSupplier.Builder createMobAttributes() { return Mob.createMobAttributes() - .add(Attributes.MAX_HEALTH, 300) + .add(Attributes.MAX_HEALTH, 400.0) .add(Attributes.MOVEMENT_SPEED, 0.25) .add(Attributes.KNOCKBACK_RESISTANCE, 0.75) .add(Attributes.FOLLOW_RANGE, 64.0); } + @Override + protected void registerGoals() { + this.goalSelector.addGoal(0, new SliderHostMimic.ShootHostEyeGoal(this)); + this.goalSelector.addGoal(1, new SliderHostMimic.HostAvoidPlayerGoal(this)); + this.goalSelector.addGoal(2, new SliderHostMimic.HostRandomMovementGoal(this)); + this.goalSelector.addGoal(3, new SliderHostMimic.InactiveGoal(this)); + + this.mostDamageTargetGoal = new MostDamageTargetGoal(this); + this.targetSelector.addGoal(1, this.mostDamageTargetGoal); + this.targetSelector.addGoal(2, new NearestAttackableTargetGoal<>(this, Player.class, false)); + } + @Override public void defineSynchedData() { super.defineSynchedData(); @@ -159,15 +131,19 @@ public void tick() { if (!this.isAwake() || (this.getTarget() instanceof Player player && (player.isCreative() || player.isSpectator()))) { this.setTarget(null); } - else this.evaporate(); - + this.evaporate(); if (this.getChatCooldown() > 0) { this.chatCooldown--; } + if (this.spawnEyeCooldown > 0) { + this.spawnEyeCooldown--; + } } - @Override - public void checkDespawn() {} + private void evaporate() { + Pair minMax = this.getDefaultBounds(this); + AetherBossMob.super.evaporate(this, minMax.getLeft(), minMax.getRight(), (blockState) -> true); + } @Override public void customServerAiStep() { @@ -176,38 +152,122 @@ public void customServerAiStep() { this.trackDungeon(); } - public boolean hurt(DamageSource source, float damage) { - Entity entity = source.getDirectEntity(); - Entity attacker = source.getEntity(); - if (entity != null && source.is(DamageTypeTags.IS_PROJECTILE)) { - if (!this.level().isClientSide && attacker instanceof Player && ((Player)attacker).getMainHandItem() != Items.AIR.getDefaultInstance()) { - this.chatCooldown = 60; - attacker.sendSystemMessage(Component.translatable("gui.aether_genesis.boss.message.projectile")); + @Override + public boolean hurt(DamageSource source, float amount) { + Optional damageResult = this.canDamageSliderHostMimic(source); + if (source.is(DamageTypeTags.BYPASSES_INVULNERABILITY)) { + super.hurt(source, amount); + if (!this.level().isClientSide() && source.getEntity() instanceof LivingEntity living) { + this.mostDamageTargetGoal.addAggro(living, amount); // AI goal for being hurt. + if (this.spawnEyeCooldown <= 0 && this.getEyeProjectiles().size() == 4) { + this.getEyeProjectiles().remove(0).discard(); + this.spawnEyeCooldown = 200; + } + } + } else if (damageResult.isPresent()) { + if (super.hurt(source, amount) && this.getHealth() > 0) { + if (!this.isBossFight()) { + this.start(); + } + if (!this.level().isClientSide() && source.getEntity() instanceof LivingEntity living) { + this.mostDamageTargetGoal.addAggro(living, amount); // AI goal for being hurt. + if (this.spawnEyeCooldown <= 0 && this.getEyeProjectiles().size() == 4) { + this.getEyeProjectiles().remove(0).discard(); + this.spawnEyeCooldown = 200; + } + } + return true; } - return false; } - if (!this.isBossFight()) { - if (this.getAwakenSound() != null) { - this.playSound(this.getAwakenSound(), 2.5F, 1.0F / (this.random.nextFloat() * 0.2F + 0.9F)); + return false; + } + + private Optional canDamageSliderHostMimic(DamageSource source) { + if (this.level().getDifficulty() != Difficulty.PEACEFUL) { + if (source.getDirectEntity() instanceof LivingEntity attacker) { + if (this.getDungeon() == null || this.getDungeon().isPlayerWithinRoomInterior(attacker)) { // Only allow damage within the boss room. + if (attacker.getMainHandItem().canPerformAction(ToolActions.PICKAXE_DIG) + || attacker.getMainHandItem().is(AetherTags.Items.SLIDER_DAMAGING_ITEMS) + || attacker.getMainHandItem().isCorrectToolForDrops(AetherBlocks.CARVED_STONE.get().defaultBlockState())) { // Check for correct tool. + return Optional.of(attacker); + } else { + return this.sendInvalidToolMessage(attacker); + } + } else { + this.sendTooFarMessage(attacker); + } + } else if (source.getDirectEntity() instanceof Projectile projectile) { + if (projectile.getOwner() instanceof LivingEntity attacker) { + if (this.getDungeon() == null || this.getDungeon().isPlayerWithinRoomInterior(attacker)) { // Only allow damage within the boss room. + if (projectile.getType().is(AetherTags.Entities.SLIDER_DAMAGING_PROJECTILES)) { + return Optional.of(attacker); + } else { + return this.sendInvalidToolMessage(attacker); + } + } else { + return this.sendTooFarMessage(attacker); + } + } } - this.setHealth(this.getMaxHealth()); - this.setAwake(true); - this.setBossFight(true); - if (this.getDungeon() != null) { - this.closeRoom(); + } + return Optional.empty(); + } + + private Optional sendInvalidToolMessage(LivingEntity attacker) { + if (!this.level().isClientSide() && attacker instanceof Player player) { + if (this.getChatCooldown() <= 0) { + if (AetherConfig.COMMON.reposition_slider_message.get()) { + player.displayClientMessage(Component.translatable("gui.aether.slider.message.attack.invalid"), true); // Invalid tool. + } else { + player.sendSystemMessage(Component.translatable("gui.aether.slider.message.attack.invalid")); // Invalid tool. + } + this.setChatCooldown(15); + } + } + return Optional.empty(); + } + + private Optional sendTooFarMessage(LivingEntity attacker) { + if (!this.level().isClientSide() && attacker instanceof Player player) { + if (this.getChatCooldown() <= 0) { + this.displayTooFarMessage(player); // Too far from Slider + this.setChatCooldown(15); } - AetherEventDispatch.onBossFightStart(this, this.getDungeon()); } - return super.hurt(source, damage); + return Optional.empty(); + } + + private void start() { + if (this.getAwakenSound() != null) { + this.playSound(this.getAwakenSound(), 2.5F, 1.0F / (this.getRandom().nextFloat() * 0.2F + 0.9F)); + } + this.setHealth(this.getMaxHealth()); + this.setAwake(true); + this.setBossFight(true); + if (this.getDungeon() != null) { + this.closeRoom(); + } + AetherEventDispatch.onBossFightStart(this, this.getDungeon()); + } + + public void reset() { + this.setDeltaMovement(Vec3.ZERO); + this.setAwake(false); + this.setBossFight(false); + this.setTarget(null); + if (this.getDungeon() != null) { + this.setPos(this.getDungeon().originCoordinates()); + this.openRoom(); + } + AetherEventDispatch.onBossFightStop(this, this.getDungeon()); } @Override public void die(DamageSource source) { this.setDeltaMovement(Vec3.ZERO); - killEyes(); this.explode(); if (this.level() instanceof ServerLevel) { - this.bossFight.setProgress(this.getHealth() / this.getMaxHealth()); + this.bossFight.setProgress(this.getHealth() / this.getMaxHealth()); // Forces an update to the boss health meter. if (this.getDungeon() != null) { this.getDungeon().grantAdvancements(source); this.tearDownRoom(); @@ -216,53 +276,32 @@ public void die(DamageSource source) { super.die(source); } - private void evaporate() { - Player player = this.level().getNearestPlayer(this, 8.5D); - if (this.getTarget() == null) - if (player != null && canAttack(player) && !player.isDeadOrDying() && !player.isCreative() && !player.isSpectator()) { - this.setTarget(player); - } - if (this.getTarget() != null && canAttack(this.getTarget()) && !this.getTarget().isDeadOrDying()) { - if (this.eyes.size() < 4) { - if (this.sendDelay <= 0) - if (!this.level().isClientSide) - sendEye(); - } else if (this.sendRespawnDelay <= 0) { - if (!this.level().isClientSide) { - sendEye(); - this.sendRespawnDelay = 100; - } - } + private void explode() { + for (int i = 0; i < (this.getHealth() <= 0 ? 16 : 48); i++) { + double x = this.position().x() + (double) (this.getRandom().nextFloat() - this.getRandom().nextFloat()) * 1.5; + double y = this.getBoundingBox().minY + 1.75 + (double) (this.getRandom().nextFloat() - this.getRandom().nextFloat()) * 1.5; + double z = this.position().z() + (double) (this.getRandom().nextFloat() - this.getRandom().nextFloat()) * 1.5; + this.level().addParticle(ParticleTypes.POOF, x, y, z, 0.0, 0.0, 0.0); } - if (!this.isAwake()) - killEyes(); - if (this.eyes.size() > 4) - this.eyes.remove(0).discard(); - if (this.scareTime > 0) - this.scareTime--; - if (this.sendDelay > 0) - this.sendDelay--; - if (this.sendRespawnDelay > 0) - this.sendRespawnDelay--; } - private void stop() { - this.setDeltaMovement(Vec3.ZERO); + @Override + public void knockback(double strength, double x, double z) { + if (this.isAwake()) { + super.knockback(strength, x, z); + } } - public void reset() { - this.stop(); - this.setAwake(false); - this.setBossFight(false); - this.setTarget(null); - this.setHealth(this.getMaxHealth()); - if (this.getDungeon() != null) { - this.setPos(this.getDungeon().originCoordinates()); - this.openRoom(); + @Override + public void push(double x, double y, double z) { + if (this.isAwake()) { + super.push(x, y, z); } - AetherEventDispatch.onBossFightStop(this, this.getDungeon()); } + @Override + public void checkDespawn() { } + /** * Called on every block in the boss room when the boss is defeated. * @@ -275,21 +314,13 @@ public BlockState convertBlock(BlockState state) { return DUNGEON_BLOCK_CONVERSIONS.getOrDefault(state.getBlock(), (blockState) -> null).apply(state); } - private void explode() { - for (int i = 0; i < (this.getHealth() <= 0 ? 16 : 48); i++) { - double x = this.position().x() + (double) (this.random.nextFloat() - this.random.nextFloat()) * 1.5; - double y = this.getBoundingBox().minY + 1.75 + (double) (this.random.nextFloat() - this.random.nextFloat()) * 1.5; - double z = this.position().z() + (double) (this.random.nextFloat() - this.random.nextFloat()) * 1.5; - this.level().addParticle(ParticleTypes.POOF, x, y, z, 0.0, 0.0, 0.0); - } - } - @Override public void startSeenByPlayer(ServerPlayer player) { super.startSeenByPlayer(player); PacketRelay.sendToPlayer(new BossInfoPacket.Display(this.bossFight.getId(), this.getId()), player); if (this.getDungeon() == null || this.getDungeon().isPlayerTracked(player)) { this.bossFight.addPlayer(player); + AetherEventDispatch.onBossFightPlayerAdd(this, this.getDungeon(), player); } } @@ -298,12 +329,14 @@ public void stopSeenByPlayer(ServerPlayer player) { super.stopSeenByPlayer(player); PacketRelay.sendToPlayer(new BossInfoPacket.Remove(this.bossFight.getId(), this.getId()), player); this.bossFight.removePlayer(player); + AetherEventDispatch.onBossFightPlayerRemove(this, this.getDungeon(), player); } @Override public void onDungeonPlayerAdded(@Nullable Player player) { if (player instanceof ServerPlayer serverPlayer) { this.bossFight.addPlayer(serverPlayer); + AetherEventDispatch.onBossFightPlayerAdd(this, this.getDungeon(), serverPlayer); } } @@ -311,6 +344,7 @@ public void onDungeonPlayerAdded(@Nullable Player player) { public void onDungeonPlayerRemoved(@Nullable Player player) { if (player instanceof ServerPlayer serverPlayer) { this.bossFight.removePlayer(serverPlayer); + AetherEventDispatch.onBossFightPlayerRemove(this, this.getDungeon(), serverPlayer); } } @@ -333,21 +367,17 @@ public void setBossName(Component component) { this.bossFight.setName(component); } + @Nullable @Override public BossRoomTracker getDungeon() { return this.bronzeDungeon; } @Override - public void setDungeon(BossRoomTracker dungeon) { + public void setDungeon(@Nullable BossRoomTracker dungeon) { this.bronzeDungeon = dungeon; } - @Override - public int getDeathScore() { - return this.deathScore; - } - @Override public boolean isBossFight() { return this.bossFight.isVisible(); @@ -382,29 +412,67 @@ public ResourceLocation getBossBarBackgroundTexture() { @Nullable @Override public Music getBossMusic() { - return MINIBOSS_MUSIC; + return SLIDER_HOST_MIMIC_MUSIC; + } + + public List getEyeProjectiles() { + return this.eyeProjectiles; + } + + /** + * @return The {@link Integer} for the cooldown until another chat message can display. + */ + public int getChatCooldown() { + return this.chatCooldown; + } + + /** + * Sets the cooldown for when another chat message can display. + * + * @param cooldown The {@link Integer} cooldown. + */ + public void setChatCooldown(int cooldown) { + this.chatCooldown = cooldown; + } + + @Override + public int getDeathScore() { + return this.deathScore; + } + + @Override + public void setCustomName(@Nullable Component name) { + super.setCustomName(name); + this.setBossName(name); + } + + protected SoundEvent getScareSound() { + return GenesisSoundEvents.ENTITY_SLIDER_HOST_MIMIC_SCARE.get(); + } + + protected SoundEvent getShootSound() { + return GenesisSoundEvents.ENTITY_SLIDER_HOST_MIMIC_SHOOT.get(); } protected SoundEvent getAwakenSound() { - return AetherSoundEvents.ENTITY_SLIDER_AWAKEN.get(); + return GenesisSoundEvents.ENTITY_SLIDER_HOST_MIMIC_AWAKEN.get(); } @Nullable @Override protected SoundEvent getAmbientSound() { - return random.nextInt(5) == 0 ? GenesisSoundEvents.ENTITY_TRACKING_GOLEM_CREEPY_SEEN.get() : null; + return this.isAwake() ? (random.nextInt(5) == 0 ? GenesisSoundEvents.ENTITY_SLIDER_HOST_MIMIC_SCARE.get() : null) : GenesisSoundEvents.ENTITY_SLIDER_HOST_MIMIC_AMBIENT.get(); } @Override - protected SoundEvent getHurtSound( DamageSource damageSource) { - return AetherSoundEvents.ENTITY_SLIDER_HURT.get(); + protected SoundEvent getHurtSound(DamageSource damageSource) { + return GenesisSoundEvents.ENTITY_SLIDER_HOST_MIMIC_HURT.get(); } @Override protected SoundEvent getDeathSound() { - return AetherSoundEvents.ENTITY_SLIDER_DEATH.get(); + return GenesisSoundEvents.ENTITY_SLIDER_HOST_MIMIC_DEATH.get(); } - @Override public SoundSource getSoundSource() { @@ -412,23 +480,27 @@ public SoundSource getSoundSource() { } @Override - public void setCustomName(@Nullable Component name) { - super.setCustomName(name); - this.setBossName(name); + public boolean canAttack(LivingEntity target) { + return target.canBeSeenAsEnemy(); } @Override - public boolean canAttack(LivingEntity target) { - return target.canBeSeenAsEnemy(); + public boolean ignoreExplosion(Explosion explosion) { + return !this.isAwake(); } @Override public float getYRot() { - return !isAwake() ? 0 : super.getYRot(); + return !this.isAwake() ? 0 : super.getYRot(); + } + + @Override + public EntityDimensions getDimensions(Pose pose) { + return this.isAwake() ? super.getDimensions(pose) : EntityDimensions.fixed(2.0F, 2.0F); } @Override - protected boolean canRide( Entity vehicle) { + protected boolean canRide(Entity vehicle) { return false; } @@ -444,23 +516,37 @@ public boolean isPushable() { @Override public boolean isNoGravity() { - return !isAwake(); + return !this.isAwake(); } @Override public boolean shouldDiscardFriction() { - return !isAwake(); + return !this.isAwake(); + } + @Override + protected boolean isAffectedByFluids() { + return !this.isAwake(); + } + + @Override + public boolean displayFireAnimation() { + return false; } @Override - public void addAdditionalSaveData( CompoundTag tag) { + public boolean isFullyFrozen() { + return false; + } + + @Override + public void addAdditionalSaveData(CompoundTag tag) { super.addAdditionalSaveData(tag); this.addBossSaveData(tag); tag.putBoolean("Awake", this.isAwake()); } @Override - public void readAdditionalSaveData( CompoundTag tag) { + public void readAdditionalSaveData(CompoundTag tag) { super.readAdditionalSaveData(tag); this.readBossSaveData(tag); if (tag.contains("Awake")) { @@ -482,15 +568,216 @@ public void readSpawnData(FriendlyByteBuf additionalData) { this.readBossSaveData(tag); } } - - public int getChatCooldown() { - return this.chatCooldown; + public static class HostAvoidPlayerGoal extends Goal { + private final SliderHostMimic sliderHostMimic; + protected double posX; + protected double posY; + protected double posZ; + + public HostAvoidPlayerGoal(SliderHostMimic sliderHostMimic) { + super(); + this.sliderHostMimic = sliderHostMimic; + this.setFlags(EnumSet.of(Goal.Flag.MOVE)); + } + + @Override + public boolean canUse() { + if (this.sliderHostMimic.isAwake()) { + if (this.sliderHostMimic.getDungeon() != null) { + for (UUID id : this.sliderHostMimic.getDungeon().dungeonPlayers()) { + Player player = this.sliderHostMimic.level().getPlayerByUUID(id); + if (player != null && player.distanceToSqr(this.sliderHostMimic) < 81) { + Vec3 vec3 = this.findRandomPosition(player); + if (vec3 != null) { + if (player.distanceToSqr(vec3.x, vec3.y, vec3.z) < player.distanceToSqr(this.sliderHostMimic)) { + return false; + } else { + this.posX = vec3.x; + this.posY = vec3.y; + this.posZ = vec3.z; + return true; + } + } + } + } + } + } + return false; + } + + protected Vec3 findRandomPosition(LivingEntity entity) { + Vec3 vec3 = DefaultRandomPos.getPosAway(this.sliderHostMimic, 16, 2, entity.position()); + if (vec3 == null || (this.sliderHostMimic.getDungeon() != null && !this.sliderHostMimic.getDungeon().roomBounds().contains(vec3))) { + return null; + } + return vec3; + } + + @Override + public boolean canContinueToUse() { + return !this.sliderHostMimic.getBoundingBox().contains(new Vec3(this.posX, this.posY, this.posZ)) + && this.sliderHostMimic.isAwake() + && !this.sliderHostMimic.getNavigation().isDone() + && !this.sliderHostMimic.getNavigation().isStuck() + && this.sliderHostMimic.getTarget() != null; + } + + @Override + public void start() { + this.sliderHostMimic.playSound(this.sliderHostMimic.getScareSound(), 2.5F, 1.0F / (this.sliderHostMimic.getRandom().nextFloat() * 0.2F + 0.9F)); + this.sliderHostMimic.getNavigation().moveTo(this.posX, this.posY, this.posZ, 1.5); + } + + @Override + public void stop() { + this.sliderHostMimic.getNavigation().stop(); + } + + @Override + public void tick() { + super.tick(); + LivingEntity avoid = null; + if (this.sliderHostMimic.getDungeon() != null) { + for (UUID id : this.sliderHostMimic.getDungeon().dungeonPlayers()) { + Player player = this.sliderHostMimic.level().getPlayerByUUID(id); + if (player != null && player.distanceToSqr(this.sliderHostMimic) < 81) { + avoid = player; + } + } + } else if (this.sliderHostMimic.getTarget() != null && this.sliderHostMimic.getTarget().distanceToSqr(this.sliderHostMimic) < 81) { + avoid = this.sliderHostMimic.getTarget(); + } + if (avoid != null) { + Vec3 vec3 = this.findRandomPosition(avoid); + if (vec3 != null) { + if (!(avoid.distanceToSqr(vec3.x, vec3.y, vec3.z) < avoid.distanceToSqr(this.sliderHostMimic))) { + this.posX = vec3.x; + this.posY = vec3.y; + this.posZ = vec3.z; + this.sliderHostMimic.getNavigation().moveTo(this.posX, this.posY, this.posZ, 1.5); + } + } + } + } + + @Override + public boolean requiresUpdateEveryTick() { + return true; + } } - public static class DoNothingGoal extends Goal { + public static class HostRandomMovementGoal extends Goal { private final SliderHostMimic sliderHostMimic; - public DoNothingGoal(SliderHostMimic sliderHostMimic) { + protected double posX; + protected double posY; + protected double posZ; + + public HostRandomMovementGoal(SliderHostMimic sliderHostMimic) { + super(); + this.sliderHostMimic = sliderHostMimic; + this.setFlags(EnumSet.of(Goal.Flag.MOVE)); + } + + @Override + public boolean canUse() { + if (this.sliderHostMimic.isAwake()) { + if (this.sliderHostMimic.getDeltaMovement().x() == 0 && this.sliderHostMimic.getDeltaMovement().z() == 0) { + Vec3 vec3 = this.findRandomPosition(); + if (vec3 != null) { + this.posX = vec3.x; + this.posY = vec3.y; + this.posZ = vec3.z; + } + return true; + } + } + return false; + } + + protected Vec3 findRandomPosition() { + if (this.sliderHostMimic.getDungeon() != null) { + List positions = BlockPos.betweenClosedStream(this.sliderHostMimic.getDungeon().roomBounds()).map((pos) -> Vec3.atLowerCornerOf(pos.immutable())).toList(); + return positions.get(this.sliderHostMimic.getRandom().nextInt(positions.size())); + } else { + Vec3 vec3 = DefaultRandomPos.getPos(this.sliderHostMimic, 4, 2); + if (vec3 == null || (this.sliderHostMimic.getDungeon() != null && !this.sliderHostMimic.getDungeon().roomBounds().contains(vec3))) { + return null; + } + return vec3; + } + } + + @Override + public boolean canContinueToUse() { + return !this.sliderHostMimic.getBoundingBox().contains(new Vec3(this.posX, this.posY, this.posZ)) + && this.sliderHostMimic.isAwake() + && !this.sliderHostMimic.getNavigation().isDone() + && !this.sliderHostMimic.getNavigation().isStuck() + && this.sliderHostMimic.getTarget() != null; + } + + @Override + public void start() { + this.sliderHostMimic.playSound(this.sliderHostMimic.getScareSound(), 2.5F, 1.0F / (this.sliderHostMimic.getRandom().nextFloat() * 0.2F + 0.9F)); + this.sliderHostMimic.getMoveControl().setWantedPosition(this.posX, this.posY, this.posZ, 1.5); + this.sliderHostMimic.getNavigation().moveTo(this.posX, this.posY, this.posZ, 1.5); + } + + @Override + public void stop() { + this.sliderHostMimic.getNavigation().stop(); + } + } + + public static class ShootHostEyeGoal extends Goal { + private final SliderHostMimic sliderHostMimic; + private int attackTime; + + public ShootHostEyeGoal(SliderHostMimic sliderHostMimic) { + this.sliderHostMimic = sliderHostMimic; + this.setFlags(EnumSet.of(Goal.Flag.MOVE, Goal.Flag.LOOK)); + } + + @Override + public boolean canUse() { + if (this.sliderHostMimic.getEyeProjectiles().size() < 4) { + LivingEntity livingentity = this.sliderHostMimic.getTarget(); + if (livingentity != null && livingentity.isAlive()) { + return this.sliderHostMimic.level().getDifficulty() != Difficulty.PEACEFUL; + } + } + return false; + } + + @Override + public void start() { + this.attackTime = 30; + } + + @Override + public void tick() { + LivingEntity livingentity = this.sliderHostMimic.getTarget(); + if (livingentity != null) { + if (this.sliderHostMimic.eyeProjectiles.size() < 4) { + if (this.attackTime <= 0) { + HostEyeProjectile hostEyeProjectile = new HostEyeProjectile(this.sliderHostMimic.level(), this.sliderHostMimic, this.sliderHostMimic.getDirection()); + this.sliderHostMimic.level().addFreshEntity(hostEyeProjectile); + this.sliderHostMimic.playSound(this.sliderHostMimic.getShootSound(), 2.5F, 1.0F / (this.sliderHostMimic.getRandom().nextFloat() * 0.2F + 0.9F)); + hostEyeProjectile.setPos(this.sliderHostMimic.position().add(0.0F, (this.sliderHostMimic.getBbHeight() / 2.0F) + 0.2F, 0.0F)); + this.sliderHostMimic.eyeProjectiles.add(hostEyeProjectile); + this.attackTime = 30; + } + } + } + --this.attackTime; + } + } + + public static class InactiveGoal extends Goal { + private final SliderHostMimic sliderHostMimic; + + public InactiveGoal(SliderHostMimic sliderHostMimic) { this.sliderHostMimic = sliderHostMimic; this.sliderHostMimic.setRot(0, 0); this.setFlags(EnumSet.of(Flag.MOVE, Flag.JUMP)); @@ -498,15 +785,13 @@ public DoNothingGoal(SliderHostMimic sliderHostMimic) { @Override public boolean canUse() { - return !this.sliderHostMimic.isBossFight(); + return !this.sliderHostMimic.isAwake(); } @Override public void start() { this.sliderHostMimic.setDeltaMovement(Vec3.ZERO); - this.sliderHostMimic.setPos(this.sliderHostMimic.position().x, - this.sliderHostMimic.position().y, - this.sliderHostMimic.position().z); + this.sliderHostMimic.setPos(this.sliderHostMimic.position().x(), this.sliderHostMimic.position().y(), this.sliderHostMimic.position().z()); } } } \ No newline at end of file diff --git a/src/main/java/com/aetherteam/genesis/entity/passive/Zephyroo.java b/src/main/java/com/aetherteam/genesis/entity/passive/Zephyroo.java index 724a67aa..e4c2681c 100644 --- a/src/main/java/com/aetherteam/genesis/entity/passive/Zephyroo.java +++ b/src/main/java/com/aetherteam/genesis/entity/passive/Zephyroo.java @@ -237,7 +237,13 @@ public AgeableMob getBreedOffspring(ServerLevel level, AgeableMob otherParent) { } protected SoundEvent getJumpSound() { - return SoundEvents.RABBIT_JUMP; + return GenesisSoundEvents.ENTITY_ZEPHYROO_JUMP.get(); + } + + @Nullable + @Override + protected SoundEvent getAmbientSound() { + return GenesisSoundEvents.ENTITY_ZEPHYROO_AMBIENT.get(); } @Nullable diff --git a/src/main/java/com/aetherteam/genesis/entity/projectile/CogProjectile.java b/src/main/java/com/aetherteam/genesis/entity/projectile/CogProjectile.java index 119c98f4..a2ef059d 100644 --- a/src/main/java/com/aetherteam/genesis/entity/projectile/CogProjectile.java +++ b/src/main/java/com/aetherteam/genesis/entity/projectile/CogProjectile.java @@ -3,13 +3,13 @@ import com.aetherteam.aether.data.resources.registries.AetherDamageTypes; import com.aetherteam.genesis.client.GenesisSoundEvents; import com.aetherteam.genesis.entity.GenesisEntityTypes; -import net.minecraft.core.BlockPos; +import com.aetherteam.genesis.entity.monster.dungeon.boss.LabyrinthEye; import net.minecraft.nbt.CompoundTag; +import net.minecraft.network.protocol.game.ClientboundAddEntityPacket; import net.minecraft.network.syncher.EntityDataAccessor; import net.minecraft.network.syncher.EntityDataSerializers; import net.minecraft.network.syncher.SynchedEntityData; import net.minecraft.sounds.SoundEvent; -import net.minecraft.sounds.SoundEvents; import net.minecraft.sounds.SoundSource; import net.minecraft.util.Mth; import net.minecraft.world.damagesource.DamageSource; @@ -19,10 +19,6 @@ import net.minecraft.world.entity.projectile.Projectile; import net.minecraft.world.entity.projectile.ProjectileUtil; import net.minecraft.world.level.Level; -import net.minecraft.world.level.block.Blocks; -import net.minecraft.world.level.block.entity.BlockEntity; -import net.minecraft.world.level.block.entity.TheEndGatewayBlockEntity; -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; @@ -31,7 +27,6 @@ public class CogProjectile extends Projectile { public static final EntityDataAccessor SIZE = SynchedEntityData.defineId(CogProjectile.class, EntityDataSerializers.BOOLEAN); - public double xPower; public double yPower; public double zPower; @@ -42,10 +37,23 @@ public CogProjectile(EntityType entityType, Level level this.setNoGravity(true); } - @Override - public void remove(RemovalReason reason) { - this.playSound(GenesisSoundEvents.ENTITY_COG_BREAK.get(), 2.0F, (this.random.nextFloat() - this.random.nextFloat()) * 0.2F + 1.2F); - super.remove(reason); + /** + * @param shooter - The entity that created this projectile + */ + public CogProjectile(Level level, Entity shooter, Boolean large) { + this(GenesisEntityTypes.FLYING_COG.get(), level); + this.setLarge(large); + this.setOwner(shooter); + this.setPos(shooter.getX(), shooter.getY() + 1, shooter.getZ()); + // Randomizes motion on spawn. + float rotation = this.random.nextFloat() * 360; + this.xPower = Mth.sin(rotation) * 0.35; + this.zPower = -Mth.cos(rotation) * 0.35; + this.yPower = Mth.sin(this.random.nextFloat() * 360) * 0.35; + double verticalOffset = 1 - Math.abs(this.yPower); + this.xPower *= verticalOffset; + this.zPower *= verticalOffset; + this.setDeltaMovement(this.xPower, this.yPower, this.zPower); } @Override @@ -53,14 +61,6 @@ protected void defineSynchedData() { this.entityData.define(SIZE, false); } - public boolean isLarge() { - return this.entityData.get(SIZE); - } - - public void setLarge(boolean large) { - this.entityData.set(SIZE, large); - } - @Override public void tick() { super.tick(); @@ -68,70 +68,53 @@ public void tick() { ++this.ticksInAir; } if (this.ticksInAir > this.getLifeSpan()) { - this.discard(); + if (!this.level().isClientSide()) { + this.discard(); + } } HitResult result = ProjectileUtil.getHitResultOnMoveVector(this, this::canHitEntity); boolean flag = false; - if (result.getType() == HitResult.Type.BLOCK) { - BlockPos blockPos = ((BlockHitResult) result).getBlockPos(); - BlockState blockState = this.level().getBlockState(blockPos); - if (blockState.is(Blocks.NETHER_PORTAL)) { - this.handleInsidePortal(blockPos); - flag = true; - } else if (blockState.is(Blocks.END_GATEWAY)) { - BlockEntity blockEntity = this.level().getBlockEntity(blockPos); - if (blockEntity instanceof TheEndGatewayBlockEntity endGatewayBlockEntity && TheEndGatewayBlockEntity.canEntityTeleport(this)) { - TheEndGatewayBlockEntity.teleportEntity(this.level(), blockPos, blockState, this, endGatewayBlockEntity); - } - flag = true; - } - } if (result.getType() != HitResult.Type.MISS && !flag && !EventHooks.onProjectileImpact(this, result)) { this.onHit(result); } this.checkInsideBlocks(); this.tickMovement(); - if(this.getOwner() != null && !this.getOwner().isAlive()) - this.discard(); + } + + @Override + public void remove(RemovalReason reason) { + this.playSound(this.getImpactExplosionSoundEvent(), 2.0F, (this.random.nextFloat() - this.random.nextFloat()) * 0.2F + 1.2F); + super.remove(reason); } protected void tickMovement() { + if (!this.level().isClientSide()) { + if (this.getOwner() == null || !this.getOwner().isAlive() || (this.getOwner() instanceof LabyrinthEye labyrinthEye && labyrinthEye.getDungeon() != null && labyrinthEye.getDungeon().dungeonPlayers().isEmpty())) { + if (this.getImpactExplosionSoundEvent() != null) { + this.playSound(this.getImpactExplosionSoundEvent(), 1.0F, 1.0F); + } + this.discard(); + } + } Vec3 vector3d = this.getDeltaMovement(); - double d2 = this.getX() + vector3d.x; - double d0 = this.getY() + vector3d.y; - double d1 = this.getZ() + vector3d.z; + double d2 = this.getX() + vector3d.x(); + double d0 = this.getY() + vector3d.y(); + double d1 = this.getZ() + vector3d.z(); this.updateRotation(); this.setPos(d2, d0, d1); } - public int getLifeSpan() { - return 500; - } - - /** - * @param shooter - The entity that created this projectile - */ - public CogProjectile(Level level, Entity shooter, Boolean large) { - this(GenesisEntityTypes.FLYING_COG.get(), level); - this.setLarge(large); - this.setOwner(shooter); - this.setPos(shooter.getX(), shooter.getY() + 1, shooter.getZ()); - float rotation = this.random.nextFloat() * 360; - this.xPower = Mth.sin(rotation) * 0.5; - this.zPower = -Mth.cos(rotation) * 0.5; - this.yPower = Mth.sin(this.random.nextFloat() * 360) * 0.45; - double verticalOffset = 1 - Math.abs(this.yPower); - this.xPower *= verticalOffset; - this.zPower *= verticalOffset; - this.setDeltaMovement(this.xPower, this.yPower, this.zPower); - } - @Override protected void onHitEntity(EntityHitResult result) { Entity entity = result.getEntity(); if (entity instanceof LivingEntity livingEntity && livingEntity != this.getOwner()) { - if (livingEntity.hurt(AetherDamageTypes.indirectEntityDamageSource(this.level(), AetherDamageTypes.FLOATING_BLOCK, this, this.getOwner()), 5.0F + random.nextInt(2))) { - this.level().playSound(null, this.getX(), this.getY(), this.getZ(), this.getImpactExplosionSoundEvent(), SoundSource.HOSTILE, 2.0F, this.random.nextFloat() - this.random.nextFloat() * 0.2F + 1.2F); + if (livingEntity.hurt(AetherDamageTypes.indirectEntityDamageSource(this.level(), AetherDamageTypes.FLOATING_BLOCK, this, this.getOwner()), 5.0F + this.random.nextInt(2))) { + if (this.getImpactExplosionSoundEvent() != null) { + this.level().playSound(null, this.getX(), this.getY(), this.getZ(), this.getImpactExplosionSoundEvent(), SoundSource.HOSTILE, 2.0F, this.random.nextFloat() - this.random.nextFloat() * 0.2F + 1.2F); + } + if (!this.level().isClientSide()) { + this.discard(); + } } } } @@ -147,10 +130,6 @@ protected void onHitBlock(BlockHitResult result) { this.setDeltaMovement(this.xPower, this.yPower, this.zPower); } - protected SoundEvent getImpactExplosionSoundEvent() { - return SoundEvents.ARMOR_STAND_BREAK; - } - @Override public boolean hurt(DamageSource source, float amount) { if (this.isInvulnerableTo(source)) { @@ -159,12 +138,12 @@ public boolean hurt(DamageSource source, float amount) { this.markHurt(); Entity entity = source.getEntity(); if (entity != null) { - if (!this.level().isClientSide) { + if (!this.level().isClientSide()) { Vec3 vec3 = entity.getLookAngle(); this.setDeltaMovement(vec3); - this.xPower = vec3.x * 0.25; - this.yPower = vec3.y * 0.15; - this.zPower = vec3.z * 0.25; + this.xPower = vec3.x() * 0.25; + this.yPower = vec3.y() * 0.15; + this.zPower = vec3.z() * 0.25; } return true; } else { @@ -173,6 +152,22 @@ public boolean hurt(DamageSource source, float amount) { } } + public boolean isLarge() { + return this.entityData.get(SIZE); + } + + public void setLarge(boolean large) { + this.entityData.set(SIZE, large); + } + + protected SoundEvent getImpactExplosionSoundEvent() { + return GenesisSoundEvents.ENTITY_COG_BREAK.get(); + } + + public int getLifeSpan() { + return 500; + } + @Override public boolean isPickable() { return true; @@ -185,6 +180,7 @@ public void addAdditionalSaveData(CompoundTag tag) { tag.putDouble("XSpeed", this.xPower); tag.putDouble("YSpeed", this.yPower); tag.putDouble("ZSpeed", this.zPower); + tag.putBoolean("Large", this.isLarge()); } @Override @@ -196,5 +192,17 @@ public void readAdditionalSaveData(CompoundTag tag) { this.xPower = tag.getDouble("XSpeed"); this.yPower = tag.getDouble("YSpeed"); this.zPower = tag.getDouble("ZSpeed"); + if (tag.contains("Large")) { + this.setLarge(tag.getBoolean("Large")); + } + } + + @Override + public void recreateFromPacket(ClientboundAddEntityPacket packet) { + super.recreateFromPacket(packet); + double d0 = packet.getXa(); + double d1 = packet.getYa(); + double d2 = packet.getZa(); + this.setDeltaMovement(d0, d1, d2); } } diff --git a/src/main/java/com/aetherteam/genesis/entity/projectile/HostEyeProjectile.java b/src/main/java/com/aetherteam/genesis/entity/projectile/HostEyeProjectile.java index ae47c953..14babdc2 100644 --- a/src/main/java/com/aetherteam/genesis/entity/projectile/HostEyeProjectile.java +++ b/src/main/java/com/aetherteam/genesis/entity/projectile/HostEyeProjectile.java @@ -1,267 +1,297 @@ package com.aetherteam.genesis.entity.projectile; -import com.aetherteam.aether.client.AetherSoundEvents; -import com.aetherteam.aether.entity.monster.dungeon.Sentry; +import com.aetherteam.genesis.client.GenesisSoundEvents; import com.aetherteam.genesis.entity.GenesisEntityTypes; -import com.aetherteam.genesis.entity.monster.dungeon.BattleSentry; -import com.aetherteam.genesis.entity.monster.dungeon.TrackingGolem; -import com.aetherteam.genesis.entity.monster.dungeon.boss.SentryGuardian; import com.aetherteam.genesis.entity.monster.dungeon.boss.SliderHostMimic; -import net.minecraft.network.syncher.EntityDataAccessor; -import net.minecraft.network.syncher.EntityDataSerializers; -import net.minecraft.network.syncher.SynchedEntityData; -import net.minecraft.sounds.SoundEvents; +import net.minecraft.core.Direction; +import net.minecraft.nbt.CompoundTag; +import net.minecraft.network.protocol.game.ClientboundAddEntityPacket; import net.minecraft.sounds.SoundSource; -import net.minecraft.tags.DamageTypeTags; +import net.minecraft.world.Difficulty; import net.minecraft.world.damagesource.DamageSource; -import net.minecraft.world.entity.Entity; -import net.minecraft.world.entity.EntityType; -import net.minecraft.world.entity.LivingEntity; -import net.minecraft.world.entity.PathfinderMob; -import net.minecraft.world.entity.player.Player; -import net.minecraft.world.level.Explosion; +import net.minecraft.world.entity.*; +import net.minecraft.world.entity.projectile.Projectile; +import net.minecraft.world.entity.projectile.ProjectileUtil; import net.minecraft.world.level.Level; +import net.minecraft.world.phys.BlockHitResult; +import net.minecraft.world.phys.HitResult; import net.minecraft.world.phys.Vec3; +import net.neoforged.neoforge.event.EventHooks; -public class HostEyeProjectile extends PathfinderMob { - public static final EntityDataAccessor MOVEMENT = SynchedEntityData.defineId(HostEyeProjectile.class, EntityDataSerializers.BOOLEAN); - public static final EntityDataAccessor TIMER = SynchedEntityData.defineId(HostEyeProjectile.class, EntityDataSerializers.INT); - public static final EntityDataAccessor DIRECTION = SynchedEntityData.defineId(HostEyeProjectile.class, EntityDataSerializers.INT); - public static final EntityDataAccessor SPEEDY = SynchedEntityData.defineId(HostEyeProjectile.class, EntityDataSerializers.FLOAT); - - private final SliderHostMimic host; - private boolean movement; - private int timer; - private int direction; - public float speedy; - public float harvey; - - public HostEyeProjectile(EntityType pEntityType, Level pLevel) { - super(pEntityType, pLevel); - host = new SliderHostMimic(GenesisEntityTypes.SLIDER_HOST_MIMIC.get(), this.level()); - } +import javax.annotation.Nullable; +import java.util.List; +public class HostEyeProjectile extends Projectile { + private SliderHostMimic projectileOwner; + private Vec3 targetPoint; + private float velocity; + private Direction moveDirection = null; + private int moveDelay = this.calculateMoveDelay(); + private int lerpSteps; + private double lerpX; + private double lerpY; + private double lerpZ; + private Vec3 targetDeltaMovement = Vec3.ZERO; - protected float getJumpPower() { - return 0.0F; + public HostEyeProjectile(EntityType entityType, Level level) { + super(entityType, level); + this.setNoGravity(true); } - @Override - public boolean isInvulnerableTo(DamageSource source) { - return this.isRemoved() || !source.is(DamageTypeTags.BYPASSES_INVULNERABILITY); - } - - protected void dealDamage(LivingEntity pLivingEntity) { - if (this.isAlive()) { - if (this.hasLineOfSight(pLivingEntity) && pLivingEntity.hurt(this.damageSources().mobAttack(this), 4)) { - this.playSound(AetherSoundEvents.ENTITY_SLIDER_COLLIDE.get(), 1.0F, (this.random.nextFloat() - this.random.nextFloat()) * 0.2F + 1.0F); - this.doEnchantDamageEffects(this, pLivingEntity); - } - } + public HostEyeProjectile(Level level, SliderHostMimic owner, Direction moveDirection) { + this(GenesisEntityTypes.HOST_EYE.get(), level); + this.projectileOwner = owner; + this.moveDirection = moveDirection; + this.setOwner(owner); } - public void playerTouch(Player pEntity) { - this.dealDamage(pEntity); - } + @Override + protected void defineSynchedData() { } + @Override public void tick() { super.tick(); - this.jumping = false; - this.setRot(0, 0); - if (this.getTarget() != null && this.getTarget() instanceof LivingEntity) { - LivingEntity e1 = this.getTarget(); - if (e1.getHealth() <= 0.0F || !canAttack(this.getTarget())) { - this.setTarget(null); - stop(); - this.timer = 0; - return; + if (this.level().isClientSide()) { + if (this.lerpSteps > 0) { + this.lerpPositionAndRotationStep(this.lerpSteps, this.lerpX, this.lerpY, this.lerpZ, 0.0, 0.0); + --this.lerpSteps; + } else { + this.reapplyPosition(); } } else { - if (this.getTarget() != null && this.getTarget().isDeadOrDying()) { - this.setTarget(null); - stop(); - this.timer = 0; - return; - } - if (this.getTarget() == null) { - this.setTarget(level().getNearestPlayer(this, -1.0)); - if (this.getTarget() == null) { - this.setTarget(null); - stop(); - this.timer = 0; - if (!this.level().isClientSide) - discard(); - return; - } - } - } - if (this.host == null || this.host.isDeadOrDying()) { - this.setTarget(null); - stop(); - this.timer = 0; - if (!this.level().isClientSide) - discard(); - return; - } - if (!this.host.canAttack(this.getTarget())) { - this.setTarget(null); - stop(); - this.timer = 0; - if (!this.level().isClientSide) - discard(); - return; - } - this.fallDistance = 0.0F; - if (this.movement) { - if (this.isPushable()) { - this.level().playLocalSound(this.position().x, this.position().y, this.position().z, SoundEvents.GENERIC_EXPLODE, SoundSource.AMBIENT, 3.0F, (0.625F + (this.level().random.nextFloat() - this.level().random.nextFloat()) * 0.2F) * 0.7F, false); - this.level().playSound(this, this.blockPosition(), AetherSoundEvents.ENTITY_SLIDER_COLLIDE.get(), SoundSource.AMBIENT, 2.5F, 1.0F / (this.random.nextFloat() * 0.2F + 0.9F)); - stop(); - } else { - if (this.speedy < 2.0F) - this.speedy += 0.035F; - this.setDeltaMovement(0, 0, 0); - if (this.direction == 0) { - this.addDeltaMovement(new Vec3(0, this.speedy, 0)); - if (this.getBoundingBox().minY > this.getTarget().getBoundingBox().minY + 0.35D) { - stop(); - this.timer = 8; - } - } else if (this.direction == 1) { - this.addDeltaMovement(new Vec3(0, this.getDeltaMovement().y -this.speedy, 0)); - if (this.getBoundingBox().minY < this.getTarget().getBoundingBox().minY - 0.25D) { - stop(); - this.timer = 8; - } - } else if (this.direction == 2) { - this.addDeltaMovement(new Vec3(this.speedy, 0, 0)); - if (this.position().x > this.getTarget().position().x + 0.125D) { - stop(); - this.timer = 8; - } - } else if (this.direction == 3) { - this.addDeltaMovement(new Vec3(this.getDeltaMovement().x -this.speedy, 0, 0)); - if (this.position().x < this.getTarget().position().x - 0.125D) { - stop(); - this.timer = 8; - } - } else if (this.direction == 4) { - this.addDeltaMovement(new Vec3(0, 0, this.speedy)); - if (this.position().z > this.getTarget().position().z + 0.125D) { - stop(); - this.timer = 8; - } - } else if (this.direction == 5) { - this.addDeltaMovement(new Vec3(0, 0, this.getDeltaMovement().z -this.speedy)); - if (this.position().z < this.getTarget().position().z - 0.125D) { - stop(); - this.timer = 8; + if (this.moveDelay <= 0) { + this.targetPoint = this.findTargetPoint(); + if (this.targetPoint != null) { + Direction moveDir = this.getMoveDirection(this.targetPoint); + if (!(this.axisDistance(this.targetPoint, this.position(), moveDir) <= 0.0)) { + if (this.velocity < this.getMaxVelocity()) { + this.velocity = Math.min(this.getMaxVelocity(), this.velocity + this.getVelocityIncrease()); + } + this.setDeltaMovement((float) moveDir.getStepX() * this.velocity, (float) moveDir.getStepY() * this.velocity, (float) moveDir.getStepZ() * this.velocity); + this.hasImpulse = true; + } else { + this.stop(); } } - } - } else { - this.setDeltaMovement(this.getDeltaMovement().x, 0, this.getDeltaMovement().z); - if (this.timer > 0) { - this.timer--; - this.setDeltaMovement(0, 0, 0); } else { - double a = Math.abs(this.position().x - this.getTarget().position().x); - double b = Math.abs(this.getBoundingBox().minY - this.getTarget().getBoundingBox().minY); - double c = Math.abs(this.position().z - this.getTarget().position().z); - if (a > c) { - this.direction = 2; - if (this.position().x > this.getTarget().position().x) - this.direction = 3; - } else { - this.direction = 4; - if (this.position().z > this.getTarget().position().z) - this.direction = 5; - } - if ((b > a && b > c) || (b > 0.25D && this.random.nextInt(5) == 0)) { - this.direction = 0; - if (this.position().y > this.getTarget().position().y) - this.direction = 1; + --this.moveDelay; + } + if (this.moveDirection == null) { + this.setDeltaMovement(this.getDeltaMovement().multiply(0, 0, 0)); + } + + HitResult hitresult = ProjectileUtil.getHitResultOnMoveVector(this, this::canHitEntity); + if (hitresult.getType() != HitResult.Type.MISS && !EventHooks.onProjectileImpact(this, hitresult)) { + this.onHit(hitresult); + } + + List entities = this.level().getEntities(this, this.getBoundingBox()); + for (Entity target : entities) { + Mob owner = this.projectileOwner; + if (target instanceof LivingEntity living && living.hurt(this.damageSources().mobAttack(owner), 4)) { + this.playSound(GenesisSoundEvents.ENTITY_HOST_EYE_COLLIDE.get(), 1.0F, (this.random.nextFloat() - this.random.nextFloat()) * 0.2F + 1.0F); + living.knockback(1.0, this.getX() - living.getX(), this.getZ() - living.getZ()); } - this.movement = true; } + + this.checkInsideBlocks(); + this.move(MoverType.SELF, this.getDeltaMovement()); } - if (this.harvey > 0.01F) - this.harvey *= 0.8F; } - public void push(Entity entity) { - if (this.movement) { - if (entity instanceof Sentry || entity instanceof TrackingGolem || entity instanceof SliderHostMimic || entity instanceof SentryGuardian || entity instanceof BattleSentry) - return; - boolean flag = entity.hurt(this.damageSources().thrown(this, this.host), 6.0F); - if (flag && entity instanceof LivingEntity) { - this.level().playSound(this, this.blockPosition(), AetherSoundEvents.ENTITY_SLIDER_COLLIDE.get(), SoundSource.AMBIENT, 2.5F, 1.0F / (this.random.nextFloat() * 0.2F + 0.9F)); - LivingEntity ek = (LivingEntity)entity; - ek.setDeltaMovement(ek.getDeltaMovement().x + 0.35D,ek.getDeltaMovement().y + 2.0D,ek.getDeltaMovement().z + 2.0D); - stop(); + @Nullable + public Vec3 findTargetPoint() { + Vec3 pos = this.targetPoint; + if (pos != null) { + return pos; + } else { + if (this.projectileOwner != null) { + LivingEntity target = this.projectileOwner.getTarget(); + return target == null ? null : target.getEyePosition(); } + return null; + } + } + + private Direction getMoveDirection(Vec3 targetPoint) { + Direction moveDir = this.moveDirection; + if (moveDir == null) { + double x = targetPoint.x - this.getX(); + double y = targetPoint.y - this.getBoundingBox().minY; + double z = targetPoint.z - this.getZ(); + moveDir = this.calculateDirection(x, y, z); + this.moveDirection = moveDir; + } + + return moveDir; + } + + public Direction calculateDirection(double x, double y, double z) { + double absX = Math.abs(x); + double absY = Math.abs(y); + double absZ = Math.abs(z); + if (absY > absX && absY > absZ) { + return y > 0.0 ? Direction.UP : Direction.DOWN; + } else if (absX > absZ) { + return x > 0.0 ? Direction.EAST : Direction.WEST; + } else { + return z > 0.0 ? Direction.SOUTH : Direction.NORTH; } } + private double axisDistance(Vec3 target, Vec3 start, Direction direction) { + double x = target.x() - start.x(); + double y = target.y() - start.y(); + double z = target.z() - start.z(); + return x * (double) direction.getStepX() + y * (double) direction.getStepY() + z * (double) direction.getStepZ(); + } + + public int calculateMoveDelay() { + return 2 + this.random.nextInt(14); + } + + public float getVelocityIncrease() { + return 0.035F - 400.0F / 30000.0F; + } + + public float getMaxVelocity() { + return 5.0F; + } + @Override - public void defineSynchedData() { - super.defineSynchedData(); - this.entityData.define(MOVEMENT, movement); - this.entityData.define(TIMER, timer); - this.entityData.define(DIRECTION, direction); - this.entityData.define(SPEEDY, speedy); + public void lerpTo(double x, double y, double z, float yRot, float xRot, int steps) { + this.lerpX = x; + this.lerpY = y; + this.lerpZ = z; + this.lerpSteps = steps + 2; + this.setDeltaMovement(this.targetDeltaMovement); } - public void stop() { - this.movement = false; - this.timer = 12; - this.direction = 0; - this.speedy = 0.0F; - this.setDeltaMovement(0, 0, 0); + @Override + public double lerpTargetX() { + return this.lerpSteps > 0 ? this.lerpX : this.getX(); } @Override - public void knockback(double pStrength, double pX, double pZ) { + public double lerpTargetY() { + return this.lerpSteps > 0 ? this.lerpY : this.getY(); + } + @Override + public double lerpTargetZ() { + return this.lerpSteps > 0 ? this.lerpZ : this.getZ(); } @Override - public void push(double x, double y, double z) { + public void lerpMotion(double x, double y, double z) { + this.targetDeltaMovement = new Vec3(x, y, z); + this.setDeltaMovement(this.targetDeltaMovement); + } + @Override + public void checkDespawn() { + if (this.level().getDifficulty() == Difficulty.PEACEFUL + || this.projectileOwner == null + || !this.projectileOwner.isAlive() + || !this.projectileOwner.isAwake()) { + if (this.projectileOwner != null) { + this.projectileOwner.getEyeProjectiles().removeIf((projectile) -> projectile.getId() == this.getId()); + } + this.discard(); + } } @Override - public boolean ignoreExplosion(Explosion explosion) { - return true; + protected boolean canHitEntity(Entity entity) { + return super.canHitEntity(entity) && !entity.equals(this.projectileOwner); } @Override - public float getYRot() { - return 0; + protected void onHitBlock(BlockHitResult pResult) { + super.onHitBlock(pResult); + this.stop(); + } + + private void stop() { + this.moveDirection = null; + this.moveDelay = this.calculateMoveDelay(); + this.targetPoint = null; + this.velocity = 0.0F; } @Override - protected boolean canRide( Entity vehicle) { + public boolean hurt(DamageSource source, float amount) { return false; } @Override - public boolean canBeCollidedWith() { - return true; + public SoundSource getSoundSource() { + return SoundSource.HOSTILE; } @Override - public boolean isPushable() { + public boolean isOnFire() { return false; } @Override - public boolean isNoGravity() { + public boolean isPickable() { return true; } @Override - public boolean shouldDiscardFriction() { - return true; + protected void addAdditionalSaveData(CompoundTag tag) { + super.addAdditionalSaveData(tag); + if (this.projectileOwner != null) { + tag.putInt("ProjectileOwner", this.projectileOwner.getId()); + } + if (this.targetPoint != null) { + tag.putDouble("xTarget", this.targetPoint.x()); + tag.putDouble("yTarget", this.targetPoint.y()); + tag.putDouble("zTarget", this.targetPoint.z()); + } + tag.putFloat("Velocity", this.velocity); + if (this.moveDirection != null) { + tag.putInt("Direction", this.moveDirection.get3DDataValue()); + } + tag.putInt("MoveDelay", this.moveDelay); + } + + @Override + protected void readAdditionalSaveData(CompoundTag tag) { + super.readAdditionalSaveData(tag); + if (tag.contains("ProjectileOwner")) { + if (this.level().getEntity(tag.getInt("ProjectileOwner")) instanceof SliderHostMimic mob) { + this.projectileOwner = mob; + } + } + double targetX = 0.0; + double targetY = 0.0; + double targetZ = 0.0; + if (tag.contains("xTarget")) { + targetX = tag.getDouble("xTarget"); + } + if (tag.contains("yTarget")) { + targetY = tag.getDouble("yTarget"); + } + if (tag.contains("zTarget")) { + targetZ = tag.getDouble("zTarget"); + } + this.targetPoint = new Vec3(targetX, targetY, targetZ); + if (tag.contains("Direction")) { + this.moveDirection = Direction.from3DDataValue(tag.getInt("Direction")); + } + if (tag.contains("MoveDelay")) { + this.moveDelay = tag.getInt("MoveDelay"); + } + } + + @Override + public void recreateFromPacket(ClientboundAddEntityPacket packet) { + super.recreateFromPacket(packet); + double d0 = packet.getXa(); + double d1 = packet.getYa(); + double d2 = packet.getZa(); + this.setDeltaMovement(d0, d1, d2); } } \ No newline at end of file diff --git a/src/main/resources/assets/aether_genesis/textures/entity/mobs/slider_host_mimic/slider_host_mimic_critical.png b/src/main/resources/assets/aether_genesis/textures/entity/mobs/slider_host_mimic/slider_host_mimic.png similarity index 100% rename from src/main/resources/assets/aether_genesis/textures/entity/mobs/slider_host_mimic/slider_host_mimic_critical.png rename to src/main/resources/assets/aether_genesis/textures/entity/mobs/slider_host_mimic/slider_host_mimic.png diff --git a/src/main/resources/assets/aether_genesis/textures/entity/mobs/slider_host_mimic/slider_host_mimic_critical_glow.png b/src/main/resources/assets/aether_genesis/textures/entity/mobs/slider_host_mimic/slider_host_mimic_glow.png similarity index 100% rename from src/main/resources/assets/aether_genesis/textures/entity/mobs/slider_host_mimic/slider_host_mimic_critical_glow.png rename to src/main/resources/assets/aether_genesis/textures/entity/mobs/slider_host_mimic/slider_host_mimic_glow.png