Skip to content

Commit 6f2972b

Browse files
committed
add ship freeze future
1 parent d9bb6bc commit 6f2972b

19 files changed

Lines changed: 594 additions & 24 deletions

File tree

common/src/main/java/com/github/litermc/vschunkloader/VSCApi.java

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package com.github.litermc.vschunkloader;
22

3+
import com.github.litermc.vschunkloader.attachment.AmmoShipAttachment;
34
import com.github.litermc.vschunkloader.attachment.ForceLoadAttachment;
45
import com.github.litermc.vschunkloader.config.Config;
56

@@ -120,6 +121,14 @@ public static boolean isForceLoadedBy(final MinecraftServer server, final long i
120121
return ForceLoadAttachment.get(ship).isForceLoadedBy(token);
121122
}
122123

124+
public static boolean canFreezeShipForChunkLoad(final ServerShip ship) {
125+
return switch (Config.shipFreezing) {
126+
case ALL -> true;
127+
case NONE -> false;
128+
case AMMO -> ship.getAttachment(AmmoShipAttachment.class) != null;
129+
};
130+
}
131+
123132
private static ServerShip getShip(final MinecraftServer server, final long id) {
124133
final ServerShipWorld shipWorld = VSGameUtilsKt.getShipObjectWorld(server);
125134
final ServerShip ship = shipWorld.getLoadedShips().getById(id);

common/src/main/java/com/github/litermc/vschunkloader/VSCListeners.java

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,19 @@
11
package com.github.litermc.vschunkloader;
22

3+
import com.github.litermc.vschunkloader.attachment.ChunkSensorAttachment;
34
import com.github.litermc.vschunkloader.config.Config;
45
import com.github.litermc.vschunkloader.util.ChunkLoaderManager;
6+
import com.github.litermc.vschunkloader.util.ChunkSensor;
57
import com.github.litermc.vschunkloader.util.TaskUtil;
68
import com.github.litermc.vschunkloader.util.Utils;
79

810
import net.minecraft.core.BlockPos;
911
import net.minecraft.server.MinecraftServer;
1012
import net.minecraft.server.level.ServerLevel;
13+
import net.minecraft.world.level.ChunkPos;
14+
import net.minecraft.world.level.chunk.LevelChunk;
1115

16+
import org.valkyrienskies.core.api.ships.LoadedServerShip;
1217
import org.valkyrienskies.core.api.ships.ServerShip;
1318
import org.valkyrienskies.core.api.world.ServerShipWorld;
1419
import org.valkyrienskies.mod.common.VSGameUtilsKt;
@@ -43,9 +48,27 @@ public static void preServerTick(final MinecraftServer server) {
4348
}
4449
ChunkLoaderManager.get(level).refreshForcedShip(ship);
4550
}
51+
52+
for (final LoadedServerShip ship : shipWorld.getLoadedShips()) {
53+
ChunkSensorAttachment.get(ship).serverTick(ship);
54+
}
4655
}
4756

4857
public static void postServerTick(final MinecraftServer server) {
4958
TaskUtil.postServerTick();
5059
}
60+
61+
public static void onServerChunkLoad(final ServerLevel level, final LevelChunk chunk) {
62+
// final ChunkPos pos = chunk.getPos();
63+
// if (!VSGameUtilsKt.isChunkInShipyard(level, pos.x, pos.z)) {
64+
// ChunkSensor.get(level).onChunkLoaded(pos);
65+
// }
66+
}
67+
68+
public static void onServerChunkUnload(final ServerLevel level, final LevelChunk chunk) {
69+
// final ChunkPos pos = chunk.getPos();
70+
// if (!VSGameUtilsKt.isChunkInShipyard(level, pos.x, pos.z)) {
71+
// ChunkSensor.get(level).onChunkUnload(pos);
72+
// }
73+
}
5174
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
package com.github.litermc.vschunkloader.accessor;
2+
3+
import com.github.litermc.vschunkloader.util.ChunkSensor;
4+
5+
public interface ChunkMapAccessor {
6+
ChunkSensor vsc$getChunkSensor();
7+
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
package com.github.litermc.vschunkloader.accessor;
2+
3+
import com.github.litermc.vschunkloader.util.ChunkSensor;
4+
5+
public interface ServerLevelAccessor {
6+
ChunkSensor vsc$getChunkSensor();
7+
}

common/src/main/java/com/github/litermc/vschunkloader/attachment/AmmoShipAttachment.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ public AmmoShipAttachment() {
4646
this(-1);
4747
}
4848

49-
public AmmoShipAttachment(final long shipId) {
49+
private AmmoShipAttachment(final long shipId) {
5050
this.server = PlatformHelper.get().getCurrentServer();
5151
this.world = VSGameUtilsKt.getShipObjectWorld(this.server);
5252
this.shipId = shipId;
Lines changed: 220 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,220 @@
1+
package com.github.litermc.vschunkloader.attachment;
2+
3+
import com.github.litermc.vschunkloader.Constants;
4+
import com.github.litermc.vschunkloader.VSCApi;
5+
import com.github.litermc.vschunkloader.platform.PlatformHelper;
6+
import com.github.litermc.vschunkloader.util.ChunkLoaderManager;
7+
import com.github.litermc.vschunkloader.util.ChunkSensor;
8+
import com.github.litermc.vschunkloader.util.TaskUtil;
9+
import com.github.litermc.vschunkloader.util.Utils;
10+
11+
import net.minecraft.core.SectionPos;
12+
import net.minecraft.server.level.ServerLevel;
13+
14+
import com.fasterxml.jackson.annotation.JsonAutoDetect;
15+
import com.fasterxml.jackson.annotation.JsonGetter;
16+
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
17+
import com.fasterxml.jackson.annotation.JsonProperty;
18+
import com.fasterxml.jackson.annotation.JsonSetter;
19+
import org.joml.AxisAngle4d;
20+
import org.joml.Matrix4d;
21+
import org.joml.Matrix4dc;
22+
import org.joml.Quaterniond;
23+
import org.joml.Vector3d;
24+
import org.joml.Vector3dc;
25+
import org.joml.primitives.AABBd;
26+
import org.joml.primitives.AABBic;
27+
import org.valkyrienskies.core.api.ships.LoadedServerShip;
28+
import org.valkyrienskies.core.api.ships.PhysShip;
29+
import org.valkyrienskies.core.api.ships.ServerShip;
30+
import org.valkyrienskies.core.api.ships.ServerShipTransformProvider;
31+
import org.valkyrienskies.core.api.ships.ShipForcesInducer;
32+
import org.valkyrienskies.core.api.ships.properties.ShipTransform;
33+
import org.valkyrienskies.core.apigame.world.ServerShipWorldCore;
34+
import org.valkyrienskies.core.impl.game.ships.PhysShipImpl;
35+
import org.valkyrienskies.mod.common.VSGameUtilsKt;
36+
import org.valkyrienskies.physics_api.PoseVel;
37+
38+
@JsonAutoDetect(
39+
fieldVisibility = JsonAutoDetect.Visibility.NONE,
40+
isGetterVisibility = JsonAutoDetect.Visibility.NONE,
41+
getterVisibility = JsonAutoDetect.Visibility.NONE,
42+
setterVisibility = JsonAutoDetect.Visibility.NONE
43+
)
44+
@JsonIgnoreProperties(ignoreUnknown = true)
45+
public final class ChunkSensorAttachment implements ShipForcesInducer {
46+
private static final double DT = 1.0 / 60;
47+
48+
private final ServerShipWorldCore world;
49+
private volatile LoadedServerShip ship = null;
50+
@JsonProperty
51+
private volatile boolean freezed = false;
52+
private volatile Vector3dc velocity = null;
53+
private volatile Vector3dc omega = null;
54+
55+
private ChunkSensorAttachment() {
56+
this.world = VSGameUtilsKt.getShipObjectWorld(PlatformHelper.get().getCurrentServer());
57+
}
58+
59+
public static ChunkSensorAttachment get(final ServerShip ship) {
60+
final ChunkSensorAttachment attachment = ship.getAttachment(ChunkSensorAttachment.class);
61+
if (attachment != null) {
62+
return attachment;
63+
}
64+
final ChunkSensorAttachment newAttachment = new ChunkSensorAttachment();
65+
ship.saveAttachment(ChunkSensorAttachment.class, newAttachment);
66+
return newAttachment;
67+
}
68+
69+
@JsonGetter("velocity")
70+
public double[] getVelocity() {
71+
return this.freezed ? new double[]{this.velocity.x(), this.velocity.y(), this.velocity.z()} : null;
72+
}
73+
74+
@JsonSetter("velocity")
75+
public void setVelocity(final double[] velocity) {
76+
if (velocity == null || velocity.length != 3) {
77+
this.velocity = null;
78+
return;
79+
}
80+
this.velocity = new Vector3d(velocity[0], velocity[1], velocity[2]);
81+
}
82+
83+
@JsonGetter("omega")
84+
public double[] getOmega() {
85+
return this.freezed ? new double[]{this.omega.x(), this.omega.y(), this.omega.z()} : null;
86+
}
87+
88+
@JsonSetter("omega")
89+
public void setOmega(final double[] omega) {
90+
if (omega == null || omega.length != 3) {
91+
this.omega = null;
92+
return;
93+
}
94+
this.omega = new Vector3d(omega[0], omega[1], omega[2]);
95+
}
96+
97+
public void serverTick(final LoadedServerShip ship) {
98+
this.ship = ship;
99+
if (!this.freezed) {
100+
return;
101+
}
102+
final long shipId = ship.getId();
103+
if (!ship.isStatic()) {
104+
this.freezed = false;
105+
this.velocity = null;
106+
this.omega = null;
107+
Constants.LOG.warn("Ship " + shipId + " had been manually unfreezed");
108+
return;
109+
}
110+
final AABBic shipBox = ship.getShipAABB();
111+
if (shipBox == null) {
112+
return;
113+
}
114+
final ServerLevel level = Utils.getLevel(ship.getChunkClaimDimension());
115+
if (level == null) {
116+
return;
117+
}
118+
final Vector3dc velocity = this.velocity;
119+
final Vector3dc omega = this.omega;
120+
if (this.isShipMovingToUnloads(level, shipBox, ship.getTransform(), velocity, omega)) {
121+
return;
122+
}
123+
Constants.LOG.debug("Ship {} is unfreezing! position={} velocity={} omega={}", shipId, ship.getTransform().getPositionInWorld(), velocity, omega);
124+
this.freezed = false;
125+
this.velocity = null;
126+
this.omega = null;
127+
ship.setStatic(false);
128+
final ServerShipTransformProvider lastProvider = ship.getTransformProvider();
129+
ship.setTransformProvider(new ServerShipTransformProvider() {
130+
@Override
131+
public NextTransformAndVelocityData provideNextTransformAndVelocity(final ShipTransform transform, final ShipTransform nextTransform) {
132+
ship.setTransformProvider(lastProvider);
133+
return new NextTransformAndVelocityData(nextTransform, velocity, omega);
134+
}
135+
});
136+
}
137+
138+
@Override
139+
public void applyForces(final PhysShip phyShip) {
140+
final ServerShip ship = this.ship;
141+
if (ship == null) {
142+
return;
143+
}
144+
if (phyShip.isStatic() || ship.isStatic()) {
145+
return;
146+
}
147+
final long shipId = phyShip.getId();
148+
if (this.freezed) {
149+
// When freezing, a ship should not be ticked.
150+
// This may happenes when someone manually set a freezed ship unstatic.
151+
// So just discard the freeze datas.
152+
this.freezed = false;
153+
this.velocity = null;
154+
this.omega = null;
155+
Constants.LOG.warn("Ship " + shipId + " had been manually unfreezed.");
156+
}
157+
final AABBic shipBox = ship.getShipAABB();
158+
if (shipBox == null) {
159+
return;
160+
}
161+
final ServerLevel level = Utils.getLevel(ship.getChunkClaimDimension());
162+
if (level == null) {
163+
return;
164+
}
165+
if (!VSCApi.canFreezeShipForChunkLoad(ship)) {
166+
return;
167+
}
168+
final PoseVel poseVel = ((PhysShipImpl) (phyShip)).getPoseVel();
169+
final Vector3dc velocity = poseVel.getVel();
170+
final Vector3dc omega = poseVel.getOmega();
171+
if (!this.isShipMovingToUnloads(level, shipBox, phyShip.getTransform(), velocity, omega)) {
172+
return;
173+
}
174+
Constants.LOG.debug("Ship {} is freezing! position={} velocity={} omega={}", shipId, phyShip.getTransform().getPositionInWorld(), velocity, omega);
175+
phyShip.setStatic(true);
176+
ship.setStatic(true);
177+
this.freezed = true;
178+
this.velocity = velocity;
179+
this.omega = omega;
180+
}
181+
182+
private boolean isShipMovingToUnloads(
183+
final ServerLevel level,
184+
final AABBic shipBox,
185+
final ShipTransform transform,
186+
final Vector3dc velocity,
187+
final Vector3dc omega
188+
) {
189+
final Matrix4dc oldTransform = transform.getShipToWorld();
190+
final Matrix4d newTransform = new Matrix4d();
191+
final double omegaSqr = omega.lengthSquared();
192+
if (omegaSqr > 1e-8) {
193+
final Vector3dc pos = transform.getPositionInWorld();
194+
newTransform
195+
.rotationAround(new Quaterniond(new AxisAngle4d(Math.sqrt(omegaSqr) * DT, omega.normalize(new Vector3d()))), pos.x(), pos.y(), pos.z())
196+
.mul(oldTransform);
197+
} else {
198+
newTransform.set(oldTransform);
199+
}
200+
newTransform.translate(velocity.mul(DT, new Vector3d()));
201+
final AABBd shipBoxd = new AABBd(shipBox.minX(), shipBox.minY(), shipBox.minZ(), shipBox.maxX(), shipBox.maxY(), shipBox.maxZ());
202+
final AABBd worldBox = shipBoxd.transform(oldTransform, new AABBd());
203+
final AABBd newWorldBox = shipBoxd.transform(newTransform, new AABBd());
204+
final int minX = Math.min(SectionPos.blockToSectionCoord(worldBox.minX), SectionPos.blockToSectionCoord(newWorldBox.minX)) - 1;
205+
final int maxX = Math.max(SectionPos.blockToSectionCoord(worldBox.maxX), SectionPos.blockToSectionCoord(newWorldBox.maxX)) + 1;
206+
final int minZ = Math.min(SectionPos.blockToSectionCoord(worldBox.minZ), SectionPos.blockToSectionCoord(newWorldBox.minZ)) - 1;
207+
final int maxZ = Math.max(SectionPos.blockToSectionCoord(worldBox.maxZ), SectionPos.blockToSectionCoord(newWorldBox.maxZ)) + 1;
208+
209+
final ChunkSensor sensor = ChunkSensor.get(level);
210+
for (int x = minX; x <= maxX; x++) {
211+
for (int z = minZ; z <= maxZ; z++) {
212+
if (!sensor.isChunkLoaded(x, z)) {
213+
TaskUtil.queueTickEnd(() -> ChunkLoaderManager.get(level).pingChunks(minX, maxX, minZ, maxZ));
214+
return true;
215+
}
216+
}
217+
}
218+
return false;
219+
}
220+
}

common/src/main/java/com/github/litermc/vschunkloader/config/Config.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,5 +63,10 @@ public final class Config {
6363
*/
6464
public static int ammoManagerAnchoringRange = 16 * 4;
6565

66+
/**
67+
* Freeze ship for chunk loading. Avoid velocity reset when moving at high speed
68+
*/
69+
public static FreezeMode shipFreezing = FreezeMode.AMMO;
70+
6671
private Config() {}
6772
}

common/src/main/java/com/github/litermc/vschunkloader/config/ConfigSpec.java

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,8 @@ public final class ConfigSpec {
2525
public static final ConfigFile.Value<Boolean> REMOVE_AMMO_AFTER_EXPIRED;
2626
public static final ConfigFile.Value<Integer> AMMO_MANAGER_ANCHORING_RANGE;
2727

28+
public static final ConfigFile.Value<FreezeMode> SHIP_FREEZING;
29+
2830
private ConfigSpec() {}
2931

3032
static {
@@ -90,6 +92,21 @@ private ConfigSpec() {}
9092

9193
builder.pop();
9294
}
95+
{
96+
builder
97+
.comment("Experimental settings. UNSTABLE. May get changed / removed in the future")
98+
.push("experimental");
99+
100+
SHIP_FREEZING = builder
101+
.comment(
102+
"Freeze ship for chunk loading. Avoid velocity reset when moving at high speed\n" +
103+
"ALL: Freeze any ship that hitting a loading chunk\n" +
104+
"AMMO: Only freeze ammo ship which hitting a loading chunk\n" +
105+
"NONE: Do not freeze any ship"
106+
)
107+
.defineEnum("ship_freezing", Config.shipFreezing);
108+
builder.pop();
109+
}
93110

94111
serverSpec = builder.build(ConfigSpec::syncServer);
95112
}
@@ -106,6 +123,7 @@ public static void syncServer(Path path) {
106123
Config.ammoMaxActivateSeconds = AMMO_MAX_ACTIVATE_SECONDS.get();
107124
Config.removeAmmoAfterExpired = REMOVE_AMMO_AFTER_EXPIRED.get();
108125
Config.ammoManagerAnchoringRange = AMMO_MANAGER_ANCHORING_RANGE.get();
126+
Config.shipFreezing = SHIP_FREEZING.get();
109127
}
110128

111129
public static void syncClient(Path path) {
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
package com.github.litermc.vschunkloader.config;
2+
3+
public enum FreezeMode {
4+
/**
5+
* Freeze any ship that hitting a loading chunk
6+
*/
7+
ALL,
8+
/**
9+
* Only freeze ammo ship which hitting a loading chunk
10+
*/
11+
AMMO,
12+
/**
13+
* Do not freeze any ship
14+
*/
15+
NONE;
16+
}

0 commit comments

Comments
 (0)