implement NMS chunk generator on bukkit

This commit is contained in:
dfsek 2022-05-29 18:35:38 -07:00
parent e94d90050a
commit ef1f2e882d
11 changed files with 527 additions and 45 deletions

View File

@ -23,6 +23,7 @@ dependencies {
because("Minecraft 1.17+ includes slf4j 1.8.0-beta4, so we need to shade it for other versions.")
}
compileOnly("io.papermc.paper:paper-api:1.18.2-R0.1-20220519.005047-123")
compileOnly(group = "org.spigotmc", name = "spigot", version = "1.18.2-R0.1-SNAPSHOT")
shadedApi("io.papermc", "paperlib", Versions.Bukkit.paperLib)

View File

@ -0,0 +1,48 @@
package com.dfsek.terra.bukkit;
import ca.solostudios.strata.Versions;
import ca.solostudios.strata.version.Version;
import com.dfsek.terra.api.addon.BaseAddon;
import com.dfsek.terra.api.event.events.config.ConfigurationLoadEvent;
import com.dfsek.terra.api.event.functional.FunctionalEventHandler;
import com.dfsek.terra.api.world.biome.Biome;
import com.dfsek.terra.bukkit.config.VanillaBiomeProperties;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class BukkitAddon implements BaseAddon {
private static final Version VERSION = Versions.getVersion(1, 0, 0);
private final PlatformImpl terraBukkitPlugin;
public BukkitAddon(PlatformImpl terraBukkitPlugin) {
this.terraBukkitPlugin = terraBukkitPlugin;
}
@Override
public void initialize() {
terraBukkitPlugin.getEventManager()
.getHandler(FunctionalEventHandler.class)
.register(this, ConfigurationLoadEvent.class)
.then(event -> {
if(event.is(Biome.class)) {
event.getLoadedObject(Biome.class).getContext().put(event.load(new VanillaBiomeProperties()));
}
})
.global();
}
@Override
public Version getVersion() {
return VERSION;
}
@Override
public String getID() {
return "terra-bukkit";
}
}

View File

@ -20,6 +20,9 @@ package com.dfsek.terra.bukkit;
import com.dfsek.tectonic.api.TypeRegistry;
import com.dfsek.tectonic.api.depth.DepthTracker;
import com.dfsek.tectonic.api.exception.LoadException;
import com.dfsek.terra.api.addon.BaseAddon;
import org.bukkit.Bukkit;
import org.bukkit.entity.EntityType;
import org.jetbrains.annotations.NotNull;
@ -27,6 +30,7 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.File;
import java.util.List;
import java.util.Locale;
import com.dfsek.terra.AbstractPlatform;
@ -86,6 +90,11 @@ public class PlatformImpl extends AbstractPlatform {
Bukkit.getScheduler().runTask(plugin, task);
}
@Override
protected Iterable<BaseAddon> platformAddon() {
return List.of(new BukkitAddon(this));
}
@Override
public @NotNull WorldHandle getWorldHandle() {
return handle;

View File

@ -21,6 +21,10 @@ import cloud.commandframework.brigadier.CloudBrigadierManager;
import cloud.commandframework.bukkit.CloudBukkitCapabilities;
import cloud.commandframework.execution.CommandExecutionCoordinator;
import cloud.commandframework.paper.PaperCommandManager;
import com.dfsek.terra.bukkit.nms.NMSBiomeInjector;
import com.dfsek.terra.bukkit.nms.NMSInjectListener;
import org.bukkit.Bukkit;
import org.bukkit.generator.ChunkGenerator;
import org.bukkit.plugin.java.JavaPlugin;
@ -56,6 +60,7 @@ public class TerraBukkitPlugin extends JavaPlugin {
}
platform.getEventManager().callEvent(new PlatformInitializationEvent());
NMSBiomeInjector.registerBiomes(platform.getRawConfigRegistry());
try {
@ -89,6 +94,7 @@ public class TerraBukkitPlugin extends JavaPlugin {
}
Bukkit.getPluginManager().registerEvents(new CommonListener(), this); // Register master event listener
Bukkit.getPluginManager().registerEvents(new NMSInjectListener(), this); // Register master event listener
PaperUtil.checkPaper(this);
}

View File

@ -0,0 +1,58 @@
package com.dfsek.terra.bukkit.config;
import com.dfsek.tectonic.api.config.template.ConfigTemplate;
import com.dfsek.tectonic.api.config.template.annotations.Default;
import com.dfsek.tectonic.api.config.template.annotations.Value;
import com.dfsek.terra.api.properties.Properties;
public class VanillaBiomeProperties implements ConfigTemplate, Properties {
@Value("colors.grass")
@Default
private Integer grassColor = null;
@Value("colors.fog")
@Default
private Integer fogColor = null;
@Value("colors.water")
@Default
private Integer waterColor = null;
@Value("colors.water-fog")
@Default
private Integer waterFogColor = null;
@Value("colors.foliage")
@Default
private Integer foliageColor = null;
@Value("colors.sky")
@Default
private Integer skyColor = null;
public Integer getFogColor() {
return fogColor;
}
public Integer getFoliageColor() {
return foliageColor;
}
public Integer getGrassColor() {
return grassColor;
}
public Integer getWaterColor() {
return waterColor;
}
public Integer getWaterFogColor() {
return waterFogColor;
}
public Integer getSkyColor() {
return skyColor;
}
}

View File

@ -0,0 +1,137 @@
package com.dfsek.terra.bukkit.nms;
import com.dfsek.terra.api.config.ConfigPack;
import com.dfsek.terra.api.world.biome.Biome;
import com.dfsek.terra.bukkit.config.VanillaBiomeProperties;
import com.dfsek.terra.bukkit.world.BukkitPlatformBiome;
import com.dfsek.terra.registry.master.ConfigRegistry;
import com.mojang.serialization.Lifecycle;
import net.minecraft.core.Holder;
import net.minecraft.core.IRegistry;
import net.minecraft.core.IRegistryWritable;
import net.minecraft.core.RegistryMaterials;
import net.minecraft.data.RegistryGeneration;
import net.minecraft.resources.MinecraftKey;
import net.minecraft.resources.ResourceKey;
import net.minecraft.server.dedicated.DedicatedServer;
import net.minecraft.world.level.biome.BiomeBase;
import net.minecraft.world.level.biome.BiomeFog;
import net.minecraft.world.level.biome.BiomeFog.GrassColor;
import net.minecraft.world.level.biome.BiomeSettingsGeneration;
import net.minecraft.world.level.biome.BiomeSettingsMobs;
import org.bukkit.Bukkit;
import org.bukkit.NamespacedKey;
import org.bukkit.craftbukkit.v1_18_R2.CraftServer;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.lang.reflect.Field;
import java.util.Locale;
import java.util.Objects;
public class NMSBiomeInjector {
private static final Logger LOGGER = LoggerFactory.getLogger(NMSBiomeInjector.class);
public static void registerBiomes(ConfigRegistry configRegistry) {
CraftServer craftserver = (CraftServer) Bukkit.getServer();
DedicatedServer dedicatedserver = craftserver.getServer();
IRegistryWritable<BiomeBase> biomeRegistry = (IRegistryWritable<BiomeBase>) dedicatedserver
.aU() // getRegistryManager
.b( // getRegistry
IRegistry.aP // biome registry key
);
try {
LOGGER.info("Hacking biome registry...");
Field frozen = RegistryMaterials.class.getDeclaredField("bL"); // registry frozen field
frozen.setAccessible(true);
frozen.set(biomeRegistry, false);
configRegistry.forEach(pack -> pack.getRegistry(Biome.class).forEach((key, biome) -> {
try {
BukkitPlatformBiome platformBiome = (BukkitPlatformBiome) biome.getPlatformBiome();
NamespacedKey vanillaBukkitKey = platformBiome.getHandle().getKey();
BiomeBase platform = createBiome(
biome,
biomeRegistry.a(new MinecraftKey(vanillaBukkitKey.getNamespace(), vanillaBukkitKey.getKey()))
);
ResourceKey<BiomeBase> delegateKey = ResourceKey.a(IRegistry.aP, new MinecraftKey("terra", createBiomeID(pack, key)));
RegistryGeneration.a(RegistryGeneration.i, delegateKey, platform);
Holder<BiomeBase> resourceKey = biomeRegistry.a(delegateKey, platform, Lifecycle.stable());
platformBiome.setResourceKey(resourceKey);
LOGGER.info("Registered biome: " + delegateKey);
} catch(NoSuchFieldException | SecurityException | IllegalArgumentException | IllegalAccessException e) {
throw new RuntimeException(e);
}
}));
frozen.set(biomeRegistry, true); // freeze registry again :)
} catch(NoSuchFieldException | SecurityException | IllegalArgumentException | IllegalAccessException exception) {
throw new RuntimeException(exception);
}
}
private static BiomeBase createBiome(Biome biome, BiomeBase vanilla)
throws NoSuchFieldException, SecurityException, IllegalArgumentException, IllegalAccessException {
BiomeBase.a builder = new BiomeBase.a(); // Builder
Field f = BiomeBase.class.getDeclaredField("l"); // category
f.setAccessible(true);
builder.a((BiomeBase.Geography) f.get(vanilla))
.a(vanilla.c()); // getPrecipitation
Field biomeSettingMobsField = BiomeBase.class.getDeclaredField("k"); // spawn settings
biomeSettingMobsField.setAccessible(true);
BiomeSettingsMobs biomeSettingMobs = (BiomeSettingsMobs) biomeSettingMobsField.get(vanilla);
builder.a(biomeSettingMobs);
Field biomeSettingGenField = BiomeBase.class.getDeclaredField("j");
biomeSettingGenField.setAccessible(true);
BiomeSettingsGeneration biomeSettingGen = (BiomeSettingsGeneration) biomeSettingGenField.get(vanilla);
builder.a(biomeSettingGen)
.a(vanilla.c())
.b(vanilla.h()) // precipitation
.a(vanilla.i()); // temp
BiomeFog.a effects = new BiomeFog.a(); // Builder
effects.a(GrassColor.a); // magic
VanillaBiomeProperties vanillaBiomeProperties = biome.getContext().get(VanillaBiomeProperties.class);
// fog
effects.a(Objects.requireNonNullElse(vanillaBiomeProperties.getFogColor(), vanilla.f()));
// water
effects.b(Objects.requireNonNullElse(vanillaBiomeProperties.getWaterColor(), vanilla.k()));
// water fog
effects.c(Objects.requireNonNullElse(vanillaBiomeProperties.getWaterFogColor(), vanilla.l()));
// sky
effects.d(Objects.requireNonNullElse(vanillaBiomeProperties.getSkyColor(), vanilla.a()));
// foliage
effects.e(Objects.requireNonNullElse(vanillaBiomeProperties.getFoliageColor(), vanilla.g()));
// grass
effects.f(Objects.requireNonNullElse(vanillaBiomeProperties.getGrassColor(), vanilla.j().g().a(0, 0, 0)));
builder.a(effects.a()); // build()
return builder.a(); // build()
}
public static String createBiomeID(ConfigPack pack, com.dfsek.terra.api.registry.key.RegistryKey biomeID) {
return pack.getID()
.toLowerCase() + "/" + biomeID.getNamespace().toLowerCase(Locale.ROOT) + "/" + biomeID.getID().toLowerCase(Locale.ROOT);
}
}

View File

@ -0,0 +1,44 @@
package com.dfsek.terra.bukkit.nms;
import com.dfsek.terra.api.world.biome.generation.BiomeProvider;
import com.dfsek.terra.bukkit.world.BukkitPlatformBiome;
import com.mojang.serialization.Codec;
import net.minecraft.core.Holder;
import net.minecraft.world.level.biome.BiomeBase;
import net.minecraft.world.level.biome.Climate.Sampler;
import net.minecraft.world.level.biome.WorldChunkManager;
public class NMSBiomeProvider extends WorldChunkManager {
private final BiomeProvider delegate;
private final WorldChunkManager vanilla;
private final long seed;
public NMSBiomeProvider(BiomeProvider delegate, WorldChunkManager vanilla, long seed) {
super(vanilla.b().stream());
this.delegate = delegate;
this.vanilla = vanilla;
this.seed = seed;
}
@Override
protected Codec<? extends WorldChunkManager> a() {
return WorldChunkManager.a;
}
@Override
public WorldChunkManager a(long seed) {
return withSeed(seed);
}
public WorldChunkManager withSeed(long seed) {
return new NMSBiomeProvider(delegate, vanilla, seed);
}
@Override
public Holder<BiomeBase> getNoiseBiome(int x, int y, int z, Sampler sampler) {
return ((BukkitPlatformBiome) delegate.getBiome(x << 2, y << 2, z << 2, seed).getPlatformBiome()).getResourceKey();
}
}

View File

@ -0,0 +1,171 @@
package com.dfsek.terra.bukkit.nms;
import com.dfsek.terra.api.config.ConfigPack;
import com.dfsek.terra.api.util.generic.Construct;
import com.dfsek.terra.api.world.biome.generation.BiomeProvider;
import com.dfsek.terra.api.world.info.WorldProperties;
import com.dfsek.terra.bukkit.generator.BukkitProtoChunk;
import com.dfsek.terra.bukkit.world.BukkitAdapter;
import com.dfsek.terra.bukkit.world.BukkitServerWorld;
import com.mojang.serialization.Codec;
import net.minecraft.core.BlockPosition;
import net.minecraft.server.level.RegionLimitedWorldAccess;
import net.minecraft.world.level.BlockColumn;
import net.minecraft.world.level.ChunkCoordIntPair;
import net.minecraft.world.level.GeneratorAccessSeed;
import net.minecraft.world.level.LevelHeightAccessor;
import net.minecraft.world.level.StructureManager;
import net.minecraft.world.level.biome.BiomeManager;
import net.minecraft.world.level.biome.Climate.Sampler;
import net.minecraft.world.level.block.state.IBlockData;
import net.minecraft.world.level.chunk.ChunkGenerator;
import net.minecraft.world.level.chunk.IChunkAccess;
import net.minecraft.world.level.levelgen.ChunkGeneratorAbstract;
import net.minecraft.world.level.levelgen.HeightMap;
import net.minecraft.world.level.levelgen.WorldGenStage;
import net.minecraft.world.level.levelgen.blending.Blender;
import org.bukkit.craftbukkit.v1_18_R2.CraftWorld;
import org.bukkit.craftbukkit.v1_18_R2.block.data.CraftBlockData;
import org.bukkit.craftbukkit.v1_18_R2.generator.CraftChunkData;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.Executor;
public class NMSChunkGeneratorDelegate extends ChunkGenerator {
private final NMSBiomeProvider biomeSource;
private final com.dfsek.terra.api.world.chunk.generation.ChunkGenerator delegate;
private final ChunkGenerator vanilla;
private final ConfigPack pack;
private final CraftWorld world;
public NMSChunkGeneratorDelegate(ChunkGenerator vanilla, ConfigPack pack, NMSBiomeProvider biomeProvider, long seed,
CraftWorld world) {
super(
vanilla.b, // structure sets
vanilla.e, // structure overrides
biomeProvider); //Last arg is WorldChunkManager
this.delegate = pack.getGeneratorProvider().newInstance(pack);
this.vanilla = vanilla;
this.biomeSource = biomeProvider;
this.pack = pack;
this.world = world;
}
@Override //applyCarvers
public void a(RegionLimitedWorldAccess regionlimitedworldaccess, long var2, BiomeManager var4, StructureManager var5,
IChunkAccess ichunkaccess, WorldGenStage.Features var7) {
// no-op
}
@Override // getSeaLevel
public int g() {
return vanilla.g();
}
@Override //fillFromNoise
public CompletableFuture<IChunkAccess> a(Executor executor, Blender blender, StructureManager structuremanager,
IChunkAccess ichunkaccess) {
return CompletableFuture.supplyAsync(() -> {
BukkitServerWorld serverWorld = new BukkitServerWorld(world);
BiomeProvider biomeProvider = pack.getBiomeProvider().caching(serverWorld);
CraftChunkData chunkData = new CraftChunkData(this.world, ichunkaccess);
int x = ichunkaccess.f().c;
int z = ichunkaccess.f().d;
delegate.generateChunkData(new BukkitProtoChunk(chunkData), new BukkitServerWorld(world), biomeProvider, x, z);
return ichunkaccess;
}, executor);
}
// @SuppressWarnings("unchecked")
@Override //buildSurface. Used to be buildBase
public void a(RegionLimitedWorldAccess regionlimitedworldaccess, StructureManager structuremanager, IChunkAccess ichunkaccess) {
}
@Override
public void a(GeneratorAccessSeed gas, StructureManager manager, IChunkAccess ica) {
vanilla.a(gas, manager, ica);
}
@Override
protected Codec<? extends ChunkGenerator> b() {
return ChunkGeneratorAbstract.a;
}
@Override // getColumn
public BlockColumn a(int var0, int var1, LevelHeightAccessor var2) {
return vanilla.a(var0, var1, var2);
}
@Override // withSeed
public ChunkGenerator a(long seed) {
return new NMSChunkGeneratorDelegate(vanilla, pack, biomeSource, seed, world);
}
//spawnOriginalMobs
public void a(RegionLimitedWorldAccess regionlimitedworldaccess) {
vanilla.a(regionlimitedworldaccess);
}
// getGenDepth
public int f() {
return vanilla.f();
}
// climateSampler
public Sampler d() {
return vanilla.d();
}
//getMinY
@Override
public int h() {
return vanilla.h();
}
@Override //getFirstFreeHeight
public int b(int i, int j, HeightMap.Type heightmap_type, LevelHeightAccessor levelheightaccessor) {
return this.a(i, j, heightmap_type, levelheightaccessor);
}
@Override //getFirstOccupiedHeight
public int c(int i, int j, HeightMap.Type heightmap_type, LevelHeightAccessor levelheightaccessor) {
return this.a(i, j, heightmap_type, levelheightaccessor) - 1;
}
@Override // getBaseHeight
public int a(int x, int z, HeightMap.Type heightmap, LevelHeightAccessor height) {
int y = world.getMaxHeight();
WorldProperties properties = BukkitAdapter.adapt(world);
BiomeProvider biomeProvider = pack.getBiomeProvider().caching(properties);
while(y >= getMinimumY() && !heightmap.e().test(
((CraftBlockData) delegate.getBlock(properties, x, y - 1, z, biomeProvider).getHandle()).getState())) {
y--;
}
return y;
}
public int getMinimumY() {
return h();
}
@Override //addDebugScreenInfo
public void a(List<String> arg0, BlockPosition arg1) {
}
}

View File

@ -0,0 +1,41 @@
package com.dfsek.terra.bukkit.nms;
import com.dfsek.terra.api.config.ConfigPack;
import com.dfsek.terra.bukkit.generator.BukkitChunkGeneratorWrapper;
import net.minecraft.server.level.PlayerChunkMap;
import net.minecraft.server.level.WorldServer;
import net.minecraft.world.level.chunk.ChunkGenerator;
import org.bukkit.craftbukkit.v1_18_R2.CraftWorld;
import org.bukkit.event.EventHandler;
import org.bukkit.event.Listener;
import org.bukkit.event.world.WorldInitEvent;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class NMSInjectListener implements Listener {
private static final Logger LOGGER = LoggerFactory.getLogger(NMSInjectListener.class);
@EventHandler
public void onWorldInit(WorldInitEvent event) {
if (event.getWorld().getGenerator() instanceof BukkitChunkGeneratorWrapper bukkitChunkGeneratorWrapper) {
LOGGER.info("A great evil has fallen upon this land: {}", event.getWorld().getName());
CraftWorld craftWorld = (CraftWorld) event.getWorld();
WorldServer serverWorld = craftWorld.getHandle();
ConfigPack pack = bukkitChunkGeneratorWrapper.getPack();
ChunkGenerator vanilla = serverWorld.k().g();
NMSBiomeProvider provider = new NMSBiomeProvider(pack.getBiomeProvider(), vanilla.e(), craftWorld.getSeed());
NMSChunkGeneratorDelegate custom = new NMSChunkGeneratorDelegate(vanilla, pack, provider,
craftWorld.getSeed(), craftWorld);
custom.conf = vanilla.conf; // world config from Spigot
serverWorld.k().a.u = custom;
LOGGER.info("Successfully injected into world.");
}
}
}

View File

@ -1,45 +0,0 @@
/*
* This file is part of Terra.
*
* Terra is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Terra is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Terra. If not, see <https://www.gnu.org/licenses/>.
*/
package com.dfsek.terra.bukkit.structure;
import java.io.Serial;
public class WorldEditNotFoundException extends RuntimeException {
@Serial
private static final long serialVersionUID = 3678822468346338227L;
public WorldEditNotFoundException() {
}
public WorldEditNotFoundException(String message) {
super(message);
}
public WorldEditNotFoundException(String message, Throwable cause) {
super(message, cause);
}
public WorldEditNotFoundException(Throwable cause) {
super(cause);
}
public WorldEditNotFoundException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) {
super(message, cause, enableSuppression, writableStackTrace);
}
}

View File

@ -19,14 +19,26 @@ package com.dfsek.terra.bukkit.world;
import com.dfsek.terra.api.world.biome.PlatformBiome;
import net.minecraft.core.Holder;
import net.minecraft.world.level.biome.BiomeBase;
public class BukkitPlatformBiome implements PlatformBiome {
private final org.bukkit.block.Biome biome;
private Holder<BiomeBase> resourceKey;
public BukkitPlatformBiome(org.bukkit.block.Biome biome) {
this.biome = biome;
}
public void setResourceKey(Holder<BiomeBase> resourceKey) {
this.resourceKey = resourceKey;
}
public Holder<BiomeBase> getResourceKey() {
return resourceKey;
}
@Override
public org.bukkit.block.Biome getHandle() {
return biome;