Skip to content

Commit 216e5ec

Browse files
committed
Support Bukkit custom ChunkGenerator; support spigot configuring simulation distance; for IzzelAliz#1641
1 parent 5c69d66 commit 216e5ec

File tree

8 files changed

+157
-60
lines changed

8 files changed

+157
-60
lines changed

arclight-common/src/main/java/io/izzel/arclight/common/bridge/bukkit/CraftServerBridge.java

+12
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,22 @@
22

33
import net.minecraft.server.level.ServerLevel;
44
import net.minecraft.server.players.PlayerList;
5+
import org.bukkit.generator.BiomeProvider;
6+
import org.bukkit.generator.ChunkGenerator;
57

68
public interface CraftServerBridge {
79

810
void bridge$setPlayerList(PlayerList playerList);
911

1012
void bridge$removeWorld(ServerLevel world);
13+
14+
ChunkGenerator bridge$consumeGeneratorCache(String name);
15+
16+
// One-shot custom chunk generation cache
17+
void bridge$offerGeneratorCache(String name, ChunkGenerator generator);
18+
19+
BiomeProvider bridge$consumeBiomeProviderCache(String name);
20+
21+
// One-shot custom chunk generation cache
22+
void bridge$offerBiomeProviderCache(String name, BiomeProvider provider);
1123
}

arclight-common/src/main/java/io/izzel/arclight/common/bridge/core/server/MinecraftServerBridge.java

+2
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@ public interface MinecraftServerBridge {
1616

1717
void bridge$setServer(CraftServer server);
1818

19+
CraftServer bridge$getServer();
20+
1921
RemoteConsoleCommandSender bridge$getRemoteConsole();
2022

2123
void bridge$setRemoteConsole(RemoteConsoleCommandSender sender);

arclight-common/src/main/java/io/izzel/arclight/common/bridge/core/world/server/ServerChunkProviderBridge.java

+2-3
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22

33
import java.io.IOException;
44
import net.minecraft.server.level.ThreadedLevelLightEngine;
5-
import net.minecraft.world.level.chunk.ChunkGenerator;
65

76
public interface ServerChunkProviderBridge {
87

@@ -16,7 +15,7 @@ public interface ServerChunkProviderBridge {
1615

1716
ThreadedLevelLightEngine bridge$getLightManager();
1817

19-
void bridge$setChunkGenerator(ChunkGenerator chunkGenerator);
20-
2118
void bridge$setViewDistance(int viewDistance);
19+
20+
void bridge$setSimulationDistance(int simDistance);
2221
}

arclight-common/src/main/java/io/izzel/arclight/common/mixin/bukkit/CraftServerMixin.java

+47-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package io.izzel.arclight.common.mixin.bukkit;
22

33
import io.izzel.arclight.common.bridge.bukkit.CraftServerBridge;
4+
import io.izzel.arclight.common.mod.server.ArclightServer;
45
import jline.console.ConsoleReader;
56
import net.minecraft.server.dedicated.DedicatedPlayerList;
67
import net.minecraft.server.dedicated.DedicatedServer;
@@ -14,6 +15,8 @@
1415
import org.bukkit.craftbukkit.v.help.SimpleHelpMap;
1516
import org.bukkit.craftbukkit.v.scheduler.CraftScheduler;
1617
import org.bukkit.event.server.ServerLoadEvent;
18+
import org.bukkit.generator.BiomeProvider;
19+
import org.bukkit.generator.ChunkGenerator;
1720
import org.bukkit.plugin.Plugin;
1821
import org.bukkit.plugin.PluginLoadOrder;
1922
import org.bukkit.plugin.PluginManager;
@@ -33,6 +36,7 @@
3336

3437
import java.io.File;
3538
import java.io.IOException;
39+
import java.util.HashMap;
3640
import java.util.List;
3741
import java.util.Locale;
3842
import java.util.Map;
@@ -66,7 +70,10 @@ public abstract class CraftServerMixin implements CraftServerBridge {
6670
@Shadow public abstract void loadPlugins();
6771
@Shadow public abstract void enablePlugins(PluginLoadOrder type);
6872
@Shadow public abstract PluginManager getPluginManager();
69-
@Shadow@Final private String serverVersion;@Accessor("logger") @Mutable public abstract void setLogger(Logger logger);
73+
@Shadow@Final private String serverVersion;
74+
@Accessor("logger") @Mutable public abstract void setLogger(Logger logger);
75+
@Shadow public abstract ChunkGenerator getGenerator(String world);
76+
@Shadow public abstract BiomeProvider getBiomeProvider(String world);
7077
// @formatter:on
7178

7279
@Inject(method = "<init>", at = @At("RETURN"))
@@ -169,4 +176,43 @@ public void reload() {
169176
this.enablePlugins(PluginLoadOrder.POSTWORLD);
170177
this.getPluginManager().callEvent(new ServerLoadEvent(ServerLoadEvent.LoadType.RELOAD));
171178
}
179+
180+
private final Map<String, ChunkGenerator> generatorCache = new HashMap<>();
181+
private final Map<String, BiomeProvider> biomeProviderCache = new HashMap<>();
182+
183+
@Override
184+
public void bridge$offerGeneratorCache(String name, ChunkGenerator generator) {
185+
// Newly created level
186+
generatorCache.put(name, generator);
187+
}
188+
189+
@Override
190+
public ChunkGenerator bridge$consumeGeneratorCache(String name) {
191+
var cache = generatorCache.remove(name);
192+
if (cache == null) {
193+
// If not provided (which means it's not newly created),
194+
// load from bukkit.yml configuration.
195+
// See CraftServer
196+
cache = getGenerator(name);
197+
}
198+
return cache;
199+
}
200+
201+
@Override
202+
public BiomeProvider bridge$consumeBiomeProviderCache(String name) {
203+
var cache = biomeProviderCache.remove(name);
204+
if (cache == null) {
205+
// If not provided (which means it's not newly created),
206+
// load from bukkit.yml configuration.
207+
// See CraftServer
208+
cache = getBiomeProvider(name);
209+
}
210+
return cache;
211+
}
212+
213+
@Override
214+
public void bridge$offerBiomeProviderCache(String name, BiomeProvider provider) {
215+
// Newly created level
216+
biomeProviderCache.put(name, provider);
217+
}
172218
}

arclight-common/src/main/java/io/izzel/arclight/common/mixin/core/server/MinecraftServerMixin.java

+9
Original file line numberDiff line numberDiff line change
@@ -485,6 +485,15 @@ public void removeLevel(ServerLevel level) {
485485
this.server = server;
486486
}
487487

488+
// Used for one-shot cache access
489+
@Override
490+
public CraftServer bridge$getServer() {
491+
if (this.server == null) {
492+
throw new IllegalStateException("CraftServer has not been initialized yet");
493+
}
494+
return this.server;
495+
}
496+
488497
@Override
489498
public RemoteConsoleCommandSender bridge$getRemoteConsole() {
490499
return remoteConsole;

arclight-common/src/main/java/io/izzel/arclight/common/mixin/core/server/level/ServerChunkCacheMixin.java

+4-7
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,9 @@
55
import io.izzel.arclight.common.bridge.core.world.server.ChunkMapBridge;
66
import io.izzel.arclight.common.bridge.core.world.server.ServerChunkProviderBridge;
77
import io.izzel.arclight.common.bridge.core.world.server.TicketManagerBridge;
8-
import io.izzel.arclight.mixin.Local;
98
import net.minecraft.server.level.*;
109
import net.minecraft.world.level.ChunkPos;
1110
import net.minecraft.world.level.GameRules;
12-
import net.minecraft.world.level.chunk.ChunkGenerator;
1311
import net.minecraft.world.level.chunk.LevelChunk;
1412
import net.minecraft.world.level.storage.LevelData;
1513
import org.bukkit.entity.SpawnCategory;
@@ -19,7 +17,6 @@
1917
import org.spongepowered.asm.mixin.gen.Accessor;
2018
import org.spongepowered.asm.mixin.gen.Invoker;
2119
import org.spongepowered.asm.mixin.injection.*;
22-
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
2320

2421
import javax.annotation.Nullable;
2522
import java.io.IOException;
@@ -58,13 +55,13 @@ public LevelChunk getChunkUnchecked(int chunkX, int chunkZ) {
5855
}
5956

6057
@Override
61-
public void bridge$setChunkGenerator(ChunkGenerator chunkGenerator) {
62-
((ChunkMapBridge) this.chunkMap).bridge$setChunkGenerator(chunkGenerator);
58+
public void bridge$setViewDistance(int viewDistance) {
59+
((ChunkMapBridge) this.chunkMap).bridge$setViewDistance(viewDistance);
6360
}
6461

6562
@Override
66-
public void bridge$setViewDistance(int viewDistance) {
67-
((ChunkMapBridge) this.chunkMap).bridge$setViewDistance(viewDistance);
63+
public void bridge$setSimulationDistance(int simDistance) {
64+
distanceManager.updateSimulationDistance(simDistance);
6865
}
6966

7067
@ModifyVariable(method = "getChunkFutureMainThread", index = 4, at = @At("HEAD"))

arclight-common/src/main/java/io/izzel/arclight/common/mixin/core/server/level/ServerLevelMixin.java

+78-29
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
11
package io.izzel.arclight.common.mixin.core.server.level;
22

33
import com.google.common.collect.Lists;
4+
import io.izzel.arclight.common.bridge.bukkit.CraftServerBridge;
45
import io.izzel.arclight.common.bridge.core.entity.EntityBridge;
56
import io.izzel.arclight.common.bridge.core.entity.player.ServerPlayerEntityBridge;
67
import io.izzel.arclight.common.bridge.core.inventory.IInventoryBridge;
8+
import io.izzel.arclight.common.bridge.core.server.MinecraftServerBridge;
79
import io.izzel.arclight.common.bridge.core.world.ExplosionBridge;
810
import io.izzel.arclight.common.bridge.core.world.server.ServerChunkProviderBridge;
911
import io.izzel.arclight.common.bridge.core.world.server.ServerWorldBridge;
@@ -45,27 +47,28 @@
4547
import net.minecraft.world.level.CustomSpawner;
4648
import net.minecraft.world.level.Explosion;
4749
import net.minecraft.world.level.Level;
48-
import net.minecraft.world.level.LevelAccessor;
50+
import net.minecraft.world.level.biome.BiomeSource;
4951
import net.minecraft.world.level.block.Block;
5052
import net.minecraft.world.level.block.entity.BlockEntity;
5153
import net.minecraft.world.level.block.state.BlockState;
54+
import net.minecraft.world.level.chunk.ChunkGenerator;
5255
import net.minecraft.world.level.chunk.LevelChunk;
5356
import net.minecraft.world.level.dimension.LevelStem;
5457
import net.minecraft.world.level.entity.PersistentEntitySectionManager;
5558
import net.minecraft.world.level.gameevent.GameEvent;
59+
import net.minecraft.world.level.levelgen.FlatLevelSource;
60+
import net.minecraft.world.level.levelgen.NoiseBasedChunkGenerator;
5661
import net.minecraft.world.level.saveddata.maps.MapId;
5762
import net.minecraft.world.level.saveddata.maps.MapItemSavedData;
58-
import net.minecraft.world.level.storage.DerivedLevelData;
59-
import net.minecraft.world.level.storage.DimensionDataStorage;
60-
import net.minecraft.world.level.storage.LevelStorageSource;
61-
import net.minecraft.world.level.storage.PrimaryLevelData;
62-
import net.minecraft.world.level.storage.ServerLevelData;
63+
import net.minecraft.world.level.storage.*;
6364
import net.minecraft.world.phys.Vec3;
6465
import org.bukkit.Bukkit;
6566
import org.bukkit.Location;
67+
import org.bukkit.World;
6668
import org.bukkit.craftbukkit.v.entity.CraftHumanEntity;
6769
import org.bukkit.craftbukkit.v.event.CraftEventFactory;
6870
import org.bukkit.craftbukkit.v.generator.CustomChunkGenerator;
71+
import org.bukkit.craftbukkit.v.generator.CustomWorldChunkManager;
6972
import org.bukkit.craftbukkit.v.util.CraftNamespacedKey;
7073
import org.bukkit.craftbukkit.v.util.WorldUUID;
7174
import org.bukkit.entity.HumanEntity;
@@ -124,32 +127,75 @@ public ResourceKey<LevelStem> getTypeKey() {
124127
return this.typeKey;
125128
}
126129

127-
// Multiworld support: per-world seed storage
128-
@Inject(method = "getSeed", at = @At("HEAD"), cancellable = true)
129-
private void arclight$getSeed(CallbackInfoReturnable<Long> cir) {
130-
if (K != null) {
131-
cir.setReturnValue(K.worldGenOptions().seed());
132-
}
133-
}
134-
135130
@ShadowConstructor
136131
public void arclight$constructor(MinecraftServer minecraftServer, Executor backgroundExecutor, LevelStorageSource.LevelStorageAccess levelSave, ServerLevelData worldInfo, ResourceKey<Level> dimension, LevelStem levelStem, ChunkProgressListener statusListener, boolean isDebug, long seed, List<CustomSpawner> specialSpawners, boolean shouldBeTicking, RandomSequences seq) {
137132
throw new RuntimeException();
138133
}
139134

140135
@CreateConstructor
141-
public void arclight$constructor(MinecraftServer minecraftServer, Executor backgroundExecutor, LevelStorageSource.LevelStorageAccess levelSave, PrimaryLevelData worldInfo, ResourceKey<Level> dimension, LevelStem levelStem, ChunkProgressListener statusListener, boolean isDebug, long seed, List<CustomSpawner> specialSpawners, boolean shouldBeTicking, RandomSequences seq, org.bukkit.World.Environment env, org.bukkit.generator.ChunkGenerator gen, org.bukkit.generator.BiomeProvider biomeProvider) {
142-
arclight$constructor(minecraftServer, backgroundExecutor, levelSave, worldInfo, dimension, levelStem, statusListener, isDebug, seed, specialSpawners, shouldBeTicking, seq);
143-
this.generator = gen;
144-
this.environment = env;
145-
this.biomeProvider = biomeProvider;
146-
if (gen != null) {
147-
CustomChunkGenerator generator = new CustomChunkGenerator((ServerLevel) (Object) this, this.chunkSource.getGenerator(), gen);
148-
((ServerChunkProviderBridge) this.chunkSource).bridge$setChunkGenerator(generator);
149-
}
136+
public void arclight$constructor(MinecraftServer server, Executor backgroundExecutor, LevelStorageSource.LevelStorageAccess levelSave, PrimaryLevelData worldInfo, ResourceKey<Level> dimension, LevelStem levelStem, ChunkProgressListener statusListener, boolean isDebug, long seed, List<CustomSpawner> specialSpawners, boolean shouldBeTicking, RandomSequences seq, org.bukkit.World.Environment env, org.bukkit.generator.ChunkGenerator gen, org.bukkit.generator.BiomeProvider biomeProvider) {
137+
var craftBridge = (CraftServerBridge)(Object) ((MinecraftServerBridge) server).bridge$getServer();
138+
assert craftBridge != null; // Already checked in bridge
139+
// We have no way but store it somewhere and use a default value
140+
// in order to avoid having to pass them as arguments.
141+
craftBridge.bridge$offerGeneratorCache(worldInfo.getLevelName(), gen);
142+
craftBridge.bridge$offerBiomeProviderCache(worldInfo.getLevelName(), biomeProvider);
143+
// Wrap the
144+
arclight$constructor(server, backgroundExecutor, levelSave, worldInfo, dimension, levelStem, statusListener, isDebug, seed, specialSpawners, shouldBeTicking, seq);
150145
bridge$getWorld();
151146
}
152147

148+
// Support custom chunk generator; in consistency with CraftBukkit
149+
// The real part is inside ServerChunkCache, when initializing ChunkMap (in ctor),
150+
// where a generator state is created, which is later used for chunk generation.
151+
// Previously we didn't modify it before ChunkMap is created,
152+
// which in turn cause custom world generation from Bukkit failing to work.
153+
@Decorate(method = "<init>", at = @At(value = "INVOKE", target = "Lnet/minecraft/world/level/dimension/LevelStem;generator()Lnet/minecraft/world/level/chunk/ChunkGenerator;"))
154+
private ChunkGenerator arclight$initChunkGenerator(LevelStem instance, @Local(ordinal = -1) MinecraftServer server, @Local(ordinal = -1) ServerLevelData worldInfo) throws Throwable {
155+
// Pulling up world info init since level info is used when selecting ChunkGenerator.
156+
if (worldInfo instanceof PrimaryLevelData primary) {
157+
((WorldInfoBridge) primary).bridge$setWorld((ServerLevel) (Object) this);
158+
this.K = primary;
159+
} else {
160+
ArclightServer.LOGGER.warn("Level {} isn't initialized with PrimaryLevelData.", this.serverLevelData.getLevelName());
161+
// damn spigot again
162+
this.K = DelegateWorldInfo.wrap(worldInfo);
163+
}
164+
165+
var craftBridge = (CraftServerBridge) (Object) ((MinecraftServerBridge) server).bridge$getServer();
166+
this.biomeProvider = craftBridge.bridge$consumeBiomeProviderCache(worldInfo.getLevelName());
167+
this.generator = craftBridge.bridge$consumeGeneratorCache(worldInfo.getLevelName());
168+
169+
if (instance.type() == LevelStem.OVERWORLD) {
170+
this.environment = World.Environment.NORMAL;
171+
} else if (instance.type() == LevelStem.NETHER) {
172+
this.environment = World.Environment.NETHER;
173+
} else if (instance.type() == LevelStem.END) {
174+
this.environment = World.Environment.THE_END;
175+
} else {
176+
this.environment = World.Environment.CUSTOM;
177+
}
178+
// Data needed by getWorld() are all initialized for possible creating CraftWorld.
179+
// CraftBukkit start: select custom chunk generator
180+
ChunkGenerator raw = (ChunkGenerator) DecorationOps.callsite().invoke(instance);
181+
if (biomeProvider != null) {
182+
BiomeSource biomeSource = new CustomWorldChunkManager(getWorld(), biomeProvider, getServer().registryAccess().registryOrThrow(Registries.BIOME));
183+
if (raw instanceof NoiseBasedChunkGenerator noise) {
184+
raw = new NoiseBasedChunkGenerator(biomeSource, noise.settings);
185+
} else if (raw instanceof FlatLevelSource flat) {
186+
ArclightServer.LOGGER.warn("Level {} is flat -- requested biome provider won't be satisfied.", this.serverLevelData.getLevelName());
187+
raw = new FlatLevelSource(flat.settings());
188+
} else {
189+
ArclightServer.LOGGER.warn("Level {} has customized generator -- requested biome provider won't be satisfied.", this.serverLevelData.getLevelName());
190+
}
191+
}
192+
if (generator != null) {
193+
raw = new CustomChunkGenerator((ServerLevel)(Object) this, raw, generator);
194+
}
195+
// CraftBukkit end
196+
return raw;
197+
}
198+
153199
@Inject(method = "<init>", at = @At("RETURN"))
154200
private void arclight$init(MinecraftServer minecraftServer, Executor backgroundExecutor, LevelStorageSource.LevelStorageAccess levelSave, ServerLevelData worldInfo, ResourceKey<Level> dimension, LevelStem levelStem, ChunkProgressListener statusListener, boolean isDebug, long seed, List<CustomSpawner> specialSpawners, boolean shouldBeTicking, RandomSequences seq, CallbackInfo ci) {
155201
this.pvpMode = minecraftServer.isPvpAllowed();
@@ -166,12 +212,6 @@ public ResourceKey<LevelStem> getTypeKey() {
166212
ArclightServer.LOGGER.warn("Assign {} to unknown level stem {}", dimension.location(), levelStem);
167213
this.typeKey = ResourceKey.create(Registries.LEVEL_STEM, dimension.location());
168214
}
169-
}
170-
if (worldInfo instanceof PrimaryLevelData data) {
171-
this.K = data;
172-
} else {
173-
// damn spigot again
174-
this.K = DelegateWorldInfo.wrap(worldInfo);
175215
if (worldInfo instanceof DerivedLevelData data) {
176216
((DerivedWorldInfoBridge) worldInfo).bridge$setDimType(this.getTypeKey());
177217
if (ArclightConfig.spec().getCompat().isSymlinkWorld()) {
@@ -182,11 +222,20 @@ public ResourceKey<LevelStem> getTypeKey() {
182222
this.spigotConfig = new SpigotWorldConfig(worldInfo.getLevelName());
183223
this.uuid = WorldUUID.getUUID(levelSave.getDimensionPath(this.dimension()).toFile());
184224
((ServerChunkProviderBridge) this.chunkSource).bridge$setViewDistance(spigotConfig.viewDistance);
225+
((ServerChunkProviderBridge) this.chunkSource).bridge$setSimulationDistance(spigotConfig.simulationDistance);
185226
((WorldInfoBridge) this.K).bridge$setWorld((ServerLevel) (Object) this);
186227
var data = this.getDataStorage().computeIfAbsent(LevelPersistentData.factory(), "bukkit_pdc");
187228
this.bridge$getWorld().readBukkitValues(data.getTag());
188229
}
189230

231+
// Multiworld support: per-world seed storage
232+
@Inject(method = "getSeed", at = @At("HEAD"), cancellable = true)
233+
private void arclight$getSeed(CallbackInfoReturnable<Long> cir) {
234+
if (K != null) {
235+
cir.setReturnValue(K.worldGenOptions().seed());
236+
}
237+
}
238+
190239
@Inject(method = "saveLevelData", at = @At("RETURN"))
191240
private void arclight$savePdc(CallbackInfo ci) {
192241
var data = this.getDataStorage().computeIfAbsent(LevelPersistentData.factory(), "bukkit_pdc");

0 commit comments

Comments
 (0)