From 8f853da998119d85f7a9359831d4df0ca63e0172 Mon Sep 17 00:00:00 2001 From: Potato <77798144+ThePlasticPotato@users.noreply.github.com> Date: Fri, 27 Dec 2024 18:07:22 -0500 Subject: [PATCH] Improved Dragging & Entity Interactions on Ships [Pathfinding, POIs, Raids, etc.] (#1027) - Mobs & Players are now dragged perfectly, even at ludicrous speed - Mob spawning on ships has been fixed - Mob pathfinding on ships has been significantly improved - Mob rotation on ships has been fixed - POI finding on ships has been fixed (Villager stations, portals, bee hives, etc) - Bee flower pathing on ships has been fixed. - Villages can now be on ships and are tracked correctly. - Raids can now target ships, and spawn around them. - Zombie Sieges can now target ships, and spawn around them. - Player targeting of blocks now works more reliably on ships while at high speeds or rotations. - Players can now hold open GUIs while on ships at high speeds or rotations. --- common/build.gradle | 2 +- .../mod/mixin/client/MixinMinecraft.java | 14 +- .../client/renderer/MixinEntityRenderer.java | 48 +++++++ .../client/renderer/MixinGameRenderer.java | 6 +- .../mod/mixin/entity/MixinEntity.java | 87 ++++++++++++ .../ai/goal/JitteredLinearRetryAccessor.java | 14 ++ .../feature/ai/goal/MixinMoveToBlockGoal.java | 25 ++++ .../ai/goal/MixinMoveToTargetSink.java | 20 +++ .../ai/goal/MixinValidateNearbyPoi.java | 21 +++ .../mixin/feature/ai/goal/bees/MixinBee.java | 26 ++++ .../ai/goal/bees/MixinEnterHiveGoal.java | 24 ++++ .../ai/goal/bees/MixinGrowCropGoal.java | 30 +++++ .../ai/goal/bees/MixinLocateHiveGoal.java | 28 ++++ .../ai/goal/bees/MixinPollinateGoal.java | 49 +++++++ .../MixinAssignProfessionFromJobSite.java | 23 ++++ .../villagers/MixinGoToClosestVillage.java | 37 ++++++ .../MixinSetClosestHomeAsWalkTarget.java | 21 +++ .../ai/goal/villagers/MixinWorkAtPoi.java | 29 ++++ .../ai/node_evaluator/MixinPathFinder.java | 40 ++++++ .../node_evaluator/MixinPathNavigation.java | 27 ++++ .../SwimNodeEvaluatorMixin.java | 2 +- .../WalkNodeEvaluatorMixin.java | 2 +- .../MixinAirAndWaterRandomPos.java | 47 +++++++ .../MixinDefaultRandomPos.java | 83 ++++++++++++ .../path_retargeting/MixinLandRandomPos.java | 87 ++++++++++++ .../feature/entity_collision/MixinEntity.java | 69 ++++++++++ .../entity_collision/MixinLivingEntity.java | 47 +++++++ .../MixinLocalPlayer.java | 99 ++++++++++++++ .../MixinServerEntity.java | 91 +++++++++++++ .../mod/mixin/feature/poi/FEATURE.md | 1 + .../mixin/feature/poi/MixinPOIManager.java | 85 ++++++++++++ .../MixinScreenHandler.java | 6 +- .../mixin/feature/wave_spawning/FEATURE.md | 1 + .../feature/wave_spawning/MixinRaid.java | 51 +++++++ .../feature/wave_spawning/MixinRaids.java | 31 +++++ .../wave_spawning/MixinVillageSiege.java | 36 +++++ .../MixinReachEntityAttributes.java | 7 +- .../mixin/server/MixinMinecraftServer.java | 5 +- .../MixinServerGamePacketListenerImpl.java | 21 +++ .../mod/mixin/server/world/MixinChunkMap.java | 15 +++ .../mixin/server/world/MixinServerLevel.java | 27 ++++ .../mod/mixin/world/entity/MixinPlayer.java | 27 ++++ .../mixinducks/world/entity/PlayerDuck.java | 6 + .../valkyrienskies/mod/common/PlayerUtil.kt | 4 +- .../valkyrienskies/mod/common/VSGameUtils.kt | 14 +- .../mod/common/config/VSGameConfig.kt | 2 +- .../networking/PacketEntityShipMotion.kt | 20 +++ .../networking/PacketMobShipRotation.kt | 8 ++ .../networking/PacketPlayerShipMotion.kt | 9 ++ .../mod/common/networking/VSGamePackets.kt | 124 ++++++++++++++++++ .../mod/common/util/EntityDragger.kt | 112 +++++++++++++--- .../common/util/EntityDraggingInformation.kt | 31 ++++- .../mod/common/util/EntityLerper.kt | 110 ++++++++++++++++ .../common/util/EntityShipCollisionUtils.kt | 3 + .../mod/common/world/POIChunkSearcher.kt | 48 +++++++ .../valkyrienskies-common.accesswidener | 8 ++ .../valkyrienskies-common.mixins.json | 27 +++- .../forge_interact/MixinIForgePlayer.java | 14 +- 58 files changed, 1915 insertions(+), 36 deletions(-) create mode 100644 common/src/main/java/org/valkyrienskies/mod/mixin/client/renderer/MixinEntityRenderer.java create mode 100644 common/src/main/java/org/valkyrienskies/mod/mixin/feature/ai/goal/JitteredLinearRetryAccessor.java create mode 100644 common/src/main/java/org/valkyrienskies/mod/mixin/feature/ai/goal/MixinMoveToBlockGoal.java create mode 100644 common/src/main/java/org/valkyrienskies/mod/mixin/feature/ai/goal/MixinMoveToTargetSink.java create mode 100644 common/src/main/java/org/valkyrienskies/mod/mixin/feature/ai/goal/MixinValidateNearbyPoi.java create mode 100644 common/src/main/java/org/valkyrienskies/mod/mixin/feature/ai/goal/bees/MixinBee.java create mode 100644 common/src/main/java/org/valkyrienskies/mod/mixin/feature/ai/goal/bees/MixinEnterHiveGoal.java create mode 100644 common/src/main/java/org/valkyrienskies/mod/mixin/feature/ai/goal/bees/MixinGrowCropGoal.java create mode 100644 common/src/main/java/org/valkyrienskies/mod/mixin/feature/ai/goal/bees/MixinLocateHiveGoal.java create mode 100644 common/src/main/java/org/valkyrienskies/mod/mixin/feature/ai/goal/bees/MixinPollinateGoal.java create mode 100644 common/src/main/java/org/valkyrienskies/mod/mixin/feature/ai/goal/villagers/MixinAssignProfessionFromJobSite.java create mode 100644 common/src/main/java/org/valkyrienskies/mod/mixin/feature/ai/goal/villagers/MixinGoToClosestVillage.java create mode 100644 common/src/main/java/org/valkyrienskies/mod/mixin/feature/ai/goal/villagers/MixinSetClosestHomeAsWalkTarget.java create mode 100644 common/src/main/java/org/valkyrienskies/mod/mixin/feature/ai/goal/villagers/MixinWorkAtPoi.java create mode 100644 common/src/main/java/org/valkyrienskies/mod/mixin/feature/ai/node_evaluator/MixinPathFinder.java create mode 100644 common/src/main/java/org/valkyrienskies/mod/mixin/feature/ai/node_evaluator/MixinPathNavigation.java create mode 100644 common/src/main/java/org/valkyrienskies/mod/mixin/feature/ai/path_retargeting/MixinAirAndWaterRandomPos.java create mode 100644 common/src/main/java/org/valkyrienskies/mod/mixin/feature/ai/path_retargeting/MixinDefaultRandomPos.java create mode 100644 common/src/main/java/org/valkyrienskies/mod/mixin/feature/ai/path_retargeting/MixinLandRandomPos.java create mode 100644 common/src/main/java/org/valkyrienskies/mod/mixin/feature/entity_collision/MixinLivingEntity.java create mode 100644 common/src/main/java/org/valkyrienskies/mod/mixin/feature/entity_movement_packets/MixinLocalPlayer.java create mode 100644 common/src/main/java/org/valkyrienskies/mod/mixin/feature/entity_movement_packets/MixinServerEntity.java create mode 100644 common/src/main/java/org/valkyrienskies/mod/mixin/feature/poi/FEATURE.md create mode 100644 common/src/main/java/org/valkyrienskies/mod/mixin/feature/poi/MixinPOIManager.java create mode 100644 common/src/main/java/org/valkyrienskies/mod/mixin/feature/wave_spawning/FEATURE.md create mode 100644 common/src/main/java/org/valkyrienskies/mod/mixin/feature/wave_spawning/MixinRaid.java create mode 100644 common/src/main/java/org/valkyrienskies/mod/mixin/feature/wave_spawning/MixinRaids.java create mode 100644 common/src/main/java/org/valkyrienskies/mod/mixin/feature/wave_spawning/MixinVillageSiege.java create mode 100644 common/src/main/kotlin/org/valkyrienskies/mod/common/networking/PacketEntityShipMotion.kt create mode 100644 common/src/main/kotlin/org/valkyrienskies/mod/common/networking/PacketMobShipRotation.kt create mode 100644 common/src/main/kotlin/org/valkyrienskies/mod/common/networking/PacketPlayerShipMotion.kt create mode 100644 common/src/main/kotlin/org/valkyrienskies/mod/common/util/EntityLerper.kt create mode 100644 common/src/main/kotlin/org/valkyrienskies/mod/common/world/POIChunkSearcher.kt diff --git a/common/build.gradle b/common/build.gradle index b62489b44..53a562e11 100644 --- a/common/build.gradle +++ b/common/build.gradle @@ -1,6 +1,6 @@ dependencies { - annotationProcessor(implementation("com.github.LlamaLad7:MixinExtras:0.1.1")) + annotationProcessor(implementation("io.github.llamalad7:mixinextras-common:0.3.5")) compileOnly 'com.google.code.findbugs:jsr305:3.0.2' // We depend on fabric loader here to use the fabric @Environment annotations diff --git a/common/src/main/java/org/valkyrienskies/mod/mixin/client/MixinMinecraft.java b/common/src/main/java/org/valkyrienskies/mod/mixin/client/MixinMinecraft.java index ae6ed83a6..b119a032a 100644 --- a/common/src/main/java/org/valkyrienskies/mod/mixin/client/MixinMinecraft.java +++ b/common/src/main/java/org/valkyrienskies/mod/mixin/client/MixinMinecraft.java @@ -103,6 +103,17 @@ public ClientShipWorldCore getShipObjectWorld() { @Shadow public abstract ClientPacketListener getConnection(); + @Inject( + method = "tick", + at = @At("HEAD") + ) + public void preTick(final CallbackInfo ci) { + // Tick the ship world and then drag entities + if (!pause && shipObjectWorld != null && level != null && getConnection() != null) { + //EntityDragger.INSTANCE.dragEntitiesWithShips(level.entitiesForRendering(), true); + } + } + @Inject( method = "tick", at = @At("TAIL") @@ -112,7 +123,8 @@ public void postTick(final CallbackInfo ci) { if (!pause && shipObjectWorld != null && level != null && getConnection() != null) { shipObjectWorld.tickNetworking(getConnection().getConnection().getRemoteAddress()); shipObjectWorld.postTick(); - EntityDragger.INSTANCE.dragEntitiesWithShips(level.entitiesForRendering()); + //EntityDragger.INSTANCE.dragEntitiesWithShips(level.entitiesForRendering()); + EntityDragger.INSTANCE.dragEntitiesWithShips(level.entitiesForRendering(), false); } } diff --git a/common/src/main/java/org/valkyrienskies/mod/mixin/client/renderer/MixinEntityRenderer.java b/common/src/main/java/org/valkyrienskies/mod/mixin/client/renderer/MixinEntityRenderer.java new file mode 100644 index 000000000..975c94f46 --- /dev/null +++ b/common/src/main/java/org/valkyrienskies/mod/mixin/client/renderer/MixinEntityRenderer.java @@ -0,0 +1,48 @@ +package org.valkyrienskies.mod.mixin.client.renderer; + +import com.llamalad7.mixinextras.injector.wrapoperation.Operation; +import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation; +import net.minecraft.client.multiplayer.ClientLevel; +import net.minecraft.client.renderer.entity.EntityRenderer; +import net.minecraft.world.entity.Entity; +import net.minecraft.world.phys.AABB; +import org.joml.Vector3d; +import org.joml.Vector3dc; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.valkyrienskies.core.api.ships.ClientShip; +import org.valkyrienskies.mod.common.VSGameUtilsKt; +import org.valkyrienskies.mod.common.util.EntityDraggingInformation; +import org.valkyrienskies.mod.common.util.IEntityDraggingInformationProvider; + +@Mixin(EntityRenderer.class) +public class MixinEntityRenderer { + + /** + * This is necessary to avoid the vanilla flickering that occurs when entities are at high speeds. + *

+ * Presumably, it is caused by the culling AABB only being updated on a subsequent tick, so we bypass that. + * @param instance + * @param original + * @return + */ + @WrapOperation(method = "shouldRender", at = @At(value = "INVOKE", target = "Lnet/minecraft/world/entity/Entity;getBoundingBoxForCulling()Lnet/minecraft/world/phys/AABB;")) + private AABB redirectAABBConstructor(Entity instance, Operation original) { + if (instance instanceof IEntityDraggingInformationProvider dragProvider && dragProvider.getDraggingInformation().isEntityBeingDraggedByAShip()) { + EntityDraggingInformation dragInfo = dragProvider.getDraggingInformation(); + ClientShip ship = VSGameUtilsKt.getShipObjectWorld((ClientLevel) instance.level).getAllShips().getById(dragInfo.getLastShipStoodOn()); + if (ship == null) { + return original.call(instance); + } + if (dragInfo.getLastShipStoodOn() != null && (dragInfo.getRelativePositionOnShip() != null || dragInfo.getServerRelativePlayerPosition() != null)) { + Vector3dc positionToTransform = dragInfo.bestRelativeEntityPosition(); + if (positionToTransform != null) { + Vector3dc transformed = ship.getRenderTransform().getShipToWorld().transformPosition(positionToTransform, + new Vector3d()); + return instance.getDimensions(instance.getPose()).makeBoundingBox(transformed.x(), transformed.y(), transformed.z()).inflate(0.5D); + } + } + } + return original.call(instance); + } +} diff --git a/common/src/main/java/org/valkyrienskies/mod/mixin/client/renderer/MixinGameRenderer.java b/common/src/main/java/org/valkyrienskies/mod/mixin/client/renderer/MixinGameRenderer.java index 8b9c6fe44..e626dc4f4 100644 --- a/common/src/main/java/org/valkyrienskies/mod/mixin/client/renderer/MixinGameRenderer.java +++ b/common/src/main/java/org/valkyrienskies/mod/mixin/client/renderer/MixinGameRenderer.java @@ -158,7 +158,7 @@ private void preRender(final float tickDelta, final long startTime, final boolea ((IEntityDraggingInformationProvider) entity).getDraggingInformation(); final Long lastShipStoodOn = entityDraggingInformation.getLastShipStoodOn(); // Then try getting [entityShouldBeHere] from [entityDraggingInformation] - if (lastShipStoodOn != null && entityDraggingInformation.isEntityBeingDraggedByAShip()) { + if (lastShipStoodOn != null && entityDraggingInformation.isEntityBeingDraggedByAShip()) { //for testing final ClientShip shipObject = VSGameUtilsKt.getShipObjectWorld(clientWorld).getLoadedShips().getById(lastShipStoodOn); if (shipObject != null) { @@ -200,6 +200,10 @@ private void preRender(final float tickDelta, final long startTime, final boolea entity.xo = (entityShouldBeHere.x() - (entity.getX() * tickDelta)) / (1.0 - tickDelta); entity.yo = (entityShouldBeHere.y() - (entity.getY() * tickDelta)) / (1.0 - tickDelta); entity.zo = (entityShouldBeHere.z() - (entity.getZ() * tickDelta)) / (1.0 - tickDelta); + //why the fuck do we do this + + //what if i just... + //dont } } } diff --git a/common/src/main/java/org/valkyrienskies/mod/mixin/entity/MixinEntity.java b/common/src/main/java/org/valkyrienskies/mod/mixin/entity/MixinEntity.java index 7b5178cfc..e81068d6b 100644 --- a/common/src/main/java/org/valkyrienskies/mod/mixin/entity/MixinEntity.java +++ b/common/src/main/java/org/valkyrienskies/mod/mixin/entity/MixinEntity.java @@ -5,7 +5,9 @@ import net.minecraft.CrashReport; import net.minecraft.CrashReportCategory; import net.minecraft.ReportedException; +import net.minecraft.client.multiplayer.ClientLevel; import net.minecraft.core.BlockPos; +import net.minecraft.server.level.ServerPlayer; import net.minecraft.util.Mth; import net.minecraft.world.entity.Entity; import net.minecraft.world.entity.EntityType; @@ -33,6 +35,7 @@ import org.valkyrienskies.core.api.ships.properties.ShipTransform; import org.valkyrienskies.mod.common.entity.ShipMountedToData; import org.valkyrienskies.mod.common.VSGameUtilsKt; +import org.valkyrienskies.mod.common.util.EntityDragger; import org.valkyrienskies.mod.common.util.EntityDraggingInformation; import org.valkyrienskies.mod.common.util.IEntityDraggingInformationProvider; import org.valkyrienskies.mod.common.util.VectorConversionsMCKt; @@ -127,11 +130,67 @@ private void preGetEyePosition(final float partialTicks, final CallbackInfoRetur /** * @reason Needed for players to pick blocks correctly when mounted to a ship + * + * Needed, because before we only fixed the clientside one. + */ + @Inject(method = "getEyePosition()Lnet/minecraft/world/phys/Vec3;", at = @At("HEAD"), cancellable = true) + private void preGetEyePositionServer(final CallbackInfoReturnable cir) { + final ShipMountedToData shipMountedToData = VSGameUtilsKt.getShipMountedToData(Entity.class.cast(this), null); + if (shipMountedToData == null) { + return; + } + final LoadedShip shipMountedTo = shipMountedToData.getShipMountedTo(); + + final ShipTransform shipTransform; + if (shipMountedTo instanceof ClientShip) { + shipTransform = ((ClientShip) shipMountedTo).getRenderTransform(); + } else { + shipTransform = shipMountedTo.getShipTransform(); + } + final Vector3dc basePos = shipTransform.getShipToWorldMatrix() + .transformPosition(shipMountedToData.getMountPosInShip(), new Vector3d()); + final Vector3dc eyeRelativePos = shipTransform.getShipCoordinatesToWorldCoordinatesRotation().transform( + new Vector3d(0.0, getEyeHeight(), 0.0) + ); + final Vec3 newEyePos = VectorConversionsMCKt.toMinecraft(basePos.add(eyeRelativePos, new Vector3d())); + cir.setReturnValue(newEyePos); + } + + /** + * @reason Needed for players to pick blocks correctly when mounted to a ship + * + * Additionally, this has to have dragging information included or it breaks. This is because of reasons that I literally + * do not know or understand, but minecraft's rendering pipeline is like that. */ @Inject(method = "calculateViewVector", at = @At("HEAD"), cancellable = true) private void preCalculateViewVector(final float xRot, final float yRot, final CallbackInfoReturnable cir) { final LoadedShip shipMountedTo = VSGameUtilsKt.getShipMountedTo(Entity.class.cast(this)); if (shipMountedTo == null) { + if (Entity.class.cast(this) instanceof final ServerPlayer sPlayer && sPlayer instanceof final IEntityDraggingInformationProvider dragProvider) { + if (dragProvider.getDraggingInformation().isEntityBeingDraggedByAShip() && dragProvider.getDraggingInformation().getServerRelativePlayerYaw() != null) { + final Ship shipDraggedBy = VSGameUtilsKt.getAllShips(level).getById(dragProvider.getDraggingInformation().getLastShipStoodOn()); + if (shipDraggedBy != null) { + final float realYRot = (float) EntityDragger.INSTANCE.serversideEyeRotationOrDefault(sPlayer, yRot); + final float f = xRot * (float) (Math.PI / 180.0); + final float g = -realYRot * (float) (Math.PI / 180.0); + final float h = Mth.cos(g); + final float i = Mth.sin(g); + final float j = Mth.cos(f); + final float k = Mth.sin(f); + final Vector3dc originalViewVector = new Vector3d(i * j, -k, h * j); + + final ShipTransform shipTransform; + if (shipDraggedBy instanceof ClientShip) { + shipTransform = ((ClientShip) shipDraggedBy).getRenderTransform(); + } else { + shipTransform = shipDraggedBy.getShipTransform(); + } + final Vec3 newViewVector = VectorConversionsMCKt.toMinecraft( + shipTransform.getShipCoordinatesToWorldCoordinatesRotation().transform(originalViewVector, new Vector3d())); + cir.setReturnValue(newViewVector); + } + } + } return; } final float f = xRot * (float) (Math.PI / 180.0); @@ -153,6 +212,28 @@ private void preCalculateViewVector(final float xRot, final float yRot, final Ca cir.setReturnValue(newViewVector); } + /** + * @reason Without this and that other mixin, things don't render correctly at high speeds. + * @see org.valkyrienskies.mod.mixin.client.renderer.MixinEntityRenderer + */ + @Inject(method = "shouldRender", at = @At("HEAD"), cancellable = true) + private void onShouldRender(double d, double e, double f, CallbackInfoReturnable cir) { + if (this.draggingInformation.isEntityBeingDraggedByAShip() && this.level.isClientSide) { + final ClientShip ship = VSGameUtilsKt.getShipObjectWorld((ClientLevel) this.level).getAllShips().getById(this.draggingInformation.getLastShipStoodOn()); + if (ship != null) { + final ShipTransform shipTransform = ship.getRenderTransform(); + if (this.draggingInformation.getRelativePositionOnShip() != null) { + Vector3dc redir = shipTransform.getShipToWorld().transformPosition(this.draggingInformation.getRelativePositionOnShip(), new Vector3d()); + double distX = redir.x() - d; + double distY = redir.y() - e; + double distZ = redir.z() - f; + double sqrDist = distX * distX + distY * distY + distZ * distZ; + cir.setReturnValue(shouldRenderAtSqrDistance(sqrDist)); + } + } + } + } + // region shadow functions and fields @Shadow public Level level; @@ -183,6 +264,12 @@ private void preCalculateViewVector(final float xRot, final float yRot, final Ca @Shadow public abstract EntityType getType(); + @Shadow + private float yRot; + + @Shadow + public abstract boolean shouldRenderAtSqrDistance(double d); + @Override @NotNull public EntityDraggingInformation getDraggingInformation() { diff --git a/common/src/main/java/org/valkyrienskies/mod/mixin/feature/ai/goal/JitteredLinearRetryAccessor.java b/common/src/main/java/org/valkyrienskies/mod/mixin/feature/ai/goal/JitteredLinearRetryAccessor.java new file mode 100644 index 000000000..ce154c9e4 --- /dev/null +++ b/common/src/main/java/org/valkyrienskies/mod/mixin/feature/ai/goal/JitteredLinearRetryAccessor.java @@ -0,0 +1,14 @@ +package org.valkyrienskies.mod.mixin.feature.ai.goal; + +import java.util.Random; +import net.minecraft.world.entity.ai.behavior.AcquirePoi.JitteredLinearRetry; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.gen.Invoker; + +@Mixin(JitteredLinearRetry.class) +public interface JitteredLinearRetryAccessor { + @Invoker("") + static JitteredLinearRetry create(Random random, long l) { + throw new AssertionError(); + } +} diff --git a/common/src/main/java/org/valkyrienskies/mod/mixin/feature/ai/goal/MixinMoveToBlockGoal.java b/common/src/main/java/org/valkyrienskies/mod/mixin/feature/ai/goal/MixinMoveToBlockGoal.java new file mode 100644 index 000000000..da31ae88f --- /dev/null +++ b/common/src/main/java/org/valkyrienskies/mod/mixin/feature/ai/goal/MixinMoveToBlockGoal.java @@ -0,0 +1,25 @@ +package org.valkyrienskies.mod.mixin.feature.ai.goal; + +import com.llamalad7.mixinextras.injector.wrapoperation.Operation; +import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation; +import net.minecraft.core.BlockPos; +import net.minecraft.core.Position; +import net.minecraft.world.entity.PathfinderMob; +import net.minecraft.world.entity.ai.goal.MoveToBlockGoal; +import org.spongepowered.asm.mixin.Final; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; +import org.spongepowered.asm.mixin.injection.At; +import org.valkyrienskies.mod.common.VSGameUtilsKt; + +@Mixin(MoveToBlockGoal.class) +public class MixinMoveToBlockGoal { + @Shadow + @Final + protected PathfinderMob mob; + + @WrapOperation(method = "tick", at = @At(value = "INVOKE", target = "Lnet/minecraft/core/BlockPos;closerToCenterThan(Lnet/minecraft/core/Position;D)Z")) + private boolean onCloserToCenterThan(BlockPos instance, Position position, double v, Operation original) { + return original.call(new BlockPos(VSGameUtilsKt.toWorldCoordinates(this.mob.level, instance)), position, v); + } +} diff --git a/common/src/main/java/org/valkyrienskies/mod/mixin/feature/ai/goal/MixinMoveToTargetSink.java b/common/src/main/java/org/valkyrienskies/mod/mixin/feature/ai/goal/MixinMoveToTargetSink.java new file mode 100644 index 000000000..9f4c2e66e --- /dev/null +++ b/common/src/main/java/org/valkyrienskies/mod/mixin/feature/ai/goal/MixinMoveToTargetSink.java @@ -0,0 +1,20 @@ +package org.valkyrienskies.mod.mixin.feature.ai.goal; + +import com.llamalad7.mixinextras.injector.wrapoperation.Operation; +import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation; +import com.llamalad7.mixinextras.sugar.Local; +import net.minecraft.core.BlockPos; +import net.minecraft.core.Vec3i; +import net.minecraft.world.entity.Mob; +import net.minecraft.world.entity.ai.behavior.MoveToTargetSink; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.valkyrienskies.mod.common.VSGameUtilsKt; + +@Mixin(MoveToTargetSink.class) +public class MixinMoveToTargetSink { + @WrapOperation(method = "reachedTarget", at = @At(value = "INVOKE", target = "Lnet/minecraft/core/BlockPos;distManhattan(Lnet/minecraft/core/Vec3i;)I")) + private int onDistManhattan(BlockPos instance, Vec3i vec3i, Operation original, @Local(argsOnly = true) Mob mob) { + return original.call(new BlockPos(VSGameUtilsKt.toWorldCoordinates(mob.level, instance)), vec3i); + } +} diff --git a/common/src/main/java/org/valkyrienskies/mod/mixin/feature/ai/goal/MixinValidateNearbyPoi.java b/common/src/main/java/org/valkyrienskies/mod/mixin/feature/ai/goal/MixinValidateNearbyPoi.java new file mode 100644 index 000000000..c080e3ec1 --- /dev/null +++ b/common/src/main/java/org/valkyrienskies/mod/mixin/feature/ai/goal/MixinValidateNearbyPoi.java @@ -0,0 +1,21 @@ +package org.valkyrienskies.mod.mixin.feature.ai.goal; + +import com.llamalad7.mixinextras.injector.wrapoperation.Operation; +import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation; +import com.llamalad7.mixinextras.sugar.Local; +import net.minecraft.core.BlockPos; +import net.minecraft.core.Position; +import net.minecraft.world.entity.LivingEntity; +import net.minecraft.world.entity.ai.behavior.ValidateNearbyPoi; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.valkyrienskies.mod.common.VSGameUtilsKt; + +@Mixin(ValidateNearbyPoi.class) +public class MixinValidateNearbyPoi { + @WrapOperation(method = "checkExtraStartConditions", at = @At(value = "INVOKE", target = "Lnet/minecraft/core/BlockPos;closerToCenterThan(Lnet/minecraft/core/Position;D)Z")) + private boolean onCloserToCenterThan(BlockPos instance, Position position, double v, Operation original, @Local + LivingEntity livingEntity) { + return original.call(new BlockPos(VSGameUtilsKt.toWorldCoordinates(livingEntity.level, instance)), position, v); + } +} diff --git a/common/src/main/java/org/valkyrienskies/mod/mixin/feature/ai/goal/bees/MixinBee.java b/common/src/main/java/org/valkyrienskies/mod/mixin/feature/ai/goal/bees/MixinBee.java new file mode 100644 index 000000000..32cb2d2d5 --- /dev/null +++ b/common/src/main/java/org/valkyrienskies/mod/mixin/feature/ai/goal/bees/MixinBee.java @@ -0,0 +1,26 @@ +package org.valkyrienskies.mod.mixin.feature.ai.goal.bees; + +import com.llamalad7.mixinextras.injector.wrapoperation.Operation; +import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation; +import net.minecraft.core.BlockPos; +import net.minecraft.core.Vec3i; +import net.minecraft.world.entity.Entity; +import net.minecraft.world.entity.EntityType; +import net.minecraft.world.entity.animal.Bee; +import net.minecraft.world.level.Level; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.valkyrienskies.mod.common.VSGameUtilsKt; + +@Mixin(Bee.class) +public abstract class MixinBee extends Entity { + + public MixinBee(EntityType entityType, Level level) { + super(entityType, level); + } + + @WrapOperation(method = "closerThan", at = @At(value = "INVOKE", target = "Lnet/minecraft/core/BlockPos;closerThan(Lnet/minecraft/core/Vec3i;D)Z")) + private boolean onCloserThan(BlockPos instance, Vec3i vec3i, double v, Operation original) { + return original.call(new BlockPos(VSGameUtilsKt.toWorldCoordinates(this.level, instance)), vec3i, v); + } +} diff --git a/common/src/main/java/org/valkyrienskies/mod/mixin/feature/ai/goal/bees/MixinEnterHiveGoal.java b/common/src/main/java/org/valkyrienskies/mod/mixin/feature/ai/goal/bees/MixinEnterHiveGoal.java new file mode 100644 index 000000000..4822cff88 --- /dev/null +++ b/common/src/main/java/org/valkyrienskies/mod/mixin/feature/ai/goal/bees/MixinEnterHiveGoal.java @@ -0,0 +1,24 @@ +package org.valkyrienskies.mod.mixin.feature.ai.goal.bees; + +import com.llamalad7.mixinextras.injector.wrapoperation.Operation; +import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation; +import net.minecraft.core.BlockPos; +import net.minecraft.core.Position; +import net.minecraft.world.entity.animal.Bee; +import org.spongepowered.asm.mixin.Final; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; +import org.spongepowered.asm.mixin.injection.At; +import org.valkyrienskies.mod.common.VSGameUtilsKt; + +@Mixin(Bee.BeeEnterHiveGoal.class) +public class MixinEnterHiveGoal { + @Shadow + @Final + Bee field_20367; + + @WrapOperation(method = "canBeeUse", at = @At(value = "INVOKE", target = "Lnet/minecraft/core/BlockPos;closerToCenterThan(Lnet/minecraft/core/Position;D)Z")) + private boolean onCloserToCenterThan(BlockPos instance, Position position, double v, Operation original) { + return original.call(new BlockPos(VSGameUtilsKt.toWorldCoordinates(this.field_20367.level, instance)), position, v); + } +} diff --git a/common/src/main/java/org/valkyrienskies/mod/mixin/feature/ai/goal/bees/MixinGrowCropGoal.java b/common/src/main/java/org/valkyrienskies/mod/mixin/feature/ai/goal/bees/MixinGrowCropGoal.java new file mode 100644 index 000000000..8d04cea70 --- /dev/null +++ b/common/src/main/java/org/valkyrienskies/mod/mixin/feature/ai/goal/bees/MixinGrowCropGoal.java @@ -0,0 +1,30 @@ +package org.valkyrienskies.mod.mixin.feature.ai.goal.bees; + +import com.llamalad7.mixinextras.injector.wrapoperation.Operation; +import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation; +import java.util.List; +import net.minecraft.core.BlockPos; +import net.minecraft.tags.BlockTags; +import net.minecraft.world.entity.animal.Bee; +import net.minecraft.world.level.Level; +import net.minecraft.world.level.block.state.BlockState; +import org.joml.Vector3d; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.valkyrienskies.mod.common.VSGameUtilsKt; + +@Mixin(Bee.BeeGrowCropGoal.class) +public class MixinGrowCropGoal { + @WrapOperation(method = "tick", at = @At(value = "INVOKE", + target = "Lnet/minecraft/world/level/Level;getBlockState(Lnet/minecraft/core/BlockPos;)Lnet/minecraft/world/level/block/state/BlockState;")) + private BlockState onTick(Level instance, BlockPos blockPos, Operation original) { + List possibleCandidates = VSGameUtilsKt.transformToNearbyShipsAndWorld(instance, blockPos.getX(), blockPos.getY(), blockPos.getZ(), 1.5); + for (Vector3d candidate : possibleCandidates) { + BlockState blockState = instance.getBlockState(new BlockPos(candidate.x, candidate.y, candidate.z)); + if (blockState.is(BlockTags.BEE_GROWABLES)) { + return blockState; + } + } + return original.call(instance, blockPos); + } +} diff --git a/common/src/main/java/org/valkyrienskies/mod/mixin/feature/ai/goal/bees/MixinLocateHiveGoal.java b/common/src/main/java/org/valkyrienskies/mod/mixin/feature/ai/goal/bees/MixinLocateHiveGoal.java new file mode 100644 index 000000000..c70ea7cb5 --- /dev/null +++ b/common/src/main/java/org/valkyrienskies/mod/mixin/feature/ai/goal/bees/MixinLocateHiveGoal.java @@ -0,0 +1,28 @@ +package org.valkyrienskies.mod.mixin.feature.ai.goal.bees; + +import com.llamalad7.mixinextras.injector.wrapoperation.Operation; +import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation; +import com.llamalad7.mixinextras.sugar.Local; +import java.util.Comparator; +import java.util.stream.Stream; +import net.minecraft.core.BlockPos; +import net.minecraft.world.entity.animal.Bee; +import net.minecraft.world.entity.animal.Bee.BeeLocateHiveGoal; +import org.spongepowered.asm.mixin.Final; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; +import org.spongepowered.asm.mixin.injection.At; +import org.valkyrienskies.mod.common.VSGameUtilsKt; + +@Mixin(BeeLocateHiveGoal.class) +public class MixinLocateHiveGoal { + @Shadow + @Final + Bee field_20375; + + @WrapOperation(method = "findNearbyHivesWithSpace", at = @At(value = "INVOKE", target = "Ljava/util/stream/Stream;sorted(Ljava/util/Comparator;)Ljava/util/stream/Stream;")) + private Stream onComparingDouble(Stream instance, Comparator comparator, + Operation> original, @Local BlockPos blockPos) { + return original.call(instance, Comparator.comparingDouble( pos -> new BlockPos(VSGameUtilsKt.toWorldCoordinates(this.field_20375.level, (BlockPos) pos)).distSqr(blockPos))); + } +} diff --git a/common/src/main/java/org/valkyrienskies/mod/mixin/feature/ai/goal/bees/MixinPollinateGoal.java b/common/src/main/java/org/valkyrienskies/mod/mixin/feature/ai/goal/bees/MixinPollinateGoal.java new file mode 100644 index 000000000..20d97823d --- /dev/null +++ b/common/src/main/java/org/valkyrienskies/mod/mixin/feature/ai/goal/bees/MixinPollinateGoal.java @@ -0,0 +1,49 @@ +package org.valkyrienskies.mod.mixin.feature.ai.goal.bees; + +import com.llamalad7.mixinextras.injector.wrapoperation.Operation; +import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation; +import java.util.Comparator; +import java.util.Optional; +import java.util.function.Predicate; +import net.minecraft.core.BlockPos; +import net.minecraft.world.entity.animal.Bee; +import net.minecraft.world.level.block.state.BlockState; +import org.spongepowered.asm.mixin.Final; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; +import org.spongepowered.asm.mixin.Unique; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; +import org.valkyrienskies.mod.common.VSGameUtilsKt; + +@Mixin(Bee.BeePollinateGoal.class) +public abstract class MixinPollinateGoal { + @Shadow + @Final + Bee field_20377; + + @Shadow + protected abstract Optional findNearestBlock(Predicate predicate, double d); + + @Shadow + @Final + private Predicate VALID_POLLINATION_BLOCKS; + @Unique + private BlockPos modifiedBeePosition = field_20377.blockPosition(); + + @Inject(method = "findNearbyFlower", at = @At("HEAD"), cancellable = true) + private void preFindNearbyFlower(CallbackInfoReturnable> cir) { + Optional res = VSGameUtilsKt.transformToNearbyShipsAndWorld(this.field_20377.level, modifiedBeePosition.getX(), modifiedBeePosition.getY(), modifiedBeePosition.getZ(), 5.0).stream().flatMap(pos -> { + this.modifiedBeePosition = new BlockPos(pos.x, pos.y, pos.z); + return findNearestBlock(VALID_POLLINATION_BLOCKS, 5.0).stream(); + }).min(Comparator.comparingDouble(pos -> VSGameUtilsKt.squaredDistanceBetweenInclShips(this.field_20377.level, modifiedBeePosition.getX(), modifiedBeePosition.getY(), modifiedBeePosition.getZ(), pos.getX(), pos.getY(), pos.getZ()))); + modifiedBeePosition = field_20377.blockPosition(); + cir.setReturnValue(res); + } + + @WrapOperation(method = "findNearestBlock", at = @At(value = "INVOKE", target = "Lnet/minecraft/world/entity/animal/Bee;blockPosition()Lnet/minecraft/core/BlockPos;")) + private BlockPos onBlockPosition(Bee instance, Operation original) { + return modifiedBeePosition; + } +} diff --git a/common/src/main/java/org/valkyrienskies/mod/mixin/feature/ai/goal/villagers/MixinAssignProfessionFromJobSite.java b/common/src/main/java/org/valkyrienskies/mod/mixin/feature/ai/goal/villagers/MixinAssignProfessionFromJobSite.java new file mode 100644 index 000000000..9622b37e7 --- /dev/null +++ b/common/src/main/java/org/valkyrienskies/mod/mixin/feature/ai/goal/villagers/MixinAssignProfessionFromJobSite.java @@ -0,0 +1,23 @@ +package org.valkyrienskies.mod.mixin.feature.ai.goal.villagers; + +import com.llamalad7.mixinextras.injector.wrapoperation.Operation; +import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation; +import com.llamalad7.mixinextras.sugar.Local; +import net.minecraft.core.BlockPos; +import net.minecraft.core.Position; +import net.minecraft.world.entity.ai.behavior.AssignProfessionFromJobSite; +import net.minecraft.world.entity.npc.Villager; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.valkyrienskies.mod.common.VSGameUtilsKt; + +@Mixin(AssignProfessionFromJobSite.class) +public class MixinAssignProfessionFromJobSite { + @WrapOperation(method = "checkExtraStartConditions(Lnet/minecraft/server/level/ServerLevel;Lnet/minecraft/world/entity/npc/Villager;)Z", at = @At( + value = "INVOKE", + target = "Lnet/minecraft/core/BlockPos;closerToCenterThan(Lnet/minecraft/core/Position;D)Z")) + private boolean onCloserToCenterThan(BlockPos instance, Position position, double v, Operation original, @Local + Villager villager) { + return original.call(new BlockPos(VSGameUtilsKt.toWorldCoordinates(villager.level, instance)), position, v); + } +} diff --git a/common/src/main/java/org/valkyrienskies/mod/mixin/feature/ai/goal/villagers/MixinGoToClosestVillage.java b/common/src/main/java/org/valkyrienskies/mod/mixin/feature/ai/goal/villagers/MixinGoToClosestVillage.java new file mode 100644 index 000000000..daeef208d --- /dev/null +++ b/common/src/main/java/org/valkyrienskies/mod/mixin/feature/ai/goal/villagers/MixinGoToClosestVillage.java @@ -0,0 +1,37 @@ +package org.valkyrienskies.mod.mixin.feature.ai.goal.villagers; + +import com.llamalad7.mixinextras.injector.wrapoperation.Operation; +import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation; +import com.llamalad7.mixinextras.sugar.Local; +import net.minecraft.core.BlockPos; +import net.minecraft.core.SectionPos; +import net.minecraft.world.entity.ai.behavior.GoToClosestVillage; +import net.minecraft.world.entity.ai.village.poi.PoiManager; +import net.minecraft.world.entity.npc.Villager; +import net.minecraft.world.phys.Vec3; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.valkyrienskies.mod.common.VSGameUtilsKt; + +@Mixin(GoToClosestVillage.class) +public class MixinGoToClosestVillage { + @WrapOperation(method = "start(Lnet/minecraft/server/level/ServerLevel;Lnet/minecraft/world/entity/npc/Villager;J)V", at = @At( + value = "INVOKE", target = "Lnet/minecraft/world/entity/ai/village/poi/PoiManager;sectionsToVillage(Lnet/minecraft/core/SectionPos;)I", ordinal = 0)) + private int onSectionsToVillageInitial(PoiManager poiManager, SectionPos sectionPos, Operation original, @Local Villager villager) { + int[] currentLevels = {original.call(poiManager, sectionPos)}; + VSGameUtilsKt.transformToNearbyShipsAndWorld(villager.level, villager.getX(), villager.getY(), villager.getZ(), 100, (double x, double y, double z) -> { + currentLevels[0] = Math.min(currentLevels[0], original.call(poiManager, SectionPos.of(new BlockPos(x, y, z)))); + }); + return currentLevels[0]; + } + + @WrapOperation(method = "start(Lnet/minecraft/server/level/ServerLevel;Lnet/minecraft/world/entity/npc/Villager;J)V", at = @At( + value = "INVOKE", target = "Lnet/minecraft/world/entity/ai/village/poi/PoiManager;sectionsToVillage(Lnet/minecraft/core/SectionPos;)I", ordinal = 1)) + private int onSectionsToVillageVec3(PoiManager poiManager, SectionPos sectionPos, Operation original, @Local Villager villager, @Local(ordinal = 1) Vec3 vec32) { + int[] currentLevels = {original.call(poiManager, sectionPos)}; + VSGameUtilsKt.transformToNearbyShipsAndWorld(villager.level, vec32.x, vec32.y, vec32.z, 100, (double x, double y, double z) -> { + currentLevels[0] = Math.min(currentLevels[0], original.call(poiManager, SectionPos.of(new BlockPos(x, y, z)))); + }); + return currentLevels[0]; + } +} diff --git a/common/src/main/java/org/valkyrienskies/mod/mixin/feature/ai/goal/villagers/MixinSetClosestHomeAsWalkTarget.java b/common/src/main/java/org/valkyrienskies/mod/mixin/feature/ai/goal/villagers/MixinSetClosestHomeAsWalkTarget.java new file mode 100644 index 000000000..b438dad80 --- /dev/null +++ b/common/src/main/java/org/valkyrienskies/mod/mixin/feature/ai/goal/villagers/MixinSetClosestHomeAsWalkTarget.java @@ -0,0 +1,21 @@ +package org.valkyrienskies.mod.mixin.feature.ai.goal.villagers; + +import com.llamalad7.mixinextras.injector.wrapoperation.Operation; +import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation; +import com.llamalad7.mixinextras.sugar.Local; + +import net.minecraft.core.BlockPos; +import net.minecraft.core.Vec3i; +import net.minecraft.world.entity.LivingEntity; +import net.minecraft.world.entity.ai.behavior.SetClosestHomeAsWalkTarget; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.valkyrienskies.mod.common.VSGameUtilsKt; + +@Mixin(SetClosestHomeAsWalkTarget.class) +public class MixinSetClosestHomeAsWalkTarget { + @WrapOperation(method = "checkExtraStartConditions", at = @At(value = "INVOKE", target = "Lnet/minecraft/core/BlockPos;distSqr(Lnet/minecraft/core/Vec3i;)D")) + private double onDistSqr(BlockPos instance, Vec3i vec3i, Operation original, @Local LivingEntity livingEntity) { + return original.call(new BlockPos(VSGameUtilsKt.toWorldCoordinates(livingEntity.level, instance)), vec3i); + } +} diff --git a/common/src/main/java/org/valkyrienskies/mod/mixin/feature/ai/goal/villagers/MixinWorkAtPoi.java b/common/src/main/java/org/valkyrienskies/mod/mixin/feature/ai/goal/villagers/MixinWorkAtPoi.java new file mode 100644 index 000000000..0d3c1b5c4 --- /dev/null +++ b/common/src/main/java/org/valkyrienskies/mod/mixin/feature/ai/goal/villagers/MixinWorkAtPoi.java @@ -0,0 +1,29 @@ +package org.valkyrienskies.mod.mixin.feature.ai.goal.villagers; + +import com.llamalad7.mixinextras.injector.wrapoperation.Operation; +import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation; +import com.llamalad7.mixinextras.sugar.Local; +import net.minecraft.core.BlockPos; +import net.minecraft.core.Position; +import net.minecraft.world.entity.ai.behavior.WorkAtPoi; +import net.minecraft.world.entity.npc.Villager; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.valkyrienskies.mod.common.VSGameUtilsKt; + +@Mixin(WorkAtPoi.class) +public class MixinWorkAtPoi { + @WrapOperation(method = "canStillUse(Lnet/minecraft/server/level/ServerLevel;Lnet/minecraft/world/entity/npc/Villager;J)Z", at = @At( + value = "INVOKE", target = "Lnet/minecraft/core/BlockPos;closerToCenterThan(Lnet/minecraft/core/Position;D)Z")) + private boolean onCloserToCenterThan(BlockPos instance, Position position, double v, Operation original, @Local + Villager villager) { + return original.call(new BlockPos(VSGameUtilsKt.toWorldCoordinates(villager.level, instance)), position, v); + } + @WrapOperation(method = "checkExtraStartConditions(Lnet/minecraft/server/level/ServerLevel;Lnet/minecraft/world/entity/npc/Villager;)Z", at = @At( + value = "INVOKE", target = "Lnet/minecraft/core/BlockPos;closerToCenterThan(Lnet/minecraft/core/Position;D)Z" + )) + private boolean onCloserToCenterThan2(BlockPos instance, Position position, double v, Operation original, @Local + Villager villager) { + return original.call(new BlockPos(VSGameUtilsKt.toWorldCoordinates(villager.level, instance)), position, v); + } +} diff --git a/common/src/main/java/org/valkyrienskies/mod/mixin/feature/ai/node_evaluator/MixinPathFinder.java b/common/src/main/java/org/valkyrienskies/mod/mixin/feature/ai/node_evaluator/MixinPathFinder.java new file mode 100644 index 000000000..491db5a3c --- /dev/null +++ b/common/src/main/java/org/valkyrienskies/mod/mixin/feature/ai/node_evaluator/MixinPathFinder.java @@ -0,0 +1,40 @@ +package org.valkyrienskies.mod.mixin.feature.ai.node_evaluator; + +import com.llamalad7.mixinextras.injector.wrapoperation.Operation; +import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation; +import com.llamalad7.mixinextras.sugar.Local; +import java.util.Map; +import java.util.function.Function; +import java.util.stream.Collector; +import java.util.stream.Collectors; +import java.util.stream.Stream; +import net.minecraft.core.BlockPos; +import net.minecraft.world.entity.Mob; +import net.minecraft.world.level.pathfinder.NodeEvaluator; +import net.minecraft.world.level.pathfinder.PathFinder; +import net.minecraft.world.level.pathfinder.Target; +import org.spongepowered.asm.mixin.Final; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; +import org.spongepowered.asm.mixin.injection.At; +import org.valkyrienskies.mod.common.VSGameUtilsKt; + +@Mixin(PathFinder.class) +public class MixinPathFinder { + @Shadow + @Final + private NodeEvaluator nodeEvaluator; + + @WrapOperation( + method = "findPath(Lnet/minecraft/world/level/PathNavigationRegion;Lnet/minecraft/world/entity/Mob;Ljava/util/Set;FIF)Lnet/minecraft/world/level/pathfinder/Path;", + at = @At( + value = "INVOKE", + target = "Ljava/util/stream/Stream;collect(Ljava/util/stream/Collector;)Ljava/lang/Object;")) + private Object onCollectPath(Stream instance, Collector> arCollector, Operation> original, @Local + Mob mob) { + return original.call(instance, Collectors.toMap(blockPos -> { + BlockPos transformedPos = new BlockPos(VSGameUtilsKt.toWorldCoordinates(mob.level, blockPos)); + return this.nodeEvaluator.getGoal(transformedPos.getX(), transformedPos.getY(), transformedPos.getZ()); + }, Function.identity())); + } +} diff --git a/common/src/main/java/org/valkyrienskies/mod/mixin/feature/ai/node_evaluator/MixinPathNavigation.java b/common/src/main/java/org/valkyrienskies/mod/mixin/feature/ai/node_evaluator/MixinPathNavigation.java new file mode 100644 index 000000000..7eb678c14 --- /dev/null +++ b/common/src/main/java/org/valkyrienskies/mod/mixin/feature/ai/node_evaluator/MixinPathNavigation.java @@ -0,0 +1,27 @@ +package org.valkyrienskies.mod.mixin.feature.ai.node_evaluator; + +import com.llamalad7.mixinextras.injector.wrapoperation.Operation; +import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation; +import net.minecraft.world.entity.ai.navigation.PathNavigation; +import net.minecraft.world.level.Level; +import net.minecraft.world.level.pathfinder.Path; +import net.minecraft.world.phys.Vec3; +import org.spongepowered.asm.mixin.Final; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; +import org.spongepowered.asm.mixin.injection.At; +import org.valkyrienskies.mod.common.VSGameUtilsKt; + +@Mixin(PathNavigation.class) +public class MixinPathNavigation { + @Shadow + @Final + protected Level level; + + @WrapOperation(method = "moveTo(DDDD)Z", at = @At(value = "INVOKE", target = "Lnet/minecraft/world/entity/ai/navigation/PathNavigation;createPath(DDDI)Lnet/minecraft/world/level/pathfinder/Path;")) + private Path onMoveToCreatePath(PathNavigation instance, double d, double e, double f, int i, + Operation original) { + Vec3 transformedPos = VSGameUtilsKt.toWorldCoordinates(this.level, new Vec3(d, e, f)); + return original.call(instance, transformedPos.x, transformedPos.y, transformedPos.z, i); + } +} diff --git a/common/src/main/java/org/valkyrienskies/mod/mixin/feature/ai/node_evaluator/SwimNodeEvaluatorMixin.java b/common/src/main/java/org/valkyrienskies/mod/mixin/feature/ai/node_evaluator/SwimNodeEvaluatorMixin.java index aefad24fa..9a624343e 100644 --- a/common/src/main/java/org/valkyrienskies/mod/mixin/feature/ai/node_evaluator/SwimNodeEvaluatorMixin.java +++ b/common/src/main/java/org/valkyrienskies/mod/mixin/feature/ai/node_evaluator/SwimNodeEvaluatorMixin.java @@ -42,7 +42,7 @@ private FluidState getFluidStateRedirectPathType(final BlockGetter instance, fin final double origZ = blockPos.getZ(); final Level finalLevel = level; VSGameUtilsKt.transformToNearbyShipsAndWorld(level, origX, - origY, origZ, 1, + origY, origZ, 2, (x, y, z) -> { final BlockPos groundPos = new BlockPos(x, y, z); final FluidState tempFluidState = getFluidState.call(finalLevel, groundPos); diff --git a/common/src/main/java/org/valkyrienskies/mod/mixin/feature/ai/node_evaluator/WalkNodeEvaluatorMixin.java b/common/src/main/java/org/valkyrienskies/mod/mixin/feature/ai/node_evaluator/WalkNodeEvaluatorMixin.java index 6d28810b5..62e9d157c 100644 --- a/common/src/main/java/org/valkyrienskies/mod/mixin/feature/ai/node_evaluator/WalkNodeEvaluatorMixin.java +++ b/common/src/main/java/org/valkyrienskies/mod/mixin/feature/ai/node_evaluator/WalkNodeEvaluatorMixin.java @@ -43,7 +43,7 @@ private static void getBlockPathTypeForShips(final BlockGetter blockGetter, fina if (blockGetter instanceof PathNavigationRegion) { VSGameUtilsKt.transformToNearbyShipsAndWorld(((PathNavigationRegionAccessor) blockGetter).getLevel(), origX, - origY, origZ, 1, + origY, origZ, 2, (x, y, z) -> { final BlockPos groundPos = new BlockPos(x, y, z); BlockPathTypes pathType = diff --git a/common/src/main/java/org/valkyrienskies/mod/mixin/feature/ai/path_retargeting/MixinAirAndWaterRandomPos.java b/common/src/main/java/org/valkyrienskies/mod/mixin/feature/ai/path_retargeting/MixinAirAndWaterRandomPos.java new file mode 100644 index 000000000..9e863e6f9 --- /dev/null +++ b/common/src/main/java/org/valkyrienskies/mod/mixin/feature/ai/path_retargeting/MixinAirAndWaterRandomPos.java @@ -0,0 +1,47 @@ +package org.valkyrienskies.mod.mixin.feature.ai.path_retargeting; + +import com.llamalad7.mixinextras.sugar.Local; +import net.minecraft.core.BlockPos; +import net.minecraft.world.entity.PathfinderMob; +import net.minecraft.world.entity.ai.util.AirAndWaterRandomPos; +import net.minecraft.world.entity.ai.util.GoalUtils; +import net.minecraft.world.entity.ai.util.RandomPos; +import net.minecraft.world.phys.AABB; +import org.joml.Vector3d; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; +import org.valkyrienskies.core.api.ships.LoadedShip; +import org.valkyrienskies.mod.common.VSGameUtilsKt; +import org.valkyrienskies.mod.common.util.VectorConversionsMCKt; + +@Mixin(AirAndWaterRandomPos.class) +public class MixinAirAndWaterRandomPos { + @Inject(method = "generateRandomPos", at = @At(value = "INVOKE", target = "Lnet/minecraft/world/entity/ai/util/GoalUtils;isOutsideLimits(Lnet/minecraft/core/BlockPos;Lnet/minecraft/world/entity/PathfinderMob;)Z"), + cancellable = true) + private static void preGenerateRandomPos(PathfinderMob pathfinderMob, int i, int j, int k, double d, double e, + double f, boolean bl, CallbackInfoReturnable cir, @Local(ordinal = 1) BlockPos blockPos2) { + if (pathfinderMob.level != null) { + if (blockPos2 == null) { + return; + } + AABB checker = new AABB(blockPos2); + Iterable ships = VSGameUtilsKt.getShipObjectWorld(pathfinderMob.level).getLoadedShips().getIntersecting( + VectorConversionsMCKt.toJOML(checker), VSGameUtilsKt.getDimensionId(pathfinderMob.level)); + if (ships.iterator().hasNext()) { + for (LoadedShip ship : ships) { + Vector3d posInShip = ship.getWorldToShip() + .transformPosition(VectorConversionsMCKt.toJOMLD(blockPos2), new Vector3d()); + BlockPos blockPosInShip = new BlockPos(VectorConversionsMCKt.toMinecraft(posInShip)); + if (!GoalUtils.isRestricted(bl, pathfinderMob, blockPosInShip) && + !GoalUtils.hasMalus(pathfinderMob, blockPos2 = RandomPos.moveUpOutOfSolid(blockPos2, pathfinderMob.level.getMaxBuildHeight(), arg2 -> GoalUtils.isSolid(pathfinderMob, arg2)))) { + cir.setReturnValue(blockPosInShip); + break; + } + } + } + } + } + +} diff --git a/common/src/main/java/org/valkyrienskies/mod/mixin/feature/ai/path_retargeting/MixinDefaultRandomPos.java b/common/src/main/java/org/valkyrienskies/mod/mixin/feature/ai/path_retargeting/MixinDefaultRandomPos.java new file mode 100644 index 000000000..00c463ae4 --- /dev/null +++ b/common/src/main/java/org/valkyrienskies/mod/mixin/feature/ai/path_retargeting/MixinDefaultRandomPos.java @@ -0,0 +1,83 @@ +package org.valkyrienskies.mod.mixin.feature.ai.path_retargeting; + +import com.llamalad7.mixinextras.injector.wrapoperation.Operation; +import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation; +import java.util.function.Supplier; +import net.minecraft.core.BlockPos; +import net.minecraft.world.entity.PathfinderMob; +import net.minecraft.world.entity.ai.util.DefaultRandomPos; +import net.minecraft.world.entity.ai.util.GoalUtils; +import net.minecraft.world.entity.ai.util.RandomPos; +import net.minecraft.world.phys.AABB; +import net.minecraft.world.phys.Vec3; +import org.joml.Vector3d; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; +import org.valkyrienskies.core.api.ships.LoadedShip; +import org.valkyrienskies.mod.common.VSGameUtilsKt; +import org.valkyrienskies.mod.common.util.VectorConversionsMCKt; + +/** + * @author Tomato + * Should allow for mobs to pathfind on ships. + */ +@Mixin(DefaultRandomPos.class) +public class MixinDefaultRandomPos { + @Inject( + method = "generateRandomPosTowardDirection", + at = @At( + value = "HEAD" + ), + cancellable = true + ) + private static void postGenerateRandomPosTowardDirection(PathfinderMob pathfinderMob, int i, boolean bl, + BlockPos blockPos, final CallbackInfoReturnable cir) { + if (pathfinderMob.level != null) { + final BlockPos blockPos3 = RandomPos.generateRandomPosTowardDirection(pathfinderMob, i, pathfinderMob.getRandom(), blockPos); + if (blockPos3 == null) { + return; + } + AABB checker = new AABB(blockPos3); + Iterable ships = VSGameUtilsKt.getShipObjectWorld(pathfinderMob.level).getLoadedShips().getIntersecting(VectorConversionsMCKt.toJOML(checker), VSGameUtilsKt.getDimensionId(pathfinderMob.level)); + if (ships.iterator().hasNext()) { + for (LoadedShip ship : ships) { + Vector3d posInShip = ship.getWorldToShip() + .transformPosition(VectorConversionsMCKt.toJOMLD(blockPos3), new Vector3d()); + BlockPos blockPosInShip = new BlockPos(VectorConversionsMCKt.toMinecraft(posInShip)); + if (!GoalUtils.isRestricted(bl, pathfinderMob, blockPosInShip) && + !GoalUtils.isNotStable(pathfinderMob.getNavigation(), blockPosInShip) && + !GoalUtils.hasMalus(pathfinderMob, blockPosInShip)) { + cir.setReturnValue(blockPosInShip); + break; + } + } + } + } + } + + @WrapOperation(method = "getPosTowards", at = @At(value = "INVOKE", + target = "Lnet/minecraft/world/entity/ai/util/RandomPos;generateRandomPos(Lnet/minecraft/world/entity/PathfinderMob;Ljava/util/function/Supplier;)Lnet/minecraft/world/phys/Vec3;")) + private static Vec3 redirectGetPosInDirection(PathfinderMob arg, Supplier supplier, + Operation original) { + Vec3 result = original.call(arg, supplier); + if (result != null) { + return VSGameUtilsKt.toWorldCoordinates(arg.level, result); + } + return null; + } + + @WrapOperation(method = "getPos", at = @At(value = "INVOKE", + target = "Lnet/minecraft/world/entity/ai/util/RandomPos;generateRandomPos(Lnet/minecraft/world/entity/PathfinderMob;Ljava/util/function/Supplier;)Lnet/minecraft/world/phys/Vec3;")) + private static Vec3 redirectGetPos(PathfinderMob arg, Supplier supplier, + Operation original) { + Vec3 result = original.call(arg, supplier); + if (result != null) { + return VSGameUtilsKt.toWorldCoordinates(arg.level, result); + } + return null; + } + + +} diff --git a/common/src/main/java/org/valkyrienskies/mod/mixin/feature/ai/path_retargeting/MixinLandRandomPos.java b/common/src/main/java/org/valkyrienskies/mod/mixin/feature/ai/path_retargeting/MixinLandRandomPos.java new file mode 100644 index 000000000..bda84f820 --- /dev/null +++ b/common/src/main/java/org/valkyrienskies/mod/mixin/feature/ai/path_retargeting/MixinLandRandomPos.java @@ -0,0 +1,87 @@ +package org.valkyrienskies.mod.mixin.feature.ai.path_retargeting; + +import static net.minecraft.world.entity.ai.util.LandRandomPos.generateRandomPosTowardDirection; +import static net.minecraft.world.entity.ai.util.LandRandomPos.movePosUpOutOfSolid; + +import com.llamalad7.mixinextras.injector.wrapoperation.Operation; +import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation; +import java.util.function.Supplier; +import java.util.function.ToDoubleFunction; +import net.minecraft.core.BlockPos; +import net.minecraft.world.entity.PathfinderMob; +import net.minecraft.world.entity.ai.util.GoalUtils; +import net.minecraft.world.entity.ai.util.LandRandomPos; +import net.minecraft.world.entity.ai.util.RandomPos; +import net.minecraft.world.phys.AABB; +import net.minecraft.world.phys.Vec3; +import org.joml.Vector3d; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; +import org.valkyrienskies.core.api.ships.LoadedShip; +import org.valkyrienskies.mod.common.VSGameUtilsKt; +import org.valkyrienskies.mod.common.util.VectorConversionsMCKt; + +/** + * @author Tomato + * Should allow for mobs to pathfind on ships. + */ +@Mixin(LandRandomPos.class) +public class MixinLandRandomPos { + + @Inject( + method = "generateRandomPosTowardDirection", + at = @At( + value = "HEAD" + ), + cancellable = true + ) + private static void postGenerateRandomPosTowardDirection(PathfinderMob pathfinderMob, int i, boolean bl, + BlockPos blockPos, CallbackInfoReturnable cir) { + if (pathfinderMob.level != null) { + final BlockPos blockPos3 = RandomPos.generateRandomPosTowardDirection(pathfinderMob, i, pathfinderMob.getRandom(), blockPos); + AABB checker = new AABB(blockPos3); + Iterable ships = VSGameUtilsKt.getShipObjectWorld(pathfinderMob.level).getLoadedShips().getIntersecting(VectorConversionsMCKt.toJOML(checker), VSGameUtilsKt.getDimensionId(pathfinderMob.level)); + if (ships.iterator().hasNext()) { + for (LoadedShip ship : ships) { + Vector3d posInShip = ship.getWorldToShip() + .transformPosition(VectorConversionsMCKt.toJOMLD(blockPos3), new Vector3d()); + BlockPos blockPosInShip = new BlockPos(VectorConversionsMCKt.toMinecraft(posInShip)); + if (!GoalUtils.isRestricted(bl, pathfinderMob, blockPosInShip) && + !GoalUtils.isNotStable(pathfinderMob.getNavigation(), blockPosInShip) && + !GoalUtils.hasMalus(pathfinderMob, blockPosInShip)) { + cir.setReturnValue(blockPosInShip); + break; + } + } + } + } + } + + @WrapOperation(method = "getPosInDirection", at = @At(value = "INVOKE", + target = "Lnet/minecraft/world/entity/ai/util/RandomPos;generateRandomPos(Lnet/minecraft/world/entity/PathfinderMob;Ljava/util/function/Supplier;)Lnet/minecraft/world/phys/Vec3;")) + private static Vec3 redirectGetPosInDirection(PathfinderMob arg, Supplier supplier, + Operation original) { + Vec3 result = original.call(arg, supplier); + if (result != null) { + return VSGameUtilsKt.toWorldCoordinates(arg.level, result); + } + return null; + } + + @Inject(method = "getPos(Lnet/minecraft/world/entity/PathfinderMob;IILjava/util/function/ToDoubleFunction;)Lnet/minecraft/world/phys/Vec3;", at = @At("HEAD"), cancellable = true) + private static void preGetPos(PathfinderMob pathfinderMob, int i, int j, + ToDoubleFunction toDoubleFunction, CallbackInfoReturnable cir) { + boolean bl = GoalUtils.mobRestricted(pathfinderMob, i); + Vec3 randomPos = RandomPos.generateRandomPos(() -> { + BlockPos blockPos = RandomPos.generateRandomDirection(pathfinderMob.getRandom(), i, j); + BlockPos blockPos2 = generateRandomPosTowardDirection(pathfinderMob, i, bl, blockPos); + return blockPos2 == null ? null : movePosUpOutOfSolid(pathfinderMob, blockPos2); + }, toDoubleFunction); + + if (randomPos != null) { + cir.setReturnValue(VSGameUtilsKt.toWorldCoordinates(pathfinderMob.level, randomPos)); + } + } +} diff --git a/common/src/main/java/org/valkyrienskies/mod/mixin/feature/entity_collision/MixinEntity.java b/common/src/main/java/org/valkyrienskies/mod/mixin/feature/entity_collision/MixinEntity.java index f41afc4dd..49aafd521 100644 --- a/common/src/main/java/org/valkyrienskies/mod/mixin/feature/entity_collision/MixinEntity.java +++ b/common/src/main/java/org/valkyrienskies/mod/mixin/feature/entity_collision/MixinEntity.java @@ -8,6 +8,7 @@ import net.minecraft.core.particles.ParticleTypes; import net.minecraft.world.entity.Entity; import net.minecraft.world.entity.EntityDimensions; +import net.minecraft.world.entity.EntityType; import net.minecraft.world.entity.MoverType; import net.minecraft.world.level.Level; import net.minecraft.world.level.block.RenderShape; @@ -38,6 +39,27 @@ public abstract class MixinEntity implements IEntityDraggingInformationProvider // region collision + @Shadow + public abstract void setPos(Vec3 arg); + + @Shadow + public abstract boolean is(Entity arg); + + @Shadow + public abstract boolean isControlledByLocalInstance(); + + @Shadow + public abstract EntityType getType(); + + @Shadow + protected boolean firstTick; + + @Shadow + public abstract Iterable getIndirectPassengers(); + + @Shadow + public abstract BlockPos getOnPos(); + /** * Cancel movement of entities that are colliding with unloaded ships */ @@ -71,9 +93,21 @@ public Vec3 collideWithShips(final Entity entity, Vec3 movement, final Operation if (collisionResultWithWorld.distanceToSqr(movement) > 1e-12) { // We collided with the world? Set the dragging ship to null. final EntityDraggingInformation entityDraggingInformation = getDraggingInformation(); + if (entityDraggingInformation.getIgnoreNextGroundStand()) { + entityDraggingInformation.setIgnoreNextGroundStand(false); + return collisionResultWithWorld; + } entityDraggingInformation.setLastShipStoodOn(null); entityDraggingInformation.setAddedMovementLastTick(new Vector3d()); entityDraggingInformation.setAddedYawRotLastTick(0.0); + + for (Entity entityRiding : entity.getIndirectPassengers()) { + final EntityDraggingInformation passengerDraggingInformation = + ((IEntityDraggingInformationProvider) entityRiding).getDraggingInformation(); + passengerDraggingInformation.setLastShipStoodOn(null); + passengerDraggingInformation.setAddedMovementLastTick(new Vector3d()); + passengerDraggingInformation.setAddedYawRotLastTick(0.0); + } } return collisionResultWithWorld; @@ -214,6 +248,41 @@ private void preSpawnSprintParticle(final CallbackInfo ci) { } } } + + @Inject( + method = "baseTick", + at = @At("TAIL") + ) + private void postBaseTick(final CallbackInfo ci) { + final EntityDraggingInformation entityDraggingInformation = getDraggingInformation(); + + if (level != null && level.isClientSide && !firstTick) { + final Ship ship = VSGameUtilsKt.getShipObjectManagingPos(level, getOnPos()); + if (ship != null) { + entityDraggingInformation.setLastShipStoodOn(ship.getId()); + getIndirectPassengers().forEach(entity -> { + final EntityDraggingInformation passengerDraggingInformation = + ((IEntityDraggingInformationProvider) entity).getDraggingInformation(); + passengerDraggingInformation.setLastShipStoodOn(ship.getId()); + }); + } else { + if (!level.getBlockState(getOnPos()).isAir()) { + if (entityDraggingInformation.getIgnoreNextGroundStand()) { + entityDraggingInformation.setIgnoreNextGroundStand(false); + } else { + entityDraggingInformation.setLastShipStoodOn(null); + getIndirectPassengers().forEach(entity -> { + final EntityDraggingInformation passengerDraggingInformation = + ((IEntityDraggingInformationProvider) entity).getDraggingInformation(); + passengerDraggingInformation.setLastShipStoodOn(null); + }); + } + + } + } + } + } + // endregion // region shadow functions and fields diff --git a/common/src/main/java/org/valkyrienskies/mod/mixin/feature/entity_collision/MixinLivingEntity.java b/common/src/main/java/org/valkyrienskies/mod/mixin/feature/entity_collision/MixinLivingEntity.java new file mode 100644 index 000000000..07b0930ce --- /dev/null +++ b/common/src/main/java/org/valkyrienskies/mod/mixin/feature/entity_collision/MixinLivingEntity.java @@ -0,0 +1,47 @@ +package org.valkyrienskies.mod.mixin.feature.entity_collision; + +import net.minecraft.world.entity.Entity; +import net.minecraft.world.entity.EntityType; +import net.minecraft.world.entity.LivingEntity; +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.level.Level; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; +import org.valkyrienskies.core.api.ships.Ship; +import org.valkyrienskies.mod.common.VSGameUtilsKt; +import org.valkyrienskies.mod.common.util.EntityDraggingInformation; +import org.valkyrienskies.mod.common.util.EntityLerper; +import org.valkyrienskies.mod.common.util.IEntityDraggingInformationProvider; + +@Mixin(LivingEntity.class) +public abstract class MixinLivingEntity extends Entity { + + public MixinLivingEntity(EntityType entityType, Level level) { + super(entityType, level); + } + + /** + * @author Tomato + * @reason Adjusted lerping for entities being dragged by ships. + */ + @Inject( + method = "tick", + at = @At(value = "INVOKE", target = "Lnet/minecraft/world/entity/LivingEntity;aiStep()V", shift = At.Shift.BEFORE) + ) + private void preAiStep(CallbackInfo ci) { + // fake lerp movement gaming + if (this.level != null && this.level.isClientSide() && !firstTick) { + if (this.isControlledByLocalInstance() || (((Entity) this instanceof Player player) && player.isLocalPlayer())) return; + EntityDraggingInformation dragInfo = ((IEntityDraggingInformationProvider) this).getDraggingInformation(); + if (dragInfo != null && dragInfo.getLastShipStoodOn() != null) { + final Ship ship = VSGameUtilsKt.getShipObjectWorld(level).getAllShips().getById(dragInfo.getLastShipStoodOn()); + if (ship != null) { + EntityLerper.INSTANCE.lerpStep(dragInfo, ship, (LivingEntity) (Object) this); + EntityLerper.INSTANCE.lerpHeadStep(dragInfo, ship, (LivingEntity) (Object) this); + } + } + } + } +} diff --git a/common/src/main/java/org/valkyrienskies/mod/mixin/feature/entity_movement_packets/MixinLocalPlayer.java b/common/src/main/java/org/valkyrienskies/mod/mixin/feature/entity_movement_packets/MixinLocalPlayer.java new file mode 100644 index 000000000..6c0d1e818 --- /dev/null +++ b/common/src/main/java/org/valkyrienskies/mod/mixin/feature/entity_movement_packets/MixinLocalPlayer.java @@ -0,0 +1,99 @@ +package org.valkyrienskies.mod.mixin.feature.entity_movement_packets; + +import com.llamalad7.mixinextras.injector.wrapoperation.Operation; +import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation; +import net.minecraft.client.Minecraft; +import net.minecraft.client.multiplayer.ClientPacketListener; +import net.minecraft.client.player.LocalPlayer; +import net.minecraft.network.protocol.Packet; +import net.minecraft.network.protocol.game.ServerboundMovePlayerPacket; +import net.minecraft.network.protocol.game.ServerboundMoveVehiclePacket; +import net.minecraft.world.entity.Entity; +import net.minecraft.world.entity.EntityType; +import net.minecraft.world.level.Level; +import org.joml.Vector3d; +import org.joml.Vector3dc; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; +import org.spongepowered.asm.mixin.injection.At; +import org.valkyrienskies.core.api.ships.ClientShip; +import org.valkyrienskies.mod.common.VSGameUtilsKt; +import org.valkyrienskies.mod.common.ValkyrienSkiesMod; +import org.valkyrienskies.mod.common.networking.PacketEntityShipMotion; +import org.valkyrienskies.mod.common.networking.PacketPlayerShipMotion; +import org.valkyrienskies.mod.common.util.EntityLerper; +import org.valkyrienskies.mod.common.util.IEntityDraggingInformationProvider; +import org.valkyrienskies.mod.common.util.VectorConversionsMCKt; + +@Mixin(LocalPlayer.class) +public abstract class MixinLocalPlayer extends Entity implements IEntityDraggingInformationProvider { + @Shadow + public abstract float getViewYRot(float f); + + public MixinLocalPlayer(EntityType entityType, Level level) { + super(entityType, level); + } + + /** + * @author Tomato + * @reason Intercept client -> server player position sending to send our own data. + */ + @WrapOperation(method = "sendPosition", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/multiplayer/ClientPacketListener;send(Lnet/minecraft/network/protocol/Packet;)V")) + private void wrapSendPosition(ClientPacketListener instance, Packet arg, Operation original) { + Packet realArg = arg; + if (getDraggingInformation().isEntityBeingDraggedByAShip()) { + if (getDraggingInformation().getLastShipStoodOn() != null) { + ClientShip ship = VSGameUtilsKt.getShipObjectWorld(Minecraft.getInstance().level).getAllShips().getById(getDraggingInformation().getLastShipStoodOn()); + if (ship != null) { + Vector3dc relativePosition = ship.getWorldToShip().transformPosition( + VectorConversionsMCKt.toJOML(getPosition(1f)), new Vector3d()); + + double relativeYaw = EntityLerper.INSTANCE.yawToShip(ship, getViewYRot(1f)); + + PacketPlayerShipMotion packet = new PacketPlayerShipMotion(ship.getId(), relativePosition.x(), relativePosition.y(), relativePosition.z(), relativeYaw); + ValkyrienSkiesMod.getVsCore().getSimplePacketNetworking().sendToServer(packet); + } + } + if (realArg instanceof ServerboundMovePlayerPacket movePacket) { + final boolean isOnGround = movePacket.isOnGround() || getDraggingInformation().isEntityBeingDraggedByAShip(); + if (movePacket.hasPosition() && movePacket.hasRotation()) { + //posrot + realArg = new ServerboundMovePlayerPacket.PosRot(movePacket.getX(0.0), movePacket.getY(0.0), movePacket.getZ(0.0), movePacket.getYRot(0.0f), movePacket.getXRot(0.0f), isOnGround); + } else if (movePacket.hasPosition()) { + //pos + realArg = new ServerboundMovePlayerPacket.Pos(movePacket.getX(0.0), movePacket.getY(0.0), movePacket.getZ(0.0), isOnGround); + } else if (movePacket.hasRotation()) { + //rot + realArg = new ServerboundMovePlayerPacket.Rot(movePacket.getYRot(0.0f), movePacket.getXRot(0.0f), isOnGround); + } else { + //status only + realArg = new ServerboundMovePlayerPacket.StatusOnly(isOnGround()); + } + } + } + original.call(instance, realArg); + } + + @WrapOperation(method = "tick", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/multiplayer/ClientPacketListener;send(Lnet/minecraft/network/protocol/Packet;)V")) + private void wrapSendVehiclePosition(ClientPacketListener instance, Packet arg, Operation original) { + if (arg instanceof ServerboundMoveVehiclePacket vehiclePacket && getRootVehicle() instanceof IEntityDraggingInformationProvider dragProvider) { + if (dragProvider.getDraggingInformation().isEntityBeingDraggedByAShip()) { + if (dragProvider.getDraggingInformation().getLastShipStoodOn() != null) { + ClientShip ship = VSGameUtilsKt.getShipObjectWorld(Minecraft.getInstance().level).getAllShips().getById( + getDraggingInformation().getLastShipStoodOn()); + if (ship != null) { + Vector3dc relativePosition = ship.getWorldToShip().transformPosition( + VectorConversionsMCKt.toJOML(getRootVehicle().getPosition(1f)), new Vector3d()); + + double relativeYaw = EntityLerper.INSTANCE.yawToShip(ship, getRootVehicle().getYRot()); + + PacketEntityShipMotion packet = new PacketEntityShipMotion(getRootVehicle().getId(), ship.getId(), relativePosition.x(), relativePosition.y(), relativePosition.z(), 0.0, 0.0, 0.0, relativeYaw, 0.0); + ValkyrienSkiesMod.getVsCore().getSimplePacketNetworking().sendToServer(packet); + } + } + } + } + original.call(instance, arg); + } + +} diff --git a/common/src/main/java/org/valkyrienskies/mod/mixin/feature/entity_movement_packets/MixinServerEntity.java b/common/src/main/java/org/valkyrienskies/mod/mixin/feature/entity_movement_packets/MixinServerEntity.java new file mode 100644 index 000000000..9abd36a73 --- /dev/null +++ b/common/src/main/java/org/valkyrienskies/mod/mixin/feature/entity_movement_packets/MixinServerEntity.java @@ -0,0 +1,91 @@ +package org.valkyrienskies.mod.mixin.feature.entity_movement_packets; + +import com.llamalad7.mixinextras.injector.wrapoperation.Operation; +import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation; +import java.util.function.Consumer; +import net.minecraft.network.protocol.game.ClientboundMoveEntityPacket; +import net.minecraft.network.protocol.game.ClientboundRotateHeadPacket; +import net.minecraft.network.protocol.game.ClientboundSetEntityMotionPacket; +import net.minecraft.network.protocol.game.ClientboundTeleportEntityPacket; +import net.minecraft.server.level.ServerEntity; +import net.minecraft.server.level.ServerLevel; +import net.minecraft.world.entity.Entity; +import org.joml.Vector3d; +import org.spongepowered.asm.mixin.Final; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; +import org.spongepowered.asm.mixin.injection.At; +import org.valkyrienskies.core.api.ships.ServerShip; +import org.valkyrienskies.core.impl.networking.simple.SimplePacket; +import org.valkyrienskies.mod.common.VSGameUtilsKt; +import org.valkyrienskies.mod.common.ValkyrienSkiesMod; +import org.valkyrienskies.mod.common.networking.PacketEntityShipMotion; +import org.valkyrienskies.mod.common.networking.PacketMobShipRotation; +import org.valkyrienskies.mod.common.util.EntityDraggingInformation; +import org.valkyrienskies.mod.common.util.EntityLerper; +import org.valkyrienskies.mod.common.util.IEntityDraggingInformationProvider; + +@Mixin(ServerEntity.class) +public class MixinServerEntity { + + @Shadow + @Final + private Entity entity; + + @Shadow + @Final + private ServerLevel level; + + /** + * @author Tomato + * @reason Intercept entity motion packets to send our own data, then cancel the original packet. + */ + @WrapOperation( + method = "sendChanges", + at = @At( + value = "INVOKE", + target = "Ljava/util/function/Consumer;accept(Ljava/lang/Object;)V") + ) + private void wrapBroadcastAccept(Consumer instance, Object t, Operation original) { + if (t instanceof ClientboundSetEntityMotionPacket || t instanceof ClientboundTeleportEntityPacket || t instanceof ClientboundMoveEntityPacket || t instanceof ClientboundRotateHeadPacket) { + if (entity instanceof IEntityDraggingInformationProvider draggedEntity) { + EntityDraggingInformation dragInfo = draggedEntity.getDraggingInformation(); + + if (dragInfo != null && dragInfo.isEntityBeingDraggedByAShip() && dragInfo.getLastShipStoodOn() != null) { + ServerShip ship = VSGameUtilsKt.getShipObjectWorld(level).getAllShips().getById(dragInfo.getLastShipStoodOn()); + if (ship != null) { + + Vector3d position = ship.getWorldToShip().transformPosition(new Vector3d(entity.getX(), entity.getY(), entity.getZ())); + if (dragInfo.getServerRelativePlayerPosition() != null) { + position = new Vector3d(dragInfo.getServerRelativePlayerPosition()); + } + Vector3d motion = ship.getTransform().transformDirectionNoScalingFromWorldToShip(new Vector3d(entity.getDeltaMovement().x(), entity.getDeltaMovement().y(), entity.getDeltaMovement().z()), new Vector3d()); + + double yaw; + if (!(t instanceof ClientboundRotateHeadPacket)) { + yaw = EntityLerper.INSTANCE.yawToShip(ship, entity.getYRot()); + } else { + yaw = EntityLerper.INSTANCE.yawToShip(ship, entity.getYHeadRot()); + } + double pitch = entity.getXRot(); + SimplePacket vsPacket; + if (!(t instanceof ClientboundRotateHeadPacket)) { + vsPacket = new PacketEntityShipMotion(entity.getId(), ship.getId(), + position.x, position.y, position.z, + motion.x, motion.y, motion.z, + yaw, pitch); + } else { + vsPacket = new PacketMobShipRotation(entity.getId(), ship.getId(), yaw, pitch); + } + + ValkyrienSkiesMod.getVsCore().getSimplePacketNetworking().sendToAllClients(vsPacket); + return; + } + + + } + } + } + original.call(instance, t); + } +} diff --git a/common/src/main/java/org/valkyrienskies/mod/mixin/feature/poi/FEATURE.md b/common/src/main/java/org/valkyrienskies/mod/mixin/feature/poi/FEATURE.md new file mode 100644 index 000000000..3928d45a2 --- /dev/null +++ b/common/src/main/java/org/valkyrienskies/mod/mixin/feature/poi/FEATURE.md @@ -0,0 +1 @@ +Allows Points of Interest (portals, villager profession stations, beehives, lightning rods) to function on ships. diff --git a/common/src/main/java/org/valkyrienskies/mod/mixin/feature/poi/MixinPOIManager.java b/common/src/main/java/org/valkyrienskies/mod/mixin/feature/poi/MixinPOIManager.java new file mode 100644 index 000000000..146597a60 --- /dev/null +++ b/common/src/main/java/org/valkyrienskies/mod/mixin/feature/poi/MixinPOIManager.java @@ -0,0 +1,85 @@ +package org.valkyrienskies.mod.mixin.feature.poi; + +import com.llamalad7.mixinextras.injector.wrapoperation.Operation; +import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation; +import com.llamalad7.mixinextras.sugar.Local; +import java.util.function.Predicate; +import java.util.stream.Stream; +import net.minecraft.core.BlockPos; +import net.minecraft.server.level.ServerLevel; +import net.minecraft.world.entity.ai.village.poi.PoiManager; +import net.minecraft.world.entity.ai.village.poi.PoiManager.Occupancy; +import net.minecraft.world.entity.ai.village.poi.PoiRecord; +import net.minecraft.world.entity.ai.village.poi.PoiType; +import net.minecraft.world.level.ChunkPos; +import net.minecraft.world.level.Level; +import net.minecraft.world.phys.AABB; +import net.minecraft.world.phys.Vec3; +import org.joml.Vector4ic; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Overwrite; +import org.spongepowered.asm.mixin.Shadow; +import org.spongepowered.asm.mixin.Unique; +import org.spongepowered.asm.mixin.injection.At; +import org.valkyrienskies.core.api.ships.LoadedServerShip; +import org.valkyrienskies.mod.common.VSGameUtilsKt; +import org.valkyrienskies.mod.common.util.VectorConversionsMCKt; +import org.valkyrienskies.mod.common.world.POIChunkSearcher; +import org.valkyrienskies.mod.mixinducks.world.OfLevel; + +/** + * @author Tomato + * This atrocious mess of a mixin allows POIs in ship space to be detected, however it requires further mixins to Goals for the ship space positions to be correctly used, from what I can understand. + */ +@Mixin(PoiManager.class) +public abstract class MixinPOIManager implements OfLevel { + + @Unique + private Level valkyrienskies$sLevel; + + @Shadow + public abstract Stream getInChunk(Predicate predicate, ChunkPos chunkPos, Occupancy occupancy); + + /** + * @author Tomato + * @reason Allows for ships to be considered as a valid POI, also this method sucks anyway. + */ + @Overwrite + public Stream getInSquare(Predicate predicate, BlockPos blockPos, int i, Occupancy occupancy) { + int j = Math.floorDiv(i, 16) + 1; + final AABB aABB = new AABB(blockPos).inflate((double) i + 1); + Stream chunkRange = ChunkPos.rangeClosed(new ChunkPos(blockPos), j); + if (this.valkyrienskies$sLevel instanceof ServerLevel sLevel) { + for (LoadedServerShip ship : VSGameUtilsKt.getShipObjectWorld(sLevel).getLoadedShips().getIntersecting( + VectorConversionsMCKt.toJOML(aABB), VSGameUtilsKt.getDimensionId(sLevel))) { + Vector4ic chunkRangeBounds = POIChunkSearcher.INSTANCE.shipChunkBounds(ship.getActiveChunksSet()); + if (chunkRangeBounds == null) { + continue; + } + chunkRange = Stream.concat(chunkRange, ChunkPos.rangeClosed(new ChunkPos(chunkRangeBounds.x(), chunkRangeBounds.y()), + new ChunkPos(chunkRangeBounds.z(), chunkRangeBounds.w()))); + } + } + return chunkRange.flatMap((chunkPos) -> this.getInChunk(predicate, chunkPos, occupancy)).filter((poiRecord) -> { + BlockPos blockPos2 = poiRecord.getPos(); + Vec3 vecPos = VSGameUtilsKt.toWorldCoordinates(valkyrienskies$sLevel, new Vec3(blockPos2.getX(), blockPos2.getY(), blockPos2.getZ())); + return Math.abs(vecPos.x() - blockPos.getX()) <= i && Math.abs(vecPos.z() - blockPos.getZ()) <= i; + }); + } + + @WrapOperation(method = "getInRange", at = @At(value = "INVOKE", target = "Ljava/util/stream/Stream;filter(Ljava/util/function/Predicate;)Ljava/util/stream/Stream;")) + private Stream onGetInRange(Stream instance, Predicate predicate, Operation> original, @Local(argsOnly = true) BlockPos arg, @Local(argsOnly = true) int i) { + final int k = i * i; + return instance.filter(poiRecord -> POIChunkSearcher.INSTANCE.getWorldPos(poiRecord, this.valkyrienskies$sLevel).distanceToSqr(Vec3.atLowerCornerOf(arg)) <= (double)k); + } + + @Override + public Level getLevel() { + return valkyrienskies$sLevel; + } + + @Override + public void setLevel(Level level) { + valkyrienskies$sLevel = level; + } +} diff --git a/common/src/main/java/org/valkyrienskies/mod/mixin/feature/screen_distance_check/MixinScreenHandler.java b/common/src/main/java/org/valkyrienskies/mod/mixin/feature/screen_distance_check/MixinScreenHandler.java index c8f5a8e4b..8a7e524ec 100644 --- a/common/src/main/java/org/valkyrienskies/mod/mixin/feature/screen_distance_check/MixinScreenHandler.java +++ b/common/src/main/java/org/valkyrienskies/mod/mixin/feature/screen_distance_check/MixinScreenHandler.java @@ -2,10 +2,13 @@ import net.minecraft.world.entity.player.Player; import net.minecraft.world.inventory.AbstractContainerMenu; +import net.minecraft.world.phys.Vec3; import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.injection.At; import org.spongepowered.asm.mixin.injection.Redirect; import org.valkyrienskies.mod.common.VSGameUtilsKt; +import org.valkyrienskies.mod.common.util.EntityDragger; +import org.valkyrienskies.mod.common.util.IEntityDraggingInformationProvider; @Mixin(AbstractContainerMenu.class) public class MixinScreenHandler { @@ -22,7 +25,8 @@ public class MixinScreenHandler { ) private static double includeShipsInDistanceCheck( final Player receiver, final double x, final double y, final double z) { - return VSGameUtilsKt.squaredDistanceToInclShips(receiver, x, y, z); + final Vec3 eye = EntityDragger.INSTANCE.serversideEyePosition(receiver); + return VSGameUtilsKt.squaredDistanceToInclShips(receiver, eye.x, eye.y, eye.z); } } diff --git a/common/src/main/java/org/valkyrienskies/mod/mixin/feature/wave_spawning/FEATURE.md b/common/src/main/java/org/valkyrienskies/mod/mixin/feature/wave_spawning/FEATURE.md new file mode 100644 index 000000000..1b88c36bf --- /dev/null +++ b/common/src/main/java/org/valkyrienskies/mod/mixin/feature/wave_spawning/FEATURE.md @@ -0,0 +1 @@ +Allows Raids and Zombie Sieges to spawn surrounding ships that count as villages. diff --git a/common/src/main/java/org/valkyrienskies/mod/mixin/feature/wave_spawning/MixinRaid.java b/common/src/main/java/org/valkyrienskies/mod/mixin/feature/wave_spawning/MixinRaid.java new file mode 100644 index 000000000..f14d11f2f --- /dev/null +++ b/common/src/main/java/org/valkyrienskies/mod/mixin/feature/wave_spawning/MixinRaid.java @@ -0,0 +1,51 @@ +package org.valkyrienskies.mod.mixin.feature.wave_spawning; + +import com.llamalad7.mixinextras.injector.wrapoperation.Operation; +import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation; +import net.minecraft.core.BlockPos; +import net.minecraft.server.level.ServerLevel; +import net.minecraft.world.entity.raid.Raid; +import org.spongepowered.asm.mixin.Final; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; +import org.spongepowered.asm.mixin.injection.At; +import org.valkyrienskies.mod.common.VSGameUtilsKt; + +@Mixin(Raid.class) +public abstract class MixinRaid { + @Shadow + public abstract BlockPos getCenter(); + + @Shadow + protected abstract void setCenter(BlockPos blockPos); + + @Shadow + @Final + private ServerLevel level; + + @WrapOperation(method = "getValidSpawnPos", at = @At(value = "INVOKE", target = "Lnet/minecraft/world/entity/raid/Raid;findRandomSpawnPos(II)Lnet/minecraft/core/BlockPos;")) + private BlockPos onFindRandomSpawnPos(Raid instance, int i, int j, Operation original) { + BlockPos originalCenter = this.getCenter(); + this.setCenter(new BlockPos(VSGameUtilsKt.toWorldCoordinates(this.level, originalCenter))); + BlockPos result = original.call(instance, i, j); + this.setCenter(originalCenter); + if (result != null) { + return result; + } else { + return original.call(instance, i, j); + } + } + + @WrapOperation(method = "tick", at = @At(value = "INVOKE", target = "Lnet/minecraft/world/entity/raid/Raid;findRandomSpawnPos(II)Lnet/minecraft/core/BlockPos;")) + private BlockPos onFindRandomSpawnPos2(Raid instance, int i, int j, Operation original) { + BlockPos originalCenter = this.getCenter(); + this.setCenter(new BlockPos(VSGameUtilsKt.toWorldCoordinates(this.level, originalCenter))); + BlockPos result = original.call(instance, i, j); + this.setCenter(originalCenter); + if (result != null) { + return result; + } else { + return original.call(instance, i, j); + } + } +} diff --git a/common/src/main/java/org/valkyrienskies/mod/mixin/feature/wave_spawning/MixinRaids.java b/common/src/main/java/org/valkyrienskies/mod/mixin/feature/wave_spawning/MixinRaids.java new file mode 100644 index 000000000..b0411192b --- /dev/null +++ b/common/src/main/java/org/valkyrienskies/mod/mixin/feature/wave_spawning/MixinRaids.java @@ -0,0 +1,31 @@ +package org.valkyrienskies.mod.mixin.feature.wave_spawning; + +import com.llamalad7.mixinextras.injector.wrapoperation.Operation; +import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation; +import java.util.Map; +import net.minecraft.core.BlockPos; +import net.minecraft.server.level.ServerLevel; +import net.minecraft.world.entity.raid.Raid; +import net.minecraft.world.entity.raid.Raids; +import org.spongepowered.asm.mixin.Final; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; +import org.spongepowered.asm.mixin.injection.At; +import org.valkyrienskies.mod.common.VSGameUtilsKt; + +@Mixin(Raids.class) +public class MixinRaids { + @Shadow + @Final + private ServerLevel level; + + @Shadow + @Final + private Map raidMap; + + @WrapOperation(method = "getNearbyRaid", at = @At(value = "INVOKE", + target = "Lnet/minecraft/world/entity/raid/Raid;getCenter()Lnet/minecraft/core/BlockPos;")) + private BlockPos onGetNearbyRaid(Raid instance, Operation original) { + return new BlockPos(VSGameUtilsKt.toWorldCoordinates(this.level, original.call(instance))); + } +} diff --git a/common/src/main/java/org/valkyrienskies/mod/mixin/feature/wave_spawning/MixinVillageSiege.java b/common/src/main/java/org/valkyrienskies/mod/mixin/feature/wave_spawning/MixinVillageSiege.java new file mode 100644 index 000000000..2c50de359 --- /dev/null +++ b/common/src/main/java/org/valkyrienskies/mod/mixin/feature/wave_spawning/MixinVillageSiege.java @@ -0,0 +1,36 @@ +package org.valkyrienskies.mod.mixin.feature.wave_spawning; + +import com.llamalad7.mixinextras.injector.wrapoperation.Operation; +import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation; +import net.minecraft.core.BlockPos; +import net.minecraft.server.level.ServerLevel; +import net.minecraft.world.entity.ai.village.VillageSiege; +import net.minecraft.world.phys.Vec3; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.valkyrienskies.mod.common.VSGameUtilsKt; + +@Mixin(VillageSiege.class) +public class MixinVillageSiege { + @WrapOperation(method = "tryToSetupSiege", at = @At(value = "INVOKE", target = "Lnet/minecraft/world/entity/ai/village/VillageSiege;findRandomSpawnPos(Lnet/minecraft/server/level/ServerLevel;Lnet/minecraft/core/BlockPos;)Lnet/minecraft/world/phys/Vec3;")) + private Vec3 onFindRandomSpawnPos(VillageSiege instance, ServerLevel k, BlockPos l, Operation original) { + BlockPos transformedCenter = new BlockPos(VSGameUtilsKt.toWorldCoordinates(k, l)); + Vec3 result = original.call(instance, k, transformedCenter); + if (result != null) { + return result; + } else { + return original.call(instance, k, l); + } + } + + @WrapOperation(method = "trySpawn", at = @At(value = "INVOKE", target = "Lnet/minecraft/world/entity/ai/village/VillageSiege;findRandomSpawnPos(Lnet/minecraft/server/level/ServerLevel;Lnet/minecraft/core/BlockPos;)Lnet/minecraft/world/phys/Vec3;")) + private Vec3 onFindRandomSpawnPos2(VillageSiege instance, ServerLevel k, BlockPos l, Operation original) { + BlockPos transformedCenter = new BlockPos(VSGameUtilsKt.toWorldCoordinates(k, l)); + Vec3 result = original.call(instance, k, transformedCenter); + if (result != null) { + return result; + } else { + return original.call(instance, k, l); + } + } +} diff --git a/common/src/main/java/org/valkyrienskies/mod/mixin/mod_compat/reachentityattributes/MixinReachEntityAttributes.java b/common/src/main/java/org/valkyrienskies/mod/mixin/mod_compat/reachentityattributes/MixinReachEntityAttributes.java index d6f48d905..85dde7b7a 100644 --- a/common/src/main/java/org/valkyrienskies/mod/mixin/mod_compat/reachentityattributes/MixinReachEntityAttributes.java +++ b/common/src/main/java/org/valkyrienskies/mod/mixin/mod_compat/reachentityattributes/MixinReachEntityAttributes.java @@ -7,11 +7,13 @@ import net.minecraft.world.entity.LivingEntity; import net.minecraft.world.entity.player.Player; import net.minecraft.world.level.Level; +import net.minecraft.world.phys.Vec3; import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.Overwrite; import org.spongepowered.asm.mixin.Pseudo; import org.spongepowered.asm.mixin.Shadow; import org.valkyrienskies.mod.common.VSGameUtilsKt; +import org.valkyrienskies.mod.common.util.EntityDragger; @Pseudo @Mixin(ReachEntityAttributes.class) @@ -24,6 +26,8 @@ public static double getReachDistance(final LivingEntity entity, final double ba /** * @author Triode * @reason Fix getting players within reach of ship blocks + * Additionally, we use the custom serversideEyePosition method from EntityDragger because otherwise we'd be invasively changing + * the eye position of the player, which many mods use and would absolutely undoubtedly break things. */ @Overwrite public static List getPlayersWithinReach(final Predicate viewerPredicate, final Level world, final int x, final int y, final int z, final double baseReachDistance) { @@ -31,7 +35,8 @@ public static List getPlayersWithinReach(final Predicate viewerP for (final Player player : world.players()) { if (viewerPredicate.test(player)) { final var reach = getReachDistance(player, baseReachDistance); - if (VSGameUtilsKt.squaredDistanceBetweenInclShips(world, x + 0.5, y + 0.5, z + 0.5, player.getX(), player.getEyeY(), player.getZ()) <= (reach * reach)) { + final Vec3 eye = EntityDragger.INSTANCE.serversideEyePosition(player); + if (VSGameUtilsKt.squaredDistanceBetweenInclShips(world, x + 0.5, y + 0.5, z + 0.5, eye.x, eye.y, eye.z) <= (reach * reach)) { playersWithinReach.add(player); } } diff --git a/common/src/main/java/org/valkyrienskies/mod/mixin/server/MixinMinecraftServer.java b/common/src/main/java/org/valkyrienskies/mod/mixin/server/MixinMinecraftServer.java index e7f6c3e42..998779188 100644 --- a/common/src/main/java/org/valkyrienskies/mod/mixin/server/MixinMinecraftServer.java +++ b/common/src/main/java/org/valkyrienskies/mod/mixin/server/MixinMinecraftServer.java @@ -202,6 +202,9 @@ private void preTick(final CallbackInfo ci) { // endregion vsPipeline.preTickGame(); + for (final ServerLevel level : getAllLevels()) { + //EntityDragger.INSTANCE.dragEntitiesWithShips(level.getAllEntities(), true); + } } /** @@ -233,7 +236,7 @@ private void postTick(final CallbackInfo ci) { vsPipeline.postTickGame(); // Only drag entities after we have updated the ship positions for (final ServerLevel level : getAllLevels()) { - EntityDragger.INSTANCE.dragEntitiesWithShips(level.getAllEntities()); + EntityDragger.INSTANCE.dragEntitiesWithShips(level.getAllEntities(), false); if (LoadedMods.getWeather2()) Weather2Compat.INSTANCE.tick(level); } diff --git a/common/src/main/java/org/valkyrienskies/mod/mixin/server/network/MixinServerGamePacketListenerImpl.java b/common/src/main/java/org/valkyrienskies/mod/mixin/server/network/MixinServerGamePacketListenerImpl.java index b7c29d647..0e82ecbbd 100644 --- a/common/src/main/java/org/valkyrienskies/mod/mixin/server/network/MixinServerGamePacketListenerImpl.java +++ b/common/src/main/java/org/valkyrienskies/mod/mixin/server/network/MixinServerGamePacketListenerImpl.java @@ -9,6 +9,7 @@ import net.minecraft.network.chat.Component; import net.minecraft.network.protocol.Packet; import net.minecraft.network.protocol.game.ClientboundPlayerPositionPacket; +import net.minecraft.network.protocol.game.ServerboundMovePlayerPacket; import net.minecraft.server.MinecraftServer; import net.minecraft.server.level.ServerLevel; import net.minecraft.server.level.ServerPlayer; @@ -17,6 +18,7 @@ import net.minecraft.world.level.ChunkPos; import net.minecraft.world.phys.Vec3; import org.joml.Vector3d; +import org.slf4j.Logger; import org.spongepowered.asm.mixin.Final; import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.Shadow; @@ -28,6 +30,7 @@ import org.valkyrienskies.mod.common.VSGameUtilsKt; import org.valkyrienskies.mod.common.config.VSGameConfig; import org.valkyrienskies.mod.common.util.VectorConversionsMCKt; +import org.valkyrienskies.mod.mixinducks.world.entity.PlayerDuck; @Mixin(ServerGamePacketListenerImpl.class) public abstract class MixinServerGamePacketListenerImpl { @@ -54,6 +57,10 @@ public abstract class MixinServerGamePacketListenerImpl { @Final private MinecraftServer server; + @Shadow + @Final + private static Logger LOGGER; + @ModifyExpressionValue( at = @At(value = "FIELD", target = "Lnet/minecraft/server/network/ServerGamePacketListenerImpl;aboveGroundTickCount:I", ordinal = 0), @@ -177,4 +184,18 @@ void onDisconnect(final Component reason, final CallbackInfo ci) { } } + @Inject( + method = "handleMovePlayer", + at = @At("TAIL") + ) + void afterHandleMovePlayer(ServerboundMovePlayerPacket serverboundMovePlayerPacket, CallbackInfo ci) { + if (this.player instanceof PlayerDuck duck) { + duck.vs_setHandledMovePacket(true); + if (duck.vs_getQueuedPositionUpdate() != null) { + this.player.setPos(duck.vs_getQueuedPositionUpdate()); + duck.vs_setQueuedPositionUpdate(null); + } + } + } + } diff --git a/common/src/main/java/org/valkyrienskies/mod/mixin/server/world/MixinChunkMap.java b/common/src/main/java/org/valkyrienskies/mod/mixin/server/world/MixinChunkMap.java index 590b3c56d..316fc4c64 100644 --- a/common/src/main/java/org/valkyrienskies/mod/mixin/server/world/MixinChunkMap.java +++ b/common/src/main/java/org/valkyrienskies/mod/mixin/server/world/MixinChunkMap.java @@ -1,14 +1,20 @@ package org.valkyrienskies.mod.mixin.server.world; +import com.llamalad7.mixinextras.injector.wrapoperation.Operation; +import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation; +import com.llamalad7.mixinextras.sugar.Local; import java.util.ArrayList; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Set; import java.util.function.Supplier; +import net.minecraft.core.BlockPos; import net.minecraft.server.level.ChunkMap; +import net.minecraft.server.level.ChunkMap.DistanceManager; import net.minecraft.server.level.ServerLevel; import net.minecraft.server.level.ServerPlayer; +import net.minecraft.world.entity.Entity; import net.minecraft.world.level.ChunkPos; import net.minecraft.world.level.storage.DimensionDataStorage; import org.spongepowered.asm.mixin.Final; @@ -102,4 +108,13 @@ private void postGetPlayersWatchingChunk(final ChunkPos chunkPos, final boolean cir.setReturnValue(new ArrayList<>(watchingPlayers)); } + @WrapOperation(method = "anyPlayerCloseEnoughForSpawning", at = @At(value = "INVOKE", target = "Lnet/minecraft/server/level/ChunkMap$DistanceManager;hasPlayersNearby(J)Z")) + private boolean onHasPlayersNearby(DistanceManager instance, long l, Operation original, @Local(argsOnly = true) ChunkPos arg) { + return original.call(instance, new ChunkPos(new BlockPos(VSGameUtilsKt.toWorldCoordinates(level, arg.getMiddleBlockPosition(63)))).toLong()); + } + + @WrapOperation(method = "playerIsCloseEnoughForSpawning", at = @At(value = "INVOKE", target = "Lnet/minecraft/server/level/ChunkMap;euclideanDistanceSquared(Lnet/minecraft/world/level/ChunkPos;Lnet/minecraft/world/entity/Entity;)D")) + private double onEuclideanDistanceSquared(ChunkPos d0, Entity d1, Operation original) { + return original.call(new ChunkPos(new BlockPos(VSGameUtilsKt.toWorldCoordinates(level, d0.getMiddleBlockPosition(63)))), d1); + } } diff --git a/common/src/main/java/org/valkyrienskies/mod/mixin/server/world/MixinServerLevel.java b/common/src/main/java/org/valkyrienskies/mod/mixin/server/world/MixinServerLevel.java index 0f48547f7..3c60e4837 100644 --- a/common/src/main/java/org/valkyrienskies/mod/mixin/server/world/MixinServerLevel.java +++ b/common/src/main/java/org/valkyrienskies/mod/mixin/server/world/MixinServerLevel.java @@ -18,12 +18,14 @@ import net.minecraft.core.BlockPos.MutableBlockPos; import net.minecraft.core.Holder; import net.minecraft.core.Position; +import net.minecraft.core.SectionPos; import net.minecraft.resources.ResourceKey; import net.minecraft.server.MinecraftServer; import net.minecraft.server.level.ChunkHolder; import net.minecraft.server.level.ServerChunkCache; import net.minecraft.server.level.ServerLevel; import net.minecraft.server.level.progress.ChunkProgressListener; +import net.minecraft.world.entity.ai.village.poi.PoiManager; import net.minecraft.world.level.ChunkPos; import net.minecraft.world.level.block.state.BlockState; import net.minecraft.world.level.chunk.ChunkAccess; @@ -44,6 +46,7 @@ import org.spongepowered.asm.mixin.injection.At; import org.spongepowered.asm.mixin.injection.Inject; import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; import org.valkyrienskies.core.api.ships.LoadedServerShip; import org.valkyrienskies.core.api.ships.Wing; import org.valkyrienskies.core.api.ships.WingManager; @@ -57,6 +60,7 @@ import org.valkyrienskies.mod.common.util.VectorConversionsMCKt; import org.valkyrienskies.mod.mixin.accessors.server.level.ChunkMapAccessor; import org.valkyrienskies.mod.mixin.accessors.server.level.DistanceManagerAccessor; +import org.valkyrienskies.mod.mixinducks.world.OfLevel; import org.valkyrienskies.mod.util.McMathUtilKt; @Mixin(ServerLevel.class) @@ -69,6 +73,9 @@ public abstract class MixinServerLevel implements IShipObjectWorldServerProvider @NotNull public abstract MinecraftServer getServer(); + @Shadow + public abstract int sectionsToVillage(SectionPos arg); + // Map from ChunkPos to the list of voxel chunks that chunk owns @Unique private final Map> vs$knownChunks = new HashMap<>(); @@ -105,6 +112,26 @@ void onInit(final MinecraftServer minecraftServer, final Executor executor, } } + @Inject(method = "getPoiManager", at = @At("HEAD")) + void onGetPoiManager(CallbackInfoReturnable cir) { + if (chunkSource.getPoiManager() instanceof final OfLevel levelProvider) { + levelProvider.setLevel((ServerLevel) (Object) this); + } + } + + @Inject(method = "isCloseToVillage", at = @At("HEAD"), cancellable = true) + void preIsCloseToVillage(BlockPos blockPos, int i, CallbackInfoReturnable cir) { + if (i <= 6) { + final boolean[] found = {false}; + VSGameUtilsKt.transformToNearbyShipsAndWorld(ServerLevel.class.cast(this), blockPos.getX(), blockPos.getY(), blockPos.getZ(), i * 100.0, (double x, double y, double z) -> { + found[0] = found[0] || this.sectionsToVillage(SectionPos.of(new BlockPos(x, y, z))) <= i; + }); + if (found[0]) { + cir.setReturnValue(true); + } + } + } + /** * Include ships in particle distance check. Seems to only be used by /particle */ diff --git a/common/src/main/java/org/valkyrienskies/mod/mixin/world/entity/MixinPlayer.java b/common/src/main/java/org/valkyrienskies/mod/mixin/world/entity/MixinPlayer.java index d02569924..4436ec460 100644 --- a/common/src/main/java/org/valkyrienskies/mod/mixin/world/entity/MixinPlayer.java +++ b/common/src/main/java/org/valkyrienskies/mod/mixin/world/entity/MixinPlayer.java @@ -1,6 +1,7 @@ package org.valkyrienskies.mod.mixin.world.entity; import net.minecraft.world.entity.player.Player; +import net.minecraft.world.phys.Vec3; import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.Unique; import org.valkyrienskies.mod.common.util.MinecraftPlayer; @@ -12,8 +13,34 @@ public class MixinPlayer implements PlayerDuck { @Unique private final MinecraftPlayer vsPlayer = new MinecraftPlayer(Player.class.cast(this)); + @Unique + private Vec3 queuedPositionUpdate = null; + + @Unique + private boolean handledMovePacket = false; + @Override public MinecraftPlayer vs_getPlayer() { return vsPlayer; } + + @Override + public Vec3 vs_getQueuedPositionUpdate() { + return this.queuedPositionUpdate; + } + + @Override + public void vs_setQueuedPositionUpdate(Vec3 queuedPositionUpdate) { + this.queuedPositionUpdate = queuedPositionUpdate; + } + + @Override + public boolean vs_handledMovePacket() { + return this.handledMovePacket; + } + + @Override + public void vs_setHandledMovePacket(boolean handledMovePacket) { + this.handledMovePacket = handledMovePacket; + } } diff --git a/common/src/main/java/org/valkyrienskies/mod/mixinducks/world/entity/PlayerDuck.java b/common/src/main/java/org/valkyrienskies/mod/mixinducks/world/entity/PlayerDuck.java index ce500ba83..cc48f61bc 100644 --- a/common/src/main/java/org/valkyrienskies/mod/mixinducks/world/entity/PlayerDuck.java +++ b/common/src/main/java/org/valkyrienskies/mod/mixinducks/world/entity/PlayerDuck.java @@ -1,9 +1,15 @@ package org.valkyrienskies.mod.mixinducks.world.entity; +import net.minecraft.world.phys.Vec3; import org.valkyrienskies.mod.common.util.MinecraftPlayer; public interface PlayerDuck { MinecraftPlayer vs_getPlayer(); + Vec3 vs_getQueuedPositionUpdate(); + void vs_setQueuedPositionUpdate(Vec3 queuedPositionUpdate); + + boolean vs_handledMovePacket(); + void vs_setHandledMovePacket(boolean handledMovePacket); } diff --git a/common/src/main/kotlin/org/valkyrienskies/mod/common/PlayerUtil.kt b/common/src/main/kotlin/org/valkyrienskies/mod/common/PlayerUtil.kt index a246a2bb7..4f4df009e 100644 --- a/common/src/main/kotlin/org/valkyrienskies/mod/common/PlayerUtil.kt +++ b/common/src/main/kotlin/org/valkyrienskies/mod/common/PlayerUtil.kt @@ -6,6 +6,7 @@ import net.minecraft.world.entity.player.Player import net.minecraft.world.level.Level import net.minecraft.world.phys.Vec3 import org.valkyrienskies.core.api.ships.LoadedShip +import org.valkyrienskies.mod.common.util.EntityDragger.serversideEyeRotationOrDefault import org.valkyrienskies.mod.common.util.toJOML import org.valkyrienskies.mod.common.util.toMinecraft import org.valkyrienskies.mod.mixin.accessors.entity.EntityAccessor @@ -38,8 +39,9 @@ object PlayerUtil { val yaw = -atan2(direction.x, direction.z) val pitch = -atan2(direction.y, sqrt((direction.x * direction.x) + (direction.z * direction.z))) - player.yRot = (yaw * (180 / Math.PI)).toFloat() + player.yRot = player.serversideEyeRotationOrDefault(yaw * (180 / Math.PI)).toFloat() player.yHeadRot = player.yRot + player.xRot = (pitch * (180 / Math.PI)).toFloat() (player as EntityAccessor).setPosNoUpdates(position.toMinecraft()) } diff --git a/common/src/main/kotlin/org/valkyrienskies/mod/common/VSGameUtils.kt b/common/src/main/kotlin/org/valkyrienskies/mod/common/VSGameUtils.kt index 6c1f081e2..03fd56c48 100644 --- a/common/src/main/kotlin/org/valkyrienskies/mod/common/VSGameUtils.kt +++ b/common/src/main/kotlin/org/valkyrienskies/mod/common/VSGameUtils.kt @@ -10,6 +10,7 @@ import net.minecraft.resources.ResourceLocation import net.minecraft.server.MinecraftServer import net.minecraft.server.level.ServerChunkCache import net.minecraft.server.level.ServerLevel +import net.minecraft.server.level.ServerPlayer import net.minecraft.util.thread.BlockableEventLoop import net.minecraft.world.entity.Entity import net.minecraft.world.entity.player.Player @@ -41,6 +42,8 @@ import org.valkyrienskies.core.util.expand import org.valkyrienskies.mod.common.entity.ShipMountedToData import org.valkyrienskies.mod.common.entity.ShipMountedToDataProvider import org.valkyrienskies.mod.common.util.DimensionIdProvider +import org.valkyrienskies.mod.common.util.EntityDragger.serversideEyePosition +import org.valkyrienskies.mod.common.util.IEntityDraggingInformationProvider import org.valkyrienskies.mod.common.util.MinecraftPlayer import org.valkyrienskies.mod.common.util.set import org.valkyrienskies.mod.common.util.toJOML @@ -118,8 +121,11 @@ val Player.playerWrapper get() = (this as PlayerDuck).vs_getPlayer() /** * Like [Entity.squaredDistanceTo] except the destination is transformed into world coordinates if it is a ship */ -fun Entity.squaredDistanceToInclShips(x: Double, y: Double, z: Double) = - level.squaredDistanceBetweenInclShips(x, y, z, this.x, this.y, this.z) +fun Entity.squaredDistanceToInclShips(x: Double, y: Double, z: Double): Double { + val eyePos = if (getShipMountedTo(this) != null) getShipMountedToData(this, null)!!.mountPosInShip.toMinecraft() else this.serversideEyePosition() + return level.squaredDistanceBetweenInclShips(x, y, z, eyePos.x, eyePos.y - 1.0, eyePos.z) +} + /** * Calculates the squared distance between to points. @@ -348,6 +354,10 @@ fun Ship.toWorldCoordinates(pos: BlockPos): Vector3d = fun Ship.toWorldCoordinates(pos: Vec3): Vec3 = shipToWorld.transformPosition(pos.toJOML()).toMinecraft() +fun Level?.toWorldCoordinates(pos: BlockPos): Vec3 { + return this?.getShipManagingPos(pos)?.toWorldCoordinates(pos)?.toMinecraft() ?: pos.toJOMLD().toMinecraft() +} + fun Level?.toWorldCoordinates(pos: Vec3): Vec3 { return this?.getShipManagingPos(pos)?.toWorldCoordinates(pos) ?: pos } diff --git a/common/src/main/kotlin/org/valkyrienskies/mod/common/config/VSGameConfig.kt b/common/src/main/kotlin/org/valkyrienskies/mod/common/config/VSGameConfig.kt index 33a626b04..d4009b817 100644 --- a/common/src/main/kotlin/org/valkyrienskies/mod/common/config/VSGameConfig.kt +++ b/common/src/main/kotlin/org/valkyrienskies/mod/common/config/VSGameConfig.kt @@ -135,7 +135,7 @@ object VSGameConfig { var allowMobSpawns = true @JsonSchema( - description = "Allow rudimentary pathfinding on ships" + description = "Allow pathfinding on ships" ) var aiOnShips = true diff --git a/common/src/main/kotlin/org/valkyrienskies/mod/common/networking/PacketEntityShipMotion.kt b/common/src/main/kotlin/org/valkyrienskies/mod/common/networking/PacketEntityShipMotion.kt new file mode 100644 index 000000000..4a7a6cc3d --- /dev/null +++ b/common/src/main/kotlin/org/valkyrienskies/mod/common/networking/PacketEntityShipMotion.kt @@ -0,0 +1,20 @@ +package org.valkyrienskies.mod.common.networking + +import org.valkyrienskies.core.impl.networking.simple.SimplePacket + +/** + * This packet is used to update an entity's relative position while being dragged by a ship, in place of + * [net.minecraft.network.protocol.game.ClientboundTeleportEntityPacket], [net.minecraft.network.protocol.game.ClientboundMoveEntityPacket], and [net.minecraft.network.protocol.game.ClientboundSetEntityMotionPacket]. + */ +data class PacketEntityShipMotion( + val entityID: Int, + val shipID: Long, + val x: Double, + val y: Double, + val z: Double, + val xVel: Double, + val yVel: Double, + val zVel: Double, + val yRot: Double, + val xRot: Double, +): SimplePacket diff --git a/common/src/main/kotlin/org/valkyrienskies/mod/common/networking/PacketMobShipRotation.kt b/common/src/main/kotlin/org/valkyrienskies/mod/common/networking/PacketMobShipRotation.kt new file mode 100644 index 000000000..1b50eb668 --- /dev/null +++ b/common/src/main/kotlin/org/valkyrienskies/mod/common/networking/PacketMobShipRotation.kt @@ -0,0 +1,8 @@ +package org.valkyrienskies.mod.common.networking + +import org.valkyrienskies.core.impl.networking.simple.SimplePacket + +/** + * This packet is used in place of [net.minecraft.network.protocol.game.ClientboundRotateHeadPacket] to update the head rotation of a mob being dragged by a ship. + */ +data class PacketMobShipRotation(val entityID: Int, val shipID: Long, val yaw: Double, val pitch: Double): SimplePacket diff --git a/common/src/main/kotlin/org/valkyrienskies/mod/common/networking/PacketPlayerShipMotion.kt b/common/src/main/kotlin/org/valkyrienskies/mod/common/networking/PacketPlayerShipMotion.kt new file mode 100644 index 000000000..8c9fce295 --- /dev/null +++ b/common/src/main/kotlin/org/valkyrienskies/mod/common/networking/PacketPlayerShipMotion.kt @@ -0,0 +1,9 @@ +package org.valkyrienskies.mod.common.networking + +import org.valkyrienskies.core.impl.networking.simple.SimplePacket + +/** + * This packet is used to update the player's relative position and yaw rotation while being dragged by a ship, alongside + * [net.minecraft.network.protocol.game.ServerboundMovePlayerPacket]. + */ +data class PacketPlayerShipMotion(val shipID: Long, val x: Double, val y: Double, val z: Double, val yRot: Double): SimplePacket diff --git a/common/src/main/kotlin/org/valkyrienskies/mod/common/networking/VSGamePackets.kt b/common/src/main/kotlin/org/valkyrienskies/mod/common/networking/VSGamePackets.kt index cc46f2221..0326f0cfc 100644 --- a/common/src/main/kotlin/org/valkyrienskies/mod/common/networking/VSGamePackets.kt +++ b/common/src/main/kotlin/org/valkyrienskies/mod/common/networking/VSGamePackets.kt @@ -1,16 +1,27 @@ package org.valkyrienskies.mod.common.networking +import net.minecraft.client.Minecraft +import net.minecraft.client.player.LocalPlayer import net.minecraft.core.Registry import net.minecraft.resources.ResourceLocation +import net.minecraft.server.level.ServerLevel import net.minecraft.server.level.ServerPlayer +import net.minecraft.world.phys.Vec3 +import org.joml.Vector3d import org.valkyrienskies.core.api.attachment.getAttachment import org.valkyrienskies.core.api.ships.LoadedServerShip import org.valkyrienskies.mod.api.SeatedControllingPlayer import org.valkyrienskies.mod.common.entity.ShipMountingEntity import org.valkyrienskies.mod.common.entity.handling.VSEntityManager import org.valkyrienskies.mod.common.getShipObjectManagingPos +import org.valkyrienskies.mod.common.shipObjectWorld +import org.valkyrienskies.mod.common.toWorldCoordinates +import org.valkyrienskies.mod.common.util.EntityLerper +import org.valkyrienskies.mod.common.util.IEntityDraggingInformationProvider import org.valkyrienskies.mod.common.util.MinecraftPlayer +import org.valkyrienskies.mod.common.util.toMinecraft import org.valkyrienskies.mod.common.vsCore +import org.valkyrienskies.mod.mixinducks.world.entity.PlayerDuck object VSGamePackets { @@ -19,6 +30,9 @@ object VSGamePackets { PacketStopChunkUpdates::class.register() PacketRestartChunkUpdates::class.register() PacketSyncVSEntityTypes::class.register() + PacketEntityShipMotion::class.register() + PacketMobShipRotation::class.register() + PacketPlayerShipMotion::class.register() } fun registerHandlers() = with(vsCore.simplePacketNetworking) { @@ -51,5 +65,115 @@ object VSGamePackets { ) } } + + PacketEntityShipMotion::class.registerClientHandler { setMotion -> + val mc = Minecraft.getInstance() + val level = mc.level ?: return@registerClientHandler + val entity = level.getEntity(setMotion.entityID) ?: return@registerClientHandler + + if (entity.isControlledByLocalInstance || mc.player?.id == entity.id) return@registerClientHandler + + val ship = level.shipObjectWorld.allShips.getById(setMotion.shipID) + ?: return@registerClientHandler + + if (entity is IEntityDraggingInformationProvider) { + if (entity.draggingInformation.lastShipStoodOn == null || entity.draggingInformation.lastShipStoodOn != setMotion.shipID) { + entity.draggingInformation.lastShipStoodOn = setMotion.shipID + entity.draggingInformation.ignoreNextGroundStand = true + } + + entity.draggingInformation.relativePositionOnShip = ship.worldToShip.transformPosition( + Vector3d(entity.x, entity.y, entity.z) + ) + entity.draggingInformation.previousRelativeVelocityOnShip = entity.draggingInformation.relativeVelocityOnShip + entity.draggingInformation.relativeYawOnShip = EntityLerper.yawToShip(ship, entity.yRot.toDouble()) + + entity.draggingInformation.lerpPositionOnShip = Vector3d(setMotion.x, setMotion.y, setMotion.z) + entity.draggingInformation.relativeVelocityOnShip = Vector3d(setMotion.xVel, setMotion.yVel, setMotion.zVel) + entity.draggingInformation.lerpYawOnShip = setMotion.yRot + + val previousWorldPosition = if (entity.draggingInformation.relativePositionOnShip != null) { + ship.renderTransform.shipToWorld.transformPosition(Vector3d(entity.draggingInformation.relativePositionOnShip)) + } else { + Vector3d(entity.x, entity.y, entity.z) + } + val worldPosition = ship.renderTransform.shipToWorld.transformPosition(Vector3d(setMotion.x, setMotion.y, setMotion.z)) + entity.setPacketCoordinates(worldPosition.x, worldPosition.y, worldPosition.z) + val worldVelocity = ship.renderTransform.shipToWorld.transformDirection(Vector3d(setMotion.xVel, setMotion.yVel, setMotion.zVel)) + entity.setDeltaMovement(worldVelocity.x, worldVelocity.y, worldVelocity.z) + entity.draggingInformation.lerpSteps = 3 + + // entity.setPos(previousWorldPosition.x, previousWorldPosition.y, previousWorldPosition.z) + // entity.lerpTo(worldPosition.x, worldPosition.y, worldPosition.z, Math.toDegrees(setMotion.yRot).toFloat(), Math.toDegrees(setMotion.xRot).toFloat(), 3, true) + } + } + + PacketMobShipRotation::class.registerClientHandler { setRotation -> + val mc = Minecraft.getInstance() ?: return@registerClientHandler + val level = mc.level ?: return@registerClientHandler + val entity = level.getEntity(setRotation.entityID) ?: return@registerClientHandler + + if (entity.isControlledByLocalInstance || entity is LocalPlayer) return@registerClientHandler + + val ship = level.shipObjectWorld.allShips.getById(setRotation.shipID) + ?: return@registerClientHandler + + if (entity is IEntityDraggingInformationProvider) { + if (entity.draggingInformation.lastShipStoodOn == null || entity.draggingInformation.lastShipStoodOn != setRotation.shipID) { + entity.draggingInformation.lastShipStoodOn = setRotation.shipID + entity.draggingInformation.ignoreNextGroundStand = true + } + entity.draggingInformation.relativeHeadYawOnShip = EntityLerper.yawToShip(ship, entity.yHeadRot.toDouble()) + entity.draggingInformation.lerpHeadYawOnShip = setRotation.yaw + entity.draggingInformation.relativePitchOnShip = entity.xRot.toDouble() + entity.draggingInformation.lerpPitchOnShip = setRotation.pitch + entity.draggingInformation.headLerpSteps = 3 + } + } + + PacketPlayerShipMotion::class.registerServerHandler { motion, iPlayer -> + val player = (iPlayer as MinecraftPlayer).player as ServerPlayer? + ?: return@registerServerHandler + + if (player is IEntityDraggingInformationProvider) { + if (player.draggingInformation.lastShipStoodOn == null || player.draggingInformation.lastShipStoodOn != motion.shipID) { + player.draggingInformation.lastShipStoodOn = motion.shipID + } + player.draggingInformation.serverRelativePlayerPosition = Vector3d(motion.x, motion.y, motion.z) + if (player.level != null) { + val sLevel = (player.level as ServerLevel) + val ship = sLevel.shipObjectWorld.allShips.getById(motion.shipID) + if (ship != null) { + val posUpdate = ship.shipToWorld.transformPosition(Vector3d(motion.x, motion.y, motion.z), Vector3d()).toMinecraft() + if ((player as PlayerDuck).vs_handledMovePacket()) { + player.setPos(posUpdate.x, posUpdate.y, posUpdate.z) + player.vs_setHandledMovePacket(false) + } else { + player.vs_setQueuedPositionUpdate(posUpdate) + } + } + } + player.draggingInformation.serverRelativePlayerYaw = motion.yRot + } + } + + PacketEntityShipMotion::class.registerServerHandler { motion, iPlayer -> + val player = (iPlayer as MinecraftPlayer).player as ServerPlayer + val entity = player.level.getEntity(motion.entityID) ?: return@registerServerHandler + + if (entity is IEntityDraggingInformationProvider) { + if (entity.draggingInformation.lastShipStoodOn == null || entity.draggingInformation.lastShipStoodOn != motion.shipID) { + entity.draggingInformation.lastShipStoodOn = motion.shipID + entity.draggingInformation.ignoreNextGroundStand = true + } + + entity.draggingInformation.relativePositionOnShip = Vector3d(motion.x, motion.y, motion.z) + entity.draggingInformation.relativeYawOnShip = motion.yRot + + if ((player.level as ServerLevel).shipObjectWorld.allShips.getById(motion.shipID) != null) { + entity.setPos(player.level.toWorldCoordinates(Vec3(motion.x, motion.y, motion.z))) + } + } + } } } diff --git a/common/src/main/kotlin/org/valkyrienskies/mod/common/util/EntityDragger.kt b/common/src/main/kotlin/org/valkyrienskies/mod/common/util/EntityDragger.kt index 2c6e4bf5f..c5cb860be 100644 --- a/common/src/main/kotlin/org/valkyrienskies/mod/common/util/EntityDragger.kt +++ b/common/src/main/kotlin/org/valkyrienskies/mod/common/util/EntityDragger.kt @@ -1,9 +1,15 @@ package org.valkyrienskies.mod.common.util +import net.minecraft.client.Minecraft +import net.minecraft.client.player.LocalPlayer import net.minecraft.server.level.ServerPlayer +import net.minecraft.util.Mth import net.minecraft.world.entity.Entity +import net.minecraft.world.entity.player.Player +import net.minecraft.world.phys.Vec3 import org.joml.Vector3d import org.joml.Vector3dc +import org.valkyrienskies.core.api.ships.ClientShip import org.valkyrienskies.mod.common.shipObjectWorld import kotlin.math.asin import kotlin.math.atan2 @@ -17,8 +23,8 @@ object EntityDragger { /** * Drag these entities with the ship they're standing on. */ - fun dragEntitiesWithShips(entities: Iterable) { - entities.forEach { entity -> + fun dragEntitiesWithShips(entities: Iterable, preTick: Boolean = false) { + for (entity in entities) { val entityDraggingInformation = (entity as IEntityDraggingInformationProvider).draggingInformation var dragTheEntity = false @@ -35,26 +41,31 @@ object EntityDragger { if (shipData != null) { dragTheEntity = true + val entityReferencePos: Vector3dc = if (preTick) { + Vector3d(entity.x, entity.y, entity.z) + } else { + Vector3d(entity.xo, entity.yo, entity.zo) + } + + val referenceTransform = if (shipData is ClientShip) shipData.transform else shipData.transform + // region Compute position dragging - val newPosIdeal = shipData.shipToWorld.transformPosition( + val newPosIdeal: Vector3dc = referenceTransform.shipToWorld.transformPosition( shipData.prevTickTransform.worldToShip.transformPosition( - Vector3d(entity.x, entity.y, entity.z) + Vector3d(entityReferencePos) ) ) - addedMovement = Vector3d( - newPosIdeal.x - entity.x, - newPosIdeal.y - entity.y, - newPosIdeal.z - entity.z - ) + addedMovement = newPosIdeal.sub(entityReferencePos, Vector3d()) // endregion // region Compute look dragging - val yViewRot = entity.getViewYRot(1.0f).toDouble() + val yViewRot = entity.yRot.toDouble() + // Get the y-look vector of the entity only using y-rotation, ignore x-rotation val entityLookYawOnly = Vector3d(sin(-Math.toRadians(yViewRot)), 0.0, cos(-Math.toRadians(yViewRot))) - val newLookIdeal = shipData.shipToWorld.transformDirection( + val newLookIdeal = referenceTransform.shipToWorld.transformDirection( shipData.prevTickTransform.worldToShip.transformDirection( entityLookYawOnly ) @@ -68,15 +79,15 @@ object EntityDragger { // The Y rotation of the entity before dragging var entityYRotCorrected = entity.yRot % 360.0 // Limit [entityYRotCorrected] to be between -180 to 180 degrees - if (entityYRotCorrected < -180.0) entityYRotCorrected += 360.0 - if (entityYRotCorrected > 180.0) entityYRotCorrected -= 360.0 + if (entityYRotCorrected <= -180.0) entityYRotCorrected += 360.0 + if (entityYRotCorrected >= 180.0) entityYRotCorrected -= 360.0 // The Y rotation of the entity after dragging val newYRotAsDegrees = Math.toDegrees(newYRot) // Limit [addedYRotFromDragging] to be between -180 to 180 degrees var addedYRotFromDragging = newYRotAsDegrees - entityYRotCorrected - if (addedYRotFromDragging < -180.0) addedYRotFromDragging += 360.0 - if (addedYRotFromDragging > 180.0) addedYRotFromDragging -= 360.0 + if (addedYRotFromDragging <= -180.0) addedYRotFromDragging += 360.0 + if (addedYRotFromDragging >= 180.0) addedYRotFromDragging -= 360.0 addedYRot = addedYRotFromDragging // endregion @@ -103,10 +114,25 @@ object EntityDragger { entityDraggingInformation.addedMovementLastTick = addedMovement // Apply [addedYRot] - // Don't apply it to server players to fix rotation of placed blocks - if (addedYRot.isFinite() && entity !is ServerPlayer) { - entity.yRot += addedYRot.toFloat() - entity.yHeadRot += addedYRot.toFloat() + if (addedYRot.isFinite()) { + if (!entity.level.isClientSide()) { + if (entity !is ServerPlayer) { + entity.yRot = ((entity.yRot + addedYRot.toFloat()) + 360f) % 360f + entity.yHeadRot = ((entity.yHeadRot + addedYRot.toFloat()) + 360f) % 360f + } else { + entity.yRot = Mth.wrapDegrees(entity.yRot + addedYRot.toFloat()) + entity.yHeadRot = Mth.wrapDegrees(entity.yHeadRot + addedYRot.toFloat()) + } + } else { + if (!entity.isControlledByLocalInstance && entity !is LocalPlayer) { + entity.yRot = Mth.wrapDegrees(entity.yRot + addedYRot.toFloat()) + entity.yHeadRot = Mth.wrapDegrees(entity.yHeadRot + addedYRot.toFloat()) + } else { + entity.yRot = (entity.yRot + addedYRot.toFloat()) + entity.yHeadRot = (entity.yHeadRot + addedYRot.toFloat()) + } + } + entityDraggingInformation.addedYawRotLastTick = addedYRot } } @@ -114,4 +140,52 @@ object EntityDragger { entityDraggingInformation.mountedToEntity = entity.vehicle != null } } + + /** + * Checks if the entity is a ServerPlayer and has a [serverRelativePlayerPosition] set. If it does, returns that, which is in ship space; otherwise, returns worldspace eye position. + */ + fun Entity.serversideEyePosition(): Vec3 { + if (this is ServerPlayer && this is IEntityDraggingInformationProvider && this.draggingInformation.isEntityBeingDraggedByAShip()) { + if (this.draggingInformation.serverRelativePlayerPosition != null) { + return this.draggingInformation.serverRelativePlayerPosition!!.toMinecraft() + } + } + return this.eyePosition + } + + /** + * Checks if the entity is a ServerPlayer and has a [serverRelativePlayerYaw] set. If it does, returns that, which is in ship space; otherwise, returns worldspace eye rotation. + */ + fun Entity.serversideEyeRotation(): Double { + if (this is ServerPlayer && this is IEntityDraggingInformationProvider && this.draggingInformation.isEntityBeingDraggedByAShip()) { + if (this.draggingInformation.serverRelativePlayerYaw != null) { + return this.draggingInformation.serverRelativePlayerYaw!! * 180.0 / Math.PI + } + } + return this.yRot.toDouble() + } + + /** + * Checks if the entity is a ServerPlayer and has a [serverRelativePlayerPosition] set. If it does, returns that, which is in ship space; otherwise, returns a default value. + */ + fun Entity.serversideEyePositionOrDefault(default: Vec3): Vec3 { + if (this is ServerPlayer && this is IEntityDraggingInformationProvider && this.draggingInformation.isEntityBeingDraggedByAShip()) { + if (this.draggingInformation.serverRelativePlayerPosition != null) { + return this.draggingInformation.serverRelativePlayerPosition!!.toMinecraft() + } + } + return default + } + + /** + * Checks if the entity is a ServerPlayer and has a [serverRelativePlayerYaw] set. If it does, returns that, which is in ship space; otherwise, returns a default value. + */ + fun Entity.serversideEyeRotationOrDefault(default: Double): Double { + if (this is ServerPlayer && this is IEntityDraggingInformationProvider && this.draggingInformation.isEntityBeingDraggedByAShip()) { + if (this.draggingInformation.serverRelativePlayerYaw != null) { + return Math.toDegrees(this.draggingInformation.serverRelativePlayerYaw!!) + } + } + return default + } } diff --git a/common/src/main/kotlin/org/valkyrienskies/mod/common/util/EntityDraggingInformation.kt b/common/src/main/kotlin/org/valkyrienskies/mod/common/util/EntityDraggingInformation.kt index 353310f39..915f9a0d3 100644 --- a/common/src/main/kotlin/org/valkyrienskies/mod/common/util/EntityDraggingInformation.kt +++ b/common/src/main/kotlin/org/valkyrienskies/mod/common/util/EntityDraggingInformation.kt @@ -16,19 +16,48 @@ class EntityDraggingInformation { field = value } var ticksSinceStoodOnShip: Int = 0 + var ignoreNextGroundStand = false var mountedToEntity: Boolean = false + var lerpPositionOnShip: Vector3dc? = null + var relativeVelocityOnShip: Vector3dc? = null + var lerpYawOnShip: Double? = null + var lerpHeadYawOnShip: Double? = null + var lerpPitchOnShip: Double? = null + + var relativePositionOnShip: Vector3dc? = null + var previousRelativeVelocityOnShip: Vector3dc? = null + var relativeYawOnShip: Double? = null + var relativeHeadYawOnShip: Double? = null + var relativePitchOnShip: Double? = null + + var lerpSteps: Int = 0 + var headLerpSteps: Int = 0 + // Used by the client rendering code only var cachedLastPosition: Vector3dc? = null var restoreCachedLastPosition = false + var serverRelativePlayerPosition: Vector3dc? = null + var serverRelativePlayerYaw: Double? = null + fun isEntityBeingDraggedByAShip(): Boolean { return (lastShipStoodOn != null) && (ticksSinceStoodOnShip < TICKS_TO_DRAG_ENTITIES) && !mountedToEntity } + fun bestRelativeEntityPosition(): Vector3dc? { + return if (serverRelativePlayerPosition != null) { + serverRelativePlayerPosition!! + } else if (relativePositionOnShip != null) { + relativePositionOnShip!! + } else { + null + } + } + companion object { // Max number of ticks we will drag an entity after the entity has jumped off the ship - private const val TICKS_TO_DRAG_ENTITIES = 20 + private const val TICKS_TO_DRAG_ENTITIES = 40 } } diff --git a/common/src/main/kotlin/org/valkyrienskies/mod/common/util/EntityLerper.kt b/common/src/main/kotlin/org/valkyrienskies/mod/common/util/EntityLerper.kt new file mode 100644 index 000000000..53562a0fb --- /dev/null +++ b/common/src/main/kotlin/org/valkyrienskies/mod/common/util/EntityLerper.kt @@ -0,0 +1,110 @@ +package org.valkyrienskies.mod.common.util + +import net.minecraft.util.Mth +import net.minecraft.world.entity.Entity +import net.minecraft.world.entity.LivingEntity +import org.joml.Vector3d +import org.joml.Vector3dc +import org.valkyrienskies.core.api.ships.ClientShip +import org.valkyrienskies.core.api.ships.Ship +import kotlin.math.atan2 +import kotlin.math.cos +import kotlin.math.sin + +object EntityLerper { + + /** + * Called from preAiStep. This function lerps the entity's movement while keeping it locked relative to the ship. + */ + fun lerpStep(dragInfo: EntityDraggingInformation, refship: Ship, entity: Entity) { + if (refship !is ClientShip) { + return + } + val ship = refship as ClientShip + if (dragInfo.lerpSteps > 0) { + val currentX: Double = dragInfo.relativePositionOnShip?.x() ?: return + val currentY: Double = dragInfo.relativePositionOnShip!!.y() + val currentZ: Double = dragInfo.relativePositionOnShip!!.z() + + val lerpX: Double = dragInfo.lerpPositionOnShip!!.x() + val lerpY: Double = dragInfo.lerpPositionOnShip!!.y() + val lerpZ: Double = dragInfo.lerpPositionOnShip!!.z() + + val currentYaw: Double = dragInfo.relativeYawOnShip ?: return + val lerpYaw: Double = dragInfo.lerpYawOnShip ?: return + + val newX: Double = currentX + (lerpX - currentX) / dragInfo.lerpSteps + val newY: Double = currentY + (lerpY - currentY) / dragInfo.lerpSteps + val newZ: Double = currentZ + (lerpZ - currentZ) / dragInfo.lerpSteps + + val newPos = ship.shipToWorld.transformPosition(newX, newY, newZ, Vector3d()) + + val currentYawWorld = yawToWorld(ship, currentYaw) + val lerpYawWorld = yawToWorld(ship, lerpYaw) + + dragInfo.relativePositionOnShip = Vector3d(newX, newY, newZ) + entity.setPos(newPos.x(), newPos.y(), newPos.z()) + + val g = Mth.wrapDegrees(lerpYawWorld - currentYawWorld) + val newYaw = (currentYawWorld + g / dragInfo.lerpSteps).toFloat() + + entity.yRot = newYaw + dragInfo.relativeYawOnShip = yawToShip(ship, newYaw.toDouble()) + + dragInfo.lerpSteps -= 1 + } + } + + /** + * Additional function to lerp head separately, as it's a separate packet. + */ + fun lerpHeadStep(dragInfo: EntityDraggingInformation, refship: Ship, entity: Entity) { + if (refship !is ClientShip) { + return + } + val ship = refship as ClientShip + if (dragInfo.headLerpSteps > 0) { + val currentHeadYaw: Double = dragInfo.relativeHeadYawOnShip ?: return + val lerpHeadYaw: Double = dragInfo.lerpHeadYawOnShip ?: return + + val currentHeadYawWorld = yawToWorld(ship, currentHeadYaw) + val lerpHeadYawWorld = yawToWorld(ship, lerpHeadYaw) + + val newHeadYaw = currentHeadYawWorld + Mth.wrapDegrees(lerpHeadYawWorld - currentHeadYawWorld) / dragInfo.headLerpSteps.toFloat() + entity.xRot += (dragInfo.lerpPitchOnShip!!.toFloat() - entity.xRot) / dragInfo.headLerpSteps.toFloat() + entity.yHeadRot = newHeadYaw.toFloat() + dragInfo.relativeHeadYawOnShip = yawToShip(ship, newHeadYaw.toDouble()) + + dragInfo.headLerpSteps-- + } + } + + /** + * Converts yaw to worldspace. + * + * Takes in radians, outputs degrees. + */ + fun yawToWorld(ship: Ship, yaw: Double): Double { + val entityYawOnly: Vector3dc = Vector3d(sin(yaw), 0.0, cos(yaw)) + + val newLookIdeal = ship.transform.transformDirectionNoScalingFromShipToWorld(entityYawOnly, Vector3d()) + + val newYRot = atan2(newLookIdeal.x(), newLookIdeal.z()) + + return Mth.wrapDegrees(newYRot * 180.0 / Math.PI) + } + + /** + * Converts yaw to shipspace. + * + * Takes in degrees, outputs radians + */ + fun yawToShip(ship: Ship, yaw: Double): Double { + val entityYawOnly: Vector3dc = Vector3d(sin(yaw * Math.PI / 180.0), 0.0, cos(yaw * Math.PI / 180.0)) + + val newLookIdeal = ship.transform.transformDirectionNoScalingFromWorldToShip(entityYawOnly, Vector3d()) + + val newYRot = atan2(newLookIdeal.x(), newLookIdeal.z()) + return newYRot + } +} diff --git a/common/src/main/kotlin/org/valkyrienskies/mod/common/util/EntityShipCollisionUtils.kt b/common/src/main/kotlin/org/valkyrienskies/mod/common/util/EntityShipCollisionUtils.kt index 63a610df9..2763c1a59 100644 --- a/common/src/main/kotlin/org/valkyrienskies/mod/common/util/EntityShipCollisionUtils.kt +++ b/common/src/main/kotlin/org/valkyrienskies/mod/common/util/EntityShipCollisionUtils.kt @@ -96,6 +96,9 @@ object EntityShipCollisionUtils { if (shipCollidingWith != null) { // Update the [IEntity.lastShipStoodOn] (entity as IEntityDraggingInformationProvider).draggingInformation.lastShipStoodOn = shipCollidingWith + for (entityRiding in entity.indirectPassengers) { + (entityRiding as IEntityDraggingInformationProvider).draggingInformation.lastShipStoodOn = shipCollidingWith + } } } return newMovement.toMinecraft() diff --git a/common/src/main/kotlin/org/valkyrienskies/mod/common/world/POIChunkSearcher.kt b/common/src/main/kotlin/org/valkyrienskies/mod/common/world/POIChunkSearcher.kt new file mode 100644 index 000000000..3e9b25549 --- /dev/null +++ b/common/src/main/kotlin/org/valkyrienskies/mod/common/world/POIChunkSearcher.kt @@ -0,0 +1,48 @@ +package org.valkyrienskies.mod.common.world + +import net.minecraft.server.level.ServerLevel +import net.minecraft.world.entity.ai.village.poi.PoiRecord +import net.minecraft.world.level.ChunkPos +import net.minecraft.world.level.Level +import net.minecraft.world.phys.AABB +import net.minecraft.world.phys.Vec3 +import org.joml.Vector4i +import org.joml.Vector4ic +import org.valkyrienskies.core.api.ships.LoadedServerShip +import org.valkyrienskies.core.api.ships.QueryableShipData +import org.valkyrienskies.core.api.ships.properties.IShipActiveChunksSet +import org.valkyrienskies.core.impl.chunk_tracking.ShipActiveChunksSet +import org.valkyrienskies.mod.common.dimensionId +import org.valkyrienskies.mod.common.getShipObjectManagingPos +import org.valkyrienskies.mod.common.shipObjectWorld +import org.valkyrienskies.mod.common.toWorldCoordinates +import org.valkyrienskies.mod.common.util.toJOML +import org.valkyrienskies.mod.common.util.toMinecraft +import kotlin.math.max +import kotlin.math.min + +object POIChunkSearcher { + fun shipChunkBounds(chunkSet: IShipActiveChunksSet): Vector4ic? { + if (chunkSet.size == 0) { + return null + } + var minChunkX = Int.MAX_VALUE + var minChunkZ = Int.MAX_VALUE + var maxChunkX = Int.MIN_VALUE + var maxChunkZ = Int.MIN_VALUE + chunkSet.forEach { chunkX, chunkZ -> + minChunkX = min(minChunkX, chunkX) + minChunkZ = min(minChunkZ, chunkZ) + maxChunkX = max(maxChunkX, chunkX) + maxChunkZ = max(maxChunkZ, chunkZ) + } + if (minChunkX == Int.MAX_VALUE || minChunkZ == Int.MAX_VALUE || maxChunkX == Int.MIN_VALUE || maxChunkZ == Int.MIN_VALUE) { + return null + } + return Vector4i(minChunkX, minChunkZ, maxChunkX, maxChunkZ) + } + + fun PoiRecord.getWorldPos(level: Level): Vec3 { + return level.toWorldCoordinates(this.pos) + } +} diff --git a/common/src/main/resources/valkyrienskies-common.accesswidener b/common/src/main/resources/valkyrienskies-common.accesswidener index f9e167710..31a23adc1 100644 --- a/common/src/main/resources/valkyrienskies-common.accesswidener +++ b/common/src/main/resources/valkyrienskies-common.accesswidener @@ -26,3 +26,11 @@ mutable field net/minecraft/world/phys/HitResult location Lnet/minecraft/ accessible field net/minecraft/world/phys/HitResult location Lnet/minecraft/world/phys/Vec3; accessible class net/minecraft/server/level/ChunkMap$DistanceManager + +accessible class net/minecraft/world/entity/ai/behavior/AcquirePoi$JitteredLinearRetry + +accessible class net/minecraft/world/entity/animal/Bee$BeePollinateGoal +accessible class net/minecraft/world/entity/animal/Bee$BeeEnterHiveGoal +accessible class net/minecraft/world/entity/animal/Bee$BeeLocateHiveGoal +accessible class net/minecraft/world/entity/animal/Bee$BeeGrowCropGoal +accessible class net/minecraft/world/entity/animal/Bee$BaseBeeGoal diff --git a/common/src/main/resources/valkyrienskies-common.mixins.json b/common/src/main/resources/valkyrienskies-common.mixins.json index 4872bf2d3..9159d3450 100644 --- a/common/src/main/resources/valkyrienskies-common.mixins.json +++ b/common/src/main/resources/valkyrienskies-common.mixins.json @@ -16,9 +16,27 @@ "accessors.world.level.pathfinder.PathAccessor", "commands.argument.selector.MixinEntitySelectorParser", "entity.MixinEntity", + "feature.ai.goal.JitteredLinearRetryAccessor", + "feature.ai.goal.MixinMoveToBlockGoal", + "feature.ai.goal.MixinMoveToTargetSink", + "feature.ai.goal.MixinValidateNearbyPoi", + "feature.ai.goal.bees.MixinBee", + "feature.ai.goal.bees.MixinEnterHiveGoal", + "feature.ai.goal.bees.MixinGrowCropGoal", + "feature.ai.goal.bees.MixinLocateHiveGoal", + "feature.ai.goal.bees.MixinPollinateGoal", + "feature.ai.goal.villagers.MixinAssignProfessionFromJobSite", + "feature.ai.goal.villagers.MixinGoToClosestVillage", + "feature.ai.goal.villagers.MixinSetClosestHomeAsWalkTarget", + "feature.ai.goal.villagers.MixinWorkAtPoi", + "feature.ai.node_evaluator.MixinPathFinder", + "feature.ai.node_evaluator.MixinPathNavigation", "feature.ai.node_evaluator.PathNavigationRegionAccessor", "feature.ai.node_evaluator.SwimNodeEvaluatorMixin", "feature.ai.node_evaluator.WalkNodeEvaluatorMixin", + "feature.ai.path_retargeting.MixinAirAndWaterRandomPos", + "feature.ai.path_retargeting.MixinDefaultRandomPos", + "feature.ai.path_retargeting.MixinLandRandomPos", "feature.bed_fix.MixinServerPlayer", "feature.block_placement_orientation.MixinBlockItem", "feature.block_placement_orientation.MixinBlockPlaceContext", @@ -31,7 +49,9 @@ "feature.dispensers.MixinDefaultDispenseItemBehavior", "feature.distance_replace.MixinEntity", "feature.entity_collision.MixinEntity", + "feature.entity_collision.MixinLivingEntity", "feature.entity_collision.MixinPlayer", + "feature.entity_movement_packets.MixinServerEntity", "feature.explosions.MixinExplosion", "feature.fire_between_ship_and_world.LavaFluidMixin", "feature.fluid_escaping_ship_config.MixinFlowingFluid", @@ -39,6 +59,7 @@ "feature.ladders.MixinLivingEntity", "feature.mass_tooltip.MixinBlockItem", "feature.mob_spawning.NaturalSpawnerMixin", + "feature.poi.MixinPOIManager", "feature.render_pathfinding.MixinDebugPackets", "feature.screen_distance_check.MixinScreenHandler", "feature.sealed_air_sync.MixinSectionBlocksUpdatePacket", @@ -50,6 +71,9 @@ "feature.spawn_player_on_ship.MixinServerGamePacketListenerImpl", "feature.tick_ship_chunks.MixinChunkMap", "feature.water_in_ships_entity.MixinEntity", + "feature.wave_spawning.MixinRaid", + "feature.wave_spawning.MixinRaids", + "feature.wave_spawning.MixinVillageSiege", "feature.world_border.MixinLevel", "feature.world_border.MixinWorldBorder", "mod_compat.bluemap.HiresTileModelAccessor", @@ -127,13 +151,14 @@ "client.multiplayer.MixinClientPacketListener", "client.player.MixinLocalPlayer", "client.player.MixinPlayer", + "client.renderer.MixinEntityRenderer", "client.renderer.MixinGameRenderer", "client.renderer.MixinLevelRenderer", "client.world.MixinClientChunkCache", "client.world.MixinClientLevel", "feature.block_tint.MixinClientLevel", "feature.commands.MixinClientSuggestionProvider", - "feature.debug_sparse_voxel_rendering.MixinDebugRenderer", + "feature.entity_movement_packets.MixinLocalPlayer", "feature.fix_render_chunk_sorting.MixinRenderChunk", "feature.fluid_camera_fix.MixinCamera", "feature.render_blockentity_distance_check.MixinBlockEntityRenderDispatcher", diff --git a/forge/src/main/java/org/valkyrienskies/mod/forge/mixin/feature/forge_interact/MixinIForgePlayer.java b/forge/src/main/java/org/valkyrienskies/mod/forge/mixin/feature/forge_interact/MixinIForgePlayer.java index 2c712438a..dfbbc7733 100644 --- a/forge/src/main/java/org/valkyrienskies/mod/forge/mixin/feature/forge_interact/MixinIForgePlayer.java +++ b/forge/src/main/java/org/valkyrienskies/mod/forge/mixin/feature/forge_interact/MixinIForgePlayer.java @@ -11,6 +11,7 @@ import org.spongepowered.asm.mixin.Shadow; import org.valkyrienskies.mod.common.VSGameUtilsKt; import org.valkyrienskies.mod.common.config.VSGameConfig; +import org.valkyrienskies.mod.common.util.EntityDragger; @Mixin(IForgePlayer.class) public interface MixinIForgePlayer { @@ -19,13 +20,15 @@ public interface MixinIForgePlayer { Player self(); /** - * Include ships in server-side distance check when player interacts with a block. + * @author Ewoudje + * @reason Include ships in server-side distance check when player interacts with a block. + * Modified by Tomato to use the serversided player reference position. */ @Overwrite(remap = false) default boolean canInteractWith(final BlockPos pos, final double padding) { if (VSGameConfig.SERVER.getEnableInteractDistanceChecks()) { final double reach = this.self().getReachDistance() + padding; - final Vec3 eyes = this.self().getEyePosition(); + final Vec3 eyes = EntityDragger.INSTANCE.serversideEyePosition(this.self()); return VSGameUtilsKt.squaredDistanceBetweenInclShips(this.self().level, pos.getX() + 0.5, pos.getY() + 0.5, @@ -37,10 +40,15 @@ default boolean canInteractWith(final BlockPos pos, final double padding) { } } + /** + * @author Ewoudje + * @reason Include ships in server-side distance check when player interacts with an entity. + * Modified by Tomato to use the serversided player reference position. + */ @Overwrite(remap = false) default boolean isCloseEnough(final Entity entity, final double distance) { if (VSGameConfig.SERVER.getEnableInteractDistanceChecks()) { - final Vec3 eye = this.self().getEyePosition(); + final Vec3 eye = EntityDragger.INSTANCE.serversideEyePosition(this.self()); final Vec3 targetCenter = entity.getPosition(1.0F).add(0.0, (double) (entity.getBbHeight() / 2.0F), 0.0); final Optional hit = entity.getBoundingBox().clip(eye, targetCenter); return (hit.isPresent() ?