diff --git a/.idea/inspectionProfiles/Project_Default.xml b/.idea/inspectionProfiles/Project_Default.xml
index 9210d9a2a..71b32a3fa 100644
--- a/.idea/inspectionProfiles/Project_Default.xml
+++ b/.idea/inspectionProfiles/Project_Default.xml
@@ -6,10 +6,6 @@
-
-
-
-
diff --git a/common/src/main/java/org/valkyrienskies/mod/mixin/feature/save_mob_location_on_ship/MixinChunkMap.java b/common/src/main/java/org/valkyrienskies/mod/mixin/feature/save_mob_location_on_ship/MixinChunkMap.java
new file mode 100644
index 000000000..e73bf5fe1
--- /dev/null
+++ b/common/src/main/java/org/valkyrienskies/mod/mixin/feature/save_mob_location_on_ship/MixinChunkMap.java
@@ -0,0 +1,86 @@
+package org.valkyrienskies.mod.mixin.feature.save_mob_location_on_ship;
+
+import net.minecraft.server.level.ChunkMap;
+import net.minecraft.server.level.ServerLevel;
+import net.minecraft.world.entity.Entity;
+import net.minecraft.world.entity.Mob;
+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.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.core.api.ships.Ship;
+import org.valkyrienskies.mod.common.VSGameUtilsKt;
+import org.valkyrienskies.mod.common.config.VSGameConfig;
+import org.valkyrienskies.mod.common.entity.ShipyardPosSavable;
+import org.valkyrienskies.mod.common.util.EntityDraggingInformation;
+import org.valkyrienskies.mod.common.util.IEntityDraggingInformationProvider;
+import org.valkyrienskies.mod.common.util.VectorConversionsMCKt;
+
+@Mixin(ChunkMap.class)
+public class MixinChunkMap {
+
+ @Shadow
+ @Final
+ ServerLevel level;
+
+ /**
+ * Save mob's shipyard position when it gets unloaded
+ *
+ * @author G_Mungus
+ */
+
+ @Inject(method = "removeEntity", at = @At("HEAD"))
+ protected void unloadEntityMixin(Entity entity, CallbackInfo info) {
+ if (entity instanceof Mob mob) {
+ Vector3d shipyardPos = valkyrienskies$getShipyardPos(mob);
+ if (shipyardPos != null &&
+ VSGameUtilsKt.getShipManagingPos(this.level, shipyardPos) != null &&
+ ((ShipyardPosSavable)mob).valkyrienskies$getUnloadedShipyardPos() == null) {
+ ((ShipyardPosSavable)mob).valkyrienskies$setUnloadedShipyardPos(shipyardPos);
+ }
+ }
+ }
+
+ /**
+ * Teleport mob to correct position on ship when loaded back in
+ *
+ * @author G_Mungus
+ */
+
+ @Inject(method = "addEntity", at = @At("RETURN"))
+ protected void loadEntityMixin(Entity entity, CallbackInfo info) {
+ if (entity instanceof Mob mob) {
+ Vector3d shipyardPos = ((ShipyardPosSavable)mob).valkyrienskies$getUnloadedShipyardPos();
+ if(shipyardPos != null) {
+ if (VSGameConfig.SERVER.getSaveMobsPositionOnShip()){
+ mob.teleportTo(shipyardPos.x, shipyardPos.y, shipyardPos.z);
+ }
+ ((ShipyardPosSavable) mob).valkyrienskies$setUnloadedShipyardPos(null);
+ }
+ }
+ }
+
+
+ /**
+ * Helper method to get shipyard pos of mob on a ship
+ *
+ * @author G_Mungus
+ */
+ @Unique
+ private Vector3d valkyrienskies$getShipyardPos(Entity entity) {
+ EntityDraggingInformation dragInfo = ((IEntityDraggingInformationProvider) entity).getDraggingInformation();
+
+ if (dragInfo.getLastShipStoodOn() != null) {
+ Ship ship = VSGameUtilsKt.getAllShips(this.level).getById(dragInfo.getLastShipStoodOn());
+ if (ship != null && ship.getWorldAABB().containsPoint(VectorConversionsMCKt.toJOML(entity.position()))) {
+ return ship.getWorldToShip().transformPosition(VectorConversionsMCKt.toJOML(entity.position()));
+ }
+ }
+
+ return null;
+ }
+}
diff --git a/common/src/main/java/org/valkyrienskies/mod/mixin/feature/save_mob_location_on_ship/MixinMob.java b/common/src/main/java/org/valkyrienskies/mod/mixin/feature/save_mob_location_on_ship/MixinMob.java
new file mode 100644
index 000000000..6e799f123
--- /dev/null
+++ b/common/src/main/java/org/valkyrienskies/mod/mixin/feature/save_mob_location_on_ship/MixinMob.java
@@ -0,0 +1,67 @@
+package org.valkyrienskies.mod.mixin.feature.save_mob_location_on_ship;
+
+import net.minecraft.nbt.CompoundTag;
+import net.minecraft.world.entity.Mob;
+import org.joml.Vector3d;
+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.common.config.VSGameConfig;
+import org.valkyrienskies.mod.common.entity.ShipyardPosSavable;
+
+@Mixin(Mob.class)
+public class MixinMob implements ShipyardPosSavable {
+
+ @Unique
+ public Vector3d valkyrienskies$unloadedShipyardPos = null;
+
+ @Override
+ public Vector3d valkyrienskies$getUnloadedShipyardPos() {
+ return valkyrienskies$unloadedShipyardPos;
+ }
+
+ @Override
+ public void valkyrienskies$setUnloadedShipyardPos(Vector3d vector3d) {
+ this.valkyrienskies$unloadedShipyardPos = vector3d;
+ }
+
+
+ /**
+ * Save mob's shipyard position to nbt, or clear it if null
+ *
+ * @author G_Mungus
+ */
+ @Inject(method = "addAdditionalSaveData", at = @At("RETURN"))
+ public void addAdditionalSaveDataMixin(CompoundTag nbt, CallbackInfo info) {
+ Vector3d position = this.valkyrienskies$getUnloadedShipyardPos();
+ if (position != null && VSGameConfig.SERVER.getSaveMobsPositionOnShip()) {
+ nbt.putDouble("valkyrienskies$unloadedX",position.x);
+ nbt.putDouble("valkyrienskies$unloadedY",position.y);
+ nbt.putDouble("valkyrienskies$unloadedZ", position.z);
+ } else {
+ nbt.remove("valkyrienskies$unloadedX");
+ nbt.remove("valkyrienskies$unloadedY");
+ nbt.remove("valkyrienskies$unloadedZ");
+ }
+ }
+
+
+ /**
+ * Read mob's shipyard position from nbt
+ *
+ * @author G_Mungus
+ */
+ @Inject(method = "readAdditionalSaveData", at = @At("RETURN"))
+ public void readAdditionalSaveData(CompoundTag nbt, CallbackInfo info) {
+ if (nbt.contains("valkyrienskies$unloadedX") && nbt.contains("valkyrienskies$unloadedY") && nbt.contains("valkyrienskies$unloadedZ")) {
+ double[] xyz = {nbt.getDouble("valkyrienskies$unloadedX"), nbt.getDouble("valkyrienskies$unloadedY"), nbt.getDouble("valkyrienskies$unloadedZ")};
+ this.valkyrienskies$setUnloadedShipyardPos(new Vector3d(xyz));
+ }
+ }
+
+
+
+}
+
diff --git a/common/src/main/java/org/valkyrienskies/mod/mixin/feature/save_mob_location_on_ship/README.md b/common/src/main/java/org/valkyrienskies/mod/mixin/feature/save_mob_location_on_ship/README.md
new file mode 100644
index 000000000..3cff745fa
--- /dev/null
+++ b/common/src/main/java/org/valkyrienskies/mod/mixin/feature/save_mob_location_on_ship/README.md
@@ -0,0 +1 @@
+When a mob on a ship gets unloaded, these mixins save the mobs shipyard position, then teleport the mob to the shipyard position when the mob loads back in. This prevents mobs from falling off ships due to being unloaded while the ship is moving.
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..1a63b62c1 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
@@ -21,6 +21,7 @@
import org.valkyrienskies.mod.common.VSGameUtilsKt;
import org.valkyrienskies.mod.common.util.MinecraftPlayer;
+
@Mixin(ChunkMap.class)
public abstract class MixinChunkMap {
@@ -102,4 +103,5 @@ private void postGetPlayersWatchingChunk(final ChunkPos chunkPos, final boolean
cir.setReturnValue(new ArrayList<>(watchingPlayers));
}
+
}
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 0f15cdeb5..2f1b731ae 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
@@ -133,6 +133,13 @@ object VSGameConfig {
)
var maxAirborneTicksForReconnectedPlayerTeleport = 4
+ @JsonSchema(
+ description = "If true, when a mob gets unloaded, its position on a ship is saved such that " +
+ "if the ship is moved, when the mob loads back in it will be teleported to the same position in the ship." +
+ " This helps prevent mobs from falling off of ships."
+ )
+ var saveMobsPositionOnShip = true
+
@JsonSchema(
description = "If true, prevents water and other fluids from flowing out of the ship's bounding box."
)
diff --git a/common/src/main/kotlin/org/valkyrienskies/mod/common/entity/ShipyardPosSavable.kt b/common/src/main/kotlin/org/valkyrienskies/mod/common/entity/ShipyardPosSavable.kt
new file mode 100644
index 000000000..1af21a5e6
--- /dev/null
+++ b/common/src/main/kotlin/org/valkyrienskies/mod/common/entity/ShipyardPosSavable.kt
@@ -0,0 +1,8 @@
+package org.valkyrienskies.mod.common.entity
+
+import org.joml.Vector3d
+
+interface ShipyardPosSavable {
+ fun `valkyrienskies$getUnloadedShipyardPos`(): Vector3d?
+ fun `valkyrienskies$setUnloadedShipyardPos`(vector3d: Vector3d?)
+}
diff --git a/common/src/main/resources/valkyrienskies-common.mixins.json b/common/src/main/resources/valkyrienskies-common.mixins.json
index bda853b73..fd9df3730 100644
--- a/common/src/main/resources/valkyrienskies-common.mixins.json
+++ b/common/src/main/resources/valkyrienskies-common.mixins.json
@@ -40,6 +40,8 @@
"feature.mass_tooltip.MixinBlockItem",
"feature.mob_spawning.NaturalSpawnerMixin",
"feature.render_pathfinding.MixinDebugPackets",
+ "feature.save_mob_location_on_ship.MixinChunkMap",
+ "feature.save_mob_location_on_ship.MixinMob",
"feature.screen_distance_check.MixinScreenHandler",
"feature.shipyard_entities.MixinEntity",
"feature.shipyard_entities.MixinEntitySection",