diff --git a/build.gradle b/build.gradle index 58eff658a..f33da4c42 100644 --- a/build.gradle +++ b/build.gradle @@ -11,7 +11,7 @@ plugins { id "dev.architectury.loom" version "1.3.355" apply false id 'io.github.juuxel.loom-vineflower' version '1.11.0' apply false // Kotlin - id "org.jetbrains.kotlin.jvm" version "1.9.10" apply false + id "org.jetbrains.kotlin.jvm" version "2.1.0" apply false id 'com.matthewprenger.cursegradle' version '1.4.0' apply false id "com.modrinth.minotaur" version "2.4.3" apply false } diff --git a/common/build.gradle b/common/build.gradle index b62489b44..a7f1a171d 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 @@ -11,15 +11,24 @@ dependencies { modCompileOnly("curse.maven:sodium-394468:3669187") + implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8:2.1.0") + // vs-core implementation("org.valkyrienskies.core:impl:${rootProject.vs_core_version}") { exclude module: "netty-buffer" exclude module: "fastutil" + exclude module: "kotlin-stdlib" } - implementation("org.valkyrienskies.core:api:${rootProject.vs_core_version}") - implementation("org.valkyrienskies.core:api-game:${rootProject.vs_core_version}") - implementation("org.valkyrienskies.core:util:${rootProject.vs_core_version}") + implementation("org.valkyrienskies.core:api:${rootProject.vs_core_version}") { + exclude module: "kotlin-stdlib" + } + implementation("org.valkyrienskies.core:api-game:${rootProject.vs_core_version}") { + exclude module: "kotlin-stdlib" + } + implementation("org.valkyrienskies.core:util:${rootProject.vs_core_version}") { + exclude module: "kotlin-stdlib" + } // FTB Stuffs modCompileOnly("curse.maven:ftb-util-404465:4210935") 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/debug_sparse_voxel_rendering/MixinDebugRenderer.java b/common/src/main/java/org/valkyrienskies/mod/mixin/feature/debug_sparse_voxel_rendering/MixinDebugRenderer.java new file mode 100644 index 000000000..ea5f5b0ed --- /dev/null +++ b/common/src/main/java/org/valkyrienskies/mod/mixin/feature/debug_sparse_voxel_rendering/MixinDebugRenderer.java @@ -0,0 +1,24 @@ +package org.valkyrienskies.mod.mixin.feature.debug_sparse_voxel_rendering; + +import com.mojang.blaze3d.vertex.PoseStack; +import net.minecraft.client.renderer.MultiBufferSource; +import net.minecraft.client.renderer.debug.DebugRenderer; +import org.spongepowered.asm.mixin.Final; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Unique; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; +import org.valkyrienskies.mod.client.SparseVoxelRenderer; + +@Mixin(DebugRenderer.class) +public class MixinDebugRenderer { + @Unique + @Final + SparseVoxelRenderer sparseVoxelRenderer = new SparseVoxelRenderer(); + + @Inject(method = "render", at = @At("HEAD")) + void render(PoseStack poseStack, MultiBufferSource.BufferSource bufferSource, double d, double e, double f, final CallbackInfo ci) { + //sparseVoxelRenderer.render(poseStack, bufferSource, d, e, f); + } +} 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/sealed_air_sync/MixinBlockUpdatePacket.java b/common/src/main/java/org/valkyrienskies/mod/mixin/feature/sealed_air_sync/MixinBlockUpdatePacket.java new file mode 100644 index 000000000..f38515020 --- /dev/null +++ b/common/src/main/java/org/valkyrienskies/mod/mixin/feature/sealed_air_sync/MixinBlockUpdatePacket.java @@ -0,0 +1,4 @@ +package org.valkyrienskies.mod.mixin.feature.sealed_air_sync; + +public class MixinBlockUpdatePacket { +} diff --git a/common/src/main/java/org/valkyrienskies/mod/mixin/feature/sealed_air_sync/MixinSectionBlocksUpdatePacket.java b/common/src/main/java/org/valkyrienskies/mod/mixin/feature/sealed_air_sync/MixinSectionBlocksUpdatePacket.java new file mode 100644 index 000000000..956fc626a --- /dev/null +++ b/common/src/main/java/org/valkyrienskies/mod/mixin/feature/sealed_air_sync/MixinSectionBlocksUpdatePacket.java @@ -0,0 +1,11 @@ +package org.valkyrienskies.mod.mixin.feature.sealed_air_sync; + +import net.minecraft.network.protocol.game.ClientboundSectionBlocksUpdatePacket; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Unique; + +@Mixin(ClientboundSectionBlocksUpdatePacket.class) +public class MixinSectionBlocksUpdatePacket { + @Unique + private boolean[] sealed; +} 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/create/MixinContraption.java b/common/src/main/java/org/valkyrienskies/mod/mixin/mod_compat/create/MixinContraption.java index 722c7a2b6..4bc259652 100644 --- a/common/src/main/java/org/valkyrienskies/mod/mixin/mod_compat/create/MixinContraption.java +++ b/common/src/main/java/org/valkyrienskies/mod/mixin/mod_compat/create/MixinContraption.java @@ -13,7 +13,6 @@ import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; import org.valkyrienskies.core.api.ships.LoadedServerShip; import org.valkyrienskies.mod.common.VSGameUtilsKt; -import org.valkyrienskies.mod.common.util.SplitHandler; import org.valkyrienskies.mod.common.util.SplittingDisablerAttachment; @Mixin(Contraption.class) 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 c3ff37678..54dbb47a5 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 @@ -46,6 +46,7 @@ import org.valkyrienskies.core.api.ships.properties.IShipActiveChunksSet; import org.valkyrienskies.core.apigame.GameServer; import org.valkyrienskies.core.apigame.ShipTeleportData; +import org.valkyrienskies.core.apigame.ships.LoadedServerShipCore; import org.valkyrienskies.core.apigame.world.IPlayer; import org.valkyrienskies.core.apigame.world.ServerShipWorldCore; import org.valkyrienskies.core.apigame.world.VSPipeline; @@ -95,12 +96,12 @@ public abstract class MixinMinecraftServer implements IShipObjectWorldServerProv method = "runServer" ) private void beforeInitServer(final CallbackInfo info) { - ValkyrienSkiesMod.setCurrentServer(MinecraftServer.class.cast(this)); + ValkyrienSkiesMod.addServer(MinecraftServer.class.cast(this)); } @Inject(at = @At("TAIL"), method = "stopServer") private void afterStopServer(final CallbackInfo ci) { - ValkyrienSkiesMod.setCurrentServer(null); + ValkyrienSkiesMod.removeServer(null); } @Nullable @@ -201,6 +202,9 @@ private void preTick(final CallbackInfo ci) { // endregion vsPipeline.preTickGame(); + for (final ServerLevel level : getAllLevels()) { + //EntityDragger.INSTANCE.dragEntitiesWithShips(level.getAllEntities(), true); + } } /** @@ -232,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); } @@ -256,7 +260,7 @@ private void handleShipPortals() { final BlockPos blockPos2 = new BlockPos(shipPos.x() + bbRadius, shipPos.y() + bbRadius, shipPos.z() + bbRadius); // Only run this code if the chunks between blockPos and blockPos2 are loaded if (level.hasChunksAt(blockPos, blockPos2)) { - shipObject.decayPortalCoolDown(); + ((LoadedServerShipCore) shipObject).decayPortalCoolDown(); final BlockPos.MutableBlockPos mutableBlockPos = new BlockPos.MutableBlockPos(); for (int i = blockPos.getX(); i <= blockPos2.getX(); ++i) { @@ -266,7 +270,7 @@ private void handleShipPortals() { final BlockState blockState = level.getBlockState(mutableBlockPos); if (blockState.getBlock() == Blocks.NETHER_PORTAL) { // Handle nether portal teleport - if (!shipObject.isOnPortalCoolDown()) { + if (!((LoadedServerShipCore) shipObject).isOnPortalCoolDown()) { // Move the ship between dimensions final ServerLevel destLevel = getLevel(level.dimension() == Level.NETHER ? Level.OVERWORLD : Level.NETHER); // TODO: Do we want portal time? @@ -276,7 +280,7 @@ private void handleShipPortals() { level.getProfiler().pop(); } } - shipObject.handleInsidePortal(); + ((LoadedServerShipCore) shipObject).handleInsidePortal(); } else if (blockState.getBlock() == Blocks.END_PORTAL) { // Handle end portal teleport final ServerLevel destLevel = level.getServer().getLevel(level.dimension() == Level.END ? Level.OVERWORLD : Level.END); 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 af9fa6e19..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; @@ -51,11 +54,13 @@ import org.valkyrienskies.core.apigame.world.chunks.TerrainUpdate; import org.valkyrienskies.mod.common.IShipObjectWorldServerProvider; import org.valkyrienskies.mod.common.VSGameUtilsKt; +import org.valkyrienskies.mod.common.ValkyrienSkiesMod; import org.valkyrienskies.mod.common.block.WingBlock; import org.valkyrienskies.mod.common.util.VSServerLevel; 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) @@ -68,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<>(); @@ -104,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 */ @@ -248,6 +276,10 @@ private void postTick(final BooleanSupplier shouldKeepTicking, final CallbackInf voxelShapeUpdates ); + if (ValkyrienSkiesMod.getVsCore().getHooks().getEnableSplitting()) { + ValkyrienSkiesMod.splitHandler.tick(ServerLevel.class.cast(this)); + } + } @Override 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/mod_compat/bluemap/WorldDuck.java b/common/src/main/java/org/valkyrienskies/mod/mixinducks/mod_compat/bluemap/WorldDuck.java index 162811d5f..0091c4bb9 100644 --- a/common/src/main/java/org/valkyrienskies/mod/mixinducks/mod_compat/bluemap/WorldDuck.java +++ b/common/src/main/java/org/valkyrienskies/mod/mixinducks/mod_compat/bluemap/WorldDuck.java @@ -1,6 +1,5 @@ package org.valkyrienskies.mod.mixinducks.mod_compat.bluemap; -import net.minecraft.resources.ResourceLocation; import net.minecraft.world.level.Level; public interface WorldDuck { diff --git a/common/src/main/java/org/valkyrienskies/mod/mixinducks/world/WithSealedPositions.java b/common/src/main/java/org/valkyrienskies/mod/mixinducks/world/WithSealedPositions.java new file mode 100644 index 000000000..6e835c004 --- /dev/null +++ b/common/src/main/java/org/valkyrienskies/mod/mixinducks/world/WithSealedPositions.java @@ -0,0 +1,9 @@ +package org.valkyrienskies.mod.mixinducks.world; + +import net.minecraft.core.BlockPos; + +public interface WithSealedPositions { + public void setSealed(BlockPos pos, boolean sealed); + + public boolean[] getSealed(); +} 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/api/ShipBlockHitResult.kt b/common/src/main/kotlin/org/valkyrienskies/mod/api/ShipBlockHitResult.kt new file mode 100644 index 000000000..729fa3656 --- /dev/null +++ b/common/src/main/kotlin/org/valkyrienskies/mod/api/ShipBlockHitResult.kt @@ -0,0 +1,69 @@ +package org.valkyrienskies.mod.api + +import net.minecraft.world.phys.BlockHitResult +import net.minecraft.world.phys.Vec3 + +/** + * Valkyrien Skies modifies vanilla methods to return an instance of [ShipBlockHitResult] when a raycast hits a block + * on a ship. + * + * By default, we set the [location] to be in world-space, while the [blockPos] is in ship-space. However, + * in some cases, mods want the [location] to be in ship-space as well. In that case, that mod (or a mixin into it) + * can check if a hit result is `instanceof ShipBlockHitResult` and use either [useLocationInShip] or + * [withLocationInShip] to change the [location]. + * + * See also: [#613](https://github.com/ValkyrienSkies/Valkyrien-Skies-2/issues/613) + * + * @constructor The default constructor uses the [locationInWorld] to set [location]. The [location] of the original + * [BlockHitResult] is ignored. + */ +class ShipBlockHitResult private constructor( + hitResult: BlockHitResult, + /** + * The location this raycast hit, in ship coordinates + */ + val locationInShip: Vec3, + /** + * The location this raycast hit, in world coordinates + */ + val locationInWorld: Vec3, +) : BlockHitResult(locationInWorld, hitResult.direction, hitResult.blockPos, hitResult.isInside) { + + companion object { + @JvmStatic + fun create(hitResult: BlockHitResult, locationInShip: Vec3, locationInWorld: Vec3) = + ShipBlockHitResult(hitResult, locationInShip, locationInWorld) + } + + init { + require(hitResult.type != Type.MISS) { "Cannot construct a ShipBlockHitResult out of a miss." } + } + + /** + * Sets [location] to [locationInShip]. This *mutates* the current hit result - use carefully. + */ + fun useLocationInShip(): BlockHitResult = also { + this.location = locationInShip + } + + /** + * Sets [location] to [locationInWorld]. This *mutates* the current hit result - use carefully. + */ + fun useLocationInWorld(): BlockHitResult = also { + this.location = locationInWorld + } + + /** + * Returns a new copy of this [ShipBlockHitResult] with [location] set to [locationInWorld]. + */ + fun withLocationInWorld(): ShipBlockHitResult = + ShipBlockHitResult(this, locationInShip, locationInWorld) + .also { it.useLocationInWorld() } + + /** + * Returns a new copy of this [ShipBlockHitResult] with [location] set to [locationInShip]. + */ + fun withLocationInShip(): ShipBlockHitResult = + ShipBlockHitResult(this, locationInShip, locationInWorld) + .also { it.useLocationInShip() } +} diff --git a/common/src/main/kotlin/org/valkyrienskies/mod/api/ValkyrienSkies.kt b/common/src/main/kotlin/org/valkyrienskies/mod/api/ValkyrienSkies.kt new file mode 100644 index 000000000..b390403f4 --- /dev/null +++ b/common/src/main/kotlin/org/valkyrienskies/mod/api/ValkyrienSkies.kt @@ -0,0 +1,611 @@ +/* + * This is basically the god class for all functions needed by addon developers. + * + * This class may be moved into a separate mod at some point, so that it can + * be shaded. Therefore, only use standard libraries, Minecraft classes, and + * classes in + * + * - org.valkyrienskies.core.api.* + * - org.valkyrienskies.mod.api.* + * - org.joml.* + * + * Notably do NOT use classes from + * - org.valkyrienskies.mod.* (except api) + * - org.valkyrienskies.core.util.* + * - org.valkyrienskies.core.apigame.* + * + * Style notes: + * + * Since this class will also be used from Java, try to make parameters nullable + * wherever possible. Null-checking is pretty cumbersome in Java, so for each + * function that takes non-nullable parameters and returns a non-nullable type, + * make a variant that takes nullable parameters and returns a nullable type as + * well. + * + * Prefer to use extension functions and fields rather than global functions. + */ +@file:JvmName("ValkyrienSkies") +package org.valkyrienskies.mod.api + +import com.mojang.blaze3d.vertex.PoseStack +import com.mojang.math.Matrix3f +import com.mojang.math.Matrix4f +import net.minecraft.client.Minecraft +import net.minecraft.core.BlockPos +import net.minecraft.core.Direction +import net.minecraft.core.Position +import net.minecraft.core.Vec3i +import net.minecraft.server.MinecraftServer +import net.minecraft.world.entity.Entity +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.jetbrains.annotations.Contract +import org.joml.Matrix4d +import org.joml.Matrix4dc +import org.joml.Quaterniondc +import org.joml.Quaternionf +import org.joml.Vector2i +import org.joml.Vector2ic +import org.joml.Vector3d +import org.joml.Vector3dc +import org.joml.Vector3f +import org.joml.Vector3i +import org.joml.Vector3ic +import org.joml.Vector4f +import org.joml.primitives.AABBd +import org.joml.primitives.AABBdc +import org.valkyrienskies.core.api.ships.Ship +import org.valkyrienskies.core.api.ships.properties.ShipId +import org.valkyrienskies.core.api.ships.properties.ShipTransform +import org.valkyrienskies.core.api.util.functions.DoubleTernaryConsumer +import org.valkyrienskies.core.api.world.ClientShipWorld +import org.valkyrienskies.core.api.world.ServerShipWorld +import org.valkyrienskies.core.api.world.ShipWorld +import org.valkyrienskies.core.api.world.properties.DimensionId +import org.valkyrienskies.mod.common.util.multiply +import org.valkyrienskies.mod.common.util.set +import org.valkyrienskies.mod.common.util.toJOML +import org.valkyrienskies.mod.common.util.toMinecraft +import kotlin.contracts.ExperimentalContracts +import kotlin.contracts.InvocationKind.EXACTLY_ONCE +import kotlin.contracts.contract +import kotlin.math.sqrt + +/** + * The singleton instance of [VsApi]. + */ +@get:JvmName("api") +val vsApi: VsApi by lazy { + try { + val modClass = Class.forName("org.valkyrienskies.mod.common.ValkyrienSkiesMod") + val getApi = modClass.getDeclaredMethod("getApi") + val instance = getApi.invoke(null) as VsApi + + instance + } catch (ex: Exception) { + throw IllegalStateException("Failed initialize the Valkyrien Skies API. " + + "Suggestion: Ensure that you have Valkyrien Skies installed.", ex) + } +} + +/** + * The String/[DimensionId] used within vs-core for representing this [Level]. + * + * This is a Kotlin-only function. + */ +val Level.dimensionId: DimensionId + @JvmSynthetic + @JvmName("getDimensionIdNonnull") + get() = vsApi.getDimensionId(this) + +/** + * The String/[DimensionId] used within vs-core for representing this [Level]. + */ +val Level?.dimensionId: DimensionId? + @Contract("null -> null; !null -> !null") + get() = this?.dimensionId + + +/** + * The [ServerShipWorld] associated with this [MinecraftServer] if it exists. + */ +val MinecraftServer?.shipWorld: ServerShipWorld? get() = + vsApi.getServerShipWorld(this) + +/** + * The [ClientShipWorld] associated with this [Minecraft] if it exists. + */ +val Minecraft?.shipWorld: ClientShipWorld? get() = + vsApi.getClientShipWorld(this) + +val Level?.shipWorld: ShipWorld? get() = + vsApi.getShipWorld(this) + +fun Level?.getShipById(id: ShipId): Ship? = + shipWorld?.allShips?.getById(id) + +fun Level?.isBlockInShipyard(blockX: Int, blockY: Int, blockZ: Int): Boolean = + isChunkInShipyard(blockX shr 4, blockZ shr 4) + +fun Level?.isChunkInShipyard(chunkX: Int, chunkZ: Int): Boolean = + vsApi.isChunkInShipyard(this, chunkX, chunkZ) + +fun Level?.getShipManagingChunk(chunkX: Int, chunkZ: Int): Ship? = + vsApi.getShipManagingChunk(this, chunkX, chunkZ) + +fun Level?.getShipManagingChunk(pos: ChunkPos?): Ship? = + pos?.let { getShipManagingChunk(pos.x, pos.z) } + +fun Level?.getShipManagingBlock(x: Int, y: Int, z: Int): Ship? = + getShipManagingChunk(x shr 4, z shr 4) + +fun Level?.getShipManagingBlock(pos: BlockPos?): Ship? = + pos?.let { getShipManagingBlock(pos.x, pos.y, pos.z) } + +fun Level?.getShipManagingBlock(x: Double, y: Double, z: Double): Ship? = + getShipManagingBlock(x.toInt(), y.toInt(), z.toInt()) + +fun Level?.getShipManagingBlock(v: Vector3dc?): Ship? = + v?.let { getShipManagingBlock(v.x(), v.y(), v.z()) } + +fun Level?.getShipManagingBlock(v: Position?): Ship? = + v?.let { getShipManagingBlock(v.x(), v.y(), v.z()) } + +/** + * Convenience function for + * `entity.level().getShipManagingBlock(entity.position())`. + * + * @see getShipManagingBlock + */ +fun Entity?.getShipManagingEntity(): Ship? = + this?.level?.getShipManagingBlock(position()) + +/** + * If both endpoints of the given [aabb] are in the same ship, transform them + * to the world and return the new AABB. Otherwise, leaves it untouched. + */ +fun Level?.toWorld(aabb: AABBdc, dest: AABBd): AABBd { + val ship1 = getShipManagingBlock(aabb.minX(), aabb.minY(), aabb.minZ()) + ?: return dest.set(aabb) + val ship2 = getShipManagingBlock(aabb.maxX(), aabb.maxY(), aabb.maxZ()) + ?: return dest.set(aabb) + + // if both endpoints of the aabb are in the same ship, do the transform + if (ship1.id == ship2.id) { + return aabb.transform(ship1.shipToWorld, dest) + } + + return dest.set(aabb) +} + +fun Level?.toWorld(aabb: AABBd) = + toWorld(aabb, aabb) + +fun Level?.toWorld(aabb: AABB): AABB = + toWorld(aabb.toJOML()).toMinecraft() + + +fun Level?.positionToWorld(pos: Vec3): Vec3 = + getShipManagingBlock(pos).positionToWorld(pos) + +fun Level?.positionToWorld(pos: Vector3d): Vector3d = + positionToWorld(pos, pos) + +fun Level?.positionToWorld(pos: Vector3dc, dest: Vector3d): Vector3d = + getShipManagingBlock(pos).positionToWorld(pos, dest) + +fun Ship?.positionToWorld(pos: Vec3): Vec3 = + this?.transform.positionToWorld(pos) + +fun Ship?.positionToWorld(pos: Vector3d): Vector3d = + positionToWorld(pos, pos) + +fun Ship?.positionToWorld(pos: Vector3dc, dest: Vector3d): Vector3d = + this?.transform.positionToWorld(pos, dest) + +fun Ship?.positionToShip(pos: Vec3): Vec3 = + this?.transform.positionToShip(pos) + +fun Ship?.positionToShip(pos: Vector3d): Vector3d = + positionToShip(pos, pos) + +fun Ship?.positionToShip(pos: Vector3dc, dest: Vector3d): Vector3d = + this?.transform.positionToShip(pos, dest) + +fun ShipTransform?.positionToWorld(pos: Vec3): Vec3 = + this?.shipToWorld?.transformPosition(pos) ?: pos + +fun ShipTransform?.positionToWorld(pos: Vector3d): Vector3d = + positionToWorld(pos, pos) + +fun ShipTransform?.positionToWorld(pos: Vector3dc, dest: Vector3d): Vector3d = + this?.shipToWorld?.transformPosition(pos, dest) ?: dest.set(pos) + +fun ShipTransform?.positionToShip(pos: Vec3): Vec3 = + this?.worldToShip?.transformPosition(pos) ?: pos + +fun ShipTransform?.positionToShip(pos: Vector3d): Vector3d = + positionToShip(pos, pos) + +fun ShipTransform?.positionToShip(pos: Vector3dc, dest: Vector3d): Vector3d = + this?.worldToShip?.transformPosition(pos, dest) ?: dest.set(pos) + +/** + * Returns all the ships whose AABBs contain x/y/z + */ +fun Level?.getShipsIntersecting(x: Double, y: Double, z: Double): Iterable = + vsApi.getShipsIntersecting(this, x, y, z) + +/** + * Returns all the ships whose AABBs intersect [aabb] + */ +fun Level?.getShipsIntersecting(aabb: AABBdc?): Iterable = + vsApi.getShipsIntersecting(this, aabb) + +/** + * Transforms the given world position x/y/z into the ship space of all ships whose AABBs contain x/y/z + */ +fun Level?.positionToNearbyShips(x: Double, y: Double, z: Double): Iterable = + getShipsIntersecting(x, y, z).map { it.positionToShip(Vector3d(x, y, z)) } + +/** + * Transforms the given world position into the ship space of all ships whose AABB intersects [aabb] + */ +fun Level?.positionToNearbyShips(x: Double, y: Double, z: Double, aabb: AABBdc?): Iterable { + if (this == null || aabb == null) return emptyList() + return getShipsIntersecting(aabb).map { it.positionToShip(Vector3d(x, y, z)) } +} + +fun Level?.positionToNearbyShips(x: Double, y: Double, z: Double, aabbRadius: Double): Iterable = + positionToNearbyShips(x, y, z, newAABBWithRadius(x, y, z, aabbRadius)) + +fun Level?.positionToNearbyShips(x: Double, y: Double, z: Double, cb: DoubleTernaryConsumer): Unit = + positionToNearbyShips(x, y, z, null, cb::accept) + +fun Level?.positionToNearbyShips(x: Double, y: Double, z: Double, aabb: AABBdc?, cb: DoubleTernaryConsumer): Unit = + positionToNearbyShips(x, y, z, aabb, cb::accept) + +fun Level?.positionToNearbyShips(x: Double, y: Double, z: Double, aabbRadius: Double, cb: DoubleTernaryConsumer): Unit = + positionToNearbyShips(x, y, z, newAABBWithRadius(x, y, z, aabbRadius), cb) + +fun Level?.positionToNearbyShipsAndWorld(x: Double, y: Double, z: Double): Iterable = + listOf(Vector3d(x, y, z)) + positionToNearbyShips(x, y, z) + +fun Level?.positionToNearbyShipsAndWorld(x: Double, y: Double, z: Double, aabb: AABBdc?): Iterable = + listOf(Vector3d(x, y, z)) + positionToNearbyShips(x, y, z, aabb) + +fun Level?.positionToNearbyShipsAndWorld(x: Double, y: Double, z: Double, aabbRadius: Double): Iterable = + positionToNearbyShipsAndWorld(x, y, z, newAABBWithRadius(x, y, z, aabbRadius)) + +fun Level?.positionToNearbyShipsAndWorld(x: Double, y: Double, z: Double, cb: DoubleTernaryConsumer): Unit = + positionToNearbyShipsAndWorld(x, y, z, null, cb::accept) + +fun Level?.positionToNearbyShipsAndWorld(x: Double, y: Double, z: Double, aabb: AABBdc?, cb: DoubleTernaryConsumer): Unit = + positionToNearbyShipsAndWorld(x, y, z, aabb, cb::accept) + +fun Level?.positionToNearbyShipsAndWorld(x: Double, y: Double, z: Double, aabbRadius: Double, cb: DoubleTernaryConsumer): Unit = + positionToNearbyShipsAndWorld(x, y, z, newAABBWithRadius(x, y, z, aabbRadius), cb) + +private fun newAABBWithRadius(x: Double, y: Double, z: Double, r: Double) = + AABBd(x - r, y - r, z - r, x + r, y + r, z + r) + +private inline fun Level?.positionToNearbyShipsAndWorld( + x: Double, + y: Double, + z: Double, + aabb: AABBdc?, + cb: (Double, Double, Double) -> Unit +) { + cb(x, y, z) + positionToNearbyShips(x, y, z, aabb, cb) +} + +/** + * Gets all ships intersecting [aabb] (or x, y, z if [aabb] is null), then + * transforms the position x, y, z to their respective ship spaces + * and calls [cb] with the transformed positions. + */ +private inline fun Level?.positionToNearbyShips( + x: Double, + y: Double, + z: Double, + aabb: AABBdc?, + cb: (Double, Double, Double) -> Unit +) { + val ships = aabb?.let(this::getShipsIntersecting) + ?: this.getShipsIntersecting(x, y, z) + + for (ship in ships) { + ship.worldToShip.transformPositionInline(x, y, z, cb) + } +} + +fun Level?.distance(x1: Double, y1: Double, z1: Double, x2: Double, y2: Double, z2: Double): Double = + sqrt(distanceSquared(x1, y1, z1, x2, y2, z2)) + +fun Level?.distance(v1: Position, v2: Position): Double = + sqrt(distanceSquared(v1, v2)) + +/** + * Calculates squared distance including ships. + * + * Transforms the points into world space, then calculates the squared distance + * between them. + */ +fun Level?.distanceSquared(x1: Double, y1: Double, z1: Double, x2: Double, y2: Double, z2: Double): Double { + var inWorldX1 = x1 + var inWorldY1 = y1 + var inWorldZ1 = z1 + var inWorldX2 = x2 + var inWorldY2 = y2 + var inWorldZ2 = z2 + + val ship1 = this.getShipManagingBlock(x1, y1, z1) + val ship2 = this.getShipManagingBlock(x2, y2, z2) + + // Do this transform manually to avoid allocation + if (ship1 != null && ship2 != null && ship1 != ship2) { + ship1.shipToWorld.transformPositionInline(x1, y1, z1) { x, y, z -> + inWorldX1 = x + inWorldY1 = y + inWorldZ1 = z + } + ship2.shipToWorld.transformPositionInline(x2, y2, z2) { x, y, z -> + inWorldX2 = x + inWorldY2 = y + inWorldZ2 = z + } + } + + val dx = inWorldX2 - inWorldX1 + val dy = inWorldY2 - inWorldY1 + val dz = inWorldZ2 - inWorldZ1 + + return dx * dx + dy * dy + dz * dz +} + +/** + * Variant of [Vec3.distanceToSqr] including ships. + */ +fun Level?.distanceSquared(v1: Position, v2: Position): Double = + distanceSquared(v1.x(), v1.y(), v1.z(), v2.x(), v2.y(), v2.z()) + +/** + * Variant of [Vec3i.distSqr] including ships. + */ +fun Level?.distanceSquared(v1: Vec3i, v2: Vec3i): Double = + distanceSquared(v1.x.toDouble(), v1.y.toDouble(), v1.z.toDouble(), + v2.x.toDouble(), v2.y.toDouble(), v2.z.toDouble()) + +/** + * Variant of [Vec3i.distToCenterSqr] including ships. + */ +fun Level?.distanceToCenterSquared(v1: Vec3i, x2: Double, y2: Double, z2: Double): Double = + distanceSquared(v1.x.toDouble() + 0.5, v1.y.toDouble() + 0.5, v1.z.toDouble() + 0.5, x2, y2, z2) + +/** + * Variant of [Vec3i.distToCenterSqr] including ships. + */ +fun Level?.distanceToCenterSquared(v1: Vec3i, v2: Position): Double = + distanceToCenterSquared(v1, v2.x(), v2.y(), v2.z()) + +/** + * Variant of [Vec3.closerThan] including ships. + */ +fun Level?.closerThan(v1: Position, v2: Position, distance: Double): Boolean = + distanceSquared(v1, v2) < distance.squared() + +/** + * Variant of [Vec3i.closerThan] including ships + */ +fun Level?.closerThan(v1: Vec3i, v2: Vec3i, distance: Double): Boolean = + distanceSquared(v1, v2) < distance.squared() + +/** + * Variant of [Vec3i.closerToCenterThan] including ships + */ +fun Level?.closerToCenterThan(v1: Vec3i, x2: Double, y2: Double, z2: Double, distance: Double): Boolean = + distanceToCenterSquared(v1, x2, y2, z2) < distance.squared() + +/** + * Variant of [Vec3i.closerToCenterThan] including ships + */ +fun Level?.closerToCenterThan(v1: Vec3i, v2: Position, distance: Double): Boolean = + distanceToCenterSquared(v1, v2) < distance.squared() + +// region Private utilities + +private fun Double.squared() = this * this + +/** + * Transform a position without allocating any intermediate objects + */ +@OptIn(ExperimentalContracts::class) +private inline fun Matrix4dc.transformPositionInline( + x: Double, + y: Double, + z: Double, + transformed: (Double, Double, Double) -> T +): T { + contract { + callsInPlace(transformed, EXACTLY_ONCE) + } + return transformed( + m00() * x + m10() * y + m20() * z + m30(), + m01() * x + m11() * y + m21() * z + m31(), + m02() * x + m12() * y + m22() * z + m32() + ) +} + +// endregion + +// region Vector Conversions + +// region JOML + +/** + * Sets the x, y, and z components to match the supplied vector [v]. + * + * @return this + */ +fun Vector3i.set(v: Vec3i) = also { + x = v.x + y = v.y + z = v.z +} + +/** + * Sets the x, y, and z components to match the supplied vector [v]. + * + * @return this + */ +fun Vector3d.set(v: Vec3i) = also { + x = v.x.toDouble() + y = v.y.toDouble() + z = v.z.toDouble() +} + +/** + * Sets the x, y, and z components to match the supplied vector [v]. + * + * @return this + */ +fun Vector3f.set(v: Vec3i) = also { + x = v.x.toFloat() + y = v.y.toFloat() + z = v.z.toFloat() +} + +/** + * Sets the x, y, and z components to match the supplied vector [v]. + * + * @return this + */ +fun Vector3d.set(v: Position) = also { + x = v.x() + y = v.y() + z = v.z() +} + +/** + * Sets the minX, minY, minZ, maxX, maxY, and maxZ components to match the + * supplied aabb [v]. + * + * @return this + */ +fun AABBd.set(v: AABB) = also { + minX = v.minX + minY = v.minY + minZ = v.minZ + maxX = v.maxX + maxY = v.maxY + maxZ = v.maxZ +} + +/** + * Converts a [Vector3ic] to a [BlockPos]. + * + * @return a new [BlockPos] with x, y, and z components matching this. + */ +fun Vector3ic.toBlockPos() = BlockPos(x(), y(), z()) + +/** + * Converts a [Vector3dc] to a [Vec3]. + * + * @return a new [Vec3] with x, y, and z components matching this. + */ +fun Vector3dc.toMinecraft() = Vec3(x(), y(), z()) + +/** + * Converts an [AABBdc] to an [AABB]. + * + * @return a new [AABB] with minX, minY, minZ, maxX, maxY, and maxZ components + * matching this. + */ +fun AABBdc.toMinecraft() = AABB(minX(), minY(), minZ(), maxX(), maxY(), maxZ()) + +/** + * Converts an [AABB] to an [AABBd]. + * + * @return a new [AABBd] with minX, minY, minZ, maxX, maxY, and maxZ components + * matching this. + */ +fun AABB.toJOML() = AABBd().set(this) + +fun Vector2ic.toChunkPos() = ChunkPos(x(), y()) +fun ChunkPos.toJOML() = Vector2i().set(this) + +fun Vec3.toJOML() = Vector3d().set(this) + +fun Vector3d.set(v: Vec3) = also { + x = v.x + y = v.y + z = v.z +} + +fun Vector2i.set(pos: ChunkPos) = also { + x = pos.x + y = pos.z +} + +@JvmOverloads +fun Matrix4dc.transformDirection(v: Vec3i, dest: Vector3d = Vector3d()): Vector3d = + transformDirection(dest.set(v.x.toDouble(), v.y.toDouble(), v.z.toDouble())) + +@JvmOverloads +fun Matrix4dc.transformDirection(dir: Direction, dest: Vector3d = Vector3d()) = transformDirection(dir.normal, dest) + +fun Matrix4dc.transform(v: Vector4f) = v.also { + it.set( + (m00() * v.x() + m01() * v.y() + m02() * v.z() + m03() * v.w()).toFloat(), + (m10() * v.x() + m11() * v.y() + m12() * v.z() + m13() * v.w()).toFloat(), + (m20() * v.x() + m21() * v.y() + m22() * v.z() + m23() * v.w()).toFloat(), + (m30() * v.x() + m31() * v.y() + m32() * v.z() + m33() * v.w()).toFloat() + ) +} + +/** + * Transforms the position [v] by this. + * + * @return a new [Vec3] representing the transformed position + * + * @see Matrix4dc.transformPosition + */ +fun Matrix4dc.transformPosition(v: Position): Vec3 { + transformPositionInline(v.x(), v.y(), v.z()) { x, y, z -> + return Vec3(x, y, z) + } +} + +// endregion + +// region Minecraft + +// fun PoseStack.multiply(modelTransform: Matrix4dc, normalTransform: Quaterniondc) = also { +// val last = last() +// +// val newPose = Matrix4d().set(last.pose()).mul(modelTransform) +// val newNormal = last.normal().mul(Matrix3f().mul(normalTransform.toMinecraft())) +// +// last.pose().set(newPose) +// last.normal().set(newNormal) +// } + +fun PoseStack.multiply(modelTransform: Matrix4dc) = also { + val last = last() + val newPose = Matrix4f().set(last.pose().toJOML()).multiply(modelTransform) + last.pose().set(newPose.toJOML()) +} + +fun Vec3i.toJOML() = Vector3i().set(this) +fun Vec3i.toJOMLd() = Vector3d().set(this) +fun Vec3i.toJOMLf() = Vector3f().set(this) + +fun Position.toJOML() = Vector3d().set(this) + +fun Quaterniondc.toFloat() = Quaternionf(x(), y(), z(), w()) +// endregion + +// endregion diff --git a/common/src/main/kotlin/org/valkyrienskies/mod/api/VsAPI.kt b/common/src/main/kotlin/org/valkyrienskies/mod/api/VsAPI.kt index 270bf4ebc..8ae41d302 100644 --- a/common/src/main/kotlin/org/valkyrienskies/mod/api/VsAPI.kt +++ b/common/src/main/kotlin/org/valkyrienskies/mod/api/VsAPI.kt @@ -1,21 +1,47 @@ package org.valkyrienskies.mod.api +import net.minecraft.client.Minecraft import net.minecraft.client.gui.screens.Screen import net.minecraft.client.multiplayer.ClientLevel -import net.minecraft.core.BlockPos -import net.minecraft.world.level.ChunkPos -import net.minecraft.world.level.Level +import net.minecraft.server.MinecraftServer import net.minecraft.server.level.ServerLevel import net.minecraft.world.entity.Entity - +import net.minecraft.world.level.Level import org.jetbrains.annotations.ApiStatus.* +import org.joml.Vector3dc +import org.joml.primitives.AABBdc import org.valkyrienskies.core.api.VsCoreApi import org.valkyrienskies.core.api.event.ListenableEvent import org.valkyrienskies.core.api.ships.* +import org.valkyrienskies.core.api.world.ClientShipWorld +import org.valkyrienskies.core.api.world.ServerShipWorld +import org.valkyrienskies.core.api.world.ShipWorld +import org.valkyrienskies.core.api.world.properties.DimensionId import org.valkyrienskies.mod.api.events.PostRenderShipEvent import org.valkyrienskies.mod.api.events.PreRenderShipEvent import org.valkyrienskies.mod.api.events.RegisterBlockStateEvent +/** + * Public API for Valkyrien Skies with both Minecraft and core APIs. This class + * is stable to use, but using the extension methods (exposed as static methods + * in `ValkyrienSkies` class to Java users) is more ergonomic in most cases. + * + * This is meant to be used by: + * - Valkyrien Skies addon developers + * - 3rd-party mod developers implementing Valkyrien Skies compatibility or + * integrations + * + * You can access the singleton instance of this via [vsApi] + * (exposed as `ValkyrienSkies.getApi()` from Java). + */ +/* + * This class may be moved into a separate mod at some point, so that it can + * be shaded. Therefore, only use Minecraft classes and classes in + * + * - org.valkyrienskies.core.api.* + * - org.valkyrienskies.mod.api.* + * - org.joml.* + */ @NonExtendable interface VsApi : VsCoreApi { @@ -31,26 +57,105 @@ interface VsApi : VsCoreApi { @get:Experimental val postRenderShipEvent: ListenableEvent - fun isShipMountingEntity(entity: Entity): Boolean @Deprecated(message = "The legacy VS config system will be replaced soon. " + "Migrate to another config library, or the new system when it's released. ") fun createConfigScreenLegacy(parent: Screen, vararg configs: Class<*>): Screen /** - * Get the ship with the chunk claim that contains [pos], if it exists. + * Returns the [DimensionId] + */ + fun getDimensionId(level: Level): DimensionId + + /** + * Returns the ship that [entity] is mounted to, if it exists. + */ + fun getShipMountedTo(entity: Entity?): Ship? + + /** + * Returns the position in the ship that the [entity] is mounted to, if + * it exists. + */ + fun getMountPosInShip(entity: Entity?): Vector3dc? + + /** + * Returns the position in the ship that the [entity] is mounted to, if + * it exists, interpolating their position using [partialTicks] + */ + fun getMountPosInShip(entity: Entity?, partialTicks: Float): Vector3dc? + + /** + * Returns the [ServerShipWorld] associated with the given [MinecraftServer] + * if it exists. + * + * This will return null if there is no [ServerShipWorld] associated with + * [server] + */ + fun getServerShipWorld(server: MinecraftServer?): ServerShipWorld? + + /** + * Returns the [ClientShipWorld] associated with the given [Minecraft] + * if it exists. + * + * This will return null if there is no [ClientShipWorld] associated with + * [client] + */ + fun getClientShipWorld(client: Minecraft?): ClientShipWorld? + + /** + * Returns the [ShipWorld] associated with the given [Level] if it exists. * - * If either parameter is null, this will return null. + * This will return null if there is no [ShipWorld] associated with [level]. + */ + fun getShipWorld(level: Level?): ShipWorld? + + /** + * Potentially returns the globally unique [ServerShipWorld] if it exists. * - * @param level The [Level] to look for the ship in. If [level] is a - * [ServerLevel], this will return a [ServerShip]. If [level] is a - * [ClientLevel], this will return a [ClientShip]. + * This is not guaranteed to always work. Prefer to use + * `getServerShipWorld(MinecraftServer)` in almost all cases. * - * @param pos A block position in the Shipyard + * This will return null if no [ServerShipWorld] is currently loaded, or if + * multiple are loaded because multiple [MinecraftServer] are loaded. */ - fun getShipManagingBlock(level: Level?, pos: BlockPos?): Ship? + @Experimental + fun getServerShipWorld(): ServerShipWorld? - fun getShipManagingChunk(level: Level?, pos: ChunkPos?): Ship? + /** + * Potentially returns the globally unique [ClientShipWorld] if it exists. + * + * This is not guaranteed to always work. Prefer to use + * `getClientShipWorld(Minecraft)` in almost all cases. + * + * This will return null if no [ClientShipWorld] is currently loaded, or if + * multiple are loaded because multiple [Minecraft] are loaded. + */ + @Experimental + fun getClientShipWorld(): ClientShipWorld? + /** + * Returns true if the chunk is in the shipyard. + * + * If [level] is null, always returns false. + */ + fun isChunkInShipyard(level: Level?, chunkX: Int, chunkZ: Int): Boolean + + /** + * Returns the ship whose shipyard contains this chunk, if it exists and is + * in [level]. + * + * If [level] is a [ServerLevel], this will return a [ServerShip]. + * If [level] is a [ClientLevel], this will return a [ClientShip]. + * + * @param level The [Level] to look for the ship in. + */ fun getShipManagingChunk(level: Level?, chunkX: Int, chunkZ: Int): Ship? + + fun getShipManagingChunk(level: ClientLevel?, chunkX: Int, chunkZ: Int): ClientShip? + + fun getShipManagingChunk(level: ServerLevel?, chunkX: Int, chunkZ: Int): ServerShip? + + fun getShipsIntersecting(level: Level?, aabb: AABBdc?): Iterable + + fun getShipsIntersecting(level: Level?, x: Double, y: Double, z: Double): Iterable } diff --git a/common/src/main/kotlin/org/valkyrienskies/mod/api_impl/events/VSApiImpl.kt b/common/src/main/kotlin/org/valkyrienskies/mod/api_impl/events/VSApiImpl.kt index 3749d133e..ac81343a4 100644 --- a/common/src/main/kotlin/org/valkyrienskies/mod/api_impl/events/VSApiImpl.kt +++ b/common/src/main/kotlin/org/valkyrienskies/mod/api_impl/events/VSApiImpl.kt @@ -1,44 +1,99 @@ package org.valkyrienskies.mod.api_impl.events +import net.minecraft.client.Minecraft import net.minecraft.client.gui.screens.Screen -import net.minecraft.core.BlockPos +import net.minecraft.client.multiplayer.ClientLevel +import net.minecraft.server.MinecraftServer +import net.minecraft.server.level.ServerLevel import net.minecraft.world.entity.Entity -import net.minecraft.world.level.ChunkPos import net.minecraft.world.level.Level +import org.joml.Vector3dc +import org.joml.primitives.AABBd +import org.joml.primitives.AABBdc +import org.valkyrienskies.core.api.VsCoreApi +import org.valkyrienskies.core.api.ships.ClientShip +import org.valkyrienskies.core.api.ships.ServerShip import org.valkyrienskies.core.api.ships.Ship +import org.valkyrienskies.core.api.world.ClientShipWorld +import org.valkyrienskies.core.api.world.ServerShipWorld +import org.valkyrienskies.core.api.world.ShipWorld import org.valkyrienskies.core.util.events.EventEmitterImpl import org.valkyrienskies.mod.api.VsApi import org.valkyrienskies.mod.api.events.PostRenderShipEvent import org.valkyrienskies.mod.api.events.PreRenderShipEvent import org.valkyrienskies.mod.api.events.RegisterBlockStateEvent -import org.valkyrienskies.mod.common.entity.ShipMountingEntity +import org.valkyrienskies.mod.common.IShipObjectWorldClientProvider +import org.valkyrienskies.mod.common.IShipObjectWorldServerProvider +import org.valkyrienskies.mod.common.ValkyrienSkiesMod +import org.valkyrienskies.mod.common.dimensionId import org.valkyrienskies.mod.common.getShipManagingPos +import org.valkyrienskies.mod.common.getShipMountedToData +import org.valkyrienskies.mod.common.getShipObjectManagingPos +import org.valkyrienskies.mod.common.getShipsIntersecting import org.valkyrienskies.mod.compat.clothconfig.VSClothConfig -class VsApiImpl : VsApi { +@Suppress("OVERRIDE_DEPRECATION") +class VsApiImpl( + private val core: VsCoreApi +) : VsApi, VsCoreApi by core { override val registerBlockStateEvent = EventEmitterImpl() override val preRenderShipEvent = EventEmitterImpl() override val postRenderShipEvent = EventEmitterImpl() - override fun isShipMountingEntity(entity: Entity): Boolean { - return entity is ShipMountingEntity - } + override fun createConfigScreenLegacy(parent: Screen, vararg configs: Class<*>): Screen + = VSClothConfig.createConfigScreenFor(parent, *configs) - override fun createConfigScreenLegacy(parent: Screen, vararg configs: Class<*>): Screen { - return VSClothConfig.createConfigScreenFor(parent, *configs) - } + override fun getDimensionId(level: Level): String = + level.dimensionId + override fun getShipMountedTo(entity: Entity?): Ship? = + entity?.let { org.valkyrienskies.mod.common.getShipMountedTo(it) } - override fun getShipManagingBlock(level: Level?, pos: BlockPos?): Ship? { - return pos?.let { level?.getShipManagingPos(it) } - } + override fun getMountPosInShip(entity: Entity?): Vector3dc? = + entity?.let { getShipMountedToData(it) }?.mountPosInShip + + override fun getMountPosInShip(entity: Entity?, partialTicks: Float): Vector3dc? = + entity?.let { getShipMountedToData(it, partialTicks) }?.mountPosInShip + + override fun getServerShipWorld(): ServerShipWorld? = + getServerShipWorld(ValkyrienSkiesMod.currentServer) + + override fun getServerShipWorld(server: MinecraftServer?): ServerShipWorld? = + (server as IShipObjectWorldServerProvider?)?.shipObjectWorld + + override fun getClientShipWorld(): ClientShipWorld? = + getClientShipWorld(Minecraft.getInstance()) - override fun getShipManagingChunk(level: Level?, pos: ChunkPos?): Ship? { - return pos?.let { level?.getShipManagingPos(it) } + override fun getShipWorld(level: Level?): ShipWorld? = + when (level) { + is ServerLevel -> getServerShipWorld(level.server) + is ClientLevel -> getClientShipWorld() + else -> null + } + + override fun isChunkInShipyard(level: Level?, chunkX: Int, chunkZ: Int): Boolean { + if (level == null) return false + return getShipWorld(level)?.isChunkInShipyard(chunkX, chunkZ, getDimensionId(level)) ?: false } - override fun getShipManagingChunk(level: Level?, chunkX: Int, chunkZ: Int): Ship? { - return level?.getShipManagingPos(chunkX, chunkZ) + override fun getClientShipWorld(client: Minecraft?): ClientShipWorld? = + (client as IShipObjectWorldClientProvider?)?.shipObjectWorld + + override fun getShipManagingChunk(level: Level?, chunkX: Int, chunkZ: Int): Ship? = + level?.getShipManagingPos(chunkX, chunkZ) + + override fun getShipManagingChunk(level: ClientLevel?, chunkX: Int, chunkZ: Int): ClientShip? = + level?.getShipObjectManagingPos(chunkX, chunkZ) + + override fun getShipManagingChunk(level: ServerLevel?, chunkX: Int, chunkZ: Int): ServerShip? = + level?.getShipObjectManagingPos(chunkX, chunkZ) + + override fun getShipsIntersecting(level: Level?, aabb: AABBdc?): Iterable { + if (level == null || aabb == null) return emptyList() + return level.getShipsIntersecting(aabb) } + + override fun getShipsIntersecting(level: Level?, x: Double, y: Double, z: Double): Iterable = + getShipsIntersecting(level, AABBd(x, y, z, x, y, z)) } diff --git a/common/src/main/kotlin/org/valkyrienskies/mod/client/SparseVoxelRenderer.kt b/common/src/main/kotlin/org/valkyrienskies/mod/client/SparseVoxelRenderer.kt new file mode 100644 index 000000000..f975f8d35 --- /dev/null +++ b/common/src/main/kotlin/org/valkyrienskies/mod/client/SparseVoxelRenderer.kt @@ -0,0 +1,41 @@ +package org.valkyrienskies.mod.client + +import com.mojang.blaze3d.vertex.PoseStack +import com.mojang.blaze3d.vertex.Tesselator +import net.minecraft.client.Minecraft +import net.minecraft.client.renderer.MultiBufferSource.BufferSource +import net.minecraft.client.renderer.RenderType +import net.minecraft.client.renderer.debug.DebugRenderer +import net.minecraft.world.phys.AABB +import org.valkyrienskies.core.util.datastructures.SparseVoxelPosition + +class SparseVoxelRenderer() { + val voxels = HashSet() + + init { + voxels.add(SparseVoxelPosition(0, 128, 0, 2)) + } + + fun render(ms: PoseStack, buffer: BufferSource, camX: Double, camY: Double, camZ: Double) { + for (voxel in voxels) { + drawVoxel(voxel, ms, buffer, camX, camY, camZ) + } + } + + fun drawVoxel(voxel: SparseVoxelPosition, poseStack: PoseStack, buffer: BufferSource, camX: Double, camY: Double, camZ: Double) { + poseStack.pushPose() + + // Draw the voxel + val random = Minecraft.getInstance().level?.random ?: return + DebugRenderer.renderFilledBox(voxel.toAABB(-camX, -camY, -camZ), 1.0f, 1.0f, 0.5f, 0.5f) + //Tesselator.getInstance().end() + + poseStack.popPose() + } + + private fun SparseVoxelPosition.toAABB(offsetX: Double = 0.0, offsetY: Double = 0.0, offsetZ: Double = 0.0): AABB { + return AABB(x.toDouble() + offsetX, y.toDouble() + offsetY, z.toDouble() + offsetZ, + x.toDouble() + extent.toDouble() + offsetX, y.toDouble() + extent.toDouble() + offsetY, z.toDouble() + extent.toDouble() + offsetZ) + + } +} diff --git a/common/src/main/kotlin/org/valkyrienskies/mod/common/BlockStateInfoProvider.kt b/common/src/main/kotlin/org/valkyrienskies/mod/common/BlockStateInfoProvider.kt index 9178dfb7a..e6a247f00 100644 --- a/common/src/main/kotlin/org/valkyrienskies/mod/common/BlockStateInfoProvider.kt +++ b/common/src/main/kotlin/org/valkyrienskies/mod/common/BlockStateInfoProvider.kt @@ -11,8 +11,8 @@ import net.minecraft.server.level.ServerLevel import net.minecraft.world.level.Level import net.minecraft.world.level.block.Block import net.minecraft.world.level.block.state.BlockState +import org.valkyrienskies.core.api.VsBeta import org.valkyrienskies.core.api.ships.Wing -import org.valkyrienskies.core.api.ships.WingManager import org.valkyrienskies.core.apigame.world.chunks.BlockType import org.valkyrienskies.mod.common.block.WingBlock import org.valkyrienskies.mod.common.config.MassDatapackResolver @@ -99,6 +99,7 @@ object BlockStateInfo { fun onSetBlock(level: Level, blockPos: BlockPos, prevBlockState: BlockState, newBlockState: BlockState) = onSetBlock(level, blockPos.x, blockPos.y, blockPos.z, prevBlockState, newBlockState) + @OptIn(VsBeta::class) fun onSetBlock(level: Level, x: Int, y: Int, z: Int, prevBlockState: BlockState, newBlockState: BlockState) { if (!::SORTED_REGISTRY.isInitialized) return @@ -111,8 +112,8 @@ object BlockStateInfo { // region Inject wings if (level is ServerLevel) { val loadedShip = level.getShipObjectManagingPos(x shr 4, z shr 4) - if (loadedShip != null) { - val wingManager = loadedShip.getAttachment(WingManager::class.java)!! + val wingManager = loadedShip?.wingManager + if (loadedShip != null && wingManager != null) { val wasOldBlockWing = prevBlockState.block is WingBlock val newBlockStateBlock = newBlockState.block val newWing: Wing? = @@ -135,8 +136,8 @@ object BlockStateInfo { newBlockMass ) - if (ValkyrienSkiesMod.vsCore.hooks.enableConnectivity) { - ValkyrienSkiesMod.splitHandler.split(level, x, y, z, prevBlockState, newBlockState) + if (ValkyrienSkiesMod.vsCore.hooks.enableSplitting) { + ValkyrienSkiesMod.splitHandler.queueSplit(level, x, y, z) } } } 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 8c4160db7..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 @@ -24,22 +25,25 @@ import org.joml.Vector3ic import org.joml.primitives.AABBd import org.joml.primitives.AABBdc import org.valkyrienskies.core.api.ships.ClientShip +import org.valkyrienskies.core.api.ships.LoadedServerShip import org.valkyrienskies.core.api.ships.LoadedShip import org.valkyrienskies.core.api.ships.ServerShip import org.valkyrienskies.core.api.ships.Ship import org.valkyrienskies.core.api.util.functions.DoubleTernaryConsumer import org.valkyrienskies.core.api.world.LevelYRange +import org.valkyrienskies.core.api.world.properties.DimensionId import org.valkyrienskies.core.apigame.world.IPlayer import org.valkyrienskies.core.apigame.world.ServerShipWorldCore import org.valkyrienskies.core.apigame.world.ShipWorldCore import org.valkyrienskies.core.apigame.world.chunks.TerrainUpdate -import org.valkyrienskies.core.apigame.world.properties.DimensionId import org.valkyrienskies.core.game.ships.ShipObjectServer import org.valkyrienskies.core.impl.hooks.VSEvents.TickEndEvent 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 @@ -117,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. @@ -266,7 +273,7 @@ fun ClientLevel?.getShipObjectManagingPos(chunkPos: ChunkPos) = // ServerWorld fun ServerLevel?.getShipObjectManagingPos(chunkX: Int, chunkZ: Int) = - getShipObjectManagingPosImpl(this, chunkX, chunkZ) as ShipObjectServer? + getShipObjectManagingPosImpl(this, chunkX, chunkZ) as LoadedServerShip? fun ServerLevel?.getShipObjectManagingPos(blockPos: Vec3i) = getShipObjectManagingPos(blockPos.x shr 4, blockPos.z shr 4) @@ -347,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/ValkyrienSkiesMod.kt b/common/src/main/kotlin/org/valkyrienskies/mod/common/ValkyrienSkiesMod.kt index 312af3a0d..5fa0c2f32 100644 --- a/common/src/main/kotlin/org/valkyrienskies/mod/common/ValkyrienSkiesMod.kt +++ b/common/src/main/kotlin/org/valkyrienskies/mod/common/ValkyrienSkiesMod.kt @@ -5,16 +5,17 @@ import net.minecraft.world.entity.EntityType import net.minecraft.world.item.Item import net.minecraft.world.level.block.Block import net.minecraft.world.level.block.entity.BlockEntityType -import org.valkyrienskies.core.api.ships.setAttachment import org.valkyrienskies.core.apigame.VSCore import org.valkyrienskies.core.apigame.VSCoreClient import org.valkyrienskies.core.impl.hooks.VSEvents +import org.valkyrienskies.mod.api_impl.events.VsApiImpl import org.valkyrienskies.mod.common.blockentity.TestHingeBlockEntity import org.valkyrienskies.mod.common.config.VSGameConfig import org.valkyrienskies.mod.common.entity.ShipMountingEntity import org.valkyrienskies.mod.common.entity.VSPhysicsEntity import org.valkyrienskies.mod.common.networking.VSGamePackets import org.valkyrienskies.mod.common.util.GameTickForceApplier +import org.valkyrienskies.mod.common.util.ShipSettings import org.valkyrienskies.mod.common.util.SplitHandler import org.valkyrienskies.mod.common.util.SplittingDisablerAttachment @@ -36,8 +37,19 @@ object ValkyrienSkiesMod { lateinit var PHYSICS_ENTITY_TYPE: EntityType lateinit var TEST_HINGE_BLOCK_ENTITY_TYPE: BlockEntityType + /** + * Keeps track of the MinecraftServers which have been created and run. Hopefully this contains at most one + * server... + */ + private val currentServers = mutableListOf() + + @JvmStatic - var currentServer: MinecraftServer? = null + var currentServer: MinecraftServer? + get() { + return currentServers.lastOrNull() + } + set(value) { currentServer = value } @JvmStatic lateinit var vsCore: VSCore @@ -45,9 +57,28 @@ object ValkyrienSkiesMod { @JvmStatic val vsCoreClient get() = vsCore as VSCoreClient + @JvmStatic + val api by lazy { + VsApiImpl(vsCore) + } + @JvmStatic lateinit var splitHandler: SplitHandler + @JvmStatic + fun addServer(server: MinecraftServer?) { + if (server != null) { + currentServers.add(server) + } + } + @JvmStatic + fun removeServer(server: MinecraftServer?) { + if (server != null) { + currentServers.remove(server) + } + } + + fun init(core: VSCore) { this.vsCore = core @@ -59,6 +90,14 @@ object ValkyrienSkiesMod { splitHandler = SplitHandler(this.vsCore.hooks.enableBlockEdgeConnectivity, this.vsCore.hooks.enableBlockCornerConnectivity) + core.registerAttachment(ShipSettings::class.java) + core.registerAttachment(GameTickForceApplier::class.java) { + useLegacySerializer() + } + core.registerAttachment(SplittingDisablerAttachment::class.java) { + useLegacySerializer() + } + VSEvents.ShipLoadEvent.on { event -> event.ship.setAttachment(GameTickForceApplier()) event.ship.setAttachment(SplittingDisablerAttachment(true)) diff --git a/common/src/main/kotlin/org/valkyrienskies/mod/common/assembly/ShipAssembler.kt b/common/src/main/kotlin/org/valkyrienskies/mod/common/assembly/ShipAssembler.kt index dade67e14..733fd920d 100644 --- a/common/src/main/kotlin/org/valkyrienskies/mod/common/assembly/ShipAssembler.kt +++ b/common/src/main/kotlin/org/valkyrienskies/mod/common/assembly/ShipAssembler.kt @@ -7,9 +7,9 @@ import net.minecraft.world.level.block.state.BlockState import org.joml.Vector3d import org.joml.Vector3i import org.joml.Vector3ic +import org.valkyrienskies.core.api.attachment.getAttachment import org.valkyrienskies.core.api.ships.ServerShip import org.valkyrienskies.core.api.ships.Ship -import org.valkyrienskies.core.api.ships.getAttachment import org.valkyrienskies.core.impl.game.ShipTeleportDataImpl import org.valkyrienskies.mod.common.BlockStateInfo.onSetBlock import org.valkyrienskies.mod.common.dimensionId @@ -111,7 +111,7 @@ object ShipAssembler { AssemblyUtil.updateBlock(level,itPos,shipPos,level.getBlockState(shipPos)) } - val shipCenterPos = ((newShip as ServerShip).inertiaData.centerOfMassInShip).add(0.5, 0.5, 0.5, Vector3d()) + val shipCenterPos = ((newShip as ServerShip).inertiaData.centerOfMass).add(0.5, 0.5, 0.5, Vector3d()) // This is giga sus, but whatever val shipPos = Vector3d(shipOGPos).add(0.5, 0.5, 0.5) if (existingShip != null) { diff --git a/common/src/main/kotlin/org/valkyrienskies/mod/common/assembly/ShipAssembly.kt b/common/src/main/kotlin/org/valkyrienskies/mod/common/assembly/ShipAssembly.kt index e297942fc..436aea8e6 100644 --- a/common/src/main/kotlin/org/valkyrienskies/mod/common/assembly/ShipAssembly.kt +++ b/common/src/main/kotlin/org/valkyrienskies/mod/common/assembly/ShipAssembly.kt @@ -2,18 +2,159 @@ package org.valkyrienskies.mod.common.assembly import net.minecraft.core.BlockPos import net.minecraft.server.level.ServerLevel +import net.minecraft.world.Clearable +import net.minecraft.world.level.ChunkPos +import net.minecraft.world.level.block.Block +import net.minecraft.world.level.block.Blocks +import net.minecraft.world.level.block.entity.BlockEntity +import net.minecraft.world.level.levelgen.structure.templatesystem.StructurePlaceSettings +import net.minecraft.world.level.levelgen.structure.templatesystem.StructureTemplate +import org.joml.Vector3d +import org.valkyrienskies.core.api.VsBeta import org.valkyrienskies.core.api.ships.ServerShip +import org.valkyrienskies.core.api.ships.properties.ShipTransformVelocity +import org.valkyrienskies.core.apigame.ShipTeleportData +import org.valkyrienskies.core.impl.game.ShipTeleportDataImpl +import org.valkyrienskies.core.impl.game.ships.ShipData +import org.valkyrienskies.core.impl.game.ships.ShipDataCommon +import org.valkyrienskies.core.impl.game.ships.ShipTransformImpl import org.valkyrienskies.core.util.datastructures.DenseBlockPosSet -import org.valkyrienskies.mod.common.util.toBlockPos +import org.valkyrienskies.mod.common.dimensionId +import org.valkyrienskies.mod.common.executeIf +import org.valkyrienskies.mod.common.isTickingChunk +import org.valkyrienskies.mod.common.shipObjectWorld +import org.valkyrienskies.mod.common.util.toJOML +import org.valkyrienskies.mod.common.util.toJOMLD +import org.valkyrienskies.mod.common.util.toMinecraft +import org.valkyrienskies.mod.util.relocateBlock +import org.valkyrienskies.mod.util.updateBlock +@OptIn(VsBeta::class) @Deprecated("Use [ShipAssembler.assembleToShip] instead") fun createNewShipWithBlocks( centerBlock: BlockPos, blocks: DenseBlockPosSet, level: ServerLevel ): ServerShip { if (blocks.isEmpty()) throw IllegalArgumentException() + //return ShipAssembler.assembleToShip(level, blocks, true, 1.0) - val blockList: MutableList = mutableListOf() - blocks.toList().forEach { blockList.add(it.toBlockPos()) } - return ShipAssembler.assembleToShip(level, blockList, true, 1.0) + val ship = level.shipObjectWorld.createNewShipAtBlock(centerBlock.toJOML(), false, 1.0, level.dimensionId) + val shipChunkX = ship.chunkClaim.xMiddle + val shipChunkZ = ship.chunkClaim.zMiddle + val worldChunkX = centerBlock.x shr 4 + val worldChunkZ = centerBlock.z shr 4 + val deltaX = worldChunkX - shipChunkX + val deltaZ = worldChunkZ - shipChunkZ + val chunksToBeUpdated = mutableMapOf>() + blocks.forEachChunk { x, _, z, _ -> + val sourcePos = ChunkPos(x, z) + val destPos = ChunkPos(x - deltaX, z - deltaZ) + chunksToBeUpdated[sourcePos] = Pair(sourcePos, destPos) + } + val chunkPairs = chunksToBeUpdated.values.toList() + val chunkPoses = chunkPairs.flatMap { it.toList() } + val chunkPosesJOML = chunkPoses.map { it.toJOML() } + // Send a list of all the chunks that we plan on updating to players, so that they + // defer all updates until assembly is finished + // with(vsCore.simplePacketNetworking) { + // PacketStopChunkUpdates(chunkPosesJOML).sendToAllClients() + // } + // Use relocateBlock to copy all the blocks into the ship + blocks.forEachChunk { chunkX, chunkY, chunkZ, chunk -> + val sourceChunk = level.getChunk(chunkX, chunkZ) + val destChunk = level.getChunk(chunkX - deltaX, chunkZ - deltaZ) + chunk.forEach { x, y, z -> + val fromPos = BlockPos((sourceChunk.pos.x shl 4) + x, (chunkY shl 4) + y, (sourceChunk.pos.z shl 4) + z) + val toPos = BlockPos((destChunk.pos.x shl 4) + x, (chunkY shl 4) + y, (destChunk.pos.z shl 4) + z) + relocateBlock(sourceChunk, fromPos, destChunk, toPos, false, ship) + } + } + // Use updateBlock to update blocks after copying + blocks.forEachChunk { chunkX, chunkY, chunkZ, chunk -> + val sourceChunk = level.getChunk(chunkX, chunkZ) + val destChunk = level.getChunk(chunkX - deltaX, chunkZ - deltaZ) + chunk.forEach { x, y, z -> + val fromPos = BlockPos((sourceChunk.pos.x shl 4) + x, (chunkY shl 4) + y, (sourceChunk.pos.z shl 4) + z) + val toPos = BlockPos((destChunk.pos.x shl 4) + x, (chunkY shl 4) + y, (destChunk.pos.z shl 4) + z) + updateBlock(destChunk.level, fromPos, toPos, destChunk.getBlockState(toPos)) + } + } + // Calculate the position of the block that the player clicked after it has been assembled + val centerInShip = Vector3d( + ((shipChunkX shl 4) + (centerBlock.x and 15)).toDouble(), + centerBlock.y.toDouble(), + ((shipChunkZ shl 4) + (centerBlock.z and 15)).toDouble() + ) + // The ship's position has shifted from the center block since we assembled the ship, compensate for that + val centerBlockPosInWorld = ship.inertiaData.centerOfMass.sub(centerInShip, Vector3d()) + .add(ship.transform.positionInWorld) + // Put the ship into the compensated position, so that all the assembled blocks stay in the same place + // TODO: AAAAAAAAA THIS IS HORRIBLE how can the API support this? + // well now it doesnt kekw + //(ship as ShipDataCommon).transform = (ship.transform).withTransformFrom(positionInWorld = centerBlockPosInWorld) + + (ship as ShipDataCommon).setFromTransform(ship.transform.copy(position = centerBlockPosInWorld)) + level.server.executeIf( + // This condition will return true if all modified chunks have been both loaded AND + // chunk update packets were sent to players + { chunkPoses.all(level::isTickingChunk) } + ) { + // Once all the chunk updates are sent to players, we can tell them to restart chunk updates + // with(vsCore.simplePacketNetworking) { + // PacketRestartChunkUpdates(chunkPosesJOML).sendToAllClients() + // } + } + + return ship +} + +fun createNewShipWithStructure( + lowerCorner: BlockPos, higherCorner: BlockPos, blocks: StructureTemplate, level: ServerLevel +): ServerShip { + //if (blocks.size.toJOML().length() < 0.0001) throw IllegalArgumentException() + + val ship = level.shipObjectWorld.createNewShipAtBlock(lowerCorner.toJOML(), false, 1.0, level.dimensionId) + val shipChunkX = ship.chunkClaim.xMiddle + val shipChunkZ = ship.chunkClaim.zMiddle + + // Calculate the position of the block that the player clicked after it has been assembled + val lowerCornerInShip = Vector3d( + ((shipChunkX shl 4) + (lowerCorner.x and 15)).toDouble(), + lowerCorner.y.toDouble(), + ((shipChunkZ shl 4) + (lowerCorner.z and 15)).toDouble() + ) + val higherCornerInShip = Vector3d( + ((shipChunkX shl 4) + (higherCorner.x and 15)).toDouble(), + higherCorner.y.toDouble(), + ((shipChunkZ shl 4) + (higherCorner.z and 15)).toDouble() + ) + + blocks.placeInWorld(level, BlockPos(lowerCornerInShip.toMinecraft()), BlockPos(lowerCornerInShip.toMinecraft()), StructurePlaceSettings(), level.random, Block.UPDATE_ALL) + + val diff = higherCorner.subtract(lowerCorner) + val centerPos = lowerCorner.toJOMLD().add(diff.x / 2.0, diff.y / 2.0, diff.z / 2.0) + + // The ship's position has shifted from the center block since we assembled the ship, compensate for that + val centerBlockPosInWorld = ship.inertiaData.centerOfMass.sub(centerPos, Vector3d()) + .add(ship.transform.positionInWorld) + // Put the ship into the compensated position, so that all the assembled blocks stay in the same place + level.shipObjectWorld + .teleportShip(ship, ShipTeleportDataImpl(newPos = centerBlockPosInWorld.add(0.0, 0.0, 0.0, Vector3d()), newPosInShip = ship.inertiaData.centerOfMass)) + + + for (x in lowerCorner.x..higherCorner.x) { + for (y in lowerCorner.y..higherCorner.y) { + for (z in lowerCorner.z..higherCorner.z) { + if (!level.getBlockState(BlockPos(x, y, z)).isAir) { + val blockEntity: BlockEntity? = level.getBlockEntity(BlockPos(x, y, z)) + Clearable.tryClear(blockEntity) + level.removeBlockEntity(BlockPos(x, y, z)) + level.setBlock(BlockPos(x, y, z), Blocks.AIR.defaultBlockState(), Block.UPDATE_CLIENTS) + + //level.getChunk(BlockPos(x, y, z)).setBlockState(BlockPos(x, y, z), Blocks.AIR.defaultBlockState(), false) + } + } + } + } + return ship } diff --git a/common/src/main/kotlin/org/valkyrienskies/mod/common/block/TestHingeBlock.kt b/common/src/main/kotlin/org/valkyrienskies/mod/common/block/TestHingeBlock.kt index b22a496ec..0149f3464 100644 --- a/common/src/main/kotlin/org/valkyrienskies/mod/common/block/TestHingeBlock.kt +++ b/common/src/main/kotlin/org/valkyrienskies/mod/common/block/TestHingeBlock.kt @@ -33,10 +33,11 @@ import org.joml.Quaterniond import org.joml.Quaterniondc import org.joml.Vector3d import org.joml.Vector3dc -import org.valkyrienskies.core.apigame.constraints.VSAttachmentConstraint -import org.valkyrienskies.core.apigame.constraints.VSHingeOrientationConstraint +import org.valkyrienskies.core.apigame.joints.VSJointMaxForceTorque +import org.valkyrienskies.core.apigame.joints.VSJointPose +import org.valkyrienskies.core.apigame.joints.VSRevoluteJoint +import org.valkyrienskies.core.impl.bodies.properties.BodyTransformVelocityImpl import org.valkyrienskies.core.impl.game.ships.ShipDataCommon -import org.valkyrienskies.core.impl.game.ships.ShipTransformImpl import org.valkyrienskies.mod.common.ValkyrienSkiesMod import org.valkyrienskies.mod.common.blockentity.TestHingeBlockEntity import org.valkyrienskies.mod.common.dimensionId @@ -172,22 +173,26 @@ object TestHingeBlock : // Put the new ship where the old ship is val newPos = shipThisIsIn.transform.shipToWorld.transformPosition(attachmentLocalPos0, Vector3d()) newPos.sub(shipThisIsIn.transform.shipToWorldRotation.transform(attachmentOffset1, Vector3d())) - val newTransform = ShipTransformImpl( + val newTransform = BodyTransformVelocityImpl( newPos, ship.transform.positionInShip, shipThisIsIn.transform.shipToWorldRotation, // Copy source ship rotation - ship.transform.shipToWorldScaling + ship.transform.shipToWorldScaling, + velocity = shipThisIsIn.velocity, + angularVelocity = shipThisIsIn.angularVelocity ) // Update the ship transform (ship as ShipDataCommon).transform = newTransform } else { val newPos = Vector3d(attachmentLocalPos0) newPos.sub(attachmentOffset1) - val newTransform = ShipTransformImpl( + val newTransform = BodyTransformVelocityImpl( newPos, ship.transform.positionInShip, ship.transform.shipToWorldRotation, - ship.transform.shipToWorldScaling + ship.transform.shipToWorldScaling, + velocity = ship.velocity, + angularVelocity = ship.angularVelocity ) // Update the ship transform (ship as ShipDataCommon).transform = newTransform @@ -200,18 +205,18 @@ object TestHingeBlock : val shipId1 = ship.id // Attachment constraint - run { - // I don't recommend setting compliance lower than 1e-10 because it tends to cause instability - // TODO: Investigate why small compliance cause instability - val attachmentCompliance = 1e-10 - val attachmentMaxForce = 1e10 - val attachmentFixedDistance = 0.0 - val attachmentConstraint = VSAttachmentConstraint( - shipId0, shipId1, attachmentCompliance, attachmentLocalPos0, attachmentLocalPos1, - attachmentMaxForce, attachmentFixedDistance - ) - blockEntity.get().constraintId = level.shipObjectWorld.createNewConstraint(attachmentConstraint) - } + // run { + // // I don't recommend setting compliance lower than 1e-10 because it tends to cause instability + // // TODO: Investigate why small compliance cause instability + // val attachmentCompliance = 1e-10 + // val attachmentMaxForce = 1e10 + // val attachmentFixedDistance = 0.0 + // val attachmentConstraint = VSRevoluteJoint( + // shipId0, shipId1, attachmentCompliance, attachmentLocalPos0, attachmentLocalPos1, + // attachmentMaxForce, attachmentFixedDistance + // ) + // blockEntity.get().constraintId = level.shipObjectWorld.createNewConstraint(attachmentConstraint) + // } // Hinge constraints will attempt to align the X-axes of both bodies, so to align the Y axis we // apply this rotation to the X-axis @@ -221,9 +226,11 @@ object TestHingeBlock : run { // I don't recommend setting compliance lower than 1e-10 because it tends to cause instability val hingeOrientationCompliance = 1e-10 + val attachmentMaxForce = 1e10 val hingeMaxTorque = 1e10 - val hingeConstraint = VSHingeOrientationConstraint( - shipId0, shipId1, hingeOrientationCompliance, hingeOrientation, hingeOrientation, hingeMaxTorque + val hingeConstraint = VSRevoluteJoint( + shipId0, VSJointPose(attachmentLocalPos0, hingeOrientation), shipId1, VSJointPose(attachmentLocalPos1, hingeOrientation), + VSJointMaxForceTorque(attachmentMaxForce.toFloat(), hingeMaxTorque.toFloat()) ) blockEntity.get().constraintId = level.shipObjectWorld.createNewConstraint(hingeConstraint) } diff --git a/common/src/main/kotlin/org/valkyrienskies/mod/common/block/TestSphereBlock.kt b/common/src/main/kotlin/org/valkyrienskies/mod/common/block/TestSphereBlock.kt index cfdb38ac8..6c917f951 100644 --- a/common/src/main/kotlin/org/valkyrienskies/mod/common/block/TestSphereBlock.kt +++ b/common/src/main/kotlin/org/valkyrienskies/mod/common/block/TestSphereBlock.kt @@ -10,19 +10,19 @@ import net.minecraft.world.phys.shapes.Shapes import net.minecraft.world.phys.shapes.VoxelShape object TestSphereBlock : Block(Properties.of(Material.STONE)) { + @Deprecated("Deprecated in Java") override fun getVisualShape( - blockState: BlockState?, blockGetter: BlockGetter?, blockPos: BlockPos?, collisionContext: CollisionContext? + blockState: BlockState, blockGetter: BlockGetter, blockPos: BlockPos, collisionContext: CollisionContext ): VoxelShape { return Shapes.empty() } - override fun getShadeBrightness(blockState: BlockState?, blockGetter: BlockGetter?, blockPos: BlockPos?): Float { + @Deprecated("Deprecated in Java") + override fun getShadeBrightness(blockState: BlockState, blockGetter: BlockGetter, blockPos: BlockPos): Float { return 1.0f } - override fun propagatesSkylightDown( - blockState: BlockState?, blockGetter: BlockGetter?, blockPos: BlockPos? - ): Boolean { + override fun propagatesSkylightDown(blockState: BlockState, blockGetter: BlockGetter, blockPos: BlockPos): Boolean { return true } } diff --git a/common/src/main/kotlin/org/valkyrienskies/mod/common/blockentity/TestHingeBlockEntity.kt b/common/src/main/kotlin/org/valkyrienskies/mod/common/blockentity/TestHingeBlockEntity.kt index 19ba56f16..07bc7f6f3 100644 --- a/common/src/main/kotlin/org/valkyrienskies/mod/common/blockentity/TestHingeBlockEntity.kt +++ b/common/src/main/kotlin/org/valkyrienskies/mod/common/blockentity/TestHingeBlockEntity.kt @@ -3,14 +3,14 @@ package org.valkyrienskies.mod.common.blockentity import net.minecraft.core.BlockPos import net.minecraft.world.level.block.entity.BlockEntity import net.minecraft.world.level.block.state.BlockState -import org.valkyrienskies.core.apigame.constraints.VSConstraintId +import org.valkyrienskies.core.apigame.joints.VSJointId import org.valkyrienskies.mod.common.ValkyrienSkiesMod class TestHingeBlockEntity(blockPos: BlockPos, blockState: BlockState) : BlockEntity( ValkyrienSkiesMod.TEST_HINGE_BLOCK_ENTITY_TYPE, blockPos, blockState ) { var otherHingePos: BlockPos? = null - var constraintId: VSConstraintId? = null + var constraintId: VSJointId? = null fun tick() { // println("Amogus") 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 ebf47e73d..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 @@ -148,6 +148,16 @@ object VSGameConfig { description = "Minimum scale of ships" ) var minScaling = 0.25 + + @JsonSchema( + description = "Enable splitting in worldspace. (Experimental!)" + ) + var enableWorldSplitting = false + + @JsonSchema( + description = "The default grace timer for splitting. A split won't occur after a block break at a position until this many ticks have passed. Note that setting this too high may prevent things like explosions from properly launching split ships. (in ticks)" + ) + var defaultSplitGraceTimer = 1 } class Common { diff --git a/common/src/main/kotlin/org/valkyrienskies/mod/common/config/VSMassDataLoader.kt b/common/src/main/kotlin/org/valkyrienskies/mod/common/config/VSMassDataLoader.kt index 6ec62527c..f9862a060 100644 --- a/common/src/main/kotlin/org/valkyrienskies/mod/common/config/VSMassDataLoader.kt +++ b/common/src/main/kotlin/org/valkyrienskies/mod/common/config/VSMassDataLoader.kt @@ -27,7 +27,7 @@ import org.valkyrienskies.core.api.physics.blockstates.LiquidState import org.valkyrienskies.core.api.physics.blockstates.SolidBlockShape import org.valkyrienskies.core.apigame.physics.blockstates.VsBlockState import org.valkyrienskies.core.apigame.world.chunks.BlockType -import org.valkyrienskies.core.game.VSBlockType +import org.valkyrienskies.mod.api_impl.events.RegisterBlockStateEventImpl import org.valkyrienskies.mod.common.BlockStateInfoProvider import org.valkyrienskies.mod.common.ValkyrienSkiesMod import org.valkyrienskies.mod.common.hooks.VSGameEvents @@ -44,7 +44,7 @@ private data class VSBlockStateInfo( val mass: Double, val friction: Double, val elasticity: Double, - val type: VSBlockType?, + val type: BlockType?, ) object MassDatapackResolver : BlockStateInfoProvider { @@ -375,8 +375,15 @@ object MassDatapackResolver : BlockStateInfoProvider { mcBlockStateToVs[blockState] = vsBlockState } + runRegisterBlockStateEvent() + registeredBlocks = true } + private fun runRegisterBlockStateEvent() { + val event = RegisterBlockStateEventImpl() + ValkyrienSkiesMod.api.registerBlockStateEvent.emit(event) + } + private val logger by logger() } diff --git a/common/src/main/kotlin/org/valkyrienskies/mod/common/entity/ShipMountingEntity.kt b/common/src/main/kotlin/org/valkyrienskies/mod/common/entity/ShipMountingEntity.kt index 34da4e64b..483cfbeaf 100644 --- a/common/src/main/kotlin/org/valkyrienskies/mod/common/entity/ShipMountingEntity.kt +++ b/common/src/main/kotlin/org/valkyrienskies/mod/common/entity/ShipMountingEntity.kt @@ -10,8 +10,8 @@ import net.minecraft.world.entity.LivingEntity import net.minecraft.world.level.Level import net.minecraft.world.phys.Vec3 import org.joml.Vector3f +import org.valkyrienskies.core.api.attachment.removeAttachment import org.valkyrienskies.core.api.ships.LoadedServerShip -import org.valkyrienskies.core.api.ships.setAttachment import org.valkyrienskies.mod.api.SeatedControllingPlayer import org.valkyrienskies.mod.common.config.VSKeyBindings import org.valkyrienskies.mod.common.getShipManagingPos @@ -74,7 +74,7 @@ open class ShipMountingEntity(type: EntityType, level: Level override fun remove(removalReason: RemovalReason) { if (this.isController && !level.isClientSide) (level.getShipObjectManagingPos(blockPosition()) as LoadedServerShip?) - ?.setAttachment(null) + ?.removeAttachment() super.remove(removalReason) } diff --git a/common/src/main/kotlin/org/valkyrienskies/mod/common/entity/VSPhysicsEntity.kt b/common/src/main/kotlin/org/valkyrienskies/mod/common/entity/VSPhysicsEntity.kt index 9adbc52d4..28c7b736b 100644 --- a/common/src/main/kotlin/org/valkyrienskies/mod/common/entity/VSPhysicsEntity.kt +++ b/common/src/main/kotlin/org/valkyrienskies/mod/common/entity/VSPhysicsEntity.kt @@ -233,6 +233,7 @@ open class VSPhysicsEntity(type: EntityType, level: Level) : En shipId: ShipId, transform: ShipTransform, radius: Double = 0.5, mass: Double = 10000.0 ): PhysicsEntityData { val inertia = 0.4 * mass * radius * radius + // todo: fix physics entities for some reason using ship inertia data??? what?? ruby pls val inertiaData: ShipInertiaData = ShipInertiaDataImpl(Vector3d(), mass, Matrix3d().scale(inertia)) val collisionShapeData = VSSphereCollisionShapeData(radius) return PhysicsEntityData( diff --git a/common/src/main/kotlin/org/valkyrienskies/mod/common/entity/handling/WorldEntityHandler.kt b/common/src/main/kotlin/org/valkyrienskies/mod/common/entity/handling/WorldEntityHandler.kt index 295792c93..9283d7302 100644 --- a/common/src/main/kotlin/org/valkyrienskies/mod/common/entity/handling/WorldEntityHandler.kt +++ b/common/src/main/kotlin/org/valkyrienskies/mod/common/entity/handling/WorldEntityHandler.kt @@ -64,7 +64,7 @@ object WorldEntityHandler : VSEntityHandler { val newPosInShipLocal = Vector3d(newPos).sub(ship.transform.positionInWorld) val shipVelocity = Vector3d(ship.velocity) // ship linear velocity - .add(Vector3d(ship.omega).cross(newPosInShipLocal)) // angular velocity + .add(Vector3d(ship.angularVelocity).cross(newPosInShipLocal)) // angular velocity .mul(0.05) // Tick velocity val entityVelocity = ship.transform.shipToWorldRotation.transform(entity.deltaMovement.toJOML()) diff --git a/common/src/main/kotlin/org/valkyrienskies/mod/common/hooks/CommonHooksImpl.kt b/common/src/main/kotlin/org/valkyrienskies/mod/common/hooks/CommonHooksImpl.kt index 490f306f5..f5e26b1e8 100644 --- a/common/src/main/kotlin/org/valkyrienskies/mod/common/hooks/CommonHooksImpl.kt +++ b/common/src/main/kotlin/org/valkyrienskies/mod/common/hooks/CommonHooksImpl.kt @@ -30,6 +30,10 @@ abstract class CommonHooksImpl : CoreHooksOut { get() = vsCore.hooks.enableWorldConnectivity set(value) {} + override var enableSplitting: Boolean + get() = vsCore.hooks.enableSplitting + set(value) {} + override val playState: PlayState get() { if (!isPhysicalClient) { diff --git a/common/src/main/kotlin/org/valkyrienskies/mod/common/item/AreaAssemblerItem.kt b/common/src/main/kotlin/org/valkyrienskies/mod/common/item/AreaAssemblerItem.kt index cad9346d5..7d5ab48fb 100644 --- a/common/src/main/kotlin/org/valkyrienskies/mod/common/item/AreaAssemblerItem.kt +++ b/common/src/main/kotlin/org/valkyrienskies/mod/common/item/AreaAssemblerItem.kt @@ -3,17 +3,23 @@ package org.valkyrienskies.mod.common.item import net.minecraft.Util import net.minecraft.core.BlockPos import net.minecraft.core.Vec3i +import net.minecraft.gametest.framework.StructureUtils import net.minecraft.network.chat.TextComponent import net.minecraft.server.level.ServerLevel import net.minecraft.world.InteractionResult import net.minecraft.world.item.Item import net.minecraft.world.item.ItemStack import net.minecraft.world.item.context.UseOnContext +import net.minecraft.world.level.block.Block +import net.minecraft.world.level.block.Blocks import net.minecraft.world.level.block.state.BlockState +import net.minecraft.world.level.levelgen.structure.templatesystem.StructureTemplate import org.joml.primitives.AABBi +import org.valkyrienskies.core.util.datastructures.DenseBlockPosSet import org.valkyrienskies.mod.common.assembly.ShipAssembler +import org.valkyrienskies.mod.common.assembly.createNewShipWithBlocks +import org.valkyrienskies.mod.common.assembly.createNewShipWithStructure import org.valkyrienskies.mod.common.dimensionId -import org.valkyrienskies.mod.common.getShipManagingPos import org.valkyrienskies.mod.common.getShipObjectManagingPos import org.valkyrienskies.mod.common.shipObjectWorld import org.valkyrienskies.mod.common.util.toJOML @@ -53,20 +59,15 @@ class AreaAssemblerItem( } else { val blockAABB = AABBi(blockPos.toJOML(), Vec3i(firstPosX, firstPosY, firstPosZ).toJOML()) blockAABB.correctBounds() - val blocks = ArrayList() + val lowerCorner = BlockPos(blockAABB.minX, blockAABB.minY, blockAABB.minZ) + val upperCorner = BlockPos(blockAABB.maxX, blockAABB.maxY, blockAABB.maxZ) + + val structure = StructureTemplate() + structure.fillFromWorld(level, lowerCorner, upperCorner.offset(1, 1, 1).subtract(lowerCorner), true, Blocks.STRUCTURE_VOID) - for (x in blockAABB.minX..blockAABB.maxX) { - for (y in blockAABB.minY..blockAABB.maxY) { - for (z in blockAABB.minZ..blockAABB.maxZ) { - if (level.getBlockState(BlockPos(x, y, z)).isAir) { - continue - } - blocks.add(BlockPos(x, y, z)) - } - } - } ctx.player?.sendMessage(TextComponent("Assembling (${blockPos.x}, ${blockPos.y}, ${blockPos.z}) to ($firstPosX, $firstPosY, $firstPosZ)!"), Util.NIL_UUID) - ShipAssembler.assembleToShip(level, blocks, true, scale.asDouble, true) + //ShipAssembler.assembleToShip(level, blocks, true, scale.asDouble, true) + createNewShipWithStructure(lowerCorner, upperCorner, structure, level) } item.tag!!.remove("firstPosX") item.tag!!.remove("firstPosY") diff --git a/common/src/main/kotlin/org/valkyrienskies/mod/common/item/ConnectionCheckerItem.kt b/common/src/main/kotlin/org/valkyrienskies/mod/common/item/ConnectionCheckerItem.kt index 4c141aa5d..4d2adcccc 100644 --- a/common/src/main/kotlin/org/valkyrienskies/mod/common/item/ConnectionCheckerItem.kt +++ b/common/src/main/kotlin/org/valkyrienskies/mod/common/item/ConnectionCheckerItem.kt @@ -8,6 +8,8 @@ import net.minecraft.world.item.Item import net.minecraft.world.item.ItemStack import net.minecraft.world.item.context.UseOnContext import net.minecraft.world.level.block.state.BlockState +import org.valkyrienskies.core.impl.config_impl.VSCoreConfig +import org.valkyrienskies.mod.common.ValkyrienSkiesMod import org.valkyrienskies.mod.common.dimensionId import org.valkyrienskies.mod.common.getShipManagingPos import org.valkyrienskies.mod.common.shipObjectWorld @@ -36,9 +38,11 @@ class ConnectionCheckerItem( if (!blockState.isAir) { // Make a ship val dimensionId = level.dimensionId - - if (parentShip != null) { + println("tested") + if (parentShip != null || ValkyrienSkiesMod.vsCore.hooks.enableWorldConnectivity) { + println("can we get much") if (item.tag != null && item.tag!!.contains("firstPosX")) { + println("so high") val firstPosX = item.tag!!.getInt("firstPosX") val firstPosY = item.tag!!.getInt("firstPosY") val firstPosZ = item.tag!!.getInt("firstPosZ") @@ -48,6 +52,7 @@ class ConnectionCheckerItem( item.tag!!.remove("firstPosY") item.tag!!.remove("firstPosZ") } else { + println("higher") item.tag = item.orCreateTag.apply { putInt("firstPosX", blockPos.x) putInt("firstPosY", blockPos.y) diff --git a/common/src/main/kotlin/org/valkyrienskies/mod/common/item/PhysicsEntityCreatorItem.kt b/common/src/main/kotlin/org/valkyrienskies/mod/common/item/PhysicsEntityCreatorItem.kt index d6f02bcc4..e7ea8cc06 100644 --- a/common/src/main/kotlin/org/valkyrienskies/mod/common/item/PhysicsEntityCreatorItem.kt +++ b/common/src/main/kotlin/org/valkyrienskies/mod/common/item/PhysicsEntityCreatorItem.kt @@ -8,10 +8,6 @@ import net.minecraft.world.item.context.UseOnContext import org.joml.Quaterniond import org.joml.Vector3d import org.joml.Vector3dc -import org.valkyrienskies.core.apigame.constraints.VSAttachmentConstraint -import org.valkyrienskies.core.apigame.constraints.VSPosDampingConstraint -import org.valkyrienskies.core.apigame.constraints.VSRotDampingAxes -import org.valkyrienskies.core.apigame.constraints.VSRotDampingConstraint import org.valkyrienskies.core.impl.game.ships.ShipTransformImpl.Companion import org.valkyrienskies.mod.common.ValkyrienSkiesMod import org.valkyrienskies.mod.common.dimensionId @@ -61,23 +57,23 @@ class PhysicsEntityCreatorItem( val attachCompliance = 1e-8 val attachMaxForce = 1e10 // Attach the click position of the ship to the surface of the physics entity - val attachConstraint = VSAttachmentConstraint( - shipOn.id, physicsEntityData.shipId, attachCompliance, ctx.clickLocation.toJOML(), offsetInGlobal.mul(-1.0, Vector3d()), - attachMaxForce, 0.0 - ) - val posDampingConstraint = VSPosDampingConstraint( - shipOn.id, physicsEntityData.shipId, attachCompliance, ctx.clickLocation.toJOML(), offsetInGlobal.mul(-1.0, Vector3d()), - attachMaxForce, 0.1 - ) - val rotDampingConstraint = VSRotDampingConstraint( - shipOn.id, physicsEntityData.shipId, attachCompliance, shipOn.transform.shipToWorldRotation.invert( - Quaterniond() - ), - Quaterniond(), 1e10, 0.1, VSRotDampingAxes.ALL_AXES - ) - level.shipObjectWorld.createNewConstraint(attachConstraint) - level.shipObjectWorld.createNewConstraint(posDampingConstraint) - level.shipObjectWorld.createNewConstraint(rotDampingConstraint) + // val attachConstraint = VSAttachmentConstraint( + // shipOn.id, physicsEntityData.shipId, attachCompliance, ctx.clickLocation.toJOML(), offsetInGlobal.mul(-1.0, Vector3d()), + // attachMaxForce, 0.0 + // ) + // val posDampingConstraint = VSPosDampingConstraint( + // shipOn.id, physicsEntityData.shipId, attachCompliance, ctx.clickLocation.toJOML(), offsetInGlobal.mul(-1.0, Vector3d()), + // attachMaxForce, 0.1 + // ) + // val rotDampingConstraint = VSRotDampingConstraint( + // shipOn.id, physicsEntityData.shipId, attachCompliance, shipOn.transform.shipToWorldRotation.invert( + // Quaterniond() + // ), + // Quaterniond(), 1e10, 0.1, VSRotDampingAxes.ALL_AXES + // ) + // level.shipObjectWorld.createNewConstraint(attachConstraint) + // level.shipObjectWorld.createNewConstraint(posDampingConstraint) + // level.shipObjectWorld.createNewConstraint(rotDampingConstraint) } } diff --git a/common/src/main/kotlin/org/valkyrienskies/mod/common/item/ShipCreatorItem.kt b/common/src/main/kotlin/org/valkyrienskies/mod/common/item/ShipCreatorItem.kt index d9335bc28..c6deab825 100644 --- a/common/src/main/kotlin/org/valkyrienskies/mod/common/item/ShipCreatorItem.kt +++ b/common/src/main/kotlin/org/valkyrienskies/mod/common/item/ShipCreatorItem.kt @@ -10,6 +10,9 @@ import net.minecraft.world.item.context.UseOnContext import net.minecraft.world.level.block.Rotation.NONE import net.minecraft.world.level.block.state.BlockState import org.joml.Vector3d +import org.valkyrienskies.core.api.ships.properties.ShipTransformVelocity +import org.valkyrienskies.core.impl.bodies.properties.BodyTransformImpl +import org.valkyrienskies.core.impl.bodies.properties.BodyTransformVelocityImpl import org.valkyrienskies.core.impl.game.ships.ShipDataCommon import org.valkyrienskies.core.impl.game.ships.ShipTransformImpl import org.valkyrienskies.mod.common.dimensionId @@ -65,7 +68,7 @@ class ShipCreatorItem( newShipScaling = Vector3d(minScaling, minScaling, minScaling) } val shipTransform = - ShipTransformImpl(newShipPosInWorld, newShipPosInShipyard, newShipRotation, newShipScaling) + BodyTransformVelocityImpl(newShipPosInWorld, newShipPosInShipyard, newShipRotation, newShipScaling, Vector3d(), Vector3d()) (serverShip as ShipDataCommon).transform = shipTransform } } 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 4be65f6eb..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,17 +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.core.api.ships.getAttachment -import org.valkyrienskies.core.api.ships.setAttachment 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 { @@ -20,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) { @@ -31,7 +44,7 @@ object VSGamePackets { val ship = seat.level.getShipObjectManagingPos(seat.blockPosition()) as? LoadedServerShip ?: return@registerServerHandler - val attachment: SeatedControllingPlayer = ship.getAttachment() + val attachment: SeatedControllingPlayer = ship.getAttachment() ?: SeatedControllingPlayer(seat.direction.opposite).apply { ship.setAttachment(this) } attachment.forwardImpulse = driving.impulse.z @@ -52,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/DimensionIdProvider.kt b/common/src/main/kotlin/org/valkyrienskies/mod/common/util/DimensionIdProvider.kt index a9649fc70..2eb2df991 100644 --- a/common/src/main/kotlin/org/valkyrienskies/mod/common/util/DimensionIdProvider.kt +++ b/common/src/main/kotlin/org/valkyrienskies/mod/common/util/DimensionIdProvider.kt @@ -1,6 +1,6 @@ package org.valkyrienskies.mod.common.util -import org.valkyrienskies.core.apigame.world.properties.DimensionId +import org.valkyrienskies.core.api.world.properties.DimensionId /** * Interface used to get the [DimensionId] from Minecraft [Level] objects 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 4b4109ae1..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 @@ -14,6 +14,7 @@ import org.joml.primitives.AABBdc import org.valkyrienskies.core.api.ships.Ship import org.valkyrienskies.core.apigame.collision.ConvexPolygonc import org.valkyrienskies.core.util.extend +import org.valkyrienskies.mod.common.dimensionId import org.valkyrienskies.mod.common.getShipsIntersecting import org.valkyrienskies.mod.common.shipObjectWorld import org.valkyrienskies.mod.common.vsCore @@ -95,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() @@ -109,7 +113,7 @@ object EntityShipCollisionUtils { val entityBoxWithMovement = entityBoundingBox.expandTowards(movement) val collidingPolygons: MutableList = ArrayList() val entityBoundingBoxExtended = entityBoundingBox.toJOML().extend(movement.toJOML()) - for (shipObject in world.shipObjectWorld.loadedShips.getIntersecting(entityBoundingBoxExtended)) { + for (shipObject in world.shipObjectWorld.loadedShips.getIntersecting(entityBoundingBoxExtended, world.dimensionId)) { val shipTransform = shipObject.transform val entityPolyInShipCoordinates: ConvexPolygonc = collider.createPolygonFromAABB( entityBoxWithMovement.toJOML(), diff --git a/common/src/main/kotlin/org/valkyrienskies/mod/common/util/MinecraftPlayer.kt b/common/src/main/kotlin/org/valkyrienskies/mod/common/util/MinecraftPlayer.kt index 603227d92..766b1b528 100644 --- a/common/src/main/kotlin/org/valkyrienskies/mod/common/util/MinecraftPlayer.kt +++ b/common/src/main/kotlin/org/valkyrienskies/mod/common/util/MinecraftPlayer.kt @@ -2,9 +2,9 @@ package org.valkyrienskies.mod.common.util import net.minecraft.world.entity.player.Player import org.joml.Vector3d +import org.valkyrienskies.core.api.world.properties.DimensionId import org.valkyrienskies.core.apigame.world.IPlayer import org.valkyrienskies.core.apigame.world.PlayerState -import org.valkyrienskies.core.apigame.world.properties.DimensionId import org.valkyrienskies.mod.common.dimensionId import org.valkyrienskies.mod.common.getShipMountedToData import org.valkyrienskies.mod.common.vsCore diff --git a/common/src/main/kotlin/org/valkyrienskies/mod/common/util/ShipSettings.kt b/common/src/main/kotlin/org/valkyrienskies/mod/common/util/ShipSettings.kt index be9f2f8c7..15b13f85e 100644 --- a/common/src/main/kotlin/org/valkyrienskies/mod/common/util/ShipSettings.kt +++ b/common/src/main/kotlin/org/valkyrienskies/mod/common/util/ShipSettings.kt @@ -1,8 +1,7 @@ package org.valkyrienskies.mod.common.util -import org.valkyrienskies.core.api.ships.ServerShip -import org.valkyrienskies.core.api.ships.getAttachment -import org.valkyrienskies.core.api.ships.saveAttachment +import org.valkyrienskies.core.api.attachment.getAttachment +import org.valkyrienskies.core.api.ships.LoadedServerShip /** * A attachment that stores ship specific settings. @@ -20,5 +19,5 @@ data class ShipSettings( var changeDimensionOnTouchPortals: Boolean = true ) -val ServerShip.settings: ShipSettings - get() = getAttachment() ?: ShipSettings().also { saveAttachment(it) } +val LoadedServerShip.settings: ShipSettings + get() = getAttachment() ?: ShipSettings().also { setAttachment(it) } diff --git a/common/src/main/kotlin/org/valkyrienskies/mod/common/util/SplitHandler.kt b/common/src/main/kotlin/org/valkyrienskies/mod/common/util/SplitHandler.kt index db2620064..f49fdc1e4 100644 --- a/common/src/main/kotlin/org/valkyrienskies/mod/common/util/SplitHandler.kt +++ b/common/src/main/kotlin/org/valkyrienskies/mod/common/util/SplitHandler.kt @@ -5,12 +5,14 @@ import net.minecraft.core.Vec3i import net.minecraft.server.level.ServerLevel import net.minecraft.world.level.Level import net.minecraft.world.level.block.state.BlockState -import org.joml.primitives.AABBi -import org.valkyrienskies.core.api.ships.getAttachment +import org.valkyrienskies.core.api.attachment.getAttachment +import org.valkyrienskies.core.api.ships.LoadedServerShip import org.valkyrienskies.core.api.world.connectivity.ConnectionStatus.CONNECTED import org.valkyrienskies.core.api.world.connectivity.ConnectionStatus.DISCONNECTED -import org.valkyrienskies.core.util.expand +import org.valkyrienskies.core.api.world.properties.DimensionId +import org.valkyrienskies.core.util.datastructures.DenseBlockPosSet import org.valkyrienskies.mod.common.assembly.ShipAssembler +import org.valkyrienskies.mod.common.config.VSGameConfig import org.valkyrienskies.mod.common.dimensionId import org.valkyrienskies.mod.common.getShipObjectManagingPos import org.valkyrienskies.mod.common.shipObjectWorld @@ -18,19 +20,44 @@ import org.valkyrienskies.mod.util.logger class SplitHandler(private val doEdges: Boolean, private val doCorners: Boolean) { - fun split(level: Level, x: Int, y: Int, z: Int, prevBlockState: BlockState, newBlockState: BlockState) { + private val splitQueue: HashMap> = hashMapOf() + + fun queueSplit(level: Level, x: Int, y: Int, z: Int) { + splitQueue[level.dimensionId]?.put(BlockPos(x, y, z), VSGameConfig.SERVER.defaultSplitGraceTimer) ?: run { + splitQueue[level.dimensionId] = hashMapOf(BlockPos(x, y, z) to VSGameConfig.SERVER.defaultSplitGraceTimer) + } + } + + fun tick(level: ServerLevel) { + if (splitQueue[level.dimensionId] != null && splitQueue[level.dimensionId]!!.isNotEmpty()) { + val splitsToProcess = HashSet() + for (splitIndex in splitQueue[level.dimensionId]!!.keys) { + if (splitQueue[level.dimensionId]!![splitIndex]!! <= 0) { + splitsToProcess.add(splitIndex) + } else { + splitQueue[level.dimensionId]!![splitIndex] = splitQueue[level.dimensionId]!![splitIndex]!! - 1 + } + } + splitsToProcess.forEach { + splitQueue[level.dimensionId]!!.remove(it) + split(level, it.x, it.y, it.z, level.getBlockState(it)) + } + } + } + + fun split(level: Level, x: Int, y: Int, z: Int, newBlockState: BlockState) { if (level is ServerLevel) { - val loadedShip = level.getShipObjectManagingPos(x shr 4, z shr 4) - if (loadedShip != null && loadedShip.getAttachment()?.canSplit() != false) { - if (!prevBlockState.isAir && newBlockState.isAir) { + val loadedShip : LoadedServerShip? = level.getShipObjectManagingPos(x shr 4, z shr 4) + if ((loadedShip != null && loadedShip.getAttachment()?.canSplit() != false) || (loadedShip == null && VSGameConfig.SERVER.enableWorldSplitting)) { + if (newBlockState.isAir) { val blockNeighbors: HashSet = HashSet() - val shipBox = loadedShip.shipAABB?.expand(1, AABBi()) ?: return + //val shipBox = loadedShip.shipAABB?.expand(1, AABBi()) ?: return for (neighborOffset in getOffsets(doEdges, doCorners)) { val neighborPos = BlockPos(x + neighborOffset.x, y + neighborOffset.y, z + neighborOffset.z) val neighborState = level.getBlockState(neighborPos) - if (!neighborState.isAir && neighborPos != BlockPos(x, y, z) && shipBox.containsPoint(neighborPos.toJOML())) { + if (!neighborState.isAir && neighborPos != BlockPos(x, y, z)) { blockNeighbors.add(neighborPos) } } @@ -84,6 +111,11 @@ class SplitHandler(private val doEdges: Boolean, private val doCorners: Boolean) toIgnore.add(component) } } + if (level.shipObjectWorld.isIsolatedSolid(otherComponent.x, otherComponent.y, otherComponent.z, level.dimensionId) == CONNECTED) { + if (!toIgnore.contains(otherComponent) && !toIgnore.contains(component)) { + toIgnore.add(component) + } + } } } @@ -92,22 +124,22 @@ class SplitHandler(private val doEdges: Boolean, private val doCorners: Boolean) if (disconnected.isEmpty()) { return } else { - loadedShip.getAttachment(SplittingDisablerAttachment::class.java)?.disableSplitting() + loadedShip?.getAttachment(SplittingDisablerAttachment::class.java)?.disableSplitting() } //begin the DFSing - val toAssemble = HashSet>() + val toAssemble = HashSet() for (starter in disconnected) { - val visited = HashSet() + val visited = DenseBlockPosSet() val queuedPositions = HashSet() queuedPositions.add(starter) while (queuedPositions.isNotEmpty()) { val current = queuedPositions.first() queuedPositions.remove(current) - visited.add(current) + visited.add(current.toJOML()) val toCheck = HashSet() for (offset in getOffsets(doEdges, doCorners)) { toCheck.add( @@ -115,25 +147,26 @@ class SplitHandler(private val doEdges: Boolean, private val doCorners: Boolean) ) } for (check in toCheck) { - if (!visited.contains(check) && !level.getBlockState(check).isAir) { + if (!visited.contains(check.toJOML()) && !level.getBlockState(check).isAir) { queuedPositions.add(check) } } } //if we have visited all blocks in the component, we can split it - toAssemble.add(visited.toList()) + toAssemble.add(visited) } if (toAssemble.isEmpty()) { - loadedShip.getAttachment(SplittingDisablerAttachment::class.java)?.enableSplitting() + loadedShip?.getAttachment(SplittingDisablerAttachment::class.java)?.enableSplitting() return } for (component in toAssemble) { - ShipAssembler.assembleToShip(level, component, true, 1.0, true) + ShipAssembler.assembleToShip(level, component.toSet().map { it.toBlockPos() }, true, 1.0, true) + //createNewShipWithBlocks(component.first().toBlockPos(), component, level) } - loadedShip.getAttachment(SplittingDisablerAttachment::class.java)?.enableSplitting() + loadedShip?.getAttachment(SplittingDisablerAttachment::class.java)?.enableSplitting() } } } 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/kotlin/org/valkyrienskies/mod/common/world/RaycastUtils.kt b/common/src/main/kotlin/org/valkyrienskies/mod/common/world/RaycastUtils.kt index 6e1699db8..8c61e7672 100644 --- a/common/src/main/kotlin/org/valkyrienskies/mod/common/world/RaycastUtils.kt +++ b/common/src/main/kotlin/org/valkyrienskies/mod/common/world/RaycastUtils.kt @@ -19,7 +19,8 @@ import org.joml.Vector3d import org.joml.primitives.AABBd import org.joml.primitives.AABBdc import org.valkyrienskies.core.api.ships.properties.ShipId -import org.valkyrienskies.core.game.ships.ShipObjectClient +import org.valkyrienskies.core.api.ships.ClientShip +import org.valkyrienskies.mod.common.dimensionId import org.valkyrienskies.mod.common.shipObjectWorld import org.valkyrienskies.mod.common.util.toJOML import org.valkyrienskies.mod.common.util.toMinecraft @@ -52,13 +53,13 @@ fun Level.clipIncludeShips( // Iterate every ship, find do the raycast in ship space, // choose the raycast with the lowest distance to the start position. - for (ship in shipObjectWorld.loadedShips.getIntersecting(clipAABB)) { + for (ship in shipObjectWorld.loadedShips.getIntersecting(clipAABB, dimensionId)) { // Skip skipShip if (ship.id == skipShip) { continue } - val worldToShip = (ship as? ShipObjectClient)?.renderTransform?.worldToShipMatrix ?: ship.worldToShip - val shipToWorld = (ship as? ShipObjectClient)?.renderTransform?.shipToWorldMatrix ?: ship.shipToWorld + val worldToShip = (ship as? ClientShip)?.renderTransform?.worldToShip ?: ship.worldToShip + val shipToWorld = (ship as? ClientShip)?.renderTransform?.shipToWorld ?: ship.shipToWorld val shipStart = worldToShip.transformPosition(ctx.from.toJOML()).toMinecraft() val shipEnd = worldToShip.transformPosition(ctx.to.toJOML()).toMinecraft() @@ -223,7 +224,7 @@ fun Level.raytraceEntities( val start = Vector3d() val end = Vector3d() - shipObjectWorld.loadedShips.getIntersecting(origBoundingBoxM.toJOML()).forEach { + shipObjectWorld.loadedShips.getIntersecting(origBoundingBoxM.toJOML(), dimensionId).forEach { it.worldToShip.transformPosition(origStartVec, start) it.worldToShip.transformPosition(origEndVec, end) diff --git a/common/src/main/kotlin/org/valkyrienskies/mod/compat/Weather2Compat.kt b/common/src/main/kotlin/org/valkyrienskies/mod/compat/Weather2Compat.kt index 975b461e8..947efe3ee 100644 --- a/common/src/main/kotlin/org/valkyrienskies/mod/compat/Weather2Compat.kt +++ b/common/src/main/kotlin/org/valkyrienskies/mod/compat/Weather2Compat.kt @@ -2,7 +2,7 @@ package org.valkyrienskies.mod.compat import net.minecraft.server.level.ServerLevel import org.joml.Vector3d -import org.valkyrienskies.core.api.ships.getAttachment +import org.valkyrienskies.core.api.attachment.getAttachment import org.valkyrienskies.mod.common.config.VSGameConfig import org.valkyrienskies.mod.common.shipObjectWorld import org.valkyrienskies.mod.common.util.GameTickForceApplier @@ -24,7 +24,7 @@ object Weather2Compat { level.shipObjectWorld.loadedShips.forEach { ship -> val forces = ship.getAttachment()!! - val com = ship.inertiaData.centerOfMassInShip + val com = ship.inertiaData.centerOfMass ship.shipToWorld.transformPosition(com, vec) val pos = vec.toMinecraft() 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 112e704b5..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,8 +59,10 @@ "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", "feature.shipyard_entities.MixinEntity", "feature.shipyard_entities.MixinEntitySection", "feature.shipyard_entities.MixinEntitySectionStorage", @@ -49,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", @@ -126,12 +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.entity_movement_packets.MixinLocalPlayer", "feature.fix_render_chunk_sorting.MixinRenderChunk", "feature.fluid_camera_fix.MixinCamera", "feature.render_blockentity_distance_check.MixinBlockEntityRenderDispatcher", @@ -155,6 +182,7 @@ "mod_compat.create.client.MixinCogwheelBlockItemHitOnShaft", "mod_compat.create.client.MixinContraptionRenderDispatcher", "mod_compat.create.client.MixinContraptionRenderInfo", + "mod_compat.create.client.MixinDeployTool", "mod_compat.create.client.MixinElevatorControlsHandler", "mod_compat.create.client.MixinFilteringRenderer", "mod_compat.create.client.MixinGhostBlockRenderer", @@ -163,13 +191,12 @@ "mod_compat.create.client.MixinMultiplePlacementHelpers", "mod_compat.create.client.MixinOutline", "mod_compat.create.client.MixinPlacementHelpers", + "mod_compat.create.client.MixinSchematicToolBase", "mod_compat.create.client.MixinSoundScapes", "mod_compat.create.client.MixinTileEntityRenderHelper", "mod_compat.create.client.MixinTrainRelocator", - "mod_compat.create.client.MixinDeployTool", - "mod_compat.create.client.MixinSchematicToolBase", - "mod_compat.create.client.trackOutlines.MixinBigOutlines", "mod_compat.create.client.MixinValueBox", + "mod_compat.create.client.trackOutlines.MixinBigOutlines", "mod_compat.flywheel.InstancingEngineAccessor", "mod_compat.flywheel.MixinBlockEntityInstanceManager", "mod_compat.flywheel.MixinInstanceManager", diff --git a/forge/build.gradle b/forge/build.gradle index f108a0c17..d1dd8d959 100644 --- a/forge/build.gradle +++ b/forge/build.gradle @@ -115,11 +115,11 @@ dependencies { transitive = false } - forgeRuntimeLibrary shadowCommon("org.valkyrienskies:physics_api_krunch:1.0.0+7db6a103f1") { + forgeRuntimeLibrary shadowCommon("org.valkyrienskies:physics_api_krunch:1.0.0+04b9857549") { transitive = false } - forgeRuntimeLibrary shadowCommon("org.valkyrienskies:physics_api:1.0.0+0ba0cc41e1") { + forgeRuntimeLibrary shadowCommon("org.valkyrienskies:physics_api:1.0.0+2990315383") { transitive = false } 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() ? diff --git a/gradle.properties b/gradle.properties index df6bc0a76..d8a3d4c91 100644 --- a/gradle.properties +++ b/gradle.properties @@ -2,7 +2,7 @@ org.gradle.jvmargs=-Xmx4G -XX:MaxMetaspaceSize=1G minecraft_version=1.18.2 enabled_platforms=quilt,fabric,forge archives_base_name=valkyrienskies-118 -mod_version=2.1.1-beta.6 +mod_version=2.5.0-beta.2 maven_group=org.valkyrienskies.mod architectury_version=4.10.86 fabric_loader_version=0.14.11 @@ -11,7 +11,7 @@ forge_version=1.18.2-40.2.4 create_fabric_version=0.5.1-i-build.1598+mc1.18.2 flywheel_version_fabric=0.6.9-38 createbigcannons_version= 0.5.2-nightly-e815ca4 -vs_core_version=1.1.0+e213cc9916 +vs_core_version=1.1.0+774ec4e001 # Prevent kotlin from autoincluding stdlib as a dependency, which breaks # gradle's composite builds (includeBuild) for some reason. We'll add it manually kotlin.stdlib.default.dependency=false