Skip to content

Commit bcab6c2

Browse files
authored
Merge pull request #11 from TeamGalacticraft/feature/sable-compat
feature: Sable compatability with Dynamic Dimensions.
2 parents f6615d0 + 7125d5d commit bcab6c2

19 files changed

Lines changed: 597 additions & 15 deletions

File tree

build.gradle.kts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,10 @@ subprojects {
9090
includeGroup("lol.bai")
9191
}
9292
}
93+
94+
maven("https://maven.ryanhcode.dev/releases")
95+
maven("https://raw.githubusercontent.com/Fuzss/modresources/main/maven/")
96+
maven("https://maven.blamejared.com")
9397
}
9498

9599
dependencies {

common/build.gradle.kts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ val modId = project.property("mod.id").toString()
22
val minecraft = project.property("minecraft.version").toString()
33
val yarn = project.property("fabric.yarn.build").toString()
44
val fabricLoader = project.property("fabric.loader.version").toString()
5+
val sable = project.property("sable.version").toString()
56

67
plugins {
78
id("fabric-loom")
@@ -28,4 +29,6 @@ dependencies {
2829

2930
// loom expects some loader classes to exist, provides mixin and mixin-extras too
3031
modCompileOnly("net.fabricmc:fabric-loader:${fabricLoader}")
32+
33+
compileOnly("dev.ryanhcode.sable:sable-common-$minecraft:$sable")
3134
}
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
/*
2+
* Copyright (c) 2021-2025 Team Galacticraft
3+
*
4+
* Permission is hereby granted, free of charge, to any person obtaining a copy
5+
* of this software and associated documentation files (the "Software"), to deal
6+
* in the Software without restriction, including without limitation the rights
7+
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8+
* copies of the Software, and to permit persons to whom the Software is
9+
* furnished to do so, subject to the following conditions:
10+
*
11+
* The above copyright notice and this permission notice shall be included in all
12+
* copies or substantial portions of the Software.
13+
*
14+
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15+
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16+
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17+
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18+
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19+
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
20+
* SOFTWARE.
21+
*/
22+
23+
package dev.galacticraft.dynamicdimensions.api;
24+
25+
import org.joml.Vector3f;
26+
27+
/**
28+
* Generic physics/environment properties for a dynamic dimension.
29+
*
30+
* DynamicDimensions does not decide what these values mean.
31+
* Optional compat layers, such as Sable compat, may consume them.
32+
*/
33+
public abstract class DynamicDimensionProperties {
34+
public abstract int priority();
35+
36+
public abstract Vector3f baseGravity();
37+
38+
public abstract double basePressure();
39+
40+
public abstract float universalDrag();
41+
42+
public abstract Vector3f magneticNorth();
43+
}

common/src/main/java/dev/galacticraft/dynamicdimensions/api/DynamicDimensionRegistry.java

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,13 @@ default boolean canDeleteDimension(@NotNull ResourceLocation id) {
122122
*/
123123
@Nullable ServerLevel createDynamicDimension(@NotNull ResourceLocation id, @NotNull ChunkGenerator chunkGenerator, @NotNull DimensionType type);
124124

125+
/**
126+
* Registers a new dimension and applies optional dynamic-dimension properties.
127+
*
128+
* @since 0.10.0
129+
*/
130+
@Nullable ServerLevel createDynamicDimension(@NotNull ResourceLocation id, @NotNull ChunkGenerator chunkGenerator, @NotNull DimensionType type, @NotNull DynamicDimensionProperties properties);
131+
125132
/**
126133
* Registers a new dimension and updates all clients with the new dimension.
127134
* If world data already exists for this dimension it will be used, otherwise it will be created.
@@ -137,6 +144,67 @@ default boolean canDeleteDimension(@NotNull ResourceLocation id) {
137144
*/
138145
@Nullable ServerLevel loadDynamicDimension(@NotNull ResourceLocation id, @NotNull ChunkGenerator chunkGenerator, @NotNull DimensionType type);
139146

147+
/**
148+
* Loads a dynamic dimension and applies optional dynamic-dimension properties.
149+
*
150+
* @since 0.10.0
151+
*/
152+
@Nullable ServerLevel loadDynamicDimension(@NotNull ResourceLocation id, @NotNull ChunkGenerator chunkGenerator, @NotNull DimensionType type, @NotNull DynamicDimensionProperties properties);
153+
154+
/**
155+
* Sets properties for a dynamic dimension.
156+
*
157+
* <p>If the dimension is already loaded, compatible backends such as Sable
158+
* may apply these immediately.</p>
159+
*
160+
* @since 0.10.0
161+
*/
162+
void setDimensionProperties(@NotNull ResourceKey<Level> key, @NotNull DynamicDimensionProperties properties);
163+
164+
/**
165+
* Sets properties for a dynamic dimension.
166+
*
167+
* @since 0.10.0
168+
*/
169+
default void setDimensionProperties(@NotNull ResourceLocation id, @NotNull DynamicDimensionProperties properties) {
170+
this.setDimensionProperties(ResourceKey.create(Registries.DIMENSION, id), properties);
171+
}
172+
173+
/**
174+
* Gets the currently stored properties for a dynamic dimension.
175+
*
176+
* @since 0.10.0
177+
*/
178+
@Nullable DynamicDimensionProperties getDimensionProperties(@NotNull ResourceKey<Level> key);
179+
180+
/**
181+
* Gets the currently stored properties for a dynamic dimension.
182+
*
183+
* @since 0.10.0
184+
*/
185+
default @Nullable DynamicDimensionProperties getDimensionProperties(@NotNull ResourceLocation id) {
186+
return this.getDimensionProperties(ResourceKey.create(Registries.DIMENSION, id));
187+
}
188+
189+
/**
190+
* Clears stored properties for a dynamic dimension.
191+
*
192+
* <p>If the dimension has been applied to a compatible backend such as Sable,
193+
* this should also remove those backend properties.</p>
194+
*
195+
* @since 0.10.0
196+
*/
197+
void clearDimensionProperties(@NotNull ResourceKey<Level> key);
198+
199+
/**
200+
* Clears stored properties for a dynamic dimension.
201+
*
202+
* @since 0.10.0
203+
*/
204+
default void clearDimensionProperties(@NotNull ResourceLocation id) {
205+
this.clearDimensionProperties(ResourceKey.create(Registries.DIMENSION, id));
206+
}
207+
140208
/**
141209
* Deletes a dynamic dimension from the server.
142210
* This may delete the dimension files permanently.

common/src/main/java/dev/galacticraft/dynamicdimensions/api/event/DynamicDimensionLoadCallback.java

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,11 @@
2222

2323
package dev.galacticraft.dynamicdimensions.api.event;
2424

25+
import dev.galacticraft.dynamicdimensions.api.DynamicDimensionProperties;
26+
import dev.galacticraft.dynamicdimensions.api.DynamicDimensionRegistry;
2527
import dev.galacticraft.dynamicdimensions.impl.platform.Services;
28+
import net.minecraft.core.registries.Registries;
29+
import net.minecraft.resources.ResourceKey;
2630
import net.minecraft.resources.ResourceLocation;
2731
import net.minecraft.server.MinecraftServer;
2832
import net.minecraft.server.level.ServerLevel;
@@ -58,5 +62,20 @@ interface DynamicDimensionLoader {
5862
* @return the newly loaded level
5963
*/
6064
@NotNull ServerLevel loadDynamicDimension(@NotNull ResourceLocation id, @NotNull ChunkGenerator chunkGenerator, @NotNull DimensionType type);
65+
66+
/**
67+
* Creates/loads a new dynamic dimension and applies the given properties before level construction,
68+
* so that compatible backends (e.g. Sable) receive the correct physics data from the start.
69+
* @param id the id of the dimension. Must be free in the level stem, dimension type, and level registries.
70+
* @param chunkGenerator the chunk generator to generate the dimension with
71+
* @param type the dimension type
72+
* @param properties the dimension properties to apply
73+
* @param server the current minecraft server
74+
* @return the newly loaded level
75+
*/
76+
default @NotNull ServerLevel loadDynamicDimension(@NotNull ResourceLocation id, @NotNull ChunkGenerator chunkGenerator, @NotNull DimensionType type, @NotNull DynamicDimensionProperties properties, @NotNull MinecraftServer server) {
77+
DynamicDimensionRegistry.from(server).setDimensionProperties(ResourceKey.create(Registries.DIMENSION, id), properties);
78+
return this.loadDynamicDimension(id, chunkGenerator, type);
79+
}
6180
}
6281
}

common/src/main/java/dev/galacticraft/dynamicdimensions/impl/DynamicDimensionRegistryImpl.java

Lines changed: 77 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -23,11 +23,13 @@
2323
package dev.galacticraft.dynamicdimensions.impl;
2424

2525
import com.google.common.collect.ImmutableList;
26+
import dev.galacticraft.dynamicdimensions.api.DynamicDimensionProperties;
2627
import dev.galacticraft.dynamicdimensions.api.DynamicDimensionRegistry;
2728
import dev.galacticraft.dynamicdimensions.api.PlayerRemover;
2829
import dev.galacticraft.dynamicdimensions.api.event.DynamicDimensionLoadCallback;
2930
import dev.galacticraft.dynamicdimensions.impl.accessor.DynamicDimensionProvider;
3031
import dev.galacticraft.dynamicdimensions.impl.accessor.PrimaryLevelDataAccessor;
32+
import dev.galacticraft.dynamicdimensions.impl.compat.DynamicDimensionPhysicsCompat;
3133
import dev.galacticraft.dynamicdimensions.impl.mixin.*;
3234
import dev.galacticraft.dynamicdimensions.impl.network.S2CPackets;
3335
import dev.galacticraft.dynamicdimensions.impl.registry.RegistryUtil;
@@ -60,7 +62,9 @@
6062
import org.jetbrains.annotations.NotNull;
6163
import org.jetbrains.annotations.Nullable;
6264

65+
import java.util.HashMap;
6366
import java.util.List;
67+
import java.util.Map;
6468
import java.util.stream.Collectors;
6569

6670
public class DynamicDimensionRegistryImpl implements DynamicDimensionRegistry {
@@ -69,6 +73,8 @@ public class DynamicDimensionRegistryImpl implements DynamicDimensionRegistry {
6973
private final Registry<DimensionType> dimTypes;
7074
private final Registry<LevelStem> stems;
7175

76+
private final Map<ResourceKey<Level>, DynamicDimensionProperties> dimensionProperties = new HashMap<>();
77+
7278
public DynamicDimensionRegistryImpl(MinecraftServer server) {
7379
this.server = server;
7480
this.dimTypes = server.registryAccess().registryOrThrow(Registries.DIMENSION_TYPE);
@@ -81,6 +87,13 @@ public void loadDynamicDimensions() {
8187
Constants.LOGGER.debug("Loading dynamic dimension '{}'", id);
8288
ResourceKey<Level> key = ResourceKey.create(Registries.DIMENSION, id);
8389

90+
// If properties were already registered before this callback fires
91+
// (e.g. by the caller calling setDimensionProperties first), stage them now.
92+
DynamicDimensionProperties properties = this.dimensionProperties.get(key);
93+
if (properties != null) {
94+
DynamicDimensionPhysicsCompat.stage(key, properties);
95+
}
96+
8497
return this.createDynamicLevel(id, chunkGenerator, type, key);
8598
});
8699

@@ -92,11 +105,58 @@ public void loadDynamicDimensions() {
92105
return this.createDynamicLevel(id, generator, type, true);
93106
}
94107

108+
@Override
109+
public @Nullable ServerLevel createDynamicDimension(@NotNull ResourceLocation id, @NotNull ChunkGenerator generator, @NotNull DimensionType type, @NotNull DynamicDimensionProperties properties) {
110+
ResourceKey<Level> key = ResourceKey.create(Registries.DIMENSION, id);
111+
this.setDimensionProperties(key, properties);
112+
113+
ServerLevel level = this.createDynamicLevel(id, generator, type, true);
114+
if (level == null) {
115+
this.clearDimensionProperties(key);
116+
}
117+
118+
return level;
119+
}
120+
95121
@Override
96122
public @Nullable ServerLevel loadDynamicDimension(@NotNull ResourceLocation id, @NotNull ChunkGenerator generator, @NotNull DimensionType type) {
97123
return this.createDynamicLevel(id, generator, type, false);
98124
}
99125

126+
@Override
127+
public @Nullable ServerLevel loadDynamicDimension(@NotNull ResourceLocation id, @NotNull ChunkGenerator generator, @NotNull DimensionType type, @NotNull DynamicDimensionProperties properties) {
128+
ResourceKey<Level> key = ResourceKey.create(Registries.DIMENSION, id);
129+
this.setDimensionProperties(key, properties);
130+
131+
ServerLevel level = this.createDynamicLevel(id, generator, type, false);
132+
if (level == null) {
133+
this.clearDimensionProperties(key);
134+
}
135+
136+
return level;
137+
}
138+
139+
@Override
140+
public void setDimensionProperties(@NotNull ResourceKey<Level> key, @NotNull DynamicDimensionProperties properties) {
141+
this.dimensionProperties.put(key, properties);
142+
143+
if (this.server.getLevel(key) != null) {
144+
DynamicDimensionPhysicsCompat.apply(key, properties);
145+
}
146+
}
147+
148+
@Override
149+
public @Nullable DynamicDimensionProperties getDimensionProperties(@NotNull ResourceKey<Level> key) {
150+
return this.dimensionProperties.get(key);
151+
}
152+
153+
154+
@Override
155+
public void clearDimensionProperties(@NotNull ResourceKey<Level> key) {
156+
this.dimensionProperties.remove(key);
157+
DynamicDimensionPhysicsCompat.remove(key);
158+
}
159+
100160
@Override
101161
public boolean dynamicDimensionExists(@NotNull ResourceKey<Level> key) {
102162
return this.dynamicDimensions.contains(key) || ((DynamicDimensionProvider) this.server).dynamicdimensions$isIdPendingCreation(key);
@@ -125,7 +185,9 @@ public boolean deleteDynamicDimension(@NotNull ResourceLocation id, @Nullable Pl
125185
ResourceKey<Level> key = ResourceKey.create(Registries.DIMENSION, id);
126186
if (!this.canDeleteDimension(key)) return false;
127187

188+
DynamicDimensionPhysicsCompat.remove(key);
128189
((DynamicDimensionProvider) this.server).dynamicdimensions$removeLevel(key, remover, true);
190+
this.dimensionProperties.remove(key);
129191

130192
return true;
131193
}
@@ -136,7 +198,9 @@ public boolean unloadDynamicDimension(@NotNull ResourceLocation id, @Nullable Pl
136198
ResourceKey<Level> key = ResourceKey.create(Registries.DIMENSION, id);
137199
if (!this.canDeleteDimension(key)) return false;
138200

201+
DynamicDimensionPhysicsCompat.remove(key);
139202
((DynamicDimensionProvider) this.server).dynamicdimensions$removeLevel(key, remover, false);
203+
140204
return true;
141205
}
142206

@@ -162,8 +226,14 @@ public boolean unloadDynamicDimension(@NotNull ResourceLocation id, @Nullable Pl
162226
}
163227

164228
private @NotNull ServerLevel createDynamicLevel(ResourceKey<Level> key, WorldData worldData, LevelStem stem, ServerLevel overworld) {
165-
// -- start createLevels --
166-
final DerivedLevelData data = new DerivedLevelData(worldData, worldData.overworldData()); //todo: do we want separate data?
229+
// Stage physics properties before ServerLevel construction so Sable's
230+
// SubLevelPhysicsSystem.initialize() mixin can flush them before reading gravity.
231+
DynamicDimensionProperties pendingProperties = this.dimensionProperties.get(key);
232+
if (pendingProperties != null) {
233+
DynamicDimensionPhysicsCompat.stage(key, pendingProperties);
234+
}
235+
236+
final DerivedLevelData data = new DerivedLevelData(worldData, worldData.overworldData());
167237
final ServerLevel level = new ServerLevel(
168238
this.server,
169239
((MinecraftServerAccessor) this.server).getExecutor(),
@@ -179,17 +249,13 @@ public boolean unloadDynamicDimension(@NotNull ResourceLocation id, @Nullable Pl
179249
null
180250
);
181251
overworld.getWorldBorder().addListener(new BorderChangeListener.DelegateBorderChangeListener(level.getWorldBorder()));
182-
// -- end createLevels --
183252

184-
// see PlayerList
185253
level.getChunkSource().setSimulationDistance(((DistanceManagerAccessor) ((ServerChunkCacheAccessor) overworld.getChunkSource()).getDistanceManager()).getSimulationDistance());
186254
level.getChunkSource().setViewDistance(((ChunkMapAccessor) overworld.getChunkSource().chunkMap).getViewDistance());
187255

188-
// -- start prepareLevels --
189256
ForcedChunksSavedData forcedChunksSavedData = level.getDataStorage().get(ForcedChunksSavedData.factory(), "chunks");
190257
if (forcedChunksSavedData != null) {
191258
LongIterator longIterator = forcedChunksSavedData.getChunks().iterator();
192-
193259
while (longIterator.hasNext()) {
194260
long l = longIterator.nextLong();
195261
ChunkPos chunkPos = new ChunkPos(l);
@@ -198,10 +264,14 @@ public boolean unloadDynamicDimension(@NotNull ResourceLocation id, @Nullable Pl
198264
}
199265

200266
level.setSpawnSettings(this.server.isSpawningMonsters(), this.server.isSpawningAnimals());
201-
// -- end prepareLevels --
202267

203268
((DynamicDimensionProvider) this.server).dynamicdimensions$registerLevel(level);
204269

270+
// Belt-and-suspenders apply after level exists, covers setDimensionProperties called post-creation.
271+
if (pendingProperties != null) {
272+
DynamicDimensionPhysicsCompat.apply(key, pendingProperties);
273+
}
274+
205275
final var serializedType = ((CompoundTag) DimensionType.DIRECT_CODEC.encode(stem.type().value(), RegistryOps.create(NbtOps.INSTANCE, this.server.registryAccess()), new CompoundTag()).getOrThrow());
206276
for (ServerPlayer player : this.server.getPlayerList().getPlayers()) {
207277
S2CPackets.sendCreateDimension(player, key.location(), serializedType);

0 commit comments

Comments
 (0)