Skip to content
Merged
4 changes: 4 additions & 0 deletions build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,10 @@ subprojects {
includeGroup("lol.bai")
}
}

maven("https://maven.ryanhcode.dev/releases")
maven("https://raw.githubusercontent.com/Fuzss/modresources/main/maven/")
maven("https://maven.blamejared.com")
}

dependencies {
Expand Down
3 changes: 3 additions & 0 deletions common/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ val modId = project.property("mod.id").toString()
val minecraft = project.property("minecraft.version").toString()
val yarn = project.property("fabric.yarn.build").toString()
val fabricLoader = project.property("fabric.loader.version").toString()
val sable = project.property("sable.version").toString()

plugins {
id("fabric-loom")
Expand All @@ -28,4 +29,6 @@ dependencies {

// loom expects some loader classes to exist, provides mixin and mixin-extras too
modCompileOnly("net.fabricmc:fabric-loader:${fabricLoader}")

compileOnly("dev.ryanhcode.sable:sable-common-$minecraft:$sable")
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
/*
* Copyright (c) 2021-2025 Team Galacticraft
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/

package dev.galacticraft.dynamicdimensions.api;

import org.joml.Vector3f;

/**
* Generic physics/environment properties for a dynamic dimension.
*
* DynamicDimensions does not decide what these values mean.
* Optional compat layers, such as Sable compat, may consume them.
*/
public abstract class DynamicDimensionProperties {
public abstract int priority();

public abstract Vector3f baseGravity();

public abstract double basePressure();

public abstract float universalDrag();

public abstract Vector3f magneticNorth();
}
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,13 @@ default boolean canDeleteDimension(@NotNull ResourceLocation id) {
*/
@Nullable ServerLevel createDynamicDimension(@NotNull ResourceLocation id, @NotNull ChunkGenerator chunkGenerator, @NotNull DimensionType type);

/**
* Registers a new dimension and applies optional dynamic-dimension properties.
*
* @since 0.10.0
Comment thread
maxryan008 marked this conversation as resolved.
*/
@Nullable ServerLevel createDynamicDimension(@NotNull ResourceLocation id, @NotNull ChunkGenerator chunkGenerator, @NotNull DimensionType type, @NotNull DynamicDimensionProperties properties);

/**
* Registers a new dimension and updates all clients with the new dimension.
* If world data already exists for this dimension it will be used, otherwise it will be created.
Expand All @@ -137,6 +144,67 @@ default boolean canDeleteDimension(@NotNull ResourceLocation id) {
*/
@Nullable ServerLevel loadDynamicDimension(@NotNull ResourceLocation id, @NotNull ChunkGenerator chunkGenerator, @NotNull DimensionType type);

/**
* Loads a dynamic dimension and applies optional dynamic-dimension properties.
*
* @since 0.10.0
*/
@Nullable ServerLevel loadDynamicDimension(@NotNull ResourceLocation id, @NotNull ChunkGenerator chunkGenerator, @NotNull DimensionType type, @NotNull DynamicDimensionProperties properties);

/**
* Sets properties for a dynamic dimension.
*
* <p>If the dimension is already loaded, compatible backends such as Sable
* may apply these immediately.</p>
*
* @since 0.10.0
*/
void setDimensionProperties(@NotNull ResourceKey<Level> key, @NotNull DynamicDimensionProperties properties);

/**
* Sets properties for a dynamic dimension.
*
* @since 0.10.0
*/
default void setDimensionProperties(@NotNull ResourceLocation id, @NotNull DynamicDimensionProperties properties) {
this.setDimensionProperties(ResourceKey.create(Registries.DIMENSION, id), properties);
}

/**
* Gets the currently stored properties for a dynamic dimension.
*
* @since 0.10.0
*/
@Nullable DynamicDimensionProperties getDimensionProperties(@NotNull ResourceKey<Level> key);

/**
* Gets the currently stored properties for a dynamic dimension.
*
* @since 0.10.0
*/
default @Nullable DynamicDimensionProperties getDimensionProperties(@NotNull ResourceLocation id) {
return this.getDimensionProperties(ResourceKey.create(Registries.DIMENSION, id));
}

/**
* Clears stored properties for a dynamic dimension.
*
* <p>If the dimension has been applied to a compatible backend such as Sable,
* this should also remove those backend properties.</p>
*
* @since 0.10.0
*/
void clearDimensionProperties(@NotNull ResourceKey<Level> key);

/**
* Clears stored properties for a dynamic dimension.
*
* @since 0.10.0
*/
default void clearDimensionProperties(@NotNull ResourceLocation id) {
this.clearDimensionProperties(ResourceKey.create(Registries.DIMENSION, id));
}

/**
* Deletes a dynamic dimension from the server.
* This may delete the dimension files permanently.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,11 @@

package dev.galacticraft.dynamicdimensions.api.event;

import dev.galacticraft.dynamicdimensions.api.DynamicDimensionProperties;
import dev.galacticraft.dynamicdimensions.api.DynamicDimensionRegistry;
import dev.galacticraft.dynamicdimensions.impl.platform.Services;
import net.minecraft.core.registries.Registries;
import net.minecraft.resources.ResourceKey;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.server.MinecraftServer;
import net.minecraft.server.level.ServerLevel;
Expand Down Expand Up @@ -58,5 +62,20 @@ interface DynamicDimensionLoader {
* @return the newly loaded level
*/
@NotNull ServerLevel loadDynamicDimension(@NotNull ResourceLocation id, @NotNull ChunkGenerator chunkGenerator, @NotNull DimensionType type);

/**
* Creates/loads a new dynamic dimension and applies the given properties before level construction,
* so that compatible backends (e.g. Sable) receive the correct physics data from the start.
* @param id the id of the dimension. Must be free in the level stem, dimension type, and level registries.
* @param chunkGenerator the chunk generator to generate the dimension with
* @param type the dimension type
* @param properties the dimension properties to apply
* @param server the current minecraft server
* @return the newly loaded level
*/
default @NotNull ServerLevel loadDynamicDimension(@NotNull ResourceLocation id, @NotNull ChunkGenerator chunkGenerator, @NotNull DimensionType type, @NotNull DynamicDimensionProperties properties, @NotNull MinecraftServer server) {
DynamicDimensionRegistry.from(server).setDimensionProperties(ResourceKey.create(Registries.DIMENSION, id), properties);
return this.loadDynamicDimension(id, chunkGenerator, type);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -23,11 +23,13 @@
package dev.galacticraft.dynamicdimensions.impl;

import com.google.common.collect.ImmutableList;
import dev.galacticraft.dynamicdimensions.api.DynamicDimensionProperties;
import dev.galacticraft.dynamicdimensions.api.DynamicDimensionRegistry;
import dev.galacticraft.dynamicdimensions.api.PlayerRemover;
import dev.galacticraft.dynamicdimensions.api.event.DynamicDimensionLoadCallback;
import dev.galacticraft.dynamicdimensions.impl.accessor.DynamicDimensionProvider;
import dev.galacticraft.dynamicdimensions.impl.accessor.PrimaryLevelDataAccessor;
import dev.galacticraft.dynamicdimensions.impl.compat.DynamicDimensionPhysicsCompat;
import dev.galacticraft.dynamicdimensions.impl.mixin.*;
import dev.galacticraft.dynamicdimensions.impl.network.S2CPackets;
import dev.galacticraft.dynamicdimensions.impl.registry.RegistryUtil;
Expand Down Expand Up @@ -60,7 +62,9 @@
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

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

private final Map<ResourceKey<Level>, DynamicDimensionProperties> dimensionProperties = new HashMap<>();

public DynamicDimensionRegistryImpl(MinecraftServer server) {
this.server = server;
this.dimTypes = server.registryAccess().registryOrThrow(Registries.DIMENSION_TYPE);
Expand All @@ -81,6 +87,13 @@ public void loadDynamicDimensions() {
Constants.LOGGER.debug("Loading dynamic dimension '{}'", id);
ResourceKey<Level> key = ResourceKey.create(Registries.DIMENSION, id);

// If properties were already registered before this callback fires
// (e.g. by the caller calling setDimensionProperties first), stage them now.
DynamicDimensionProperties properties = this.dimensionProperties.get(key);
if (properties != null) {
DynamicDimensionPhysicsCompat.stage(key, properties);
}

return this.createDynamicLevel(id, chunkGenerator, type, key);
});

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

@Override
public @Nullable ServerLevel createDynamicDimension(@NotNull ResourceLocation id, @NotNull ChunkGenerator generator, @NotNull DimensionType type, @NotNull DynamicDimensionProperties properties) {
ResourceKey<Level> key = ResourceKey.create(Registries.DIMENSION, id);
this.setDimensionProperties(key, properties);

ServerLevel level = this.createDynamicLevel(id, generator, type, true);
if (level == null) {
this.clearDimensionProperties(key);
}

return level;
}

@Override
public @Nullable ServerLevel loadDynamicDimension(@NotNull ResourceLocation id, @NotNull ChunkGenerator generator, @NotNull DimensionType type) {
return this.createDynamicLevel(id, generator, type, false);
}

@Override
public @Nullable ServerLevel loadDynamicDimension(@NotNull ResourceLocation id, @NotNull ChunkGenerator generator, @NotNull DimensionType type, @NotNull DynamicDimensionProperties properties) {
ResourceKey<Level> key = ResourceKey.create(Registries.DIMENSION, id);
this.setDimensionProperties(key, properties);

ServerLevel level = this.createDynamicLevel(id, generator, type, false);
if (level == null) {
this.clearDimensionProperties(key);
}

return level;
}

@Override
public void setDimensionProperties(@NotNull ResourceKey<Level> key, @NotNull DynamicDimensionProperties properties) {
this.dimensionProperties.put(key, properties);

if (this.server.getLevel(key) != null) {
DynamicDimensionPhysicsCompat.apply(key, properties);
}
}

@Override
public @Nullable DynamicDimensionProperties getDimensionProperties(@NotNull ResourceKey<Level> key) {
return this.dimensionProperties.get(key);
}


@Override
public void clearDimensionProperties(@NotNull ResourceKey<Level> key) {
this.dimensionProperties.remove(key);
DynamicDimensionPhysicsCompat.remove(key);
}

@Override
public boolean dynamicDimensionExists(@NotNull ResourceKey<Level> key) {
return this.dynamicDimensions.contains(key) || ((DynamicDimensionProvider) this.server).dynamicdimensions$isIdPendingCreation(key);
Expand Down Expand Up @@ -125,7 +185,9 @@ public boolean deleteDynamicDimension(@NotNull ResourceLocation id, @Nullable Pl
ResourceKey<Level> key = ResourceKey.create(Registries.DIMENSION, id);
if (!this.canDeleteDimension(key)) return false;

DynamicDimensionPhysicsCompat.remove(key);
((DynamicDimensionProvider) this.server).dynamicdimensions$removeLevel(key, remover, true);
this.dimensionProperties.remove(key);

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

DynamicDimensionPhysicsCompat.remove(key);
((DynamicDimensionProvider) this.server).dynamicdimensions$removeLevel(key, remover, false);

return true;
}

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

private @NotNull ServerLevel createDynamicLevel(ResourceKey<Level> key, WorldData worldData, LevelStem stem, ServerLevel overworld) {
// -- start createLevels --
final DerivedLevelData data = new DerivedLevelData(worldData, worldData.overworldData()); //todo: do we want separate data?
// Stage physics properties before ServerLevel construction so Sable's
// SubLevelPhysicsSystem.initialize() mixin can flush them before reading gravity.
DynamicDimensionProperties pendingProperties = this.dimensionProperties.get(key);
if (pendingProperties != null) {
DynamicDimensionPhysicsCompat.stage(key, pendingProperties);
}

final DerivedLevelData data = new DerivedLevelData(worldData, worldData.overworldData());
final ServerLevel level = new ServerLevel(
this.server,
((MinecraftServerAccessor) this.server).getExecutor(),
Expand All @@ -179,17 +249,13 @@ public boolean unloadDynamicDimension(@NotNull ResourceLocation id, @Nullable Pl
null
);
overworld.getWorldBorder().addListener(new BorderChangeListener.DelegateBorderChangeListener(level.getWorldBorder()));
// -- end createLevels --

// see PlayerList
level.getChunkSource().setSimulationDistance(((DistanceManagerAccessor) ((ServerChunkCacheAccessor) overworld.getChunkSource()).getDistanceManager()).getSimulationDistance());
level.getChunkSource().setViewDistance(((ChunkMapAccessor) overworld.getChunkSource().chunkMap).getViewDistance());

// -- start prepareLevels --
ForcedChunksSavedData forcedChunksSavedData = level.getDataStorage().get(ForcedChunksSavedData.factory(), "chunks");
if (forcedChunksSavedData != null) {
LongIterator longIterator = forcedChunksSavedData.getChunks().iterator();

while (longIterator.hasNext()) {
long l = longIterator.nextLong();
ChunkPos chunkPos = new ChunkPos(l);
Expand All @@ -198,10 +264,14 @@ public boolean unloadDynamicDimension(@NotNull ResourceLocation id, @Nullable Pl
}

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

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

// Belt-and-suspenders apply after level exists, covers setDimensionProperties called post-creation.
if (pendingProperties != null) {
DynamicDimensionPhysicsCompat.apply(key, pendingProperties);
}

final var serializedType = ((CompoundTag) DimensionType.DIRECT_CODEC.encode(stem.type().value(), RegistryOps.create(NbtOps.INSTANCE, this.server.registryAccess()), new CompoundTag()).getOrThrow());
for (ServerPlayer player : this.server.getPlayerList().getPlayers()) {
S2CPackets.sendCreateDimension(player, key.location(), serializedType);
Expand Down
Loading