mirror of
https://github.com/VolmitSoftware/Iris.git
synced 2025-12-24 17:49:16 +00:00
Merge branch 'mca' into merge
This commit is contained in:
@@ -1,3 +1,5 @@
|
||||
import java.util.function.Consumer
|
||||
|
||||
/*
|
||||
* Iris is a World Generator for Minecraft Bukkit Servers
|
||||
* Copyright (c) 2021 Arcane Arts (Volmit Software)
|
||||
@@ -212,6 +214,8 @@ shadowJar {
|
||||
dependsOn(":nms:${it.key}:build")
|
||||
from("${project(":nms:${it.key}").layout.buildDirectory.asFile.get()}/libs/${it.key}.jar")
|
||||
}
|
||||
NMS_BINDINGS.each {dependsOn(":nms:${it.key}:build")}
|
||||
//dependsOn(':com.volmit.gui:build')
|
||||
|
||||
//minimize()
|
||||
append("plugin.yml")
|
||||
@@ -280,6 +284,10 @@ allprojects {
|
||||
compileOnly 'rhino:js:1.7R2'
|
||||
compileOnly 'com.github.ben-manes.caffeine:caffeine:3.0.6'
|
||||
compileOnly 'org.apache.commons:commons-lang3:3.12.0'
|
||||
compileOnly 'net.bytebuddy:byte-buddy:1.14.14'
|
||||
compileOnly 'net.bytebuddy:byte-buddy-agent:1.12.8'
|
||||
compileOnly 'org.bytedeco:javacpp:1.5.10'
|
||||
compileOnly 'org.bytedeco:cuda-platform:12.3-8.9-1.5.10'
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -64,12 +64,10 @@ import com.volmit.iris.util.scheduling.Queue;
|
||||
import com.volmit.iris.util.scheduling.ShurikenQueue;
|
||||
import io.papermc.lib.PaperLib;
|
||||
import lombok.Getter;
|
||||
import net.bytebuddy.agent.ByteBuddyAgent;
|
||||
import net.kyori.adventure.platform.bukkit.BukkitAudiences;
|
||||
import net.kyori.adventure.text.serializer.ComponentSerializer;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.GameMode;
|
||||
import org.bukkit.Location;
|
||||
import org.bukkit.WorldCreator;
|
||||
import org.bukkit.*;
|
||||
import org.bukkit.block.data.BlockData;
|
||||
import org.bukkit.command.Command;
|
||||
import org.bukkit.command.CommandSender;
|
||||
@@ -94,12 +92,14 @@ import java.lang.management.OperatingSystemMXBean;
|
||||
import java.net.URL;
|
||||
import java.util.Date;
|
||||
import java.util.Map;
|
||||
import java.util.Properties;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import static com.volmit.iris.core.safeguard.IrisSafeguard.*;
|
||||
import static com.volmit.iris.core.safeguard.ServerBootSFG.passedserversoftware;
|
||||
import static com.volmit.iris.util.misc.getHardware.getCPUModel;
|
||||
import static org.bukkit.Bukkit.getServer;
|
||||
|
||||
@SuppressWarnings("CanBeFinal")
|
||||
public class Iris extends VolmitPlugin implements Listener {
|
||||
@@ -456,36 +456,45 @@ public class Iris extends VolmitPlugin implements Listener {
|
||||
}
|
||||
private void enable() {
|
||||
instance = this;
|
||||
InitializeSafeguard();
|
||||
ByteBuddyAgent.install();
|
||||
services = new KMap<>();
|
||||
setupAudience();
|
||||
initialize("com.volmit.iris.core.service").forEach((i) -> services.put((Class<? extends IrisService>) i.getClass(), (IrisService) i));
|
||||
INMS.get();
|
||||
IO.delete(new File("iris"));
|
||||
IrisSafeguard.IrisSafeguardSystem();
|
||||
IrisSafeguard.instance.IrisSafeguardSystem();
|
||||
getSender().setTag(getTag());
|
||||
INMS.get().injectBukkit();
|
||||
if (IrisSafeguard.instance.unstablemode && !IrisSafeguard.instance.acceptUnstable) IrisSafeguard.instance.earlySplash();
|
||||
compat = IrisCompat.configured(getDataFile("compat.json"));
|
||||
linkMultiverseCore = new MultiverseCoreLink();
|
||||
linkMythicMobs = new MythicMobsLink();
|
||||
configWatcher = new FileWatcher(getDataFile("settings.json"));
|
||||
services.values().forEach(IrisService::onEnable);
|
||||
services.values().forEach(this::registerListener);
|
||||
J.s(() -> {
|
||||
J.a(() -> PaperLib.suggestPaper(this));
|
||||
J.a(() -> IO.delete(getTemp()));
|
||||
J.a(LazyPregenerator::loadLazyGenerators, 100);
|
||||
J.a(this::bstats);
|
||||
J.ar(this::checkConfigHotload, 60);
|
||||
J.sr(this::tickQueue, 0);
|
||||
J.s(this::setupPapi);
|
||||
J.a(ServerConfigurator::configure, 20);
|
||||
splash();
|
||||
UtilsSFG.splash();
|
||||
|
||||
autoStartStudio();
|
||||
checkForBukkitWorlds();
|
||||
IrisToolbelt.retainMantleDataForSlice(String.class.getCanonicalName());
|
||||
IrisToolbelt.retainMantleDataForSlice(BlockData.class.getCanonicalName());
|
||||
});
|
||||
installMainDimension();
|
||||
if (!IrisSafeguard.instance.acceptUnstable && IrisSafeguard.instance.unstablemode) {
|
||||
Iris.info(C.RED + "World loading has been disabled until the incompatibility is resolved.");
|
||||
Iris.info(C.DARK_RED + "Alternatively, go to plugins/iris/settings.json and set ignoreBootMode to true.");
|
||||
} else {
|
||||
J.s(() -> {
|
||||
J.a(() -> PaperLib.suggestPaper(this));
|
||||
J.a(() -> IO.delete(getTemp()));
|
||||
J.a(LazyPregenerator::loadLazyGenerators, 100);
|
||||
J.a(this::bstats);
|
||||
J.ar(this::checkConfigHotload, 60);
|
||||
J.sr(this::tickQueue, 0);
|
||||
J.s(this::setupPapi);
|
||||
J.a(ServerConfigurator::configure, 20);
|
||||
splash();
|
||||
UtilsSFG.splash();
|
||||
autoStartStudio();
|
||||
checkForBukkitWorlds();
|
||||
IrisToolbelt.retainMantleDataForSlice(String.class.getCanonicalName());
|
||||
IrisToolbelt.retainMantleDataForSlice(BlockData.class.getCanonicalName());
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private void checkForBukkitWorlds() {
|
||||
@@ -599,10 +608,10 @@ public class Iris extends VolmitPlugin implements Listener {
|
||||
|
||||
@Override
|
||||
public String getTag(String subTag) {
|
||||
if (unstablemode) {
|
||||
if (IrisSafeguard.instance.unstablemode) {
|
||||
return C.BOLD + "" + C.DARK_GRAY + "[" + C.BOLD + "" + C.RED + "Iris" + C.BOLD + C.DARK_GRAY + "]" + C.RESET + "" + C.GRAY + ": ";
|
||||
}
|
||||
if (warningmode) {
|
||||
if (IrisSafeguard.instance.warningmode) {
|
||||
return C.BOLD + "" + C.DARK_GRAY + "[" + C.BOLD + "" + C.GOLD + "Iris" + C.BOLD + C.DARK_GRAY + "]" + C.RESET + "" + C.GRAY + ": ";
|
||||
}
|
||||
return C.BOLD + "" + C.DARK_GRAY + "[" + C.BOLD + "" + C.IRIS + "Iris" + C.BOLD + C.DARK_GRAY + "]" + C.RESET + "" + C.GRAY + ": ";
|
||||
@@ -611,7 +620,7 @@ public class Iris extends VolmitPlugin implements Listener {
|
||||
|
||||
private boolean setupChecks() {
|
||||
boolean passed = true;
|
||||
Iris.info("Version Information: " + instance.getServer().getVersion() + " | " + instance.getServer().getBukkitVersion());
|
||||
Iris.info("Server type & version: " + instance.getServer().getVersion() + " | " + instance.getServer().getBukkitVersion());
|
||||
if (INMS.get() instanceof NMSBinding1X) {
|
||||
passed = false;
|
||||
Iris.warn("============================================");
|
||||
@@ -677,6 +686,48 @@ public class Iris extends VolmitPlugin implements Listener {
|
||||
s.sendMessage(C.IRIS + "[" + C.DARK_GRAY + "Iris" + C.IRIS + "]" + C.GRAY + ": " + msg);
|
||||
}
|
||||
|
||||
private void installMainDimension() {
|
||||
try {
|
||||
Properties props = new Properties();
|
||||
props.load(new FileInputStream("server.properties"));
|
||||
String world = props.getProperty("level-name");
|
||||
if (world == null) return;
|
||||
|
||||
FileConfiguration fc = new YamlConfiguration();
|
||||
fc.load(new File("bukkit.yml"));
|
||||
String id = fc.getString("worlds." + world + ".generator");
|
||||
if (id.startsWith("Iris:")) {
|
||||
id = id.split("\\Q:\\E")[1];
|
||||
} else if (id.equalsIgnoreCase("Iris")) {
|
||||
id = IrisSettings.get().getGenerator().getDefaultWorldType();
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
|
||||
IrisDimension dim;
|
||||
if (id == null || id.isEmpty()) {
|
||||
dim = IrisData.loadAnyDimension(IrisSettings.get().getGenerator().getDefaultWorldType());
|
||||
} else {
|
||||
dim = IrisData.loadAnyDimension(id);
|
||||
}
|
||||
|
||||
File w = new File(Bukkit.getWorldContainer(), world);
|
||||
File packFolder = new File(w, "/iris/pack");
|
||||
if (!packFolder.exists() || packFolder.listFiles().length == 0) {
|
||||
packFolder.mkdirs();
|
||||
service(StudioSVC.class).installIntoWorld(getSender(), dim.getLoadKey(), w);
|
||||
}
|
||||
if (packFolder.exists()) {
|
||||
IrisDimension worldDim = IrisData.get(packFolder).getDimensionLoader().load(id);
|
||||
if (worldDim != null) dim = worldDim;
|
||||
}
|
||||
|
||||
INMS.get().registerDimension("overworld", dim);
|
||||
} catch (Throwable e) {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public BiomeProvider getDefaultBiomeProvider(@NotNull String worldName, @Nullable String id) {
|
||||
@@ -719,6 +770,11 @@ public class Iris extends VolmitPlugin implements Listener {
|
||||
Iris.info("Resolved missing dimension, proceeding with generation.");
|
||||
}
|
||||
}
|
||||
File packFolder = new File(Bukkit.getWorldContainer(), worldName + "/iris/pack");
|
||||
if (packFolder.exists()) {
|
||||
IrisDimension worldDim = IrisData.get(packFolder).getDimensionLoader().load(id);
|
||||
if (worldDim != null) dim = worldDim;
|
||||
}
|
||||
|
||||
Iris.debug("Assuming IrisDimension: " + dim.getName());
|
||||
|
||||
@@ -738,6 +794,9 @@ public class Iris extends VolmitPlugin implements Listener {
|
||||
ff.mkdirs();
|
||||
service(StudioSVC.class).installIntoWorld(getSender(), dim.getLoadKey(), w.worldFolder());
|
||||
}
|
||||
if (!INMS.get().registerDimension(worldName, dim)) {
|
||||
throw new IllegalStateException("Unable to register dimension " + dim.getName());
|
||||
}
|
||||
|
||||
return new BukkitChunkGenerator(w, false, ff, dim.getLoadKey(), false);
|
||||
}
|
||||
@@ -750,10 +809,10 @@ public class Iris extends VolmitPlugin implements Listener {
|
||||
String padd = Form.repeat(" ", 8);
|
||||
String padd2 = Form.repeat(" ", 4);
|
||||
String[] info = {"", "", "", "", "", padd2 + C.IRIS + " Iris", padd2 + C.GRAY + " by " + "<rainbow>Volmit Software", padd2 + C.GRAY + " v" + C.IRIS + getDescription().getVersion()};
|
||||
if (unstablemode) {
|
||||
if (IrisSafeguard.instance.unstablemode) {
|
||||
info = new String[]{"", "", "", "", "", padd2 + C.RED + " Iris", padd2 + C.GRAY + " by " + C.DARK_RED + "Volmit Software", padd2 + C.GRAY + " v" + C.RED + getDescription().getVersion()};
|
||||
}
|
||||
if (warningmode) {
|
||||
if (IrisSafeguard.instance.warningmode) {
|
||||
info = new String[]{"", "", "", "", "", padd2 + C.GOLD + " Iris", padd2 + C.GRAY + " by " + C.GOLD + "Volmit Software", padd2 + C.GRAY + " v" + C.GOLD + getDescription().getVersion()};
|
||||
}
|
||||
|
||||
@@ -798,18 +857,15 @@ public class Iris extends VolmitPlugin implements Listener {
|
||||
padd + C.GRAY + " " + C.DARK_GRAY + "@@@" + C.GRAY + "@@@@@@@@@@@@@@"
|
||||
};
|
||||
String[] splash;
|
||||
File freeSpace = new File(Bukkit.getWorldContainer() + ".");
|
||||
if (unstablemode) {
|
||||
if (IrisSafeguard.instance.unstablemode) {
|
||||
splash = splashunstable;
|
||||
} else if (warningmode) {
|
||||
} else if (IrisSafeguard.instance.warningmode) {
|
||||
splash = splashwarning;
|
||||
} else {
|
||||
splash = splashstable;
|
||||
}
|
||||
|
||||
if (!passedserversoftware) {
|
||||
Iris.info("Server type & version: " + C.RED + Bukkit.getVersion());
|
||||
} else { Iris.info("Server type & version: " + Bukkit.getVersion()); }
|
||||
setupChecks();
|
||||
Iris.info("Java: " + getJava());
|
||||
if (!instance.getServer().getVersion().contains("Purpur")) {
|
||||
if (instance.getServer().getVersion().contains("Spigot") && instance.getServer().getVersion().contains("Bukkit")) {
|
||||
@@ -824,7 +880,6 @@ public class Iris extends VolmitPlugin implements Listener {
|
||||
}
|
||||
Iris.info("Bukkit distro: " + Bukkit.getName());
|
||||
Iris.info("Custom Biomes: " + INMS.get().countCustomBiomes());
|
||||
setupChecks();
|
||||
printPacks();
|
||||
|
||||
for (int i = 0; i < info.length; i++) {
|
||||
|
||||
@@ -34,6 +34,7 @@ import java.io.IOException;
|
||||
@Data
|
||||
public class IrisSettings {
|
||||
public static IrisSettings settings;
|
||||
private IrisSafeGuard safeguard = new IrisSafeGuard();
|
||||
private IrisSettingsGeneral general = new IrisSettingsGeneral();
|
||||
private IrisSettingsWorld world = new IrisSettingsWorld();
|
||||
private IrisSettingsGUI gui = new IrisSettingsGUI();
|
||||
@@ -42,6 +43,7 @@ public class IrisSettings {
|
||||
private IrisSettingsConcurrency concurrency = new IrisSettingsConcurrency();
|
||||
private IrisSettingsStudio studio = new IrisSettingsStudio();
|
||||
private IrisSettingsPerformance performance = new IrisSettingsPerformance();
|
||||
private IrisWorldDump worldDump = new IrisWorldDump();
|
||||
|
||||
public static int getThreadCount(int c) {
|
||||
return switch (c) {
|
||||
@@ -102,6 +104,12 @@ public class IrisSettings {
|
||||
}
|
||||
}
|
||||
|
||||
@Data
|
||||
public static class IrisSafeGuard {
|
||||
public boolean ignoreBootMode = false;
|
||||
public boolean userUnstableWarning = true;
|
||||
}
|
||||
|
||||
@Data
|
||||
public static class IrisSettingsAutoconfiguration {
|
||||
public boolean configureSpigotTimeoutTime = true;
|
||||
@@ -146,7 +154,6 @@ public class IrisSettings {
|
||||
|
||||
@Data
|
||||
public static class IrisSettingsGeneral {
|
||||
public boolean ignoreBootMode = false;
|
||||
public boolean commandSounds = true;
|
||||
public boolean debug = false;
|
||||
public boolean disableNMS = false;
|
||||
@@ -187,4 +194,8 @@ public class IrisSettings {
|
||||
public boolean disableTimeAndWeather = true;
|
||||
public boolean autoStartDefaultStudio = false;
|
||||
}
|
||||
@Data
|
||||
public static class IrisWorldDump {
|
||||
public int mcaCacheSize = 3;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,28 +19,13 @@
|
||||
package com.volmit.iris.core;
|
||||
|
||||
import com.volmit.iris.Iris;
|
||||
import com.volmit.iris.core.loader.IrisData;
|
||||
import com.volmit.iris.core.nms.INMS;
|
||||
import com.volmit.iris.core.nms.datapack.DataVersion;
|
||||
import com.volmit.iris.core.nms.datapack.IDataFixer;
|
||||
import com.volmit.iris.engine.object.IrisBiome;
|
||||
import com.volmit.iris.engine.object.IrisBiomeCustom;
|
||||
import com.volmit.iris.engine.object.IrisDimension;
|
||||
import com.volmit.iris.engine.object.IrisRange;
|
||||
import com.volmit.iris.util.collection.KList;
|
||||
import com.volmit.iris.util.collection.KSet;
|
||||
import com.volmit.iris.util.format.C;
|
||||
import com.volmit.iris.util.plugin.VolmitSender;
|
||||
import com.volmit.iris.util.scheduling.J;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.configuration.InvalidConfigurationException;
|
||||
import org.bukkit.configuration.file.FileConfiguration;
|
||||
import org.bukkit.configuration.file.YamlConfiguration;
|
||||
import org.bukkit.entity.Player;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
public class ServerConfigurator {
|
||||
@@ -53,12 +38,10 @@ public class ServerConfigurator {
|
||||
if (s.isConfigurePaperWatchdogDelay()) {
|
||||
J.attempt(ServerConfigurator::increasePaperWatchdog);
|
||||
}
|
||||
|
||||
installDataPacks(true);
|
||||
}
|
||||
|
||||
private static void increaseKeepAliveSpigot() throws IOException, InvalidConfigurationException {
|
||||
File spigotConfig = new File("config/spigot.yml");
|
||||
File spigotConfig = new File("spigot.yml");
|
||||
FileConfiguration f = new YamlConfiguration();
|
||||
f.load(spigotConfig);
|
||||
long tt = f.getLong("settings.timeout-time");
|
||||
@@ -70,6 +53,7 @@ public class ServerConfigurator {
|
||||
f.save(spigotConfig);
|
||||
}
|
||||
}
|
||||
|
||||
private static void increasePaperWatchdog() throws IOException, InvalidConfigurationException {
|
||||
File spigotConfig = new File("config/paper-global.yml");
|
||||
FileConfiguration f = new YamlConfiguration();
|
||||
@@ -83,187 +67,4 @@ public class ServerConfigurator {
|
||||
f.save(spigotConfig);
|
||||
}
|
||||
}
|
||||
|
||||
private static List<File> getDatapacksFolder() {
|
||||
if (!IrisSettings.get().getGeneral().forceMainWorld.isEmpty()) {
|
||||
return new KList<File>().qadd(new File(Bukkit.getWorldContainer(), IrisSettings.get().getGeneral().forceMainWorld + "/datapacks"));
|
||||
}
|
||||
KList<File> worlds = new KList<>();
|
||||
Bukkit.getServer().getWorlds().forEach(w -> worlds.add(new File(w.getWorldFolder(), "datapacks")));
|
||||
return worlds;
|
||||
}
|
||||
|
||||
public static void installDataPacks(boolean fullInstall) {
|
||||
installDataPacks(DataVersion.getDefault(), fullInstall);
|
||||
}
|
||||
|
||||
public static void installDataPacks(IDataFixer fixer, boolean fullInstall) {
|
||||
Iris.info("Checking Data Packs...");
|
||||
File packs = new File("plugins/Iris/packs");
|
||||
double ultimateMaxHeight = 0;
|
||||
double ultimateMinHeight = 0;
|
||||
if (packs.exists() && packs.isDirectory()) {
|
||||
for (File pack : packs.listFiles()) {
|
||||
IrisData data = IrisData.get(pack);
|
||||
if (pack.isDirectory()) {
|
||||
File dimensionsFolder = new File(pack, "dimensions");
|
||||
if (dimensionsFolder.exists() && dimensionsFolder.isDirectory()) {
|
||||
for (File file : dimensionsFolder.listFiles()) {
|
||||
if (file.isFile() && file.getName().endsWith(".json")) {
|
||||
IrisDimension dim = data.getDimensionLoader().load(file.getName().split("\\Q.\\E")[0]);
|
||||
if (ultimateMaxHeight < dim.getDimensionHeight().getMax()) {
|
||||
ultimateMaxHeight = dim.getDimensionHeight().getMax();
|
||||
}
|
||||
if (ultimateMinHeight > dim.getDimensionHeight().getMin()) {
|
||||
ultimateMinHeight = dim.getDimensionHeight().getMin();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (packs.exists()) {
|
||||
for (File i : packs.listFiles()) {
|
||||
if (i.isDirectory()) {
|
||||
Iris.verbose("Checking Pack: " + i.getPath());
|
||||
IrisData data = IrisData.get(i);
|
||||
File dims = new File(i, "dimensions");
|
||||
|
||||
if (dims.exists()) {
|
||||
for (File j : dims.listFiles()) {
|
||||
if (j.getName().endsWith(".json")) {
|
||||
IrisDimension dim = data.getDimensionLoader().load(j.getName().split("\\Q.\\E")[0]);
|
||||
|
||||
if (dim == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
Iris.verbose(" Checking Dimension " + dim.getLoadFile().getPath());
|
||||
for (File dpack : getDatapacksFolder()) {
|
||||
dim.installDataPack(fixer, () -> data, dpack, ultimateMaxHeight, ultimateMinHeight);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Iris.info("Data Packs Setup!");
|
||||
|
||||
if (fullInstall)
|
||||
verifyDataPacksPost(IrisSettings.get().getAutoConfiguration().isAutoRestartOnCustomBiomeInstall());
|
||||
}
|
||||
|
||||
private static void verifyDataPacksPost(boolean allowRestarting) {
|
||||
File packs = new File("plugins/Iris/packs");
|
||||
|
||||
boolean bad = false;
|
||||
if (packs.exists()) {
|
||||
for (File i : packs.listFiles()) {
|
||||
if (i.isDirectory()) {
|
||||
Iris.verbose("Checking Pack: " + i.getPath());
|
||||
IrisData data = IrisData.get(i);
|
||||
File dims = new File(i, "dimensions");
|
||||
|
||||
if (dims.exists()) {
|
||||
for (File j : dims.listFiles()) {
|
||||
if (j.getName().endsWith(".json")) {
|
||||
IrisDimension dim = data.getDimensionLoader().load(j.getName().split("\\Q.\\E")[0]);
|
||||
|
||||
if (dim == null) {
|
||||
Iris.error("Failed to load " + j.getPath() + " ");
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!verifyDataPackInstalled(dim)) {
|
||||
bad = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (bad) {
|
||||
if (allowRestarting) {
|
||||
restart();
|
||||
} else if (INMS.get().supportsDataPacks()) {
|
||||
Iris.error("============================================================================");
|
||||
Iris.error(C.ITALIC + "You need to restart your server to properly generate custom biomes.");
|
||||
Iris.error(C.ITALIC + "By continuing, Iris will use backup biomes in place of the custom biomes.");
|
||||
Iris.error("----------------------------------------------------------------------------");
|
||||
Iris.error(C.UNDERLINE + "IT IS HIGHLY RECOMMENDED YOU RESTART THE SERVER BEFORE GENERATING!");
|
||||
Iris.error("============================================================================");
|
||||
|
||||
for (Player i : Bukkit.getOnlinePlayers()) {
|
||||
if (i.isOp() || i.hasPermission("iris.all")) {
|
||||
VolmitSender sender = new VolmitSender(i, Iris.instance.getTag("WARNING"));
|
||||
sender.sendMessage("There are some Iris Packs that have custom biomes in them");
|
||||
sender.sendMessage("You need to restart your server to use these packs.");
|
||||
}
|
||||
}
|
||||
|
||||
J.sleep(3000);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static void restart() {
|
||||
J.s(() -> {
|
||||
Iris.warn("New data pack entries have been installed in Iris! Restarting server!");
|
||||
Iris.warn("This will only happen when your pack changes (updates/first time setup)");
|
||||
Iris.warn("(You can disable this auto restart in iris settings)");
|
||||
J.s(() -> {
|
||||
Iris.warn("Looks like the restart command diddn't work. Stopping the server instead!");
|
||||
Bukkit.shutdown();
|
||||
}, 100);
|
||||
Bukkit.dispatchCommand(Bukkit.getConsoleSender(), "restart");
|
||||
});
|
||||
}
|
||||
|
||||
public static boolean verifyDataPackInstalled(IrisDimension dimension) {
|
||||
IrisData idm = IrisData.get(Iris.instance.getDataFolder("packs", dimension.getLoadKey()));
|
||||
KSet<String> keys = new KSet<>();
|
||||
boolean warn = false;
|
||||
|
||||
for (IrisBiome i : dimension.getAllBiomes(() -> idm)) {
|
||||
if (i.isCustom()) {
|
||||
for (IrisBiomeCustom j : i.getCustomDerivitives()) {
|
||||
keys.add(dimension.getLoadKey() + ":" + j.getId());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!INMS.get().supportsDataPacks()) {
|
||||
if (!keys.isEmpty()) {
|
||||
Iris.warn("===================================================================================");
|
||||
Iris.warn("Pack " + dimension.getLoadKey() + " has " + keys.size() + " custom biome(s). ");
|
||||
Iris.warn("Your server version does not yet support datapacks for iris.");
|
||||
Iris.warn("The world will generate these biomes as backup biomes.");
|
||||
Iris.warn("====================================================================================");
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
for (String i : keys) {
|
||||
Object o = INMS.get().getCustomBiomeBaseFor(i);
|
||||
|
||||
if (o == null) {
|
||||
Iris.warn("The Biome " + i + " is not registered on the server.");
|
||||
warn = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (warn) {
|
||||
Iris.error("The Pack " + dimension.getLoadKey() + " is INCAPABLE of generating custom biomes");
|
||||
Iris.error("If not done automatically, restart your server before generating with this pack!");
|
||||
}
|
||||
|
||||
return !warn;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,134 +0,0 @@
|
||||
/*
|
||||
* Iris is a World Generator for Minecraft Bukkit Servers
|
||||
* Copyright (c) 2022 Arcane Arts (Volmit Software)
|
||||
*
|
||||
* This program 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.
|
||||
*
|
||||
* This program 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 this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.volmit.iris.core.commands;
|
||||
|
||||
import com.volmit.iris.Iris;
|
||||
import com.volmit.iris.core.pregenerator.DeepSearchPregenerator;
|
||||
import com.volmit.iris.core.pregenerator.PregenTask;
|
||||
import com.volmit.iris.core.pregenerator.TurboPregenerator;
|
||||
import com.volmit.iris.core.tools.IrisToolbelt;
|
||||
import com.volmit.iris.util.data.Dimension;
|
||||
import com.volmit.iris.util.decree.DecreeExecutor;
|
||||
import com.volmit.iris.util.decree.annotations.Decree;
|
||||
import com.volmit.iris.util.decree.annotations.Param;
|
||||
import com.volmit.iris.util.format.C;
|
||||
import com.volmit.iris.util.math.Position2;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.World;
|
||||
import org.bukkit.util.Vector;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
|
||||
@Decree(name = "DeepSearch", aliases = "search", description = "Pregenerate your Iris worlds!")
|
||||
public class CommandDeepSearch implements DecreeExecutor {
|
||||
public String worldName;
|
||||
@Decree(description = "DeepSearch a world")
|
||||
public void start(
|
||||
@Param(description = "The radius of the pregen in blocks", aliases = "size")
|
||||
int radius,
|
||||
@Param(description = "The world to pregen", contextual = true)
|
||||
World world,
|
||||
@Param(aliases = "middle", description = "The center location of the pregen. Use \"me\" for your current location", defaultValue = "0,0")
|
||||
Vector center
|
||||
) {
|
||||
|
||||
worldName = world.getName();
|
||||
File worldDirectory = new File(Bukkit.getWorldContainer(), world.getName());
|
||||
File TurboFile = new File(worldDirectory, "DeepSearch.json");
|
||||
if (TurboFile.exists()) {
|
||||
if (DeepSearchPregenerator.getInstance() != null) {
|
||||
sender().sendMessage(C.BLUE + "DeepSearch is already in progress");
|
||||
Iris.info(C.YELLOW + "DeepSearch is already in progress");
|
||||
return;
|
||||
} else {
|
||||
try {
|
||||
TurboFile.delete();
|
||||
} catch (Exception e){
|
||||
Iris.error("Failed to delete the old instance file of DeepSearch!");
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
if (sender().isPlayer() && access() == null) {
|
||||
sender().sendMessage(C.RED + "The engine access for this world is null!");
|
||||
sender().sendMessage(C.RED + "Please make sure the world is loaded & the engine is initialized. Generate a new chunk, for example.");
|
||||
}
|
||||
|
||||
DeepSearchPregenerator.DeepSearchJob DeepSearchJob = DeepSearchPregenerator.DeepSearchJob.builder()
|
||||
.world(world)
|
||||
.radiusBlocks(radius)
|
||||
.position(0)
|
||||
.build();
|
||||
|
||||
File SearchGenFile = new File(worldDirectory, "DeepSearch.json");
|
||||
DeepSearchPregenerator pregenerator = new DeepSearchPregenerator(DeepSearchJob, SearchGenFile);
|
||||
pregenerator.start();
|
||||
|
||||
String msg = C.GREEN + "DeepSearch started in " + C.GOLD + worldName + C.GREEN + " of " + C.GOLD + (radius * 2) + C.GREEN + " by " + C.GOLD + (radius * 2) + C.GREEN + " blocks from " + C.GOLD + center.getX() + "," + center.getZ();
|
||||
sender().sendMessage(msg);
|
||||
Iris.info(msg);
|
||||
} catch (Throwable e) {
|
||||
sender().sendMessage(C.RED + "Epic fail. See console.");
|
||||
Iris.reportError(e);
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
@Decree(description = "Stop the active DeepSearch task", aliases = "x")
|
||||
public void stop(@Param(aliases = "world", description = "The world to pause") World world) throws IOException {
|
||||
DeepSearchPregenerator DeepSearchInstance = DeepSearchPregenerator.getInstance();
|
||||
File worldDirectory = new File(Bukkit.getWorldContainer(), world.getName());
|
||||
File turboFile = new File(worldDirectory, "DeepSearch.json");
|
||||
|
||||
if (DeepSearchInstance != null) {
|
||||
DeepSearchInstance.shutdownInstance(world);
|
||||
sender().sendMessage(C.LIGHT_PURPLE + "Closed Turbogen instance for " + world.getName());
|
||||
} else if (turboFile.exists() && turboFile.delete()) {
|
||||
sender().sendMessage(C.LIGHT_PURPLE + "Closed Turbogen instance for " + world.getName());
|
||||
} else if (turboFile.exists()) {
|
||||
Iris.error("Failed to delete the old instance file of Turbo Pregen!");
|
||||
} else {
|
||||
sender().sendMessage(C.YELLOW + "No active pregeneration tasks to stop");
|
||||
}
|
||||
}
|
||||
|
||||
@Decree(description = "Pause / continue the active pregeneration task", aliases = {"t", "resume", "unpause"})
|
||||
public void pause(
|
||||
@Param(aliases = "world", description = "The world to pause")
|
||||
World world
|
||||
) {
|
||||
if (TurboPregenerator.getInstance() != null) {
|
||||
TurboPregenerator.setPausedTurbo(world);
|
||||
sender().sendMessage(C.GREEN + "Paused/unpaused Turbo Pregen, now: " + (TurboPregenerator.isPausedTurbo(world) ? "Paused" : "Running") + ".");
|
||||
} else {
|
||||
File worldDirectory = new File(Bukkit.getWorldContainer(), world.getName());
|
||||
File TurboFile = new File(worldDirectory, "DeepSearch.json");
|
||||
if (TurboFile.exists()){
|
||||
TurboPregenerator.loadTurboGenerator(world.getName());
|
||||
sender().sendMessage(C.YELLOW + "Started DeepSearch back up!");
|
||||
} else {
|
||||
sender().sendMessage(C.YELLOW + "No active DeepSearch tasks to pause/unpause.");
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -28,7 +28,9 @@ import com.volmit.iris.core.pregenerator.ChunkUpdater;
|
||||
import com.volmit.iris.core.service.IrisEngineSVC;
|
||||
import com.volmit.iris.core.tools.IrisPackBenchmarking;
|
||||
import com.volmit.iris.core.tools.IrisToolbelt;
|
||||
import com.volmit.iris.core.tools.IrisWorldDump;
|
||||
import com.volmit.iris.engine.framework.Engine;
|
||||
import com.volmit.iris.engine.jvm.VMJavaFX;
|
||||
import com.volmit.iris.engine.mantle.components.MantleObjectComponent;
|
||||
import com.volmit.iris.engine.object.IrisBiome;
|
||||
import com.volmit.iris.engine.object.IrisCave;
|
||||
@@ -150,21 +152,18 @@ public class CommandDeveloper implements DecreeExecutor {
|
||||
@Decree(description = "Test")
|
||||
public void packBenchmark(
|
||||
@Param(description = "The pack to bench", aliases = {"pack"})
|
||||
IrisDimension dimension
|
||||
IrisDimension dimension,
|
||||
@Param(description = "Headless", defaultValue = "true")
|
||||
boolean headless,
|
||||
@Param(description = "GUI", defaultValue = "false")
|
||||
boolean gui
|
||||
) {
|
||||
Iris.info("test");
|
||||
IrisPackBenchmarking benchmark = new IrisPackBenchmarking(dimension, 1);
|
||||
IrisPackBenchmarking benchmark = new IrisPackBenchmarking(dimension, 1, headless, gui);
|
||||
benchmark.runBenchmark();
|
||||
|
||||
}
|
||||
|
||||
@Decree(description = "Upgrade to another Minecraft version")
|
||||
public void upgrade(
|
||||
@Param(description = "The version to upgrade to", defaultValue = "latest") DataVersion version) {
|
||||
sender().sendMessage(C.GREEN + "Upgrading to " + version.getVersion() + "...");
|
||||
ServerConfigurator.installDataPacks(version.get(), false);
|
||||
sender().sendMessage(C.GREEN + "Done upgrading! You can now update your server version to " + version.getVersion());
|
||||
}
|
||||
|
||||
@Decree(description = "Test")
|
||||
public void updater(
|
||||
@Param(description = "Updater for chunks")
|
||||
@@ -179,12 +178,22 @@ public class CommandDeveloper implements DecreeExecutor {
|
||||
|
||||
@Decree(description = "test")
|
||||
public void mca (
|
||||
@Param(description = "String") String world) {
|
||||
@Param(description = "String") World world) {
|
||||
try {
|
||||
File[] McaFiles = new File(world, "region").listFiles((dir, name) -> name.endsWith(".mca"));
|
||||
for (File mca : McaFiles) {
|
||||
MCAFile MCARegion = MCAUtil.read(mca);
|
||||
}
|
||||
IrisWorldDump dump = new IrisWorldDump(world, sender());
|
||||
dump.start();
|
||||
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Decree(description = "test")
|
||||
public void javafx () {
|
||||
try {
|
||||
VMJavaFX javaFX = new VMJavaFX(sender());
|
||||
javaFX.start();
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
@@ -19,13 +19,19 @@
|
||||
package com.volmit.iris.core.commands;
|
||||
|
||||
import com.volmit.iris.Iris;
|
||||
import com.volmit.iris.core.nms.INMS;
|
||||
import com.volmit.iris.core.service.StudioSVC;
|
||||
import com.volmit.iris.engine.object.*;
|
||||
import com.volmit.iris.util.decree.DecreeExecutor;
|
||||
import com.volmit.iris.util.decree.DecreeOrigin;
|
||||
import com.volmit.iris.util.decree.annotations.Decree;
|
||||
import com.volmit.iris.util.decree.annotations.Param;
|
||||
import com.volmit.iris.util.decree.specialhandlers.NullableBiomeHandler;
|
||||
import com.volmit.iris.util.decree.specialhandlers.NullablePlayerHandler;
|
||||
import com.volmit.iris.util.decree.specialhandlers.NullableRegionHandler;
|
||||
import com.volmit.iris.util.format.C;
|
||||
import org.bukkit.Location;
|
||||
import org.bukkit.block.Biome;
|
||||
|
||||
import java.awt.*;
|
||||
|
||||
@@ -51,12 +57,31 @@ public class CommandEdit implements DecreeExecutor {
|
||||
|
||||
|
||||
@Decree(description = "Edit the biome you specified", aliases = {"b"}, origin = DecreeOrigin.PLAYER)
|
||||
public void biome(@Param(contextual = false, description = "The biome to edit") IrisBiome biome) {
|
||||
public void biome(@Param(contextual = false, description = "The biome to edit", defaultValue = "---", customHandler = NullableBiomeHandler.class) IrisBiome biome ) {
|
||||
if (noStudio()) {
|
||||
return;
|
||||
}
|
||||
if(biome == null) {
|
||||
try {
|
||||
IrisBiome b = engine().getBiome(player().getLocation().getBlockX(), player().getLocation().getBlockY() - player().getWorld().getMinHeight(), player().getLocation().getBlockZ());
|
||||
Desktop.getDesktop().open(b.getLoadFile());
|
||||
sender().sendMessage(C.GREEN + "Opening " + b.getTypeName() + " " + b.getLoadFile().getName().split("\\Q.\\E")[0] + " in VSCode! ");
|
||||
} catch (Throwable e) {
|
||||
Iris.reportError(e);
|
||||
sender().sendMessage("Non-Iris Biome: " + player().getLocation().getBlock().getBiome().name());
|
||||
if (player().getLocation().getBlock().getBiome().equals(Biome.CUSTOM)) {
|
||||
try {
|
||||
sender().sendMessage("Data Pack Biome: " + INMS.get().getTrueBiomeBaseKey(player().getLocation()) + " (ID: " + INMS.get().getTrueBiomeBaseId(INMS.get().getTrueBiomeBase(player().getLocation())) + ")");
|
||||
} catch (Throwable ee) {
|
||||
Iris.reportError(ee);
|
||||
}
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
if (biome == null || biome.getLoadFile() == null) {
|
||||
if (biome.getLoadFile() == null) {
|
||||
sender().sendMessage(C.GOLD + "Cannot find the file; Perhaps it was not loaded directly from a file?");
|
||||
return;
|
||||
}
|
||||
@@ -69,10 +94,20 @@ public class CommandEdit implements DecreeExecutor {
|
||||
}
|
||||
|
||||
@Decree(description = "Edit the region you specified", aliases = {"r"}, origin = DecreeOrigin.PLAYER)
|
||||
public void region(@Param(contextual = false, description = "The region to edit") IrisRegion region) {
|
||||
public void region(@Param(contextual = false, description = "The region to edit", defaultValue = "---", customHandler = NullableRegionHandler.class) IrisRegion region) {
|
||||
if (noStudio()) {
|
||||
return;
|
||||
}
|
||||
if(region == null) {
|
||||
try {
|
||||
IrisRegion r = engine().getRegion(player().getLocation().getBlockX(), player().getLocation().getBlockZ());
|
||||
Desktop.getDesktop().open(r.getLoadFile());
|
||||
sender().sendMessage(C.GREEN + "Opening " + r.getTypeName() + " " + r.getLoadFile().getName().split("\\Q.\\E")[0] + " in VSCode! ");
|
||||
} catch (Throwable e) {
|
||||
sender().sendMessage(C.RED + "Failed to get region.");
|
||||
}
|
||||
return;
|
||||
}
|
||||
try {
|
||||
if (region == null || region.getLoadFile() == null) {
|
||||
sender().sendMessage(C.GOLD + "Cannot find the file; Perhaps it was not loaded directly from a file?");
|
||||
|
||||
@@ -24,6 +24,7 @@ import com.volmit.iris.core.ServerConfigurator;
|
||||
import com.volmit.iris.core.loader.IrisData;
|
||||
import com.volmit.iris.core.nms.datapack.DataVersion;
|
||||
import com.volmit.iris.core.pregenerator.ChunkUpdater;
|
||||
import com.volmit.iris.core.safeguard.IrisSafeguard;
|
||||
import com.volmit.iris.core.service.StudioSVC;
|
||||
import com.volmit.iris.core.tools.IrisBenchmarking;
|
||||
import com.volmit.iris.core.tools.IrisToolbelt;
|
||||
@@ -43,6 +44,7 @@ import com.volmit.iris.util.format.C;
|
||||
import com.volmit.iris.util.format.Form;
|
||||
import com.volmit.iris.util.plugin.VolmitSender;
|
||||
import com.volmit.iris.util.scheduling.J;
|
||||
import io.lumine.mythic.bukkit.adapters.BukkitPlayer;
|
||||
import lombok.Getter;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.Difficulty;
|
||||
@@ -65,7 +67,6 @@ import java.util.List;
|
||||
import static com.volmit.iris.Iris.service;
|
||||
import static com.volmit.iris.core.service.EditSVC.deletingWorld;
|
||||
import static com.volmit.iris.core.tools.IrisBenchmarking.inProgress;
|
||||
import static com.volmit.iris.core.safeguard.IrisSafeguard.unstablemode;
|
||||
import static com.volmit.iris.core.safeguard.ServerBootSFG.incompatibilities;
|
||||
import static org.bukkit.Bukkit.getServer;
|
||||
|
||||
@@ -98,24 +99,7 @@ public class CommandIris implements DecreeExecutor {
|
||||
@Param(description = "If it should convert the dimension to match the vanilla height system.", defaultValue = "false")
|
||||
boolean vanillaheight
|
||||
) {
|
||||
if(sender() instanceof Player) {
|
||||
if (incompatibilities.get("Multiverse-Core")) {
|
||||
sender().sendMessage(C.RED + "Your server has an incompatibility that may corrupt all worlds on the server if not handled properly.");
|
||||
sender().sendMessage(C.RED + "it is strongly advised for you to take action. see log for full detail");
|
||||
sender().sendMessage(C.RED + "----------------------------------------------------------------");
|
||||
sender().sendMessage(C.RED + "Command ran: /iris create");
|
||||
sender().sendMessage(C.RED + UtilsSFG.MSGIncompatibleWarnings());
|
||||
sender().sendMessage(C.RED + "----------------------------------------------------------------");
|
||||
}
|
||||
if (unstablemode && !incompatibilities.get("Multiverse-Core")) {
|
||||
sender().sendMessage(C.RED + "Your server is experiencing an incompatibility with the Iris plugin.");
|
||||
sender().sendMessage(C.RED + "Please rectify this problem to avoid further complications.");
|
||||
sender().sendMessage(C.RED + "----------------------------------------------------------------");
|
||||
sender().sendMessage(C.RED + "Command ran: /iris create");
|
||||
sender().sendMessage(C.RED + UtilsSFG.MSGIncompatibleWarnings());
|
||||
sender().sendMessage(C.RED + "----------------------------------------------------------------");
|
||||
}
|
||||
}
|
||||
|
||||
if (name.equals("iris")) {
|
||||
sender().sendMessage(C.RED + "You cannot use the world name \"iris\" for creating worlds as Iris uses this directory for studio worlds.");
|
||||
sender().sendMessage(C.RED + "May we suggest the name \"IrisWorld\" instead?");
|
||||
|
||||
@@ -22,11 +22,10 @@ import com.volmit.iris.Iris;
|
||||
import com.volmit.iris.core.IrisSettings;
|
||||
import com.volmit.iris.core.gui.NoiseExplorerGUI;
|
||||
import com.volmit.iris.core.gui.VisionGUI;
|
||||
import com.volmit.iris.core.loader.IrisData;
|
||||
import com.volmit.iris.core.project.IrisProject;
|
||||
import com.volmit.iris.core.service.ConversionSVC;
|
||||
import com.volmit.iris.core.service.StudioSVC;
|
||||
import com.volmit.iris.core.tools.IrisConverter;
|
||||
import com.volmit.iris.core.tools.IrisNoiseBenchmark;
|
||||
import com.volmit.iris.core.tools.IrisToolbelt;
|
||||
import com.volmit.iris.engine.framework.Engine;
|
||||
import com.volmit.iris.engine.object.*;
|
||||
@@ -42,22 +41,17 @@ import com.volmit.iris.util.decree.annotations.Param;
|
||||
import com.volmit.iris.util.format.C;
|
||||
import com.volmit.iris.util.format.Form;
|
||||
import com.volmit.iris.util.function.Function2;
|
||||
import com.volmit.iris.util.function.NoiseProvider;
|
||||
import com.volmit.iris.util.interpolation.InterpolationMethod;
|
||||
import com.volmit.iris.util.io.IO;
|
||||
import com.volmit.iris.util.json.JSONArray;
|
||||
import com.volmit.iris.util.json.JSONObject;
|
||||
import com.volmit.iris.util.math.M;
|
||||
import com.volmit.iris.util.math.Position2;
|
||||
import com.volmit.iris.util.math.RNG;
|
||||
import com.volmit.iris.util.math.Spiraler;
|
||||
import com.volmit.iris.util.noise.CNG;
|
||||
import com.volmit.iris.util.parallel.BurstExecutor;
|
||||
import com.volmit.iris.util.parallel.MultiBurst;
|
||||
import com.volmit.iris.util.plugin.VolmitSender;
|
||||
import com.volmit.iris.util.scheduling.J;
|
||||
import com.volmit.iris.util.scheduling.O;
|
||||
import com.volmit.iris.util.scheduling.PrecisionStopwatch;
|
||||
import com.volmit.iris.util.scheduling.jobs.QueueJob;
|
||||
import io.papermc.lib.PaperLib;
|
||||
import org.bukkit.*;
|
||||
@@ -79,14 +73,12 @@ import java.util.Date;
|
||||
import java.util.Objects;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.concurrent.Future;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
@Decree(name = "studio", aliases = {"std", "s"}, description = "Studio Commands", studio = true)
|
||||
public class CommandStudio implements DecreeExecutor {
|
||||
private CommandFind find;
|
||||
private CommandEdit edit;
|
||||
//private CommandDeepSearch deepSearch;
|
||||
|
||||
public static String hrf(Duration duration) {
|
||||
return duration.toString().substring(2).replaceAll("(\\d[HMS])(?!$)", "$1 ").toLowerCase();
|
||||
@@ -286,7 +278,8 @@ public class CommandStudio implements DecreeExecutor {
|
||||
sender().sendMessage(C.RED + "No studio world open!");
|
||||
return;
|
||||
}
|
||||
Iris.service(StudioSVC.class).getActiveProject().getActiveProvider().getEngine().hotload();
|
||||
var provider = Iris.service(StudioSVC.class).getActiveProject().getActiveProvider();
|
||||
provider.getEngine().hotload();
|
||||
sender().sendMessage(C.GREEN + "Hotloaded");
|
||||
}
|
||||
|
||||
@@ -391,18 +384,10 @@ public class CommandStudio implements DecreeExecutor {
|
||||
}
|
||||
|
||||
@Decree(description = "Render a world map (External GUI)", aliases = "render")
|
||||
public void map(
|
||||
@Param(name = "world", description = "The world to open the generator for", contextual = true)
|
||||
World world
|
||||
) {
|
||||
public void map() {
|
||||
if (noGUI()) return;
|
||||
|
||||
if (!IrisToolbelt.isIrisWorld(world)) {
|
||||
sender().sendMessage(C.RED + "You need to be in or specify an Iris-generated world!");
|
||||
return;
|
||||
}
|
||||
|
||||
VisionGUI.launch(IrisToolbelt.access(world).getEngine(), 0);
|
||||
if (noStudio()) return;
|
||||
VisionGUI.launch(IrisToolbelt.access(player().getWorld()).getEngine(), 0);
|
||||
sender().sendMessage(C.GREEN + "Opening map!");
|
||||
}
|
||||
|
||||
@@ -423,188 +408,8 @@ public class CommandStudio implements DecreeExecutor {
|
||||
@Param(description = "The dimension to profile", contextual = true, defaultValue = "default")
|
||||
IrisDimension dimension
|
||||
) {
|
||||
// Todo: Make this more accurate
|
||||
File pack = dimension.getLoadFile().getParentFile().getParentFile();
|
||||
File report = Iris.instance.getDataFile("profile.txt");
|
||||
IrisProject project = new IrisProject(pack);
|
||||
IrisData data = IrisData.get(pack);
|
||||
|
||||
KList<String> fileText = new KList<>();
|
||||
|
||||
KMap<NoiseStyle, Double> styleTimings = new KMap<>();
|
||||
KMap<InterpolationMethod, Double> interpolatorTimings = new KMap<>();
|
||||
KMap<String, Double> generatorTimings = new KMap<>();
|
||||
KMap<String, Double> biomeTimings = new KMap<>();
|
||||
KMap<String, Double> regionTimings = new KMap<>();
|
||||
|
||||
sender().sendMessage("Calculating Performance Metrics for Noise generators");
|
||||
|
||||
for (NoiseStyle i : NoiseStyle.values()) {
|
||||
CNG c = i.create(new RNG(i.hashCode()));
|
||||
|
||||
for (int j = 0; j < 3000; j++) {
|
||||
c.noise(j, j + 1000, j * j);
|
||||
c.noise(j, -j);
|
||||
}
|
||||
|
||||
PrecisionStopwatch px = PrecisionStopwatch.start();
|
||||
|
||||
for (int j = 0; j < 100000; j++) {
|
||||
c.noise(j, j + 1000, j * j);
|
||||
c.noise(j, -j);
|
||||
}
|
||||
|
||||
styleTimings.put(i, px.getMilliseconds());
|
||||
}
|
||||
|
||||
fileText.add("Noise Style Performance Impacts: ");
|
||||
|
||||
for (NoiseStyle i : styleTimings.sortKNumber()) {
|
||||
fileText.add(i.name() + ": " + styleTimings.get(i));
|
||||
}
|
||||
|
||||
fileText.add("");
|
||||
|
||||
sender().sendMessage("Calculating Interpolator Timings...");
|
||||
|
||||
for (InterpolationMethod i : InterpolationMethod.values()) {
|
||||
IrisInterpolator in = new IrisInterpolator();
|
||||
in.setFunction(i);
|
||||
in.setHorizontalScale(8);
|
||||
|
||||
NoiseProvider np = (x, z) -> Math.random();
|
||||
|
||||
for (int j = 0; j < 3000; j++) {
|
||||
in.interpolate(j, -j, np);
|
||||
}
|
||||
|
||||
PrecisionStopwatch px = PrecisionStopwatch.start();
|
||||
|
||||
for (int j = 0; j < 100000; j++) {
|
||||
in.interpolate(j + 10000, -j - 100000, np);
|
||||
}
|
||||
|
||||
interpolatorTimings.put(i, px.getMilliseconds());
|
||||
}
|
||||
|
||||
fileText.add("Noise Interpolator Performance Impacts: ");
|
||||
|
||||
for (InterpolationMethod i : interpolatorTimings.sortKNumber()) {
|
||||
fileText.add(i.name() + ": " + interpolatorTimings.get(i));
|
||||
}
|
||||
|
||||
fileText.add("");
|
||||
|
||||
sender().sendMessage("Processing Generator Scores: ");
|
||||
|
||||
KMap<String, KList<String>> btx = new KMap<>();
|
||||
|
||||
for (String i : data.getGeneratorLoader().getPossibleKeys()) {
|
||||
KList<String> vv = new KList<>();
|
||||
IrisGenerator g = data.getGeneratorLoader().load(i);
|
||||
KList<IrisNoiseGenerator> composites = g.getAllComposites();
|
||||
double score = 0;
|
||||
int m = 0;
|
||||
for (IrisNoiseGenerator j : composites) {
|
||||
m++;
|
||||
score += styleTimings.get(j.getStyle().getStyle());
|
||||
vv.add("Composite Noise Style " + m + " " + j.getStyle().getStyle().name() + ": " + styleTimings.get(j.getStyle().getStyle()));
|
||||
}
|
||||
|
||||
score += interpolatorTimings.get(g.getInterpolator().getFunction());
|
||||
vv.add("Interpolator " + g.getInterpolator().getFunction().name() + ": " + interpolatorTimings.get(g.getInterpolator().getFunction()));
|
||||
generatorTimings.put(i, score);
|
||||
btx.put(i, vv);
|
||||
}
|
||||
|
||||
fileText.add("Project Generator Performance Impacts: ");
|
||||
|
||||
for (String i : generatorTimings.sortKNumber()) {
|
||||
fileText.add(i + ": " + generatorTimings.get(i));
|
||||
|
||||
btx.get(i).forEach((ii) -> fileText.add(" " + ii));
|
||||
}
|
||||
|
||||
fileText.add("");
|
||||
|
||||
KMap<String, KList<String>> bt = new KMap<>();
|
||||
|
||||
for (String i : data.getBiomeLoader().getPossibleKeys()) {
|
||||
KList<String> vv = new KList<>();
|
||||
IrisBiome b = data.getBiomeLoader().load(i);
|
||||
double score = 0;
|
||||
|
||||
int m = 0;
|
||||
for (IrisBiomePaletteLayer j : b.getLayers()) {
|
||||
m++;
|
||||
score += styleTimings.get(j.getStyle().getStyle());
|
||||
vv.add("Palette Layer " + m + ": " + styleTimings.get(j.getStyle().getStyle()));
|
||||
}
|
||||
|
||||
score += styleTimings.get(b.getBiomeStyle().getStyle());
|
||||
vv.add("Biome Style: " + styleTimings.get(b.getBiomeStyle().getStyle()));
|
||||
score += styleTimings.get(b.getChildStyle().getStyle());
|
||||
vv.add("Child Style: " + styleTimings.get(b.getChildStyle().getStyle()));
|
||||
biomeTimings.put(i, score);
|
||||
bt.put(i, vv);
|
||||
}
|
||||
|
||||
fileText.add("Project Biome Performance Impacts: ");
|
||||
|
||||
for (String i : biomeTimings.sortKNumber()) {
|
||||
fileText.add(i + ": " + biomeTimings.get(i));
|
||||
|
||||
bt.get(i).forEach((ff) -> fileText.add(" " + ff));
|
||||
}
|
||||
|
||||
fileText.add("");
|
||||
|
||||
for (String i : data.getRegionLoader().getPossibleKeys()) {
|
||||
IrisRegion b = data.getRegionLoader().load(i);
|
||||
double score = 0;
|
||||
|
||||
score += styleTimings.get(b.getLakeStyle().getStyle());
|
||||
score += styleTimings.get(b.getRiverStyle().getStyle());
|
||||
regionTimings.put(i, score);
|
||||
}
|
||||
|
||||
fileText.add("Project Region Performance Impacts: ");
|
||||
|
||||
for (String i : regionTimings.sortKNumber()) {
|
||||
fileText.add(i + ": " + regionTimings.get(i));
|
||||
}
|
||||
|
||||
fileText.add("");
|
||||
|
||||
double m = 0;
|
||||
for (double i : biomeTimings.v()) {
|
||||
m += i;
|
||||
}
|
||||
m /= biomeTimings.size();
|
||||
double mm = 0;
|
||||
for (double i : generatorTimings.v()) {
|
||||
mm += i;
|
||||
}
|
||||
mm /= generatorTimings.size();
|
||||
m += mm;
|
||||
double mmm = 0;
|
||||
for (double i : regionTimings.v()) {
|
||||
mmm += i;
|
||||
}
|
||||
mmm /= regionTimings.size();
|
||||
m += mmm;
|
||||
|
||||
fileText.add("Average Score: " + m);
|
||||
sender().sendMessage("Score: " + Form.duration(m, 0));
|
||||
|
||||
try {
|
||||
IO.writeAll(report, fileText.toString("\n"));
|
||||
} catch (IOException e) {
|
||||
Iris.reportError(e);
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
sender().sendMessage(C.GREEN + "Done! " + report.getPath());
|
||||
IrisNoiseBenchmark noiseBenchmark = new IrisNoiseBenchmark(dimension, sender());
|
||||
noiseBenchmark.runAll();
|
||||
}
|
||||
|
||||
@Decree(description = "Spawn an Iris entity", aliases = "summon", origin = DecreeOrigin.PLAYER)
|
||||
|
||||
@@ -88,8 +88,7 @@ public class CommandWhat implements DecreeExecutor {
|
||||
@Decree(description = "What region am i in?", origin = DecreeOrigin.PLAYER)
|
||||
public void region() {
|
||||
try {
|
||||
Chunk chunk = world().getChunkAt(player().getLocation().getBlockZ() / 16, player().getLocation().getBlockZ() / 16);
|
||||
IrisRegion r = engine().getRegion(chunk);
|
||||
IrisRegion r = engine().getRegion(player().getLocation());
|
||||
sender().sendMessage("IRegion: " + r.getLoadKey() + " (" + r.getName() + ")");
|
||||
|
||||
} catch (Throwable e) {
|
||||
|
||||
@@ -80,6 +80,7 @@ public class VisionGUI extends JPanel implements MouseWheelListener, KeyListener
|
||||
private boolean lowtile = false;
|
||||
private boolean follow = false;
|
||||
private boolean alt = false;
|
||||
private boolean dragging = false;
|
||||
private IrisRenderer renderer;
|
||||
private IrisWorld world;
|
||||
private double velocity = 0;
|
||||
@@ -201,6 +202,7 @@ public class VisionGUI extends JPanel implements MouseWheelListener, KeyListener
|
||||
|
||||
@Override
|
||||
public void mouseDragged(MouseEvent e) {
|
||||
dragging = true;
|
||||
Point cp = e.getPoint();
|
||||
ox += (lx - cp.getX()) * scale;
|
||||
oz += (lz - cp.getY()) * scale;
|
||||
@@ -413,7 +415,7 @@ public class VisionGUI extends JPanel implements MouseWheelListener, KeyListener
|
||||
|
||||
private double getWorldX(double screenX) {
|
||||
//return (mscale * screenX) + ((oxp / scale) * mscale);
|
||||
return (mscale * screenX) + ((oxp / scale));
|
||||
return (mscale * screenX) + ((oxp / scale) * mscale);
|
||||
}
|
||||
|
||||
private double getWorldZ(double screenZ) {
|
||||
|
||||
22
core/src/main/java/com/volmit/iris/core/nms/IHeadless.java
Normal file
22
core/src/main/java/com/volmit/iris/core/nms/IHeadless.java
Normal file
@@ -0,0 +1,22 @@
|
||||
package com.volmit.iris.core.nms;
|
||||
|
||||
import com.volmit.iris.core.pregenerator.PregenListener;
|
||||
import com.volmit.iris.util.documentation.ChunkCoordinates;
|
||||
import com.volmit.iris.util.documentation.RegionCoordinates;
|
||||
import com.volmit.iris.util.parallel.MultiBurst;
|
||||
|
||||
import java.io.Closeable;
|
||||
|
||||
public interface IHeadless extends Closeable {
|
||||
|
||||
void save();
|
||||
|
||||
@ChunkCoordinates
|
||||
boolean exists(int x, int z);
|
||||
|
||||
@RegionCoordinates
|
||||
void generateRegion(MultiBurst burst, int x, int z, PregenListener listener);
|
||||
|
||||
@ChunkCoordinates
|
||||
void generateChunk(int x, int z);
|
||||
}
|
||||
@@ -20,6 +20,8 @@ package com.volmit.iris.core.nms;
|
||||
|
||||
import com.volmit.iris.core.nms.datapack.DataVersion;
|
||||
import com.volmit.iris.engine.framework.Engine;
|
||||
import com.volmit.iris.engine.object.IrisBiomeCustom;
|
||||
import com.volmit.iris.engine.object.IrisDimension;
|
||||
import com.volmit.iris.util.collection.KList;
|
||||
import com.volmit.iris.util.collection.KMap;
|
||||
import com.volmit.iris.util.mantle.Mantle;
|
||||
@@ -115,4 +117,14 @@ public interface INMSBinding {
|
||||
default DataVersion getDataVersion() {
|
||||
return DataVersion.V1192;
|
||||
}
|
||||
|
||||
boolean registerDimension(String name, IrisDimension dimension);
|
||||
|
||||
boolean registerBiome(String dimensionId, IrisBiomeCustom biome, boolean replace);
|
||||
|
||||
void injectBukkit();
|
||||
|
||||
default IHeadless createHeadless(Engine engine) {
|
||||
throw new IllegalStateException("Headless mode not supported");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,20 +18,29 @@
|
||||
|
||||
package com.volmit.iris.core.nms.v1X;
|
||||
|
||||
import com.google.common.base.Preconditions;
|
||||
import com.volmit.iris.Iris;
|
||||
import com.volmit.iris.core.nms.INMSBinding;
|
||||
import com.volmit.iris.core.nms.container.BlockPos;
|
||||
import com.volmit.iris.engine.framework.Engine;
|
||||
import com.volmit.iris.engine.object.IrisBiomeCustom;
|
||||
import com.volmit.iris.engine.object.IrisDimension;
|
||||
import com.volmit.iris.util.collection.KList;
|
||||
import com.volmit.iris.util.collection.KMap;
|
||||
import com.volmit.iris.util.format.C;
|
||||
import com.volmit.iris.util.mantle.Mantle;
|
||||
import com.volmit.iris.util.math.Vector3d;
|
||||
import com.volmit.iris.util.nbt.mca.palette.MCABiomeContainer;
|
||||
import com.volmit.iris.util.nbt.mca.palette.MCAPaletteAccess;
|
||||
import com.volmit.iris.util.nbt.tag.CompoundTag;
|
||||
import net.bytebuddy.ByteBuddy;
|
||||
import net.bytebuddy.asm.Advice;
|
||||
import net.bytebuddy.dynamic.loading.ClassReloadingStrategy;
|
||||
import net.bytebuddy.matcher.ElementMatchers;
|
||||
import org.bukkit.Chunk;
|
||||
import org.bukkit.Location;
|
||||
import org.bukkit.World;
|
||||
import org.bukkit.WorldCreator;
|
||||
import org.bukkit.block.Biome;
|
||||
import org.bukkit.entity.Dolphin;
|
||||
import org.bukkit.entity.Entity;
|
||||
@@ -40,6 +49,8 @@ import org.bukkit.event.entity.CreatureSpawnEvent;
|
||||
import org.bukkit.generator.ChunkGenerator;
|
||||
import org.bukkit.inventory.ItemStack;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
public class NMSBinding1X implements INMSBinding {
|
||||
private static final boolean supportsCustomHeight = testCustomHeight();
|
||||
|
||||
@@ -97,6 +108,16 @@ public class NMSBinding1X implements INMSBinding {
|
||||
return location.getWorld().spawnEntity(location, type);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean registerDimension(String name, IrisDimension dimension) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean registerBiome(String dimensionId, IrisBiomeCustom biome, boolean replace) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void deserializeTile(CompoundTag s, Location newPosition) {
|
||||
|
||||
@@ -225,4 +246,39 @@ public class NMSBinding1X implements INMSBinding {
|
||||
Iris.error("Cannot use the global data palette! Iris is incapable of using MCA generation on this version of minecraft!");
|
||||
return null;
|
||||
}
|
||||
|
||||
public void injectBukkit() {
|
||||
try {
|
||||
Iris.info("Injecting Bukkit");
|
||||
new ByteBuddy()
|
||||
.redefine(WorldCreator.class)
|
||||
.visit(Advice.to(WorldCreatorAdvice.class).on(ElementMatchers.isConstructor().and(ElementMatchers.takesArguments(String.class))))
|
||||
.make()
|
||||
.load(WorldCreator.class.getClassLoader(), ClassReloadingStrategy.fromInstalledAgent());
|
||||
Iris.info("Injected Bukkit Successfully!");
|
||||
} catch (Exception e) {
|
||||
Iris.info(C.RED + "Failed to Inject Bukkit!");
|
||||
e.printStackTrace();
|
||||
Iris.reportError(e);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private static class WorldCreatorAdvice {
|
||||
@Advice.OnMethodEnter
|
||||
static void enter(@Advice.Argument(0) String name) {
|
||||
File isIrisWorld = new File(name, "iris");
|
||||
boolean isFromIris = false;
|
||||
StackTraceElement[] stackTrace = Thread.currentThread().getStackTrace();
|
||||
for (StackTraceElement stack : stackTrace) {
|
||||
if (stack.getClassName().contains("Iris")) {
|
||||
isFromIris = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!isFromIris) {
|
||||
Preconditions.checkArgument(!isIrisWorld.exists(), "Only Iris can load Iris Worlds!");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -106,7 +106,7 @@ public class ChunkUpdater {
|
||||
try {
|
||||
if (!paused.get()) {
|
||||
long eta = computeETA();
|
||||
long elapsedSeconds = (System.currentTimeMillis() - startTime.get()) / 1000;
|
||||
long elapsedSeconds = (System.currentTimeMillis() - startTime.get()) / 3000;
|
||||
int processed = chunksProcessed.get();
|
||||
double cps = elapsedSeconds > 0 ? processed / (double) elapsedSeconds : 0;
|
||||
chunksPerSecond.put(cps);
|
||||
|
||||
@@ -1,275 +0,0 @@
|
||||
package com.volmit.iris.core.pregenerator;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import com.volmit.iris.Iris;
|
||||
import com.volmit.iris.core.tools.IrisToolbelt;
|
||||
import com.volmit.iris.engine.framework.Engine;
|
||||
import com.volmit.iris.engine.object.IrisBiome;
|
||||
import com.volmit.iris.util.collection.KList;
|
||||
import com.volmit.iris.util.format.C;
|
||||
import com.volmit.iris.util.format.Form;
|
||||
import com.volmit.iris.util.io.IO;
|
||||
import com.volmit.iris.util.math.M;
|
||||
import com.volmit.iris.util.math.Position2;
|
||||
import com.volmit.iris.util.math.RollingSequence;
|
||||
import com.volmit.iris.util.math.Spiraler;
|
||||
import com.volmit.iris.util.scheduling.ChronoLatch;
|
||||
import com.volmit.iris.util.scheduling.J;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.Getter;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.World;
|
||||
import org.bukkit.event.EventHandler;
|
||||
import org.bukkit.event.Listener;
|
||||
import org.bukkit.event.world.WorldUnloadEvent;
|
||||
import org.bukkit.scheduler.BukkitRunnable;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileWriter;
|
||||
import java.io.IOException;
|
||||
import java.util.HashMap;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.concurrent.atomic.AtomicLong;
|
||||
import java.util.concurrent.locks.ReentrantLock;
|
||||
|
||||
public class DeepSearchPregenerator extends Thread implements Listener {
|
||||
@Getter
|
||||
private static DeepSearchPregenerator instance;
|
||||
private final DeepSearchJob job;
|
||||
private final File destination;
|
||||
private final int maxPosition;
|
||||
private World world;
|
||||
private final ChronoLatch latch;
|
||||
private static AtomicInteger foundChunks;
|
||||
private final AtomicInteger foundLast;
|
||||
private final AtomicInteger foundTotalChunks;
|
||||
private final AtomicLong startTime;
|
||||
private final RollingSequence chunksPerSecond;
|
||||
private final RollingSequence chunksPerMinute;
|
||||
private final AtomicInteger chunkCachePos;
|
||||
private final AtomicInteger chunkCacheSize;
|
||||
private int pos;
|
||||
private final AtomicInteger foundCacheLast;
|
||||
private final AtomicInteger foundCache;
|
||||
private LinkedHashMap<Integer, Position2> chunkCache;
|
||||
private KList<Position2> chunkQueue;
|
||||
private final ReentrantLock cacheLock;
|
||||
|
||||
private static final Map<String, DeepSearchJob> jobs = new HashMap<>();
|
||||
|
||||
public DeepSearchPregenerator(DeepSearchJob job, File destination) {
|
||||
this.job = job;
|
||||
this.chunkCacheSize = new AtomicInteger(); // todo
|
||||
this.chunkCachePos = new AtomicInteger(1000);
|
||||
this.foundCacheLast = new AtomicInteger();
|
||||
this.foundCache = new AtomicInteger();
|
||||
this.cacheLock = new ReentrantLock();
|
||||
this.destination = destination;
|
||||
this.chunkCache = new LinkedHashMap<>();
|
||||
this.maxPosition = new Spiraler(job.getRadiusBlocks() * 2, job.getRadiusBlocks() * 2, (x, z) -> {
|
||||
}).count();
|
||||
this.world = Bukkit.getWorld(job.getWorld().getUID());
|
||||
this.chunkQueue = new KList<>();
|
||||
this.latch = new ChronoLatch(3000);
|
||||
this.startTime = new AtomicLong(M.ms());
|
||||
this.chunksPerSecond = new RollingSequence(10);
|
||||
this.chunksPerMinute = new RollingSequence(10);
|
||||
foundChunks = new AtomicInteger(0);
|
||||
this.foundLast = new AtomicInteger(0);
|
||||
this.foundTotalChunks = new AtomicInteger((int) Math.ceil(Math.pow((2.0 * job.getRadiusBlocks()) / 16, 2)));
|
||||
|
||||
this.pos = 0;
|
||||
jobs.put(job.getWorld().getName(), job);
|
||||
DeepSearchPregenerator.instance = this;
|
||||
}
|
||||
|
||||
@EventHandler
|
||||
public void on(WorldUnloadEvent e) {
|
||||
if (e.getWorld().equals(world)) {
|
||||
interrupt();
|
||||
}
|
||||
}
|
||||
|
||||
public void run() {
|
||||
while (!interrupted()) {
|
||||
tick();
|
||||
}
|
||||
try {
|
||||
saveNow();
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
public void tick() {
|
||||
DeepSearchJob job = jobs.get(world.getName());
|
||||
// chunkCache(); //todo finish this
|
||||
if (latch.flip() && !job.paused) {
|
||||
if (cacheLock.isLocked()) {
|
||||
Iris.info("DeepFinder: Caching: " + chunkCachePos.get() + " Of " + chunkCacheSize.get());
|
||||
} else {
|
||||
long eta = computeETA();
|
||||
save();
|
||||
int secondGenerated = foundChunks.get() - foundLast.get();
|
||||
foundLast.set(foundChunks.get());
|
||||
secondGenerated = secondGenerated / 3;
|
||||
chunksPerSecond.put(secondGenerated);
|
||||
chunksPerMinute.put(secondGenerated * 60);
|
||||
Iris.info("DeepFinder: " + C.IRIS + world.getName() + C.RESET + " Searching: " + Form.f(foundChunks.get()) + " of " + Form.f(foundTotalChunks.get()) + " " + Form.f((int) chunksPerSecond.getAverage()) + "/s ETA: " + Form.duration((double) eta, 2));
|
||||
}
|
||||
|
||||
}
|
||||
if (foundChunks.get() >= foundTotalChunks.get()) {
|
||||
Iris.info("Completed DeepSearch!");
|
||||
interrupt();
|
||||
}
|
||||
}
|
||||
|
||||
private long computeETA() {
|
||||
return (long) ((foundTotalChunks.get() - foundChunks.get()) / chunksPerSecond.getAverage()) * 1000;
|
||||
// todo broken
|
||||
}
|
||||
|
||||
private final ExecutorService executorService = Executors.newSingleThreadExecutor();
|
||||
|
||||
private void queueSystem(Position2 chunk) {
|
||||
if (chunkQueue.isEmpty()) {
|
||||
for (int limit = 512; limit != 0; limit--) {
|
||||
pos = job.getPosition() + 1;
|
||||
chunkQueue.add(getChunk(pos));
|
||||
}
|
||||
} else {
|
||||
//MCAUtil.read();
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
private void findInChunk(World world, int x, int z) throws IOException {
|
||||
int xx = x * 16;
|
||||
int zz = z * 16;
|
||||
Engine engine = IrisToolbelt.access(world).getEngine();
|
||||
for (int i = 0; i < 16; i++) {
|
||||
for (int j = 0; j < 16; j++) {
|
||||
int height = engine.getHeight(xx + i, zz + j);
|
||||
if (height > 300) {
|
||||
File found = new File("plugins" + "iris" + "found.txt");
|
||||
FileWriter writer = new FileWriter(found);
|
||||
if (!found.exists()) {
|
||||
found.createNewFile();
|
||||
}
|
||||
IrisBiome biome = engine.getBiome(xx, engine.getHeight(), zz);
|
||||
Iris.info("Found at! " + xx + ", " + zz + "Biome ID: " + biome.getName() + ", ");
|
||||
writer.write("Biome at: X: " + xx + " Z: " + zz + "Biome ID: " + biome.getName() + ", ");
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public Position2 getChunk(int position) {
|
||||
int p = -1;
|
||||
AtomicInteger xx = new AtomicInteger();
|
||||
AtomicInteger zz = new AtomicInteger();
|
||||
Spiraler s = new Spiraler(job.getRadiusBlocks() * 2, job.getRadiusBlocks() * 2, (x, z) -> {
|
||||
xx.set(x);
|
||||
zz.set(z);
|
||||
});
|
||||
|
||||
while (s.hasNext() && p++ < position) {
|
||||
s.next();
|
||||
}
|
||||
|
||||
return new Position2(xx.get(), zz.get());
|
||||
}
|
||||
|
||||
public void save() {
|
||||
J.a(() -> {
|
||||
try {
|
||||
saveNow();
|
||||
} catch (Throwable e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public static void setPausedDeep(World world) {
|
||||
DeepSearchJob job = jobs.get(world.getName());
|
||||
if (isPausedDeep(world)){
|
||||
job.paused = false;
|
||||
} else {
|
||||
job.paused = true;
|
||||
}
|
||||
|
||||
if ( job.paused) {
|
||||
Iris.info(C.BLUE + "DeepSearch: " + C.IRIS + world.getName() + C.BLUE + " Paused");
|
||||
} else {
|
||||
Iris.info(C.BLUE + "DeepSearch: " + C.IRIS + world.getName() + C.BLUE + " Resumed");
|
||||
}
|
||||
}
|
||||
|
||||
public static boolean isPausedDeep(World world) {
|
||||
DeepSearchJob job = jobs.get(world.getName());
|
||||
return job != null && job.isPaused();
|
||||
}
|
||||
|
||||
public void shutdownInstance(World world) throws IOException {
|
||||
Iris.info("DeepSearch: " + C.IRIS + world.getName() + C.BLUE + " Shutting down..");
|
||||
DeepSearchJob job = jobs.get(world.getName());
|
||||
File worldDirectory = new File(Bukkit.getWorldContainer(), world.getName());
|
||||
File deepFile = new File(worldDirectory, "DeepSearch.json");
|
||||
|
||||
if (job == null) {
|
||||
Iris.error("No DeepSearch job found for world: " + world.getName());
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
if (!job.isPaused()) {
|
||||
job.setPaused(true);
|
||||
}
|
||||
save();
|
||||
jobs.remove(world.getName());
|
||||
new BukkitRunnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
while (deepFile.exists()){
|
||||
deepFile.delete();
|
||||
J.sleep(1000);
|
||||
}
|
||||
Iris.info("DeepSearch: " + C.IRIS + world.getName() + C.BLUE + " File deleted and instance closed.");
|
||||
}
|
||||
}.runTaskLater(Iris.instance, 20L);
|
||||
} catch (Exception e) {
|
||||
Iris.error("Failed to shutdown DeepSearch for " + world.getName());
|
||||
e.printStackTrace();
|
||||
} finally {
|
||||
saveNow();
|
||||
interrupt();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public void saveNow() throws IOException {
|
||||
IO.writeAll(this.destination, new Gson().toJson(job));
|
||||
}
|
||||
|
||||
@Data
|
||||
@Builder
|
||||
public static class DeepSearchJob {
|
||||
private World world;
|
||||
@Builder.Default
|
||||
private int radiusBlocks = 5000;
|
||||
@Builder.Default
|
||||
private int position = 0;
|
||||
@Builder.Default
|
||||
boolean paused = false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -82,11 +82,8 @@ public class AsyncPregenMethod implements PregeneratorMethod {
|
||||
private void completeChunk(int x, int z, PregenListener listener) {
|
||||
try {
|
||||
future.add(PaperLib.getChunkAtAsync(world, x, z, true).thenApply((i) -> {
|
||||
if (i == null) {
|
||||
|
||||
}
|
||||
Chunk c = Bukkit.getWorld(world.getUID()).getChunkAt(x, z);
|
||||
lastUse.put(c, M.ms());
|
||||
if (i == null) return 0;
|
||||
lastUse.put(i, M.ms());
|
||||
listener.onChunkGenerated(x, z);
|
||||
listener.onChunkCleaned(x, z);
|
||||
return 0;
|
||||
|
||||
@@ -0,0 +1,87 @@
|
||||
package com.volmit.iris.core.pregenerator.methods;
|
||||
|
||||
import com.volmit.iris.Iris;
|
||||
import com.volmit.iris.core.IrisSettings;
|
||||
import com.volmit.iris.core.nms.IHeadless;
|
||||
import com.volmit.iris.core.nms.INMS;
|
||||
import com.volmit.iris.core.pregenerator.PregenListener;
|
||||
import com.volmit.iris.core.pregenerator.PregeneratorMethod;
|
||||
import com.volmit.iris.engine.framework.Engine;
|
||||
import com.volmit.iris.util.mantle.Mantle;
|
||||
import com.volmit.iris.util.parallel.MultiBurst;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.concurrent.Semaphore;
|
||||
|
||||
public class HeadlessPregenMethod implements PregeneratorMethod {
|
||||
private final Engine engine;
|
||||
private final IHeadless headless;
|
||||
private final Semaphore semaphore;
|
||||
private final int max;
|
||||
|
||||
public HeadlessPregenMethod(Engine engine) {
|
||||
this.max = IrisSettings.getThreadCount(IrisSettings.get().getConcurrency().getParallelism());
|
||||
this.engine = engine;
|
||||
this.headless = INMS.get().createHeadless(engine);
|
||||
this.semaphore = new Semaphore(max);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void init() {}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
try {
|
||||
semaphore.acquire(max);
|
||||
} catch (InterruptedException ignored) {}
|
||||
headless.save();
|
||||
try {
|
||||
headless.close();
|
||||
} catch (IOException e) {
|
||||
Iris.error("Failed to close headless");
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void save() {
|
||||
headless.save();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supportsRegions(int x, int z, PregenListener listener) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getMethod(int x, int z) {
|
||||
return "Headless";
|
||||
}
|
||||
|
||||
@Override
|
||||
public void generateRegion(int x, int z, PregenListener listener) {}
|
||||
|
||||
@Override
|
||||
public void generateChunk(int x, int z, PregenListener listener) {
|
||||
try {
|
||||
semaphore.acquire();
|
||||
} catch (InterruptedException ignored) {
|
||||
semaphore.release();
|
||||
return;
|
||||
}
|
||||
MultiBurst.burst.complete(() -> {
|
||||
try {
|
||||
listener.onChunkGenerating(x, z);
|
||||
headless.generateChunk(x, z);
|
||||
listener.onChunkGenerated(x, z);
|
||||
} finally {
|
||||
semaphore.release();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mantle getMantle() {
|
||||
return engine.getMantle().getMantle();
|
||||
}
|
||||
}
|
||||
@@ -119,8 +119,7 @@ public class MedievalPregenMethod implements PregeneratorMethod {
|
||||
|
||||
listener.onChunkGenerating(x, z);
|
||||
futures.add(J.sfut(() -> {
|
||||
world.getChunkAt(x, z);
|
||||
Chunk c = Bukkit.getWorld(world.getUID()).getChunkAt(x, z);
|
||||
Chunk c = world.getChunkAt(x, z);
|
||||
lastUse.put(c, M.ms());
|
||||
listener.onChunkGenerated(x, z);
|
||||
listener.onChunkCleaned(x, z);
|
||||
|
||||
@@ -1,15 +1,71 @@
|
||||
package com.volmit.iris.core.safeguard;
|
||||
|
||||
import com.volmit.iris.Iris;
|
||||
import com.volmit.iris.core.IrisSettings;
|
||||
import com.volmit.iris.core.nms.INMS;
|
||||
import com.volmit.iris.core.safeguard.handler.onCommandWarning;
|
||||
import com.volmit.iris.util.collection.KList;
|
||||
import com.volmit.iris.util.format.C;
|
||||
import com.volmit.iris.util.format.Form;
|
||||
import com.volmit.iris.util.misc.getHardware;
|
||||
|
||||
import static org.bukkit.Bukkit.getServer;
|
||||
|
||||
public class IrisSafeguard {
|
||||
public static boolean unstablemode = false;
|
||||
public static boolean warningmode = false;
|
||||
public static boolean stablemode = false;
|
||||
public static IrisSafeguard instance;
|
||||
public boolean acceptUnstable = false;
|
||||
public boolean unstablemode = false;
|
||||
public boolean warningmode = false;
|
||||
public boolean stablemode = false;
|
||||
|
||||
public static void IrisSafeguardSystem() {
|
||||
public static void InitializeSafeguard() {
|
||||
instance = new IrisSafeguard();
|
||||
}
|
||||
|
||||
public void IrisSafeguardSystem() {
|
||||
acceptUnstable = IrisSettings.get().getSafeguard().ignoreBootMode;
|
||||
getServer().getPluginManager().registerEvents(new onCommandWarning(), Iris.instance);
|
||||
Iris.info("Enabled Iris SafeGuard");
|
||||
ServerBootSFG.BootCheck();
|
||||
}
|
||||
|
||||
public void earlySplash() {
|
||||
String padd = Form.repeat(" ", 8);
|
||||
String padd2 = Form.repeat(" ", 4);
|
||||
String[] info = new String[]{"", "", "", "", "", padd2 + C.RED + " Iris", padd2 + C.GRAY + " by " + C.DARK_RED + "Volmit Software", padd2 + C.GRAY + " v" + C.RED + Iris.instance.getDescription().getVersion()};
|
||||
String[] splashunstable = {
|
||||
padd + C.GRAY + " @@@@@@@@@@@@@@" + C.DARK_GRAY + "@@@",
|
||||
padd + C.GRAY + " @@&&&&&&&&&" + C.DARK_GRAY + "&&&&&&" + C.RED + " .(((()))). ",
|
||||
padd + C.GRAY + "@@@&&&&&&&&" + C.DARK_GRAY + "&&&&&" + C.RED + " .((((((())))))). ",
|
||||
padd + C.GRAY + "@@@&&&&&" + C.DARK_GRAY + "&&&&&&&" + C.RED + " ((((((((())))))))) " + C.GRAY + " @",
|
||||
padd + C.GRAY + "@@@&&&&" + C.DARK_GRAY + "@@@@@&" + C.RED + " ((((((((-))))))))) " + C.GRAY + " @@",
|
||||
padd + C.GRAY + "@@@&&" + C.RED + " ((((((({ })))))))) " + C.GRAY + " &&@@@",
|
||||
padd + C.GRAY + "@@" + C.RED + " ((((((((-))))))))) " + C.DARK_GRAY + "&@@@@@" + C.GRAY + "&&&&@@@",
|
||||
padd + C.GRAY + "@" + C.RED + " ((((((((())))))))) " + C.DARK_GRAY + "&&&&&" + C.GRAY + "&&&&&&&@@@",
|
||||
padd + C.GRAY + "" + C.RED + " '((((((()))))))' " + C.DARK_GRAY + "&&&&&" + C.GRAY + "&&&&&&&&@@@",
|
||||
padd + C.GRAY + "" + C.RED + " '(((())))' " + C.DARK_GRAY + "&&&&&&&&" + C.GRAY + "&&&&&&&@@",
|
||||
padd + C.GRAY + " " + C.DARK_GRAY + "@@@" + C.GRAY + "@@@@@@@@@@@@@@"
|
||||
};
|
||||
|
||||
for (int i = 0; i < info.length; i++) {
|
||||
splashunstable[i] += info[i];
|
||||
}
|
||||
Iris.info("Java: " + Iris.instance.getJava());
|
||||
if (!Iris.instance.getServer().getVersion().contains("Purpur")) {
|
||||
if (Iris.instance.getServer().getVersion().contains("Spigot") && Iris.instance.getServer().getVersion().contains("Bukkit")) {
|
||||
Iris.info(C.RED + " Iris requires paper or above to function properly..");
|
||||
} else {
|
||||
Iris.info(C.YELLOW + "Purpur is recommended to use with iris.");
|
||||
}
|
||||
}
|
||||
if (getHardware.getProcessMemory() < 5999) {
|
||||
Iris.warn("6GB+ Ram is recommended");
|
||||
Iris.warn("Process Memory: " + getHardware.getProcessMemory() + " MB");
|
||||
}
|
||||
Iris.info("Custom Biomes: " + INMS.get().countCustomBiomes());
|
||||
Iris.info("\n\n " + new KList<>(splashunstable).toString("\n") + "\n");
|
||||
UtilsSFG.splash();
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -3,18 +3,20 @@ package com.volmit.iris.core.safeguard;
|
||||
import com.volmit.iris.Iris;
|
||||
import com.volmit.iris.core.IrisSettings;
|
||||
import com.volmit.iris.util.format.C;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.scheduler.BukkitScheduler;
|
||||
|
||||
public class ModesSFG {
|
||||
public static void selectMode() {
|
||||
if (IrisSafeguard.unstablemode) {
|
||||
if (IrisSafeguard.instance.unstablemode) {
|
||||
Iris.safeguard(C.DARK_RED + "Iris is running in Unstable Mode");
|
||||
unstable();
|
||||
}
|
||||
if (IrisSafeguard.warningmode) {
|
||||
if (IrisSafeguard.instance.warningmode) {
|
||||
Iris.safeguard(C.GOLD + "Iris is running in Warning Mode");
|
||||
warning();
|
||||
}
|
||||
if (IrisSafeguard.stablemode) {
|
||||
if (IrisSafeguard.instance.stablemode) {
|
||||
stable();
|
||||
}
|
||||
}
|
||||
@@ -27,7 +29,7 @@ public class ModesSFG {
|
||||
|
||||
UtilsSFG.printIncompatibleWarnings();
|
||||
|
||||
if (IrisSafeguard.unstablemode) {
|
||||
if (IrisSafeguard.instance.unstablemode) {
|
||||
Iris.info("");
|
||||
Iris.info(C.DARK_GRAY + "--==<" + C.RED + " IMPORTANT " + C.DARK_GRAY + ">==--");
|
||||
Iris.info(C.RED + "Iris is running in unstable mode which may cause the following issues:");
|
||||
@@ -44,27 +46,26 @@ public class ModesSFG {
|
||||
Iris.info(C.DARK_RED + "ATTENTION: " + C.RED + "While running Iris in unstable mode, you won't be eligible for support.");
|
||||
Iris.info(C.DARK_RED + "CAUSE: " + C.RED + UtilsSFG.MSGIncompatibleWarnings());
|
||||
|
||||
if (IrisSettings.get().getGeneral().ignoreBootMode) {
|
||||
if (IrisSettings.get().getSafeguard().ignoreBootMode) {
|
||||
Iris.info(C.DARK_RED + "Boot Unstable is set to true, continuing with the startup process.");
|
||||
} else {
|
||||
Iris.info(C.DARK_RED + "Go to plugins/iris/settings.json and set ignoreBootMode to true if you wish to proceed.");
|
||||
while (true) {
|
||||
try {
|
||||
Thread.sleep(1000);
|
||||
} catch (InterruptedException e) {
|
||||
// no
|
||||
}
|
||||
Iris.info(C.DARK_RED + "Shutting down server in " + C.UNDERLINE + "" + C.DARK_RED + "50 Seconds");
|
||||
try {
|
||||
Thread.sleep(50000);
|
||||
Bukkit.shutdown();
|
||||
} catch (Exception ignored) {
|
||||
}
|
||||
}
|
||||
Iris.info("");
|
||||
}
|
||||
Iris.info("");
|
||||
}
|
||||
|
||||
public static void warning() {
|
||||
|
||||
UtilsSFG.printIncompatibleWarnings();
|
||||
|
||||
if (IrisSafeguard.warningmode) {
|
||||
if (IrisSafeguard.instance.warningmode) {
|
||||
Iris.info("");
|
||||
Iris.info(C.DARK_GRAY + "--==<" + C.GOLD + " IMPORTANT " + C.DARK_GRAY + ">==--");
|
||||
Iris.info(C.GOLD + "Iris is running in warning mode which may cause the following issues:");
|
||||
|
||||
@@ -1,8 +0,0 @@
|
||||
package com.volmit.iris.core.safeguard;
|
||||
|
||||
public class PerformanceSFG {
|
||||
public static void calculatePerformance() {
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
@@ -116,16 +116,16 @@ public class ServerBootSFG {
|
||||
safeguardPassed = (severityHigh == 0 && severityMedium == 0 && severityLow == 0);
|
||||
count = severityHigh + severityMedium + severityLow;
|
||||
if (safeguardPassed) {
|
||||
stablemode = true;
|
||||
IrisSafeguard.instance.stablemode = true;
|
||||
Iris.safeguard("Stable mode has been activated.");
|
||||
}
|
||||
if (!safeguardPassed) {
|
||||
if (severityMedium >= 1 && severityHigh == 0) {
|
||||
warningmode = true;
|
||||
IrisSafeguard.instance.warningmode = true;
|
||||
Iris.safeguard("Warning mode has been activated.");
|
||||
}
|
||||
if (severityHigh >= 1) {
|
||||
unstablemode = true;
|
||||
IrisSafeguard.instance.unstablemode = true;
|
||||
Iris.safeguard("Unstable mode has been activated.");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,10 +14,10 @@ public class UtilsSFG {
|
||||
if (ServerBootSFG.safeguardPassed) {
|
||||
Iris.safeguard(C.BLUE + "0 Conflicts found");
|
||||
} else {
|
||||
if (IrisSafeguard.unstablemode) {
|
||||
if (IrisSafeguard.instance.unstablemode) {
|
||||
Iris.safeguard(C.DARK_RED + "" + ServerBootSFG.count + " Conflicts found");
|
||||
}
|
||||
if (IrisSafeguard.warningmode) {
|
||||
if (IrisSafeguard.instance.warningmode) {
|
||||
Iris.safeguard(C.YELLOW + "" + ServerBootSFG.count + " Conflicts found");
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
package com.volmit.iris.core.safeguard.handler;
|
||||
|
||||
import com.volmit.iris.core.IrisSettings;
|
||||
import com.volmit.iris.core.safeguard.IrisSafeguard;
|
||||
import com.volmit.iris.util.format.C;
|
||||
import com.volmit.iris.util.plugin.VolmitSender;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.event.EventHandler;
|
||||
import org.bukkit.event.Listener;
|
||||
import org.bukkit.event.player.PlayerCommandPreprocessEvent;
|
||||
|
||||
public class onCommandWarning implements Listener {
|
||||
@EventHandler
|
||||
public void onPlayerCommandPreprocess(PlayerCommandPreprocessEvent event) {
|
||||
if (IrisSettings.get().getSafeguard().userUnstableWarning && IrisSafeguard.instance.unstablemode) {
|
||||
String command = event.getMessage();
|
||||
Player player = event.getPlayer();
|
||||
if (command.startsWith("/iris")) {
|
||||
VolmitSender sender = new VolmitSender(player);
|
||||
boolean perm = sender.hasPermission("iris.all") || sender.isOp();
|
||||
if (perm) {
|
||||
sender.sendMessage(C.DARK_GRAY + "[" + C.RED + "!" + C.DARK_GRAY+ "]" + C.DARK_RED + "Iris is running unstably! Please resolve this.");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -22,7 +22,6 @@ import com.google.gson.Gson;
|
||||
import com.google.gson.JsonSyntaxException;
|
||||
import com.volmit.iris.Iris;
|
||||
import com.volmit.iris.core.IrisSettings;
|
||||
import com.volmit.iris.core.ServerConfigurator;
|
||||
import com.volmit.iris.core.loader.IrisData;
|
||||
import com.volmit.iris.core.pack.IrisPack;
|
||||
import com.volmit.iris.core.project.IrisProject;
|
||||
@@ -296,7 +295,6 @@ public class StudioSVC implements IrisService {
|
||||
}
|
||||
|
||||
sender.sendMessage("Successfully Aquired " + d.getName());
|
||||
ServerConfigurator.installDataPacks(true);
|
||||
}
|
||||
|
||||
public KMap<String, String> getListing(boolean cached) {
|
||||
|
||||
@@ -21,13 +21,10 @@ package com.volmit.iris.core.tools;
|
||||
import com.google.common.util.concurrent.AtomicDouble;
|
||||
import com.volmit.iris.Iris;
|
||||
import com.volmit.iris.core.IrisSettings;
|
||||
import com.volmit.iris.core.ServerConfigurator;
|
||||
import com.volmit.iris.core.pregenerator.PregenTask;
|
||||
import com.volmit.iris.core.service.StudioSVC;
|
||||
import com.volmit.iris.engine.framework.Engine;
|
||||
import com.volmit.iris.engine.object.IrisDimension;
|
||||
import com.volmit.iris.engine.platform.PlatformChunkGenerator;
|
||||
import com.volmit.iris.core.safeguard.UtilsSFG;
|
||||
import com.volmit.iris.util.exceptions.IrisException;
|
||||
import com.volmit.iris.util.format.C;
|
||||
import com.volmit.iris.util.format.Form;
|
||||
@@ -46,7 +43,6 @@ import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
import java.util.function.Supplier;
|
||||
import static com.volmit.iris.core.safeguard.IrisSafeguard.unstablemode;
|
||||
|
||||
/**
|
||||
* Makes it a lot easier to setup an engine, world, studio or whatever
|
||||
@@ -145,7 +141,6 @@ public class IrisCreator {
|
||||
.studio(studio)
|
||||
.smartVanillaHeight(smartVanillaHeight)
|
||||
.create();
|
||||
ServerConfigurator.installDataPacks(false);
|
||||
|
||||
access = (PlatformChunkGenerator) wc.generator();
|
||||
PlatformChunkGenerator finalAccess1 = access;
|
||||
|
||||
@@ -0,0 +1,254 @@
|
||||
package com.volmit.iris.core.tools;
|
||||
|
||||
import com.volmit.iris.Iris;
|
||||
import com.volmit.iris.core.loader.IrisData;
|
||||
import com.volmit.iris.core.project.IrisProject;
|
||||
import com.volmit.iris.engine.object.*;
|
||||
import com.volmit.iris.util.collection.KList;
|
||||
import com.volmit.iris.util.collection.KMap;
|
||||
import com.volmit.iris.util.decree.DecreeOrigin;
|
||||
import com.volmit.iris.util.decree.annotations.Decree;
|
||||
import com.volmit.iris.util.decree.annotations.Param;
|
||||
import com.volmit.iris.util.format.C;
|
||||
import com.volmit.iris.util.format.Form;
|
||||
import com.volmit.iris.util.function.NoiseProvider;
|
||||
import com.volmit.iris.util.interpolation.InterpolationMethod;
|
||||
import com.volmit.iris.util.io.IO;
|
||||
import com.volmit.iris.util.math.RNG;
|
||||
import com.volmit.iris.util.noise.CNG;
|
||||
import com.volmit.iris.util.plugin.VolmitSender;
|
||||
import com.volmit.iris.util.scheduling.PrecisionStopwatch;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
|
||||
public class IrisNoiseBenchmark {
|
||||
private IrisDimension dimension;
|
||||
private VolmitSender sender;
|
||||
|
||||
public IrisNoiseBenchmark(IrisDimension dimension, VolmitSender sender) {
|
||||
this.dimension = dimension;
|
||||
this.sender = sender;
|
||||
}
|
||||
|
||||
public void runAll() {
|
||||
// Todo: Make this more accurate
|
||||
File pack = dimension.getLoadFile().getParentFile().getParentFile();
|
||||
File report = Iris.instance.getDataFile(pack.getName() + "-profile.txt");
|
||||
IrisProject project = new IrisProject(pack);
|
||||
IrisData data = IrisData.get(pack);
|
||||
|
||||
KList<String> fileText = new KList<>();
|
||||
|
||||
KMap<NoiseStyle, Double> styleTimings = new KMap<>();
|
||||
KMap<InterpolationMethod, Double> interpolatorTimings = new KMap<>();
|
||||
KMap<String, Double> generatorTimings = new KMap<>();
|
||||
KMap<String, Double> biomeTimings = new KMap<>();
|
||||
KMap<String, Double> regionTimings = new KMap<>();
|
||||
KMap<String, Double> dimensionTimings = new KMap<>();
|
||||
|
||||
sender.sendMessage("Calculating Performance Metrics for Noise generators");
|
||||
|
||||
for (NoiseStyle i : NoiseStyle.values()) {
|
||||
CNG c = i.create(new RNG(i.hashCode()));
|
||||
|
||||
for (int j = 0; j < 3000; j++) {
|
||||
c.noise(j, j + 1000, j * j);
|
||||
c.noise(j, -j);
|
||||
}
|
||||
|
||||
PrecisionStopwatch px = PrecisionStopwatch.start();
|
||||
|
||||
for (int j = 0; j < 100000; j++) {
|
||||
c.noise(j, j + 1000, j * j);
|
||||
c.noise(j, -j);
|
||||
}
|
||||
|
||||
styleTimings.put(i, px.getMilliseconds());
|
||||
}
|
||||
|
||||
fileText.add("Noise Style Performance Impacts: ");
|
||||
|
||||
for (NoiseStyle i : styleTimings.sortKNumber()) {
|
||||
fileText.add(i.name() + ": " + styleTimings.get(i));
|
||||
}
|
||||
|
||||
fileText.add("");
|
||||
|
||||
sender.sendMessage("Calculating Interpolator Timings...");
|
||||
|
||||
for (InterpolationMethod i : InterpolationMethod.values()) {
|
||||
IrisInterpolator in = new IrisInterpolator();
|
||||
in.setFunction(i);
|
||||
in.setHorizontalScale(8);
|
||||
|
||||
NoiseProvider np = (x, z) -> Math.random();
|
||||
|
||||
for (int j = 0; j < 3000; j++) {
|
||||
in.interpolate(j, -j, np);
|
||||
}
|
||||
|
||||
PrecisionStopwatch px = PrecisionStopwatch.start();
|
||||
|
||||
for (int j = 0; j < 100000; j++) {
|
||||
in.interpolate(j + 10000, -j - 100000, np);
|
||||
}
|
||||
|
||||
interpolatorTimings.put(i, px.getMilliseconds());
|
||||
}
|
||||
|
||||
fileText.add("Noise Interpolator Performance Impacts: ");
|
||||
|
||||
for (InterpolationMethod i : interpolatorTimings.sortKNumber()) {
|
||||
fileText.add(i.name() + ": " + interpolatorTimings.get(i));
|
||||
}
|
||||
|
||||
fileText.add("");
|
||||
|
||||
sender.sendMessage("Processing Generator Scores: ");
|
||||
|
||||
KMap<String, KList<String>> btx = new KMap<>();
|
||||
|
||||
for (String i : data.getGeneratorLoader().getPossibleKeys()) {
|
||||
KList<String> vv = new KList<>();
|
||||
IrisGenerator g = data.getGeneratorLoader().load(i);
|
||||
KList<IrisNoiseGenerator> composites = g.getAllComposites();
|
||||
double score = 0;
|
||||
int m = 0;
|
||||
for (IrisNoiseGenerator j : composites) {
|
||||
m++;
|
||||
score += styleTimings.get(j.getStyle().getStyle());
|
||||
vv.add("Composite Noise Style " + m + " " + j.getStyle().getStyle().name() + ": " + styleTimings.get(j.getStyle().getStyle()));
|
||||
}
|
||||
|
||||
score += interpolatorTimings.get(g.getInterpolator().getFunction());
|
||||
vv.add("Interpolator " + g.getInterpolator().getFunction().name() + ": " + interpolatorTimings.get(g.getInterpolator().getFunction()));
|
||||
generatorTimings.put(i, score);
|
||||
btx.put(i, vv);
|
||||
}
|
||||
|
||||
fileText.add("Project Generator Performance Impacts: ");
|
||||
|
||||
for (String i : generatorTimings.sortKNumber()) {
|
||||
fileText.add(i + ": " + generatorTimings.get(i));
|
||||
|
||||
btx.get(i).forEach((ii) -> fileText.add(" " + ii));
|
||||
}
|
||||
|
||||
fileText.add("");
|
||||
|
||||
KMap<String, KList<String>> bt = new KMap<>();
|
||||
|
||||
for (String i : data.getBiomeLoader().getPossibleKeys()) {
|
||||
KList<String> vv = new KList<>();
|
||||
IrisBiome b = data.getBiomeLoader().load(i);
|
||||
double score = 0;
|
||||
|
||||
int m = 0;
|
||||
for (IrisBiomePaletteLayer j : b.getLayers()) {
|
||||
m++;
|
||||
score += styleTimings.get(j.getStyle().getStyle());
|
||||
vv.add("Palette Layer " + m + ": " + styleTimings.get(j.getStyle().getStyle()));
|
||||
}
|
||||
|
||||
score += styleTimings.get(b.getBiomeStyle().getStyle());
|
||||
vv.add("Biome Style: " + styleTimings.get(b.getBiomeStyle().getStyle()));
|
||||
score += styleTimings.get(b.getChildStyle().getStyle());
|
||||
vv.add("Child Style: " + styleTimings.get(b.getChildStyle().getStyle()));
|
||||
biomeTimings.put(i, score);
|
||||
bt.put(i, vv);
|
||||
}
|
||||
|
||||
fileText.add("Project Biome Performance Impacts: ");
|
||||
|
||||
for (String i : biomeTimings.sortKNumber()) {
|
||||
fileText.add(i + ": " + biomeTimings.get(i));
|
||||
|
||||
bt.get(i).forEach((ff) -> fileText.add(" " + ff));
|
||||
}
|
||||
|
||||
fileText.add("");
|
||||
|
||||
for (String i : data.getRegionLoader().getPossibleKeys()) {
|
||||
IrisRegion b = data.getRegionLoader().load(i);
|
||||
double score = 0;
|
||||
|
||||
score += styleTimings.get(b.getLakeStyle().getStyle());
|
||||
score += styleTimings.get(b.getRiverStyle().getStyle());
|
||||
regionTimings.put(i, score);
|
||||
}
|
||||
|
||||
fileText.add("Project Region Performance Impacts: ");
|
||||
|
||||
for (String i : regionTimings.sortKNumber()) {
|
||||
fileText.add(i + ": " + regionTimings.get(i));
|
||||
}
|
||||
|
||||
fileText.add("");
|
||||
|
||||
double m = 0;
|
||||
for (double i : biomeTimings.v()) {
|
||||
m += i;
|
||||
}
|
||||
m /= biomeTimings.size();
|
||||
double mm = 0;
|
||||
for (double i : generatorTimings.v()) {
|
||||
mm += i;
|
||||
}
|
||||
mm /= generatorTimings.size();
|
||||
m += mm;
|
||||
double mmm = 0;
|
||||
for (double i : regionTimings.v()) {
|
||||
mmm += i;
|
||||
}
|
||||
mmm /= regionTimings.size();
|
||||
m += mmm;
|
||||
|
||||
KMap<String, KList<String>> dt = new KMap<>();
|
||||
|
||||
for (String i : data.getDimensionLoader().getPossibleKeys()) {
|
||||
IrisDimension d = data.getDimensionLoader().load(i);
|
||||
KList<String> vv = new KList<>();
|
||||
double score = 0;
|
||||
|
||||
score += styleTimings.get(d.getContinentalStyle().getStyle());
|
||||
vv.add("Continental Style: " + styleTimings.get(d.getContinentalStyle().getStyle()));
|
||||
score += styleTimings.get(d.getRegionStyle().getStyle());
|
||||
vv.add("Region Style: " + styleTimings.get(d.getRegionStyle().getStyle()));
|
||||
score += styleTimings.get(d.getBiomeStyle(InferredType.LAND).getStyle());
|
||||
vv.add("LandBiome Style: " + styleTimings.get(d.getBiomeStyle(InferredType.LAND).getStyle()));
|
||||
score += styleTimings.get(d.getBiomeStyle(InferredType.SEA).getStyle());
|
||||
vv.add("OceanBiome Style: " + styleTimings.get(d.getBiomeStyle(InferredType.SEA).getStyle()));
|
||||
score += styleTimings.get(d.getCaveBiomeStyle().getStyle());
|
||||
vv.add("CaveBiome Style: " + styleTimings.get(d.getCaveBiomeStyle().getStyle()));
|
||||
score += styleTimings.get(d.getShoreBiomeStyle().getStyle());
|
||||
vv.add("ShoreBiome Style: " + styleTimings.get(d.getShoreBiomeStyle().getStyle()));
|
||||
dimensionTimings.put(i, score);
|
||||
dt.put(i, vv);
|
||||
|
||||
}
|
||||
|
||||
fileText.add("Project Dimension Performance Impacts: ");
|
||||
|
||||
for (String i : dimensionTimings.sortKNumber()) {
|
||||
fileText.add(i + ": " + dimensionTimings.get(i));
|
||||
|
||||
dt.get(i).forEach((ff) -> fileText.add(" " + ff));
|
||||
}
|
||||
|
||||
fileText.add("");
|
||||
|
||||
fileText.add("Average Score: " + m);
|
||||
sender.sendMessage("Score: " + Form.duration(m, 0));
|
||||
|
||||
try {
|
||||
IO.writeAll(report, fileText.toString("\n"));
|
||||
} catch (IOException e) {
|
||||
Iris.reportError(e);
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
sender.sendMessage(C.GREEN + "Done! " + report.getPath());
|
||||
|
||||
}
|
||||
}
|
||||
@@ -2,9 +2,17 @@ package com.volmit.iris.core.tools;
|
||||
|
||||
|
||||
import com.volmit.iris.Iris;
|
||||
import com.volmit.iris.core.IrisSettings;
|
||||
import com.volmit.iris.core.loader.IrisData;
|
||||
import com.volmit.iris.core.pregenerator.PregenTask;
|
||||
import com.volmit.iris.core.pregenerator.methods.HeadlessPregenMethod;
|
||||
import com.volmit.iris.core.pregenerator.methods.HybridPregenMethod;
|
||||
import com.volmit.iris.core.service.StudioSVC;
|
||||
import com.volmit.iris.engine.IrisEngine;
|
||||
import com.volmit.iris.engine.framework.Engine;
|
||||
import com.volmit.iris.engine.framework.EngineTarget;
|
||||
import com.volmit.iris.engine.object.IrisDimension;
|
||||
import com.volmit.iris.engine.object.IrisWorld;
|
||||
import com.volmit.iris.util.collection.KList;
|
||||
import com.volmit.iris.util.collection.KMap;
|
||||
import com.volmit.iris.util.exceptions.IrisException;
|
||||
@@ -36,28 +44,32 @@ public class IrisPackBenchmarking {
|
||||
public static boolean benchmarkInProgress = false;
|
||||
private IrisDimension IrisDimension;
|
||||
private int radius;
|
||||
private final boolean headless;
|
||||
private final boolean gui;
|
||||
private boolean finished = false;
|
||||
private Engine engine;
|
||||
PrecisionStopwatch stopwatch;
|
||||
|
||||
public IrisPackBenchmarking(IrisDimension dimension, int r) {
|
||||
public IrisPackBenchmarking(IrisDimension dimension, int r, boolean headless, boolean gui) {
|
||||
instance = this;
|
||||
this.IrisDimension = dimension;
|
||||
this.radius = r;
|
||||
runBenchmark();
|
||||
this.headless = headless;
|
||||
this.gui = gui;
|
||||
}
|
||||
|
||||
private void runBenchmark() {
|
||||
public void runBenchmark() {
|
||||
this.stopwatch = new PrecisionStopwatch();
|
||||
ExecutorService service = Executors.newSingleThreadExecutor();
|
||||
service.submit(() -> {
|
||||
Iris.info("Setting up benchmark environment ");
|
||||
benchmarkInProgress = true;
|
||||
File file = new File("benchmark");
|
||||
File file = new File(Bukkit.getWorldContainer(), "benchmark");
|
||||
if (file.exists()) {
|
||||
deleteDirectory(file.toPath());
|
||||
}
|
||||
createBenchmark();
|
||||
while (!IrisToolbelt.isIrisWorld(Bukkit.getWorld("benchmark"))) {
|
||||
engine = createBenchmark();
|
||||
while (!headless && !IrisToolbelt.isIrisWorld(Bukkit.getWorld("benchmark"))) {
|
||||
J.sleep(1000);
|
||||
Iris.debug("Iris PackBenchmark: Waiting...");
|
||||
}
|
||||
@@ -75,7 +87,6 @@ public class IrisPackBenchmarking {
|
||||
public void finishedBenchmark(KList<Integer> cps) {
|
||||
try {
|
||||
String time = Form.duration(stopwatch.getMillis());
|
||||
Engine engine = IrisToolbelt.access(Bukkit.getWorld("benchmark")).getEngine();
|
||||
Iris.info("-----------------");
|
||||
Iris.info("Results:");
|
||||
Iris.info("- Total time: " + time);
|
||||
@@ -88,8 +99,8 @@ public class IrisPackBenchmarking {
|
||||
File profilers = new File("plugins" + File.separator + "Iris" + File.separator + "packbenchmarks");
|
||||
profilers.mkdir();
|
||||
|
||||
File results = new File("plugins " + File.separator + "Iris", IrisDimension.getName() + LocalDateTime.now(Clock.systemDefaultZone()) + ".txt");
|
||||
results.createNewFile();
|
||||
File results = new File("plugins" + File.separator + "Iris", IrisDimension.getName() + " " + LocalDateTime.now(Clock.systemDefaultZone()).toString().replace(':', '-') + ".txt");
|
||||
results.getParentFile().mkdirs();
|
||||
KMap<String, Double> metrics = engine.getMetrics().pull();
|
||||
try (FileWriter writer = new FileWriter(results)) {
|
||||
writer.write("-----------------\n");
|
||||
@@ -123,15 +134,34 @@ public class IrisPackBenchmarking {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
private void createBenchmark(){
|
||||
private Engine createBenchmark(){
|
||||
try {
|
||||
IrisToolbelt.createWorld()
|
||||
.dimension(IrisDimension.getName())
|
||||
if (headless) {
|
||||
Iris.info("Using headless benchmark!");
|
||||
IrisWorld world = IrisWorld.builder()
|
||||
.name("benchmark")
|
||||
.minHeight(IrisDimension.getMinHeight())
|
||||
.maxHeight(IrisDimension.getMaxHeight())
|
||||
.seed(1337)
|
||||
.worldFolder(new File(Bukkit.getWorldContainer(), "benchmark"))
|
||||
.environment(IrisDimension.getEnvironment())
|
||||
.build();
|
||||
Iris.service(StudioSVC.class).installIntoWorld(
|
||||
Iris.getSender(),
|
||||
IrisDimension.getLoadKey(),
|
||||
world.worldFolder());
|
||||
var data = IrisData.get(new File(world.worldFolder(), "iris/pack"));
|
||||
var dim = data.getDimensionLoader().load(IrisDimension.getLoadKey());
|
||||
return new IrisEngine(new EngineTarget(world, dim, data), false);
|
||||
}
|
||||
Iris.info("Using Standard benchmark!");
|
||||
return IrisToolbelt.access(IrisToolbelt.createWorld()
|
||||
.dimension(IrisDimension.getLoadKey())
|
||||
.name("benchmark")
|
||||
.seed(1337)
|
||||
.studio(false)
|
||||
.benchmark(true)
|
||||
.create();
|
||||
.create()).getEngine();
|
||||
} catch (IrisException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
@@ -142,12 +172,12 @@ public class IrisPackBenchmarking {
|
||||
int z = 0;
|
||||
IrisToolbelt.pregenerate(PregenTask
|
||||
.builder()
|
||||
.gui(false)
|
||||
.gui(gui)
|
||||
.center(new Position2(x, z))
|
||||
.width(5)
|
||||
.height(5)
|
||||
.build(), Bukkit.getWorld("benchmark")
|
||||
);
|
||||
.build(), headless ? new HeadlessPregenMethod(engine) : new HybridPregenMethod(engine.getWorld().realWorld(),
|
||||
IrisSettings.getThreadCount(IrisSettings.get().getConcurrency().getParallelism())), engine);
|
||||
}
|
||||
|
||||
private double calculateAverage(KList<Integer> list) {
|
||||
|
||||
@@ -19,6 +19,7 @@
|
||||
package com.volmit.iris.core.tools;
|
||||
|
||||
import com.volmit.iris.core.loader.IrisData;
|
||||
import com.volmit.iris.core.nms.INMS;
|
||||
import com.volmit.iris.engine.object.*;
|
||||
import com.volmit.iris.engine.platform.BukkitChunkGenerator;
|
||||
import org.bukkit.Bukkit;
|
||||
@@ -84,6 +85,9 @@ public class IrisWorldCreator {
|
||||
? dim.getLoader().getDataFolder() :
|
||||
new File(w.worldFolder(), "iris/pack"), dimensionName, smartVanillaHeight);
|
||||
|
||||
if (!INMS.get().registerDimension(name, dim)) {
|
||||
throw new IllegalStateException("Unable to register dimension " + dim.getName());
|
||||
}
|
||||
|
||||
return new WorldCreator(name)
|
||||
.environment(findEnvironment())
|
||||
|
||||
241
core/src/main/java/com/volmit/iris/core/tools/IrisWorldDump.java
Normal file
241
core/src/main/java/com/volmit/iris/core/tools/IrisWorldDump.java
Normal file
@@ -0,0 +1,241 @@
|
||||
package com.volmit.iris.core.tools;
|
||||
|
||||
import com.volmit.iris.Iris;
|
||||
import com.volmit.iris.core.IrisSettings;
|
||||
import com.volmit.iris.engine.framework.Engine;
|
||||
import com.volmit.iris.util.collection.KList;
|
||||
import com.volmit.iris.util.collection.KMap;
|
||||
import com.volmit.iris.util.format.Form;
|
||||
import com.volmit.iris.util.math.M;
|
||||
import com.volmit.iris.util.math.RollingSequence;
|
||||
import com.volmit.iris.util.nbt.mca.Chunk;
|
||||
import com.volmit.iris.util.nbt.mca.MCAFile;
|
||||
import com.volmit.iris.util.nbt.mca.MCAUtil;
|
||||
import com.volmit.iris.util.nbt.tag.CompoundTag;
|
||||
import com.volmit.iris.util.nbt.tag.StringTag;
|
||||
import com.volmit.iris.util.plugin.VolmitSender;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
import org.bukkit.World;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.concurrent.*;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.concurrent.atomic.AtomicLong;
|
||||
import java.util.concurrent.atomic.AtomicReferenceArray;
|
||||
|
||||
public class IrisWorldDump {
|
||||
private KMap<blockData, Long> storage;
|
||||
private AtomicLong airStorage;
|
||||
private World world;
|
||||
private File MCADirectory;
|
||||
private AtomicInteger threads;
|
||||
private AtomicInteger regionsProcessed;
|
||||
private AtomicInteger chunksProcessed;
|
||||
private AtomicInteger totalToProcess;
|
||||
private AtomicInteger totalMaxChunks;
|
||||
private AtomicInteger totalMCAFiles;
|
||||
private RollingSequence chunksPerSecond;
|
||||
private Engine engine = null;
|
||||
private Boolean IrisWorld;
|
||||
private VolmitSender sender;
|
||||
private ExecutorService executor;
|
||||
private ScheduledExecutorService scheduler;
|
||||
private AtomicLong startTime;
|
||||
private File dumps;
|
||||
private File worldDump;
|
||||
private int mcaCacheSize;
|
||||
private File temp;
|
||||
private File blocks;
|
||||
private File structures;
|
||||
|
||||
public IrisWorldDump(World world, VolmitSender sender) {
|
||||
sender.sendMessage("Initializing IrisWorldDump...");
|
||||
this.world = world;
|
||||
this.sender = sender;
|
||||
this.MCADirectory = new File(world.getWorldFolder(), "region");
|
||||
this.dumps = new File("plugins" + File.separator + "iris", "dumps");
|
||||
this.worldDump = new File(dumps, world.getName());
|
||||
this.mcaCacheSize = IrisSettings.get().getWorldDump().mcaCacheSize;
|
||||
this.regionsProcessed = new AtomicInteger(0);
|
||||
this.chunksProcessed = new AtomicInteger(0);
|
||||
this.totalToProcess = new AtomicInteger(0);
|
||||
this.chunksPerSecond = new RollingSequence(10);
|
||||
this.temp = new File(worldDump, "temp");
|
||||
this.executor = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors() - 1);
|
||||
this.scheduler = Executors.newSingleThreadScheduledExecutor();
|
||||
this.startTime = new AtomicLong();
|
||||
this.storage = new KMap<>();
|
||||
this.airStorage = new AtomicLong(0);
|
||||
|
||||
this.blocks = new File(worldDump, "blocks");
|
||||
this.structures = new File(worldDump, "structures");
|
||||
|
||||
try {
|
||||
this.engine = IrisToolbelt.access(world).getEngine();
|
||||
this.IrisWorld = true;
|
||||
} catch (Exception e) {
|
||||
this.IrisWorld = false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public void start() {
|
||||
|
||||
if (!dumps.exists()) {
|
||||
if (!dumps.mkdirs()) {
|
||||
System.err.println("Failed to create dump directory.");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
CompletableFuture<Integer> mcaCount = CompletableFuture.supplyAsync(this::totalMcaFiles);
|
||||
CompletableFuture<Integer> chunkCount = CompletableFuture.supplyAsync(this::totalMCAChunks);
|
||||
this.totalMCAFiles = new AtomicInteger(mcaCount.get());
|
||||
this.totalMaxChunks = new AtomicInteger(chunkCount.get());
|
||||
} catch (InterruptedException | ExecutionException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
dump();
|
||||
updater();
|
||||
}
|
||||
|
||||
private void updater() {
|
||||
startTime.set(System.currentTimeMillis());
|
||||
scheduler.scheduleAtFixedRate(() -> {
|
||||
long eta = computeETA();
|
||||
long elapsedSeconds = (System.currentTimeMillis() - startTime.get()) / 3000;
|
||||
int processed = chunksProcessed.get();
|
||||
double cps = elapsedSeconds > 0 ? processed / (double) elapsedSeconds : 0;
|
||||
chunksPerSecond.put(cps);
|
||||
double percentage = ((double) chunksProcessed.get() / (double) totalMaxChunks.get()) * 100;
|
||||
Iris.info("Processed: " + Form.f(processed) + " of " + Form.f(totalMaxChunks.get()) + " (%.0f%%) " + Form.f(chunksPerSecond.getAverage()) + "/s, ETA: " + Form.duration(eta, 2), percentage);
|
||||
|
||||
}, 1, 3, TimeUnit.SECONDS);
|
||||
|
||||
}
|
||||
|
||||
public class blockData {
|
||||
@Getter
|
||||
@Setter
|
||||
private String block;
|
||||
private int biome;
|
||||
private int height;
|
||||
|
||||
public blockData(String b, int bm, int h) {
|
||||
this.block = b;
|
||||
this.height = h;
|
||||
this.biome = bm;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private void dump() {
|
||||
Iris.info("Starting the dump process.");
|
||||
int threads = Runtime.getRuntime().availableProcessors();
|
||||
AtomicInteger f = new AtomicInteger();
|
||||
for (File mcaFile : MCADirectory.listFiles()) {
|
||||
if (mcaFile.getName().endsWith(".mca")) {
|
||||
executor.submit(() -> {
|
||||
try {
|
||||
processMCARegion( MCAUtil.read(mcaFile));
|
||||
} catch (Exception e) {
|
||||
f.getAndIncrement();
|
||||
Iris.error("Failed to read mca file");
|
||||
e.printStackTrace();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void processMCARegion(MCAFile mca) {
|
||||
AtomicReferenceArray<Chunk> chunks = new AtomicReferenceArray<>(1024);
|
||||
for (int i = 0; i < chunks.length(); i++) {
|
||||
chunks.set(i, mca.getChunks().get(i));
|
||||
}
|
||||
for (int i = 0; i < chunks.length(); i++) {
|
||||
Chunk chunk = chunks.get(i);
|
||||
if (chunk != null) {
|
||||
int CHUNK_HEIGHT = (world.getMaxHeight() - world.getMinHeight());
|
||||
for (int x = 0; x < 16; x++) {
|
||||
for (int z = 0; z < 16; z++) {
|
||||
for (int y = 0; y < CHUNK_HEIGHT; y++) {
|
||||
CompoundTag tag = chunk.getBlockStateAt(x, y, z);
|
||||
int biome = chunk.getBiomeAt(x, y, z);
|
||||
if (tag == null) {
|
||||
String blockName = "minecraft:air";
|
||||
//storage.compute(blockName, (key, count) -> (count == null) ? 1 : count + 1);
|
||||
airStorage.getAndIncrement();
|
||||
int ii = 0;
|
||||
} else {
|
||||
StringTag nameTag = tag.getStringTag("Name");
|
||||
String blockName = nameTag.getValue();
|
||||
blockData data = new blockData(blockName, biome, y);
|
||||
storage.compute(data, (key, count) -> (count == null) ? 1 : count + 1);
|
||||
int ii = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
chunksProcessed.getAndIncrement();
|
||||
}
|
||||
}
|
||||
regionsProcessed.getAndIncrement();
|
||||
}
|
||||
|
||||
private int totalMCAChunks() {
|
||||
AtomicInteger chunks = new AtomicInteger();
|
||||
CountDownLatch latch = new CountDownLatch(totalMcaFiles() * 1024);
|
||||
for (File mcafile : MCADirectory.listFiles()) {
|
||||
executor.submit(() -> {
|
||||
try {
|
||||
if (mcafile.getName().endsWith(".mca")) {
|
||||
MCAFile mca = MCAUtil.read(mcafile);
|
||||
for (int width = 0; width < 32; width++) {
|
||||
for (int depth = 0; depth < 32; depth++) {
|
||||
Chunk chunk = mca.getChunk(width, depth);
|
||||
if (chunk != null) {
|
||||
chunks.getAndIncrement();
|
||||
}
|
||||
latch.countDown();
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
Iris.error("Failed to read mca file");
|
||||
e.printStackTrace();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
try {
|
||||
latch.await();
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
return chunks.get();
|
||||
}
|
||||
|
||||
private int totalMcaFiles() {
|
||||
int size = 0;
|
||||
for (File mca : MCADirectory.listFiles()) {
|
||||
if (mca.getName().endsWith(".mca")) {
|
||||
size++;
|
||||
}
|
||||
}
|
||||
return size;
|
||||
}
|
||||
|
||||
private long computeETA() {
|
||||
return (long) (totalMaxChunks.get() > 1024 ? // Generated chunks exceed 1/8th of total?
|
||||
// If yes, use smooth function (which gets more accurate over time since its less sensitive to outliers)
|
||||
((totalMaxChunks.get() - chunksProcessed.get()) * ((double) (M.ms() - startTime.get()) / (double) chunksProcessed.get())) :
|
||||
// If no, use quick function (which is less accurate over time but responds better to the initial delay)
|
||||
((totalMaxChunks.get() - chunksProcessed.get()) / chunksPerSecond.getAverage()) * 1000
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -21,8 +21,6 @@ package com.volmit.iris.engine;
|
||||
import com.google.common.util.concurrent.AtomicDouble;
|
||||
import com.google.gson.Gson;
|
||||
import com.volmit.iris.Iris;
|
||||
import com.volmit.iris.core.IrisSettings;
|
||||
import com.volmit.iris.core.ServerConfigurator;
|
||||
import com.volmit.iris.core.events.IrisEngineHotloadEvent;
|
||||
import com.volmit.iris.core.gui.PregeneratorJob;
|
||||
import com.volmit.iris.core.nms.container.BlockPos;
|
||||
@@ -53,7 +51,6 @@ import com.volmit.iris.util.scheduling.PrecisionStopwatch;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.ToString;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.Material;
|
||||
import org.bukkit.block.Biome;
|
||||
import org.bukkit.block.data.BlockData;
|
||||
@@ -66,8 +63,6 @@ import java.util.Set;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.concurrent.atomic.AtomicLong;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
@Data
|
||||
@EqualsAndHashCode(exclude = "context")
|
||||
@@ -242,11 +237,6 @@ public class IrisEngine implements Engine {
|
||||
getTarget().setDimension(getData().getDimensionLoader().load(getDimension().getLoadKey()));
|
||||
prehotload();
|
||||
setupEngine();
|
||||
J.a(() -> {
|
||||
synchronized (ServerConfigurator.class) {
|
||||
ServerConfigurator.installDataPacks(false);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -286,6 +276,13 @@ public class IrisEngine implements Engine {
|
||||
return generated.get();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addGenerated() {
|
||||
if (generated.incrementAndGet() == 661) {
|
||||
J.a(() -> getData().savePrefetch(this));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public double getGeneratedPerSecond() {
|
||||
if (perSecondLatch.flip()) {
|
||||
@@ -468,11 +465,7 @@ public class IrisEngine implements Engine {
|
||||
|
||||
getMantle().getMantle().flag(x >> 4, z >> 4, MantleFlag.REAL, true);
|
||||
getMetrics().getTotal().put(p.getMilliseconds());
|
||||
generated.incrementAndGet();
|
||||
|
||||
if (generated.get() == 661) {
|
||||
J.a(() -> getData().savePrefetch(this));
|
||||
}
|
||||
addGenerated();
|
||||
} catch (Throwable e) {
|
||||
Iris.reportError(e);
|
||||
fail("Failed to generate " + x + ", " + z, e);
|
||||
|
||||
@@ -41,9 +41,11 @@ public class IrisSeaFloorDecorator extends IrisEngineDecorator {
|
||||
if (!decorator.isStacking()) {
|
||||
if (height >= 0 || height < getEngine().getHeight()) {
|
||||
if (null != decorator.getBlockDataForTop(biome, getRng(), realX, height, realZ, getData())) {
|
||||
data.set(x, height, z, decorator.getBlockData100(biome, getRng(), realX, height, realZ, getData()));
|
||||
height++;
|
||||
data.set(x, height, z, decorator.getBlockDataForTop(biome, getRng(), realX, height, realZ, getData()));
|
||||
if (height == getDimension().getFluidHeight() - 1) {
|
||||
data.set(x, height, z, decorator.getBlockData100(biome, getRng(), realX, height, realZ, getData()));
|
||||
height++;
|
||||
data.set(x, height, z, decorator.getBlockDataForTop(biome, getRng(), realX, height, realZ, getData()));
|
||||
}
|
||||
} else {
|
||||
data.set(x, height, z, decorator.getBlockData100(biome, getRng(), realX, height, realZ, getData()));
|
||||
}
|
||||
@@ -73,6 +75,5 @@ public class IrisSeaFloorDecorator extends IrisEngineDecorator {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -577,6 +577,8 @@ public interface Engine extends DataProvider, Fallible, LootProvider, BlockUpdat
|
||||
|
||||
int getGenerated();
|
||||
|
||||
void addGenerated();
|
||||
|
||||
default <T> IrisPosition lookForStreamResult(T find, ProceduralStream<T> stream, Function2<T, T, Boolean> matcher, long timeout) {
|
||||
AtomicInteger checked = new AtomicInteger();
|
||||
AtomicLong time = new AtomicLong(M.ms());
|
||||
|
||||
32
core/src/main/java/com/volmit/iris/engine/jvm/VMJavaFX.java
Normal file
32
core/src/main/java/com/volmit/iris/engine/jvm/VMJavaFX.java
Normal file
@@ -0,0 +1,32 @@
|
||||
package com.volmit.iris.engine.jvm;
|
||||
|
||||
import com.volmit.iris.util.plugin.VolmitSender;
|
||||
|
||||
public class VMJavaFX {
|
||||
private VolmitSender sender;
|
||||
public VMJavaFX(VolmitSender user) {
|
||||
this.sender = user;
|
||||
|
||||
}
|
||||
|
||||
public void start() {
|
||||
try {
|
||||
// Start JavaFX in a new JVM
|
||||
ProcessBuilder processBuilder = new ProcessBuilder(
|
||||
"java",
|
||||
"--module-path", "path/to/javafx-sdk/lib", // Set path to JavaFX SDK
|
||||
"--add-modules", "javafx.controls,javafx.fxml",
|
||||
"-jar", "path/to/javafx-application.jar"
|
||||
);
|
||||
processBuilder.inheritIO();
|
||||
processBuilder.start();
|
||||
sender.sendMessage("JavaFX application is launched!");
|
||||
} catch (Exception e) {
|
||||
sender.sendMessage("Failed to launch JavaFX application.");
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
@@ -170,7 +170,6 @@ public class IrisBiome extends IrisRegistrant implements IRare {
|
||||
@Desc("Collection of ores to be generated")
|
||||
@ArrayType(type = IrisOreGenerator.class, min = 1)
|
||||
private KList<IrisOreGenerator> ores = new KList<>();
|
||||
|
||||
public BlockData generateOres(int x, int y, int z, RNG rng, IrisData data) {
|
||||
if (ores.isEmpty()) {
|
||||
return null;
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
package com.volmit.iris.engine.object;
|
||||
|
||||
import com.volmit.iris.Iris;
|
||||
import com.volmit.iris.core.nms.datapack.IDataFixer;
|
||||
import com.volmit.iris.core.nms.datapack.DataVersion;
|
||||
import com.volmit.iris.engine.object.annotations.*;
|
||||
import com.volmit.iris.util.collection.KList;
|
||||
import com.volmit.iris.util.collection.KMap;
|
||||
@@ -92,7 +92,7 @@ public class IrisBiomeCustom {
|
||||
@Desc("The color of foliage (hex format). Leave blank / don't define to not change")
|
||||
private String foliageColor = "";
|
||||
|
||||
public String generateJson(IDataFixer fixer) {
|
||||
public String generateJson() {
|
||||
JSONObject effects = new JSONObject();
|
||||
effects.put("sky_color", parseColor(getSkyColor()));
|
||||
effects.put("fog_color", parseColor(getFogColor()));
|
||||
@@ -158,7 +158,7 @@ public class IrisBiomeCustom {
|
||||
j.put("spawners", spawners);
|
||||
}
|
||||
|
||||
return fixer.fixCustomBiome(this, j).toString(4);
|
||||
return DataVersion.getDefault().fixCustomBiome(this, j).toString(4);
|
||||
}
|
||||
|
||||
private int parseColor(String c) {
|
||||
|
||||
@@ -1,581 +1,388 @@
|
||||
/*
|
||||
* Iris is a World Generator for Minecraft Bukkit Servers
|
||||
* Copyright (c) 2022 Arcane Arts (Volmit Software)
|
||||
*
|
||||
* This program 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.
|
||||
*
|
||||
* This program 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 this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.volmit.iris.engine.object;
|
||||
|
||||
import com.volmit.iris.Iris;
|
||||
import com.volmit.iris.core.loader.IrisData;
|
||||
import com.volmit.iris.core.loader.IrisRegistrant;
|
||||
import com.volmit.iris.core.nms.datapack.IDataFixer;
|
||||
import com.volmit.iris.engine.data.cache.AtomicCache;
|
||||
import com.volmit.iris.engine.object.annotations.*;
|
||||
import com.volmit.iris.util.collection.KList;
|
||||
import com.volmit.iris.util.data.DataProvider;
|
||||
import com.volmit.iris.util.io.IO;
|
||||
import com.volmit.iris.util.json.JSONObject;
|
||||
import com.volmit.iris.util.math.Position2;
|
||||
import com.volmit.iris.util.math.RNG;
|
||||
import com.volmit.iris.util.noise.CNG;
|
||||
import com.volmit.iris.util.plugin.VolmitSender;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.experimental.Accessors;
|
||||
import org.bukkit.Material;
|
||||
import org.bukkit.World.Environment;
|
||||
import org.bukkit.block.data.BlockData;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
|
||||
@Accessors(chain = true)
|
||||
@AllArgsConstructor
|
||||
@NoArgsConstructor
|
||||
@Desc("Represents a dimension")
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = false)
|
||||
public class IrisDimension extends IrisRegistrant {
|
||||
public static final BlockData STONE = Material.STONE.createBlockData();
|
||||
public static final BlockData WATER = Material.WATER.createBlockData();
|
||||
private static final String DP_OVERWORLD_DEFAULT = """
|
||||
{
|
||||
"ambient_light": 0.0,
|
||||
"bed_works": true,
|
||||
"coordinate_scale": 1.0,
|
||||
"effects": "minecraft:overworld",
|
||||
"has_ceiling": false,
|
||||
"has_raids": true,
|
||||
"has_skylight": true,
|
||||
"infiniburn": "#minecraft:infiniburn_overworld",
|
||||
"monster_spawn_block_light_limit": 0,
|
||||
"monster_spawn_light_level": {
|
||||
"type": "minecraft:uniform",
|
||||
"value": {
|
||||
"max_inclusive": 7,
|
||||
"min_inclusive": 0
|
||||
}
|
||||
},
|
||||
"natural": true,
|
||||
"piglin_safe": false,
|
||||
"respawn_anchor_works": false,
|
||||
"ultrawarm": false
|
||||
}""";
|
||||
|
||||
private static final String DP_NETHER_DEFAULT = """
|
||||
{
|
||||
"ambient_light": 0.1,
|
||||
"bed_works": false,
|
||||
"coordinate_scale": 8.0,
|
||||
"effects": "minecraft:the_nether",
|
||||
"fixed_time": 18000,
|
||||
"has_ceiling": true,
|
||||
"has_raids": false,
|
||||
"has_skylight": false,
|
||||
"infiniburn": "#minecraft:infiniburn_nether",
|
||||
"monster_spawn_block_light_limit": 15,
|
||||
"monster_spawn_light_level": 7,
|
||||
"natural": false,
|
||||
"piglin_safe": true,
|
||||
"respawn_anchor_works": true,
|
||||
"ultrawarm": true
|
||||
}""";
|
||||
|
||||
private static final String DP_END_DEFAULT = """
|
||||
{
|
||||
"ambient_light": 0.0,
|
||||
"bed_works": false,
|
||||
"coordinate_scale": 1.0,
|
||||
"effects": "minecraft:the_end",
|
||||
"fixed_time": 6000,
|
||||
"has_ceiling": false,
|
||||
"has_raids": true,
|
||||
"has_skylight": false,
|
||||
"infiniburn": "#minecraft:infiniburn_end",
|
||||
"monster_spawn_block_light_limit": 0,
|
||||
"monster_spawn_light_level": {
|
||||
"type": "minecraft:uniform",
|
||||
"value": {
|
||||
"max_inclusive": 7,
|
||||
"min_inclusive": 0
|
||||
}
|
||||
},
|
||||
"natural": false,
|
||||
"piglin_safe": false,
|
||||
"respawn_anchor_works": false,
|
||||
"ultrawarm": false
|
||||
}""";
|
||||
private final transient AtomicCache<Position2> parallaxSize = new AtomicCache<>();
|
||||
private final transient AtomicCache<CNG> rockLayerGenerator = new AtomicCache<>();
|
||||
private final transient AtomicCache<CNG> fluidLayerGenerator = new AtomicCache<>();
|
||||
private final transient AtomicCache<CNG> coordFracture = new AtomicCache<>();
|
||||
private final transient AtomicCache<Double> sinr = new AtomicCache<>();
|
||||
private final transient AtomicCache<Double> cosr = new AtomicCache<>();
|
||||
private final transient AtomicCache<Double> rad = new AtomicCache<>();
|
||||
private final transient AtomicCache<Boolean> featuresUsed = new AtomicCache<>();
|
||||
private final transient AtomicCache<KList<Position2>> strongholdsCache = new AtomicCache<>();
|
||||
@MinNumber(2)
|
||||
@Required
|
||||
@Desc("The human readable name of this dimension")
|
||||
private String name = "A Dimension";
|
||||
@MinNumber(1)
|
||||
@MaxNumber(2032)
|
||||
@Desc("Maximum height at which players can be teleported to through gameplay.")
|
||||
private int logicalHeight = 256;
|
||||
@Desc("Maximum height at which players can be teleported to through gameplay.")
|
||||
private int logicalHeightEnd = 256;
|
||||
@Desc("Maximum height at which players can be teleported to through gameplay.")
|
||||
private int logicalHeightNether = 256;
|
||||
@RegistryListResource(IrisJigsawStructure.class)
|
||||
@Desc("If defined, Iris will place the given jigsaw structure where minecraft should place the overworld stronghold.")
|
||||
private String stronghold;
|
||||
@Desc("If set to true, Iris will remove chunks to allow visualizing cross sections of chunks easily")
|
||||
private boolean debugChunkCrossSections = false;
|
||||
@Desc("Vertically split up the biome palettes with 3 air blocks in between to visualize them")
|
||||
private boolean explodeBiomePalettes = false;
|
||||
@Desc("Studio Mode for testing different parts of the world")
|
||||
private StudioMode studioMode = StudioMode.NORMAL;
|
||||
@MinNumber(1)
|
||||
@MaxNumber(16)
|
||||
@Desc("Customize the palette height explosion")
|
||||
private int explodeBiomePaletteSize = 3;
|
||||
@MinNumber(2)
|
||||
@MaxNumber(16)
|
||||
@Desc("Every X/Z % debugCrossSectionsMod == 0 cuts the chunk")
|
||||
private int debugCrossSectionsMod = 3;
|
||||
@Desc("The average distance between strongholds")
|
||||
private int strongholdJumpDistance = 1280;
|
||||
@Desc("Define the maximum strongholds to place")
|
||||
private int maxStrongholds = 14;
|
||||
@Desc("Tree growth override settings")
|
||||
private IrisTreeSettings treeSettings = new IrisTreeSettings();
|
||||
@Desc("Spawn Entities in this dimension over time. Iris will continually replenish these mobs just like vanilla does.")
|
||||
@ArrayType(min = 1, type = String.class)
|
||||
@RegistryListResource(IrisSpawner.class)
|
||||
private KList<String> entitySpawners = new KList<>();
|
||||
@Desc("Reference loot tables in this area")
|
||||
private IrisLootReference loot = new IrisLootReference();
|
||||
@MinNumber(0)
|
||||
@Desc("The version of this dimension. Changing this will stop users from accidentally upgrading (and breaking their worlds).")
|
||||
private int version = 1;
|
||||
@ArrayType(min = 1, type = IrisBlockDrops.class)
|
||||
@Desc("Define custom block drops for this dimension")
|
||||
private KList<IrisBlockDrops> blockDrops = new KList<>();
|
||||
@Desc("Should bedrock be generated or not.")
|
||||
private boolean bedrock = true;
|
||||
@MinNumber(0)
|
||||
@MaxNumber(1)
|
||||
@Desc("The land chance. Up to 1.0 for total land or 0.0 for total sea")
|
||||
private double landChance = 0.625;
|
||||
@Desc("The placement style of regions")
|
||||
private IrisGeneratorStyle regionStyle = NoiseStyle.CELLULAR_IRIS_DOUBLE.style();
|
||||
@Desc("The placement style of land/sea")
|
||||
private IrisGeneratorStyle continentalStyle = NoiseStyle.CELLULAR_IRIS_DOUBLE.style();
|
||||
@Desc("The placement style of biomes")
|
||||
private IrisGeneratorStyle landBiomeStyle = NoiseStyle.CELLULAR_IRIS_DOUBLE.style();
|
||||
@Desc("The placement style of biomes")
|
||||
private IrisGeneratorStyle shoreBiomeStyle = NoiseStyle.CELLULAR_IRIS_DOUBLE.style();
|
||||
@Desc("The placement style of biomes")
|
||||
private IrisGeneratorStyle seaBiomeStyle = NoiseStyle.CELLULAR_IRIS_DOUBLE.style();
|
||||
@Desc("The placement style of biomes")
|
||||
private IrisGeneratorStyle caveBiomeStyle = NoiseStyle.CELLULAR_IRIS_DOUBLE.style();
|
||||
@Desc("Instead of filling objects with air, fills them with cobweb so you can see them")
|
||||
private boolean debugSmartBore = false;
|
||||
@Desc("Generate decorations or not")
|
||||
private boolean decorate = true;
|
||||
@Desc("Use post processing or not")
|
||||
private boolean postProcessing = true;
|
||||
@Desc("Add slabs in post processing")
|
||||
private boolean postProcessingSlabs = true;
|
||||
@Desc("Add painted walls in post processing")
|
||||
private boolean postProcessingWalls = true;
|
||||
@Desc("Carving configuration for the dimension")
|
||||
private IrisCarving carving = new IrisCarving();
|
||||
@Desc("Configuration of fluid bodies such as rivers & lakes")
|
||||
private IrisFluidBodies fluidBodies = new IrisFluidBodies();
|
||||
@Desc("forceConvertTo320Height")
|
||||
private Boolean forceConvertTo320Height = false;
|
||||
@Desc("The world environment")
|
||||
private Environment environment = Environment.NORMAL;
|
||||
@RegistryListResource(IrisRegion.class)
|
||||
@Required
|
||||
@ArrayType(min = 1, type = String.class)
|
||||
@Desc("Define all of the regions to include in this dimension. Dimensions -> Regions -> Biomes -> Objects etc")
|
||||
private KList<String> regions = new KList<>();
|
||||
@ArrayType(min = 1, type = IrisJigsawStructurePlacement.class)
|
||||
@Desc("Jigsaw structures")
|
||||
private KList<IrisJigsawStructurePlacement> jigsawStructures = new KList<>();
|
||||
@Required
|
||||
@MinNumber(0)
|
||||
@MaxNumber(1024)
|
||||
@Desc("The fluid height for this dimension")
|
||||
private int fluidHeight = 63;
|
||||
@Desc("Define the min and max Y bounds of this dimension. Please keep in mind that Iris internally generates from 0 to (max - min). \n\nFor example at -64 to 320, Iris is internally generating to 0 to 384, then on outputting chunks, it shifts it down by the min height (64 blocks). The default is -64 to 320. \n\nThe fluid height is placed at (fluid height + min height). So a fluid height of 63 would actually show up in the world at 1.")
|
||||
private IrisRange dimensionHeight = new IrisRange(-64, 320);
|
||||
@Desc("Define the min and max Y bounds of this dimension. Please keep in mind that Iris internally generates from 0 to (max - min). \n\nFor example at -64 to 320, Iris is internally generating to 0 to 384, then on outputting chunks, it shifts it down by the min height (64 blocks). The default is -64 to 320. \n\nThe fluid height is placed at (fluid height + min height). So a fluid height of 63 would actually show up in the world at 1.")
|
||||
private IrisRange dimensionHeightEnd = new IrisRange(-64, 320);
|
||||
@Desc("Define the min and max Y bounds of this dimension. Please keep in mind that Iris internally generates from 0 to (max - min). \n\nFor example at -64 to 320, Iris is internally generating to 0 to 384, then on outputting chunks, it shifts it down by the min height (64 blocks). The default is -64 to 320. \n\nThe fluid height is placed at (fluid height + min height). So a fluid height of 63 would actually show up in the world at 1.")
|
||||
private IrisRange dimensionHeightNether = new IrisRange(-64, 320);
|
||||
@Desc("Enable smart vanilla height")
|
||||
private boolean smartVanillaHeight = false;
|
||||
@RegistryListResource(IrisBiome.class)
|
||||
@Desc("Keep this either undefined or empty. Setting any biome name into this will force iris to only generate the specified biome. Great for testing.")
|
||||
private String focus = "";
|
||||
@RegistryListResource(IrisRegion.class)
|
||||
@Desc("Keep this either undefined or empty. Setting any region name into this will force iris to only generate the specified region. Great for testing.")
|
||||
private String focusRegion = "";
|
||||
@MinNumber(0.0001)
|
||||
@MaxNumber(512)
|
||||
@Desc("Zoom in or out the biome size. Higher = bigger biomes")
|
||||
private double biomeZoom = 5D;
|
||||
@MinNumber(0)
|
||||
@MaxNumber(360)
|
||||
@Desc("You can rotate the input coordinates by an angle. This can make terrain appear more natural (less sharp corners and lines). This literally rotates the entire dimension by an angle. Hint: Try 12 degrees or something not on a 90 or 45 degree angle.")
|
||||
private double dimensionAngleDeg = 0;
|
||||
@Required
|
||||
@Desc("Define the mode of this dimension (required!)")
|
||||
private IrisDimensionMode mode = new IrisDimensionMode();
|
||||
@MinNumber(0)
|
||||
@MaxNumber(8192)
|
||||
@Desc("Coordinate fracturing applies noise to the input coordinates. This creates the 'iris swirls' and wavy features. The distance pushes these waves further into places they shouldnt be. This is a block value multiplier.")
|
||||
private double coordFractureDistance = 20;
|
||||
@MinNumber(0.0001)
|
||||
@MaxNumber(512)
|
||||
@Desc("Coordinate fracturing zoom. Higher = less frequent warping, Lower = more frequent and rapid warping / swirls.")
|
||||
private double coordFractureZoom = 8;
|
||||
@MinNumber(0.0001)
|
||||
@MaxNumber(512)
|
||||
@Desc("This zooms in the land space")
|
||||
private double landZoom = 1;
|
||||
@MinNumber(0.0001)
|
||||
@MaxNumber(512)
|
||||
@Desc("This zooms oceanic biomes")
|
||||
private double seaZoom = 1;
|
||||
@MinNumber(0.0001)
|
||||
@MaxNumber(512)
|
||||
@Desc("Zoom in continents")
|
||||
private double continentZoom = 1;
|
||||
@MinNumber(0.0001)
|
||||
@MaxNumber(512)
|
||||
@Desc("Change the size of regions")
|
||||
private double regionZoom = 1;
|
||||
@Desc("Disable this to stop placing objects, entities, features & updates")
|
||||
private boolean useMantle = true;
|
||||
@Desc("Prevent Leaf decay as if placed in creative mode")
|
||||
private boolean preventLeafDecay = false;
|
||||
@ArrayType(min = 1, type = IrisDepositGenerator.class)
|
||||
@Desc("Define global deposit generators")
|
||||
private KList<IrisDepositGenerator> deposits = new KList<>();
|
||||
@ArrayType(min = 1, type = IrisShapedGeneratorStyle.class)
|
||||
@Desc("Overlay additional noise on top of the interoplated terrain.")
|
||||
private KList<IrisShapedGeneratorStyle> overlayNoise = new KList<>();
|
||||
@Desc("If true, the spawner system has infinite energy. This is NOT recommended because it would allow for mobs to keep spawning over and over without a rate limit")
|
||||
private boolean infiniteEnergy = false;
|
||||
@MinNumber(0)
|
||||
@MaxNumber(10000)
|
||||
@Desc("This is the maximum energy you can have in a dimension")
|
||||
private double maximumEnergy = 1000;
|
||||
@MinNumber(0.0001)
|
||||
@MaxNumber(512)
|
||||
@Desc("The rock zoom mostly for zooming in on a wispy palette")
|
||||
private double rockZoom = 5;
|
||||
@Desc("The palette of blocks for 'stone'")
|
||||
private IrisMaterialPalette rockPalette = new IrisMaterialPalette().qclear().qadd("stone");
|
||||
@Desc("The palette of blocks for 'water'")
|
||||
private IrisMaterialPalette fluidPalette = new IrisMaterialPalette().qclear().qadd("water");
|
||||
@Desc("Remove cartographers so they do not crash the server (Iris worlds only)")
|
||||
private boolean removeCartographersDueToCrash = true;
|
||||
@Desc("Notify players of cancelled cartographer villager in this radius in blocks (set to -1 to disable, -2 for everyone)")
|
||||
private int notifyPlayersOfCartographerCancelledRadius = 30;
|
||||
@Desc("Collection of ores to be generated")
|
||||
@ArrayType(type = IrisOreGenerator.class, min = 1)
|
||||
private KList<IrisOreGenerator> ores = new KList<>();
|
||||
@MinNumber(0)
|
||||
@MaxNumber(318)
|
||||
@Desc("The Subterrain Fluid Layer Height")
|
||||
private int caveLavaHeight = 8;
|
||||
|
||||
public int getMaxHeight() {
|
||||
return (int) getDimensionHeight().getMax();
|
||||
}
|
||||
|
||||
public int getMinHeight() {
|
||||
return (int) getDimensionHeight().getMin();
|
||||
}
|
||||
|
||||
public BlockData generateOres(int x, int y, int z, RNG rng, IrisData data) {
|
||||
if (ores.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
BlockData b = null;
|
||||
for (IrisOreGenerator i : ores) {
|
||||
|
||||
b = i.generate(x, y, z, rng, data);
|
||||
if (b != null) {
|
||||
return b;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public KList<Position2> getStrongholds(long seed) {
|
||||
return strongholdsCache.aquire(() -> {
|
||||
KList<Position2> pos = new KList<>();
|
||||
int jump = strongholdJumpDistance;
|
||||
RNG rng = new RNG((seed * 223) + 12945);
|
||||
for (int i = 0; i < maxStrongholds + 1; i++) {
|
||||
int m = i + 1;
|
||||
pos.add(new Position2(
|
||||
(int) ((rng.i(jump * i) + (jump * i)) * (rng.b() ? -1D : 1D)),
|
||||
(int) ((rng.i(jump * i) + (jump * i)) * (rng.b() ? -1D : 1D))
|
||||
));
|
||||
}
|
||||
|
||||
pos.remove(0);
|
||||
|
||||
return pos;
|
||||
});
|
||||
}
|
||||
|
||||
public int getFluidHeight() {
|
||||
return fluidHeight - (int) dimensionHeight.getMin();
|
||||
}
|
||||
|
||||
public CNG getCoordFracture(RNG rng, int signature) {
|
||||
return coordFracture.aquire(() ->
|
||||
{
|
||||
CNG coordFracture = CNG.signature(rng.nextParallelRNG(signature));
|
||||
coordFracture.scale(0.012 / coordFractureZoom);
|
||||
return coordFracture;
|
||||
});
|
||||
}
|
||||
|
||||
public double getDimensionAngle() {
|
||||
return rad.aquire(() -> Math.toRadians(dimensionAngleDeg));
|
||||
}
|
||||
|
||||
public Environment getEnvironment() {
|
||||
return environment;
|
||||
}
|
||||
|
||||
public boolean hasFocusRegion() {
|
||||
return !focusRegion.equals("");
|
||||
}
|
||||
|
||||
public String getFocusRegion() {
|
||||
return focusRegion;
|
||||
}
|
||||
|
||||
public double sinRotate() {
|
||||
return sinr.aquire(() -> Math.sin(getDimensionAngle()));
|
||||
}
|
||||
|
||||
public double cosRotate() {
|
||||
return cosr.aquire(() -> Math.cos(getDimensionAngle()));
|
||||
}
|
||||
|
||||
public KList<IrisRegion> getAllRegions(DataProvider g) {
|
||||
KList<IrisRegion> r = new KList<>();
|
||||
|
||||
for (String i : getRegions()) {
|
||||
r.add(g.getData().getRegionLoader().load(i));
|
||||
}
|
||||
|
||||
return r;
|
||||
}
|
||||
|
||||
public KList<IrisRegion> getAllAnyRegions() {
|
||||
KList<IrisRegion> r = new KList<>();
|
||||
|
||||
for (String i : getRegions()) {
|
||||
r.add(IrisData.loadAnyRegion(i));
|
||||
}
|
||||
|
||||
return r;
|
||||
}
|
||||
|
||||
public KList<IrisBiome> getAllBiomes(DataProvider g) {
|
||||
return g.getData().getBiomeLoader().loadAll(g.getData().getBiomeLoader().getPossibleKeys());
|
||||
}
|
||||
|
||||
public KList<IrisBiome> getAllAnyBiomes() {
|
||||
KList<IrisBiome> r = new KList<>();
|
||||
|
||||
for (IrisRegion i : getAllAnyRegions()) {
|
||||
if (i == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
r.addAll(i.getAllAnyBiomes());
|
||||
}
|
||||
|
||||
return r;
|
||||
}
|
||||
|
||||
public IrisGeneratorStyle getBiomeStyle(InferredType type) {
|
||||
switch (type) {
|
||||
case CAVE:
|
||||
return caveBiomeStyle;
|
||||
case LAND:
|
||||
return landBiomeStyle;
|
||||
case SEA:
|
||||
return seaBiomeStyle;
|
||||
case SHORE:
|
||||
return shoreBiomeStyle;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return landBiomeStyle;
|
||||
}
|
||||
|
||||
public boolean installDataPack(IDataFixer fixer, DataProvider data, File datapacks, double ultimateMaxHeight, double ultimateMinHeight) {
|
||||
boolean write = false;
|
||||
boolean changed = false;
|
||||
|
||||
IO.delete(new File(datapacks, "iris/data/" + getLoadKey().toLowerCase()));
|
||||
|
||||
for (IrisBiome i : getAllBiomes(data)) {
|
||||
if (i.isCustom()) {
|
||||
write = true;
|
||||
|
||||
for (IrisBiomeCustom j : i.getCustomDerivitives()) {
|
||||
File output = new File(datapacks, "iris/data/" + getLoadKey().toLowerCase() + "/worldgen/biome/" + j.getId() + ".json");
|
||||
|
||||
if (!output.exists()) {
|
||||
changed = true;
|
||||
}
|
||||
|
||||
Iris.verbose(" Installing Data Pack Biome: " + output.getPath());
|
||||
output.getParentFile().mkdirs();
|
||||
try {
|
||||
IO.writeAll(output, j.generateJson(fixer));
|
||||
} catch (IOException e) {
|
||||
Iris.reportError(e);
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!dimensionHeight.equals(new IrisRange(-64, 320)) && this.name.equalsIgnoreCase("overworld")) {
|
||||
Iris.verbose(" Installing Data Pack Dimension Types: \"minecraft:overworld\", \"minecraft:the_nether\", \"minecraft:the_end\"");
|
||||
dimensionHeight.setMax(ultimateMaxHeight);
|
||||
dimensionHeight.setMin(ultimateMinHeight);
|
||||
changed = writeDimensionType(fixer, changed, datapacks);
|
||||
}
|
||||
|
||||
if (write) {
|
||||
File mcm = new File(datapacks, "iris/pack.mcmeta");
|
||||
try {
|
||||
IO.writeAll(mcm, """
|
||||
{
|
||||
"pack": {
|
||||
"description": "Iris Data Pack. This pack contains all installed Iris Packs' resources.",
|
||||
"pack_format": 10
|
||||
}
|
||||
}
|
||||
""");
|
||||
} catch (IOException e) {
|
||||
Iris.reportError(e);
|
||||
e.printStackTrace();
|
||||
}
|
||||
Iris.verbose(" Installing Data Pack MCMeta: " + mcm.getPath());
|
||||
}
|
||||
|
||||
return changed;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getFolderName() {
|
||||
return "dimensions";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getTypeName() {
|
||||
return "Dimension";
|
||||
}
|
||||
|
||||
@Override
|
||||
public void scanForErrors(JSONObject p, VolmitSender sender) {
|
||||
|
||||
}
|
||||
|
||||
public boolean writeDimensionType(IDataFixer fixer, boolean changed, File datapacks) {
|
||||
File dimTypeOverworld = new File(datapacks, "iris/data/minecraft/dimension_type/overworld.json");
|
||||
if (!dimTypeOverworld.exists())
|
||||
changed = true;
|
||||
dimTypeOverworld.getParentFile().mkdirs();
|
||||
try {
|
||||
IO.writeAll(dimTypeOverworld, generateDatapackJsonOverworld(fixer));
|
||||
} catch (IOException e) {
|
||||
Iris.reportError(e);
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
|
||||
File dimTypeNether = new File(datapacks, "iris/data/minecraft/dimension_type/the_nether.json");
|
||||
if (!dimTypeNether.exists())
|
||||
changed = true;
|
||||
dimTypeNether.getParentFile().mkdirs();
|
||||
try {
|
||||
IO.writeAll(dimTypeNether, generateDatapackJsonNether(fixer));
|
||||
} catch (IOException e) {
|
||||
Iris.reportError(e);
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
|
||||
File dimTypeEnd = new File(datapacks, "iris/data/minecraft/dimension_type/the_end.json");
|
||||
if (!dimTypeEnd.exists())
|
||||
changed = true;
|
||||
dimTypeEnd.getParentFile().mkdirs();
|
||||
try {
|
||||
IO.writeAll(dimTypeEnd, generateDatapackJsonEnd(fixer));
|
||||
} catch (IOException e) {
|
||||
Iris.reportError(e);
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
return changed;
|
||||
}
|
||||
|
||||
private String generateDatapackJsonOverworld(IDataFixer fixer) {
|
||||
JSONObject obj = new JSONObject(DP_OVERWORLD_DEFAULT);
|
||||
obj.put("min_y", dimensionHeight.getMin());
|
||||
obj.put("height", dimensionHeight.getMax() - dimensionHeight.getMin());
|
||||
obj.put("logical_height", logicalHeight);
|
||||
return fixer.fixDimension(obj).toString(4);
|
||||
}
|
||||
|
||||
private String generateDatapackJsonNether(IDataFixer fixer) {
|
||||
JSONObject obj = new JSONObject(DP_NETHER_DEFAULT);
|
||||
obj.put("min_y", dimensionHeightNether.getMin());
|
||||
obj.put("height", dimensionHeightNether.getMax() - dimensionHeightNether.getMin());
|
||||
obj.put("logical_height", logicalHeightNether);
|
||||
return fixer.fixDimension(obj).toString(4);
|
||||
}
|
||||
|
||||
private String generateDatapackJsonEnd(IDataFixer fixer) {
|
||||
JSONObject obj = new JSONObject(DP_END_DEFAULT);
|
||||
obj.put("min_y", dimensionHeightEnd.getMin());
|
||||
obj.put("height", dimensionHeightEnd.getMax() - dimensionHeightEnd.getMin());
|
||||
obj.put("logical_height", logicalHeightEnd);
|
||||
return fixer.fixDimension(obj).toString(4);
|
||||
}
|
||||
}
|
||||
/*
|
||||
* Iris is a World Generator for Minecraft Bukkit Servers
|
||||
* Copyright (c) 2022 Arcane Arts (Volmit Software)
|
||||
*
|
||||
* This program 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.
|
||||
*
|
||||
* This program 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 this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.volmit.iris.engine.object;
|
||||
|
||||
import com.volmit.iris.core.loader.IrisData;
|
||||
import com.volmit.iris.core.loader.IrisRegistrant;
|
||||
import com.volmit.iris.engine.data.cache.AtomicCache;
|
||||
import com.volmit.iris.engine.object.annotations.*;
|
||||
import com.volmit.iris.util.collection.KList;
|
||||
import com.volmit.iris.util.data.DataProvider;
|
||||
import com.volmit.iris.util.json.JSONObject;
|
||||
import com.volmit.iris.util.math.Position2;
|
||||
import com.volmit.iris.util.math.RNG;
|
||||
import com.volmit.iris.util.noise.CNG;
|
||||
import com.volmit.iris.util.plugin.VolmitSender;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.experimental.Accessors;
|
||||
import org.bukkit.Material;
|
||||
import org.bukkit.World.Environment;
|
||||
import org.bukkit.block.data.BlockData;
|
||||
|
||||
@Accessors(chain = true)
|
||||
@AllArgsConstructor
|
||||
@NoArgsConstructor
|
||||
@Desc("Represents a dimension")
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = false)
|
||||
public class IrisDimension extends IrisRegistrant {
|
||||
public static final BlockData STONE = Material.STONE.createBlockData();
|
||||
public static final BlockData WATER = Material.WATER.createBlockData();
|
||||
private final transient AtomicCache<Position2> parallaxSize = new AtomicCache<>();
|
||||
private final transient AtomicCache<CNG> rockLayerGenerator = new AtomicCache<>();
|
||||
private final transient AtomicCache<CNG> fluidLayerGenerator = new AtomicCache<>();
|
||||
private final transient AtomicCache<CNG> coordFracture = new AtomicCache<>();
|
||||
private final transient AtomicCache<Double> sinr = new AtomicCache<>();
|
||||
private final transient AtomicCache<Double> cosr = new AtomicCache<>();
|
||||
private final transient AtomicCache<Double> rad = new AtomicCache<>();
|
||||
private final transient AtomicCache<Boolean> featuresUsed = new AtomicCache<>();
|
||||
private final transient AtomicCache<KList<Position2>> strongholdsCache = new AtomicCache<>();
|
||||
@MinNumber(2)
|
||||
@Required
|
||||
@Desc("The human readable name of this dimension")
|
||||
private String name = "A Dimension";
|
||||
@MinNumber(1)
|
||||
@MaxNumber(2032)
|
||||
@Desc("Maximum height at which players can be teleported to through gameplay.")
|
||||
private int logicalHeight = 256;
|
||||
@RegistryListResource(IrisJigsawStructure.class)
|
||||
@Desc("If defined, Iris will place the given jigsaw structure where minecraft should place the overworld stronghold.")
|
||||
private String stronghold;
|
||||
@Desc("If set to true, Iris will remove chunks to allow visualizing cross sections of chunks easily")
|
||||
private boolean debugChunkCrossSections = false;
|
||||
@Desc("Vertically split up the biome palettes with 3 air blocks in between to visualize them")
|
||||
private boolean explodeBiomePalettes = false;
|
||||
@Desc("Studio Mode for testing different parts of the world")
|
||||
private StudioMode studioMode = StudioMode.NORMAL;
|
||||
@MinNumber(1)
|
||||
@MaxNumber(16)
|
||||
@Desc("Customize the palette height explosion")
|
||||
private int explodeBiomePaletteSize = 3;
|
||||
@MinNumber(2)
|
||||
@MaxNumber(16)
|
||||
@Desc("Every X/Z % debugCrossSectionsMod == 0 cuts the chunk")
|
||||
private int debugCrossSectionsMod = 3;
|
||||
@Desc("The average distance between strongholds")
|
||||
private int strongholdJumpDistance = 1280;
|
||||
@Desc("Define the maximum strongholds to place")
|
||||
private int maxStrongholds = 14;
|
||||
@Desc("Tree growth override settings")
|
||||
private IrisTreeSettings treeSettings = new IrisTreeSettings();
|
||||
@Desc("Spawn Entities in this dimension over time. Iris will continually replenish these mobs just like vanilla does.")
|
||||
@ArrayType(min = 1, type = String.class)
|
||||
@RegistryListResource(IrisSpawner.class)
|
||||
private KList<String> entitySpawners = new KList<>();
|
||||
@Desc("Reference loot tables in this area")
|
||||
private IrisLootReference loot = new IrisLootReference();
|
||||
@MinNumber(0)
|
||||
@Desc("The version of this dimension. Changing this will stop users from accidentally upgrading (and breaking their worlds).")
|
||||
private int version = 1;
|
||||
@ArrayType(min = 1, type = IrisBlockDrops.class)
|
||||
@Desc("Define custom block drops for this dimension")
|
||||
private KList<IrisBlockDrops> blockDrops = new KList<>();
|
||||
@Desc("Should bedrock be generated or not.")
|
||||
private boolean bedrock = true;
|
||||
@MinNumber(0)
|
||||
@MaxNumber(1)
|
||||
@Desc("The land chance. Up to 1.0 for total land or 0.0 for total sea")
|
||||
private double landChance = 0.625;
|
||||
@Desc("The placement style of regions")
|
||||
private IrisGeneratorStyle regionStyle = NoiseStyle.CELLULAR_IRIS_DOUBLE.style();
|
||||
@Desc("The placement style of land/sea")
|
||||
private IrisGeneratorStyle continentalStyle = NoiseStyle.CELLULAR_IRIS_DOUBLE.style();
|
||||
@Desc("The placement style of biomes")
|
||||
private IrisGeneratorStyle landBiomeStyle = NoiseStyle.CELLULAR_IRIS_DOUBLE.style();
|
||||
@Desc("The placement style of biomes")
|
||||
private IrisGeneratorStyle shoreBiomeStyle = NoiseStyle.CELLULAR_IRIS_DOUBLE.style();
|
||||
@Desc("The placement style of biomes")
|
||||
private IrisGeneratorStyle seaBiomeStyle = NoiseStyle.CELLULAR_IRIS_DOUBLE.style();
|
||||
@Desc("The placement style of biomes")
|
||||
private IrisGeneratorStyle caveBiomeStyle = NoiseStyle.CELLULAR_IRIS_DOUBLE.style();
|
||||
@Desc("Instead of filling objects with air, fills them with cobweb so you can see them")
|
||||
private boolean debugSmartBore = false;
|
||||
@Desc("Generate decorations or not")
|
||||
private boolean decorate = true;
|
||||
@Desc("Use post processing or not")
|
||||
private boolean postProcessing = true;
|
||||
@Desc("Add slabs in post processing")
|
||||
private boolean postProcessingSlabs = true;
|
||||
@Desc("Add painted walls in post processing")
|
||||
private boolean postProcessingWalls = true;
|
||||
@Desc("Carving configuration for the dimension")
|
||||
private IrisCarving carving = new IrisCarving();
|
||||
@Desc("Configuration of fluid bodies such as rivers & lakes")
|
||||
private IrisFluidBodies fluidBodies = new IrisFluidBodies();
|
||||
@Desc("forceConvertTo320Height")
|
||||
private Boolean forceConvertTo320Height = false;
|
||||
@Desc("The world environment")
|
||||
private Environment environment = Environment.NORMAL;
|
||||
@RegistryListResource(IrisRegion.class)
|
||||
@Required
|
||||
@ArrayType(min = 1, type = String.class)
|
||||
@Desc("Define all of the regions to include in this dimension. Dimensions -> Regions -> Biomes -> Objects etc")
|
||||
private KList<String> regions = new KList<>();
|
||||
@ArrayType(min = 1, type = IrisJigsawStructurePlacement.class)
|
||||
@Desc("Jigsaw structures")
|
||||
private KList<IrisJigsawStructurePlacement> jigsawStructures = new KList<>();
|
||||
@Required
|
||||
@MinNumber(0)
|
||||
@MaxNumber(1024)
|
||||
@Desc("The fluid height for this dimension")
|
||||
private int fluidHeight = 63;
|
||||
@Desc("Define the min and max Y bounds of this dimension. Please keep in mind that Iris internally generates from 0 to (max - min). \n\nFor example at -64 to 320, Iris is internally generating to 0 to 384, then on outputting chunks, it shifts it down by the min height (64 blocks). The default is -64 to 320. \n\nThe fluid height is placed at (fluid height + min height). So a fluid height of 63 would actually show up in the world at 1.")
|
||||
private IrisRange dimensionHeight = new IrisRange(-64, 320);
|
||||
@Desc("Enable smart vanilla height")
|
||||
private boolean smartVanillaHeight = false;
|
||||
@RegistryListResource(IrisBiome.class)
|
||||
@Desc("Keep this either undefined or empty. Setting any biome name into this will force iris to only generate the specified biome. Great for testing.")
|
||||
private String focus = "";
|
||||
@RegistryListResource(IrisRegion.class)
|
||||
@Desc("Keep this either undefined or empty. Setting any region name into this will force iris to only generate the specified region. Great for testing.")
|
||||
private String focusRegion = "";
|
||||
@MinNumber(0.0001)
|
||||
@MaxNumber(512)
|
||||
@Desc("Zoom in or out the biome size. Higher = bigger biomes")
|
||||
private double biomeZoom = 5D;
|
||||
@MinNumber(0)
|
||||
@MaxNumber(360)
|
||||
@Desc("You can rotate the input coordinates by an angle. This can make terrain appear more natural (less sharp corners and lines). This literally rotates the entire dimension by an angle. Hint: Try 12 degrees or something not on a 90 or 45 degree angle.")
|
||||
private double dimensionAngleDeg = 0;
|
||||
@Required
|
||||
@Desc("Define the mode of this dimension (required!)")
|
||||
private IrisDimensionMode mode = new IrisDimensionMode();
|
||||
@MinNumber(0)
|
||||
@MaxNumber(8192)
|
||||
@Desc("Coordinate fracturing applies noise to the input coordinates. This creates the 'iris swirls' and wavy features. The distance pushes these waves further into places they shouldnt be. This is a block value multiplier.")
|
||||
private double coordFractureDistance = 20;
|
||||
@MinNumber(0.0001)
|
||||
@MaxNumber(512)
|
||||
@Desc("Coordinate fracturing zoom. Higher = less frequent warping, Lower = more frequent and rapid warping / swirls.")
|
||||
private double coordFractureZoom = 8;
|
||||
@MinNumber(0.0001)
|
||||
@MaxNumber(512)
|
||||
@Desc("This zooms in the land space")
|
||||
private double landZoom = 1;
|
||||
@MinNumber(0.0001)
|
||||
@MaxNumber(512)
|
||||
@Desc("This zooms oceanic biomes")
|
||||
private double seaZoom = 1;
|
||||
@MinNumber(0.0001)
|
||||
@MaxNumber(512)
|
||||
@Desc("Zoom in continents")
|
||||
private double continentZoom = 1;
|
||||
@MinNumber(0.0001)
|
||||
@MaxNumber(512)
|
||||
@Desc("Change the size of regions")
|
||||
private double regionZoom = 1;
|
||||
@Desc("Disable this to stop placing objects, entities, features & updates")
|
||||
private boolean useMantle = true;
|
||||
@Desc("Prevent Leaf decay as if placed in creative mode")
|
||||
private boolean preventLeafDecay = false;
|
||||
@ArrayType(min = 1, type = IrisDepositGenerator.class)
|
||||
@Desc("Define global deposit generators")
|
||||
private KList<IrisDepositGenerator> deposits = new KList<>();
|
||||
@ArrayType(min = 1, type = IrisShapedGeneratorStyle.class)
|
||||
@Desc("Overlay additional noise on top of the interoplated terrain.")
|
||||
private KList<IrisShapedGeneratorStyle> overlayNoise = new KList<>();
|
||||
@Desc("If true, the spawner system has infinite energy. This is NOT recommended because it would allow for mobs to keep spawning over and over without a rate limit")
|
||||
private boolean infiniteEnergy = false;
|
||||
@MinNumber(0)
|
||||
@MaxNumber(10000)
|
||||
@Desc("This is the maximum energy you can have in a dimension")
|
||||
private double maximumEnergy = 1000;
|
||||
@MinNumber(0.0001)
|
||||
@MaxNumber(512)
|
||||
@Desc("The rock zoom mostly for zooming in on a wispy palette")
|
||||
private double rockZoom = 5;
|
||||
@Desc("The palette of blocks for 'stone'")
|
||||
private IrisMaterialPalette rockPalette = new IrisMaterialPalette().qclear().qadd("stone");
|
||||
@Desc("The palette of blocks for 'water'")
|
||||
private IrisMaterialPalette fluidPalette = new IrisMaterialPalette().qclear().qadd("water");
|
||||
@Desc("Remove cartographers so they do not crash the server (Iris worlds only)")
|
||||
private boolean removeCartographersDueToCrash = true;
|
||||
@Desc("Notify players of cancelled cartographer villager in this radius in blocks (set to -1 to disable, -2 for everyone)")
|
||||
private int notifyPlayersOfCartographerCancelledRadius = 30;
|
||||
@Desc("Collection of ores to be generated")
|
||||
@ArrayType(type = IrisOreGenerator.class, min = 1)
|
||||
private KList<IrisOreGenerator> ores = new KList<>();
|
||||
@MinNumber(0)
|
||||
@MaxNumber(318)
|
||||
@Desc("The Subterrain Fluid Layer Height")
|
||||
private int caveLavaHeight = 8;
|
||||
|
||||
public int getMaxHeight() {
|
||||
return (int) getDimensionHeight().getMax();
|
||||
}
|
||||
|
||||
public int getMinHeight() {
|
||||
return (int) getDimensionHeight().getMin();
|
||||
}
|
||||
|
||||
public BlockData generateOres(int x, int y, int z, RNG rng, IrisData data) {
|
||||
if (ores.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
BlockData b = null;
|
||||
for (IrisOreGenerator i : ores) {
|
||||
|
||||
b = i.generate(x, y, z, rng, data);
|
||||
if (b != null) {
|
||||
return b;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public KList<Position2> getStrongholds(long seed) {
|
||||
return strongholdsCache.aquire(() -> {
|
||||
KList<Position2> pos = new KList<>();
|
||||
int jump = strongholdJumpDistance;
|
||||
RNG rng = new RNG((seed * 223) + 12945);
|
||||
for (int i = 0; i < maxStrongholds + 1; i++) {
|
||||
int m = i + 1;
|
||||
pos.add(new Position2(
|
||||
(int) ((rng.i(jump * i) + (jump * i)) * (rng.b() ? -1D : 1D)),
|
||||
(int) ((rng.i(jump * i) + (jump * i)) * (rng.b() ? -1D : 1D))
|
||||
));
|
||||
}
|
||||
|
||||
pos.remove(0);
|
||||
|
||||
return pos;
|
||||
});
|
||||
}
|
||||
|
||||
public int getFluidHeight() {
|
||||
return fluidHeight - (int) dimensionHeight.getMin();
|
||||
}
|
||||
|
||||
public CNG getCoordFracture(RNG rng, int signature) {
|
||||
return coordFracture.aquire(() ->
|
||||
{
|
||||
CNG coordFracture = CNG.signature(rng.nextParallelRNG(signature));
|
||||
coordFracture.scale(0.012 / coordFractureZoom);
|
||||
return coordFracture;
|
||||
});
|
||||
}
|
||||
|
||||
public double getDimensionAngle() {
|
||||
return rad.aquire(() -> Math.toRadians(dimensionAngleDeg));
|
||||
}
|
||||
|
||||
public Environment getEnvironment() {
|
||||
return environment;
|
||||
}
|
||||
|
||||
public IrisRange getDimensionHeight() {
|
||||
return smartVanillaHeight ? new IrisRange(-64, 320) : dimensionHeight;
|
||||
}
|
||||
|
||||
public int getLogicalHeight() {
|
||||
return smartVanillaHeight ? 256 : logicalHeight;
|
||||
}
|
||||
|
||||
public boolean hasFocusRegion() {
|
||||
return !focusRegion.equals("");
|
||||
}
|
||||
|
||||
public String getFocusRegion() {
|
||||
return focusRegion;
|
||||
}
|
||||
|
||||
public double sinRotate() {
|
||||
return sinr.aquire(() -> Math.sin(getDimensionAngle()));
|
||||
}
|
||||
|
||||
public double cosRotate() {
|
||||
return cosr.aquire(() -> Math.cos(getDimensionAngle()));
|
||||
}
|
||||
|
||||
public KList<IrisRegion> getAllRegions(DataProvider g) {
|
||||
KList<IrisRegion> r = new KList<>();
|
||||
|
||||
for (String i : getRegions()) {
|
||||
r.add(g.getData().getRegionLoader().load(i));
|
||||
}
|
||||
|
||||
return r;
|
||||
}
|
||||
|
||||
public KList<IrisRegion> getAllAnyRegions() {
|
||||
KList<IrisRegion> r = new KList<>();
|
||||
|
||||
for (String i : getRegions()) {
|
||||
r.add(IrisData.loadAnyRegion(i));
|
||||
}
|
||||
|
||||
return r;
|
||||
}
|
||||
|
||||
public KList<IrisBiome> getAllBiomes(DataProvider g) {
|
||||
return g.getData().getBiomeLoader().loadAll(g.getData().getBiomeLoader().getPossibleKeys());
|
||||
}
|
||||
|
||||
public KList<IrisBiome> getAllAnyBiomes() {
|
||||
KList<IrisBiome> r = new KList<>();
|
||||
|
||||
for (IrisRegion i : getAllAnyRegions()) {
|
||||
if (i == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
r.addAll(i.getAllAnyBiomes());
|
||||
}
|
||||
|
||||
return r;
|
||||
}
|
||||
|
||||
public IrisGeneratorStyle getBiomeStyle(InferredType type) {
|
||||
switch (type) {
|
||||
case CAVE:
|
||||
return caveBiomeStyle;
|
||||
case LAND:
|
||||
return landBiomeStyle;
|
||||
case SEA:
|
||||
return seaBiomeStyle;
|
||||
case SHORE:
|
||||
return shoreBiomeStyle;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return landBiomeStyle;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getFolderName() {
|
||||
return "dimensions";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getTypeName() {
|
||||
return "Dimension";
|
||||
}
|
||||
|
||||
@Override
|
||||
public void scanForErrors(JSONObject p, VolmitSender sender) {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -454,16 +454,21 @@ public class IrisEntity extends IrisRegistrant {
|
||||
return ae.get();
|
||||
}
|
||||
|
||||
if (isSpecialType()) {
|
||||
if (isSpecialType() && Iris.linkMythicMobs.isEnabled()) {
|
||||
if (specialType.toLowerCase().startsWith("mythicmobs:")) {
|
||||
return Iris.linkMythicMobs.spawnMob(specialType.substring(11), at);
|
||||
} else {
|
||||
Iris.warn("Invalid mob type to spawn: '" + specialType + "'!");
|
||||
return null;
|
||||
}
|
||||
} else {
|
||||
if (isSpecialType()) {
|
||||
Iris.warn("MythicMobs is not enabled, falling back to: " + type + "'!");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
return INMS.get().spawnEntity(at, getType(), getReason());
|
||||
}
|
||||
|
||||
|
||||
@@ -21,6 +21,7 @@ package com.volmit.iris.engine.platform;
|
||||
import com.volmit.iris.Iris;
|
||||
import com.volmit.iris.core.loader.IrisData;
|
||||
import com.volmit.iris.core.nms.INMS;
|
||||
import com.volmit.iris.core.safeguard.IrisSafeguard;
|
||||
import com.volmit.iris.core.service.StudioSVC;
|
||||
import com.volmit.iris.engine.IrisEngine;
|
||||
import com.volmit.iris.engine.data.chunk.TerrainChunk;
|
||||
@@ -32,6 +33,7 @@ import com.volmit.iris.engine.object.StudioMode;
|
||||
import com.volmit.iris.engine.platform.studio.StudioGenerator;
|
||||
import com.volmit.iris.util.collection.KList;
|
||||
import com.volmit.iris.util.data.IrisBiomeStorage;
|
||||
import com.volmit.iris.util.format.C;
|
||||
import com.volmit.iris.util.hunk.Hunk;
|
||||
import com.volmit.iris.util.hunk.view.BiomeGridHunkHolder;
|
||||
import com.volmit.iris.util.hunk.view.ChunkDataHunkHolder;
|
||||
@@ -263,6 +265,15 @@ public class BukkitChunkGenerator extends ChunkGenerator implements PlatformChun
|
||||
}
|
||||
|
||||
private Engine getEngine(WorldInfo world) {
|
||||
// if (!IrisSafeguard.instance.acceptUnstable && IrisSafeguard.instance.unstablemode) {
|
||||
// Iris.info(C.RED + "------------------------------------------------------------");
|
||||
// Iris.info(C.DARK_RED + "Cancelled World Loading of " + world.getName() + "!");
|
||||
// Iris.info(C.RED + "World loading has been disabled until the incompatibility is resolved.");
|
||||
// Iris.info(C.DARK_RED + "Alternatively, go to plugins/iris/settings.json and set ignoreBootMode to true.");
|
||||
// Iris.info(C.RED + "------------------------------------------------------------");
|
||||
// return null;
|
||||
// }
|
||||
|
||||
if (setup.get()) {
|
||||
return getEngine();
|
||||
}
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
package com.volmit.iris.util.decree.specialhandlers;
|
||||
|
||||
import com.volmit.iris.engine.object.IrisBiome;
|
||||
import com.volmit.iris.util.decree.exceptions.DecreeParsingException;
|
||||
import com.volmit.iris.util.decree.handlers.BiomeHandler;
|
||||
import com.volmit.iris.util.decree.handlers.PlayerHandler;
|
||||
import org.bukkit.entity.Player;
|
||||
|
||||
public class NullableBiomeHandler extends BiomeHandler {
|
||||
|
||||
@Override
|
||||
public IrisBiome parse(String in, boolean force) throws DecreeParsingException {
|
||||
return getPossibilities(in).stream().filter((i) -> toString(i).equalsIgnoreCase(in)).findFirst().orElse(null);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
package com.volmit.iris.util.decree.specialhandlers;
|
||||
|
||||
import com.volmit.iris.engine.object.IrisBiome;
|
||||
import com.volmit.iris.engine.object.IrisRegion;
|
||||
import com.volmit.iris.util.decree.exceptions.DecreeParsingException;
|
||||
import com.volmit.iris.util.decree.handlers.BiomeHandler;
|
||||
import com.volmit.iris.util.decree.handlers.RegionHandler;
|
||||
|
||||
import javax.swing.plaf.synth.Region;
|
||||
|
||||
public class NullableRegionHandler extends RegionHandler {
|
||||
|
||||
@Override
|
||||
public IrisRegion parse(String in, boolean force) throws DecreeParsingException {
|
||||
return getPossibilities(in).stream().filter((i) -> toString(i).equalsIgnoreCase(in)).findFirst().orElse(null);
|
||||
}
|
||||
}
|
||||
@@ -20,7 +20,9 @@ package com.volmit.iris.util.nbt.mca;
|
||||
|
||||
import com.volmit.iris.Iris;
|
||||
import com.volmit.iris.core.nms.INMS;
|
||||
import com.volmit.iris.core.tools.IrisWorldDump;
|
||||
import com.volmit.iris.util.collection.KMap;
|
||||
import com.volmit.iris.util.format.Form;
|
||||
import com.volmit.iris.util.nbt.io.NBTDeserializer;
|
||||
import com.volmit.iris.util.nbt.io.NBTSerializer;
|
||||
import com.volmit.iris.util.nbt.io.NamedTag;
|
||||
@@ -57,6 +59,8 @@ public class Chunk {
|
||||
private ListTag<ListTag<?>> postProcessing;
|
||||
private String status;
|
||||
private CompoundTag structures;
|
||||
private int d;
|
||||
private int f;
|
||||
|
||||
Chunk(int lastMCAUpdate) {
|
||||
this.lastMCAUpdate = lastMCAUpdate;
|
||||
@@ -69,6 +73,8 @@ public class Chunk {
|
||||
*/
|
||||
public Chunk(CompoundTag data) {
|
||||
this.data = data;
|
||||
d = 0;
|
||||
f = 0;
|
||||
initReferences(ALL_DATA);
|
||||
setStatus("full");
|
||||
}
|
||||
@@ -150,8 +156,8 @@ public class Chunk {
|
||||
if ((loadFlags & STRUCTURES) != 0) {
|
||||
structures = level.getCompoundTag("Structures");
|
||||
}
|
||||
if ((loadFlags & (BLOCK_LIGHTS | BLOCK_STATES | SKY_LIGHT)) != 0 && level.containsKey("Sections")) {
|
||||
for (CompoundTag section : level.getListTag("Sections").asCompoundTagList()) {
|
||||
if ((loadFlags & (BLOCK_LIGHTS | BLOCK_STATES | SKY_LIGHT)) != 0 && level.containsKey("sections")) {
|
||||
for (CompoundTag section : level.getListTag("sections").asCompoundTagList()) {
|
||||
int sectionIndex = section.getByte("Y");
|
||||
if (sectionIndex > 15 || sectionIndex < 0) {
|
||||
continue;
|
||||
|
||||
@@ -37,12 +37,17 @@ public class Section {
|
||||
|
||||
public Section(CompoundTag sectionRoot, int dataVersion, long loadFlags) {
|
||||
data = sectionRoot;
|
||||
ListTag<?> rawPalette = sectionRoot.getListTag("Palette");
|
||||
//ListTag<?> rawPalette = sectionRoot.getListTag("palette");
|
||||
ListTag<?> rawPalette = sectionRoot.getCompoundTag("biomes").getListTag("palette");
|
||||
if (rawPalette == null) {
|
||||
return;
|
||||
}
|
||||
palette = INMS.get().createPalette();
|
||||
palette.readFromSection(sectionRoot);
|
||||
try {
|
||||
palette.readFromSection(sectionRoot);
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
ByteArrayTag blockLight = sectionRoot.getByteArrayTag("BlockLight");
|
||||
ByteArrayTag skyLight = sectionRoot.getByteArrayTag("SkyLight");
|
||||
this.blockLight = blockLight != null ? blockLight.getValue() : null;
|
||||
|
||||
@@ -0,0 +1,112 @@
|
||||
/*
|
||||
* Iris is a World Generator for Minecraft Bukkit Servers
|
||||
* Copyright (c) 2022 Arcane Arts (Volmit Software)
|
||||
*
|
||||
* This program 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.
|
||||
*
|
||||
* This program 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
|
||||
* abyte with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.volmit.iris.util.nbt.mca.palette;
|
||||
|
||||
import org.apache.commons.lang3.Validate;
|
||||
// todo Cool idea but im way to dumb for this for now
|
||||
public class MCABitStorageByteArray {
|
||||
private final byte[] data;
|
||||
private final int bits;
|
||||
private final int mask;
|
||||
private final int size;
|
||||
private final int valuesPerByte;
|
||||
|
||||
private final int divideMul;
|
||||
private final int divideAdd;
|
||||
private final int divideShift;
|
||||
|
||||
public MCABitStorageByteArray(int bits, int length) {
|
||||
this(bits, length, null);
|
||||
}
|
||||
|
||||
public MCABitStorageByteArray(int bits, int length, byte[] data) {
|
||||
Validate.inclusiveBetween(1L, 8L, bits); // Ensure bits are between 1 and 8
|
||||
this.size = length;
|
||||
this.bits = bits;
|
||||
this.mask = (1 << bits) - 1;
|
||||
this.valuesPerByte = 8 / bits;
|
||||
int[] divisionParams = computeDivisionParameters(this.valuesPerByte);
|
||||
this.divideMul = divisionParams[0];
|
||||
this.divideAdd = divisionParams[1];
|
||||
this.divideShift = divisionParams[2];
|
||||
int numBytes = (length + this.valuesPerByte - 1) / this.valuesPerByte;
|
||||
if (data != null) {
|
||||
if (data.length != numBytes)
|
||||
throw new IllegalArgumentException("Data array length does not match the required size.");
|
||||
this.data = data;
|
||||
} else {
|
||||
this.data = new byte[numBytes];
|
||||
}
|
||||
}
|
||||
|
||||
private int[] computeDivisionParameters(int denom) {
|
||||
long two32 = 1L << 32;
|
||||
long magic = two32 / denom;
|
||||
int shift = 0;
|
||||
while ((1L << (shift + 32)) < magic * denom) {
|
||||
shift++;
|
||||
}
|
||||
return new int[]{(int) magic, 0, shift};
|
||||
}
|
||||
|
||||
private int cellIndex(int index) {
|
||||
long indexLong = Integer.toUnsignedLong(this.divideMul);
|
||||
long addLong = Integer.toUnsignedLong(this.divideAdd);
|
||||
return (int) ((index * indexLong + addLong) >>> 32 >>> this.divideShift);
|
||||
}
|
||||
|
||||
public int getAndSet(int index, int newValue) {
|
||||
Validate.inclusiveBetween(0L, (this.size - 1), index);
|
||||
Validate.inclusiveBetween(0L, this.mask, newValue);
|
||||
int byteIndex = cellIndex(index);
|
||||
int bitOffset = (index - byteIndex * this.valuesPerByte) * this.bits;
|
||||
int currentValue = (this.data[byteIndex] >> bitOffset) & this.mask;
|
||||
this.data[byteIndex] = (byte) ((this.data[byteIndex] & ~(this.mask << bitOffset)) | (newValue & this.mask) << bitOffset);
|
||||
return currentValue;
|
||||
}
|
||||
|
||||
public void set(int index, int value) {
|
||||
Validate.inclusiveBetween(0L, (this.size - 1), index);
|
||||
Validate.inclusiveBetween(0L, this.mask, value);
|
||||
int byteIndex = cellIndex(index);
|
||||
int bitOffset = (index - byteIndex * this.valuesPerByte) * this.bits;
|
||||
this.data[byteIndex] = (byte) ((this.data[byteIndex] & ~(this.mask << bitOffset)) | (value & this.mask) << bitOffset);
|
||||
}
|
||||
|
||||
public int get(int index) {
|
||||
Validate.inclusiveBetween(0L, (this.size - 1), index);
|
||||
int byteIndex = cellIndex(index);
|
||||
int bitOffset = (index - byteIndex * this.valuesPerByte) * this.bits;
|
||||
return (this.data[byteIndex] >> bitOffset) & this.mask;
|
||||
}
|
||||
|
||||
public byte[] getRaw() {
|
||||
return this.data;
|
||||
}
|
||||
|
||||
public int getSize() {
|
||||
return this.size;
|
||||
}
|
||||
|
||||
public int getBits() {
|
||||
return this.bits;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -20,9 +20,10 @@ package com.volmit.iris.util.nbt.mca.palette;
|
||||
|
||||
import org.apache.commons.lang3.Validate;
|
||||
|
||||
import java.util.BitSet;
|
||||
import java.util.function.IntConsumer;
|
||||
|
||||
public class MCABitStorage {
|
||||
public class MCABitStorageLongArray {
|
||||
private static final int[] MAGIC = new int[]{
|
||||
-1, -1, 0, Integer.MIN_VALUE, 0, 0, 1431655765, 1431655765, 0, Integer.MIN_VALUE,
|
||||
0, 1, 858993459, 858993459, 0, 715827882, 715827882, 0, 613566756, 613566756,
|
||||
@@ -61,11 +62,11 @@ public class MCABitStorage {
|
||||
|
||||
private final int divideShift;
|
||||
|
||||
public MCABitStorage(int bits, int length) {
|
||||
public MCABitStorageLongArray(int bits, int length) {
|
||||
this(bits, length, null);
|
||||
}
|
||||
|
||||
public MCABitStorage(int bits, int length, long[] data) {
|
||||
public MCABitStorageLongArray(int bits, int length, long[] data) {
|
||||
Validate.inclusiveBetween(1L, 32L, bits);
|
||||
this.size = length;
|
||||
this.bits = bits;
|
||||
@@ -41,7 +41,8 @@ public class MCAPalettedContainer<T> implements MCAPaletteResize<T> {
|
||||
|
||||
private final T defaultValue;
|
||||
|
||||
protected MCABitStorage storage;
|
||||
// Todo multiple storage systems cause long isnt the only one?
|
||||
protected MCABitStorageLongArray storage;
|
||||
|
||||
private MCAPalette<T> palette;
|
||||
|
||||
@@ -74,11 +75,11 @@ public class MCAPalettedContainer<T> implements MCAPaletteResize<T> {
|
||||
this.bits = MCAMth.ceillog2(this.registry.size());
|
||||
}
|
||||
this.palette.idFor(this.defaultValue);
|
||||
this.storage = new MCABitStorage(this.bits, 4096);
|
||||
this.storage = new MCABitStorageLongArray(this.bits, 4096);
|
||||
}
|
||||
|
||||
public int onResize(int var0, T var1) {
|
||||
MCABitStorage var2 = this.storage;
|
||||
MCABitStorageLongArray var2 = this.storage;
|
||||
MCAPalette<T> var3 = this.palette;
|
||||
setBits(var0);
|
||||
for (int var4 = 0; var4 < var2.getSize(); var4++) {
|
||||
@@ -121,6 +122,12 @@ public class MCAPalettedContainer<T> implements MCAPaletteResize<T> {
|
||||
T var1 = this.palette.valueFor(this.storage.get(var0));
|
||||
return (var1 == null) ? this.defaultValue : var1;
|
||||
}
|
||||
/**
|
||||
/**
|
||||
* Reads and processes block data from encoded byte arrays.
|
||||
* @param var0 BlockID Strings - List of block types identified by strings.
|
||||
* @param var1 Encoded Locations - Long array containing compactly encoded block IDs, representing sequential block positions within a chunk.
|
||||
*/
|
||||
|
||||
public void read(ListTag var0, long[] var1) {
|
||||
int var2 = Math.max(4, MCAMth.ceillog2(var0.size()));
|
||||
@@ -131,18 +138,69 @@ public class MCAPalettedContainer<T> implements MCAPaletteResize<T> {
|
||||
if (this.palette == this.globalPalette) {
|
||||
MCAPalette<T> var4 = new MCAHashMapPalette<>(this.registry, var2, this.dummyPaletteResize, this.reader, this.writer);
|
||||
var4.read(var0);
|
||||
MCABitStorage var5 = new MCABitStorage(var2, 4096, var1);
|
||||
MCABitStorageLongArray var5 = new MCABitStorageLongArray(var2, 4096, var1);
|
||||
for (int var6 = 0; var6 < 4096; var6++)
|
||||
this.storage.set(var6, this.globalPalette.idFor(var4.valueFor(var5.get(var6))));
|
||||
} else if (var3 == this.bits) {
|
||||
System.arraycopy(var1, 0, this.storage.getRaw(), 0, var1.length);
|
||||
} else {
|
||||
MCABitStorage var4 = new MCABitStorage(var3, 4096, var1);
|
||||
MCABitStorageLongArray var4 = new MCABitStorageLongArray(var3, 4096, var1);
|
||||
for (int var5 = 0; var5 < 4096; var5++)
|
||||
this.storage.set(var5, var4.get(var5));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads and processes block data from encoded byte arrays.
|
||||
* @param var0 BlockID Strings - List of block types identified by strings.
|
||||
* @param var1 Encoded Locations - Byte array containing compactly encoded block IDs, representing sequential block positions within a chunk.
|
||||
* Currently, Minecraft doesn't use ByteArray storage.
|
||||
*/
|
||||
|
||||
public void read(ListTag var0, byte[] var1) {
|
||||
int requiredBits = Math.max(4, MCAMth.ceillog2(var0.size()));
|
||||
if (requiredBits != this.bits) {
|
||||
setBits(requiredBits);
|
||||
}
|
||||
this.palette.read(var0);
|
||||
|
||||
int bitsPerByte = 8 * var1.length / 4096;
|
||||
if (this.palette == this.globalPalette) {
|
||||
MCAPalette<T> var4 = new MCAHashMapPalette<>(this.registry, requiredBits, this.dummyPaletteResize, this.reader, this.writer);
|
||||
var4.read(var0);
|
||||
MCABitStorageByteArray var5 = new MCABitStorageByteArray(requiredBits, 4096, var1);
|
||||
for (int var6 = 0; var6 < 4096; var6++) {
|
||||
this.storage.set(var6, this.globalPalette.idFor(var4.valueFor(var5.get(var6))));
|
||||
}
|
||||
} else if (bitsPerByte == this.bits) {
|
||||
System.arraycopy(var1, 0, this.storage.getRaw(), 0, var1.length);
|
||||
} else {
|
||||
MCABitStorageByteArray var4 = new MCABitStorageByteArray(bitsPerByte, 4096, var1);
|
||||
for (int var5 = 0; var5 < 4096; var5++) {
|
||||
this.storage.set(var5, var4.get(var5));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads and processes block data from encoded byte arrays.
|
||||
* @param var0 BlockID Strings - List of block types identified by strings.
|
||||
* This method is primarily used to read air sections.
|
||||
*/
|
||||
|
||||
public void read(ListTag var0) {
|
||||
int requiredBits = Math.max(4, MCAMth.ceillog2(var0.size()));
|
||||
if (requiredBits != this.bits) {
|
||||
setBits(requiredBits);
|
||||
}
|
||||
this.palette.read(var0);
|
||||
int defaultValue = 0;
|
||||
for (int i = 0; i < 4096; i++) {
|
||||
this.storage.set(i, defaultValue);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public void write(CompoundTag var0, String var1, String var2) {
|
||||
MCAHashMapPalette<T> var3 = new MCAHashMapPalette<>(this.registry, this.bits, this.dummyPaletteResize, this.reader, this.writer);
|
||||
T var4 = this.defaultValue;
|
||||
@@ -160,7 +218,7 @@ public class MCAPalettedContainer<T> implements MCAPaletteResize<T> {
|
||||
var3.write(paletteList);
|
||||
var0.put(var1, paletteList);
|
||||
int var8 = Math.max(4, MCAMth.ceillog2(paletteList.size()));
|
||||
MCABitStorage var9 = new MCABitStorage(var8, 4096);
|
||||
MCABitStorageLongArray var9 = new MCABitStorageLongArray(var8, 4096);
|
||||
for (int var10 = 0; var10 < var6.length; var10++) {
|
||||
var9.set(var10, var6[var10]);
|
||||
}
|
||||
|
||||
@@ -18,7 +18,10 @@
|
||||
|
||||
package com.volmit.iris.util.nbt.mca.palette;
|
||||
|
||||
import com.volmit.iris.Iris;
|
||||
import com.volmit.iris.util.nbt.tag.ByteArrayTag;
|
||||
import com.volmit.iris.util.nbt.tag.CompoundTag;
|
||||
import com.volmit.iris.util.nbt.tag.LongArrayTag;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
|
||||
import java.util.function.Function;
|
||||
@@ -42,6 +45,22 @@ public class MCAWrappedPalettedContainer<T> implements MCAPaletteAccess {
|
||||
}
|
||||
|
||||
public void readFromSection(CompoundTag tag) {
|
||||
container.read(tag.getListTag("Palette"), tag.getLongArrayTag("BlockStates").getValue());
|
||||
// container.read(tag.getCompoundTag("block_states").getListTag("palette"), tag.getCompoundTag("block_states").getLongArrayTag("data").getValue());
|
||||
CompoundTag blockStates = tag.getCompoundTag("block_states");
|
||||
if (blockStates == null) {
|
||||
throw new IllegalArgumentException("block_states tag is missing");
|
||||
}
|
||||
LongArrayTag longData = blockStates.getLongArrayTag("data");
|
||||
if (longData != null && longData.getValue() != null) {
|
||||
container.read(tag.getCompoundTag("block_states").getListTag("palette"), tag.getCompoundTag("block_states").getLongArrayTag("data").getValue());
|
||||
} else {
|
||||
ByteArrayTag byteData = blockStates.getByteArrayTag("data");
|
||||
if (byteData == null) {
|
||||
container.read(tag.getCompoundTag("block_states").getListTag("palette"));
|
||||
} else {
|
||||
throw new IllegalArgumentException("No palette data tag found or data value is null");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -6,6 +6,10 @@ authors: [ cyberpwn, NextdoorPsycho, Vatuu ]
|
||||
website: volmit.com
|
||||
description: More than a Dimension!
|
||||
libraries:
|
||||
- org.bytedeco:cuda-platform:12.3-8.9-1.5.10
|
||||
- org.bytedeco:javacpp:1.5.10
|
||||
- net.bytebuddy:byte-buddy:1.14.14
|
||||
- net.bytebuddy:byte-buddy-agent:1.12.8
|
||||
- com.googlecode.concurrentlinkedhashmap:concurrentlinkedhashmap-lru:1.4.2
|
||||
- com.github.ben-manes.caffeine:caffeine:3.0.5
|
||||
- org.apache.commons:commons-lang3:3.12.0
|
||||
|
||||
@@ -2,6 +2,7 @@ package com.volmit.iris.core.nms.v1_19_R1;
|
||||
|
||||
import com.mojang.serialization.Codec;
|
||||
import com.volmit.iris.Iris;
|
||||
import com.volmit.iris.core.nms.INMS;
|
||||
import com.volmit.iris.engine.data.cache.AtomicCache;
|
||||
import com.volmit.iris.engine.framework.Engine;
|
||||
import com.volmit.iris.engine.object.IrisBiome;
|
||||
@@ -11,6 +12,7 @@ import com.volmit.iris.util.math.RNG;
|
||||
import net.minecraft.core.Holder;
|
||||
import net.minecraft.core.Registry;
|
||||
import net.minecraft.core.RegistryAccess;
|
||||
import net.minecraft.resources.ResourceKey;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
import net.minecraft.world.level.biome.Biome;
|
||||
import net.minecraft.world.level.biome.BiomeSource;
|
||||
@@ -25,6 +27,7 @@ import java.lang.reflect.Field;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
public class CustomBiomeSource extends BiomeSource {
|
||||
private final long seed;
|
||||
@@ -118,8 +121,28 @@ public class CustomBiomeSource extends BiomeSource {
|
||||
for (IrisBiome i : engine.getAllBiomes()) {
|
||||
if (i.isCustom()) {
|
||||
for (IrisBiomeCustom j : i.getCustomDerivitives()) {
|
||||
m.put(j.getId(), customRegistry.getHolder(customRegistry.getResourceKey(customRegistry
|
||||
.get(new ResourceLocation(engine.getDimension().getLoadKey() + ":" + j.getId()))).get()).get());
|
||||
ResourceLocation location = new ResourceLocation(engine.getDimension().getLoadKey() + ":" + j.getId());
|
||||
Biome biome = customRegistry.get(location);
|
||||
if (biome == null) {
|
||||
INMS.get().registerBiome(location.getNamespace(), j, false);
|
||||
biome = customRegistry.get(location);
|
||||
if (biome == null) {
|
||||
Iris.error("Cannot find biome for IrisBiomeCustom " + j.getId() + " from engine " + engine.getName());
|
||||
continue;
|
||||
}
|
||||
}
|
||||
Optional<ResourceKey<Biome>> optionalBiomeKey = customRegistry.getResourceKey(biome);
|
||||
if (optionalBiomeKey.isEmpty()) {
|
||||
Iris.error("Cannot find biome for IrisBiomeCustom " + j.getId() + " from engine " + engine.getName());
|
||||
continue;
|
||||
}
|
||||
ResourceKey<Biome> biomeKey = optionalBiomeKey.get();
|
||||
Optional<Holder<Biome>> optionalReferenceHolder = customRegistry.getHolder(biomeKey);
|
||||
if (optionalReferenceHolder.isEmpty()) {
|
||||
Iris.error("Cannot find reference to biome " + biomeKey + " for engine " + engine.getName());
|
||||
continue;
|
||||
}
|
||||
m.put(j.getId(), optionalReferenceHolder.get());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,14 +4,44 @@ import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.DataInputStream;
|
||||
import java.io.DataOutputStream;
|
||||
import java.io.File;
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.Method;
|
||||
import java.lang.reflect.Modifier;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.Vector;
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import com.google.common.base.Preconditions;
|
||||
import com.google.gson.JsonElement;
|
||||
import com.google.gson.JsonObject;
|
||||
import com.mojang.datafixers.util.Pair;
|
||||
import com.mojang.serialization.Codec;
|
||||
import com.mojang.serialization.JsonOps;
|
||||
import com.mojang.serialization.Lifecycle;
|
||||
import com.volmit.iris.engine.object.IrisBiomeCustom;
|
||||
import com.volmit.iris.engine.object.IrisDimension;
|
||||
import com.volmit.iris.util.format.C;
|
||||
import it.unimi.dsi.fastutil.objects.Reference2IntMap;
|
||||
import net.bytebuddy.ByteBuddy;
|
||||
import net.bytebuddy.asm.Advice;
|
||||
import net.bytebuddy.dynamic.loading.ClassReloadingStrategy;
|
||||
import net.bytebuddy.matcher.ElementMatchers;
|
||||
import net.minecraft.core.MappedRegistry;
|
||||
import net.minecraft.resources.ResourceKey;
|
||||
import net.minecraft.server.MinecraftServer;
|
||||
import net.minecraft.server.level.progress.ChunkProgressListener;
|
||||
import net.minecraft.util.GsonHelper;
|
||||
import net.minecraft.world.level.Level;
|
||||
import net.minecraft.world.level.dimension.DimensionType;
|
||||
import net.minecraft.world.level.dimension.LevelStem;
|
||||
import net.minecraft.world.level.storage.LevelStorageSource;
|
||||
import net.minecraft.world.level.storage.PrimaryLevelData;
|
||||
import org.bukkit.*;
|
||||
import org.bukkit.block.Biome;
|
||||
import org.bukkit.block.data.BlockData;
|
||||
@@ -25,6 +55,7 @@ import org.bukkit.craftbukkit.v1_19_R1.inventory.CraftItemStack;
|
||||
import org.bukkit.entity.Dolphin;
|
||||
import org.bukkit.entity.Entity;
|
||||
import org.bukkit.event.entity.CreatureSpawnEvent;
|
||||
import org.bukkit.generator.BiomeProvider;
|
||||
import org.bukkit.generator.ChunkGenerator;
|
||||
import org.bukkit.inventory.ItemStack;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
@@ -170,6 +201,17 @@ public class NMSBinding implements INMSBinding {
|
||||
return null;
|
||||
}
|
||||
|
||||
private RegistryAccess getRegistryAccess(World world) {
|
||||
try {
|
||||
var field = getField(Level.class, RegistryAccess.class);
|
||||
field.setAccessible(true);
|
||||
return (RegistryAccess) field.get(((CraftWorld) world).getHandle());
|
||||
} catch (Throwable e) {
|
||||
e.printStackTrace();
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void deserializeTile(CompoundTag c, Location pos) {
|
||||
((CraftWorld) pos.getWorld()).getHandle().getChunkAt(new BlockPos(pos.getBlockX(), 0, pos.getBlockZ())).setBlockEntityNbt(convert(c));
|
||||
@@ -253,8 +295,7 @@ public class NMSBinding implements INMSBinding {
|
||||
|
||||
@Override
|
||||
public Object getBiomeBase(World world, Biome biome) {
|
||||
return CraftBlock.biomeToBiomeBase(((CraftWorld) world).getHandle()
|
||||
.registryAccess().registry(Registry.BIOME_REGISTRY).orElse(null), biome);
|
||||
return CraftBlock.biomeToBiomeBase(getRegistryAccess(world).registry(Registry.BIOME_REGISTRY).orElse(null), biome);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -291,8 +332,11 @@ public class NMSBinding implements INMSBinding {
|
||||
public int getBiomeId(Biome biome) {
|
||||
for (World i : Bukkit.getWorlds()) {
|
||||
if (i.getEnvironment().equals(World.Environment.NORMAL)) {
|
||||
Registry<net.minecraft.world.level.biome.Biome> registry = ((CraftWorld) i).getHandle().registryAccess().registry(Registry.BIOME_REGISTRY).orElse(null);
|
||||
return registry.getId((net.minecraft.world.level.biome.Biome) getBiomeBase(registry, biome));
|
||||
var registry = getRegistryAccess(i).registry(Registry.BIOME_REGISTRY).orElse(null);
|
||||
if (registry != null) {
|
||||
var holder = (Holder<net.minecraft.world.level.biome.Biome>) getBiomeBase(registry, biome);
|
||||
return registry.getId(holder.value());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -517,6 +561,139 @@ public class NMSBinding implements INMSBinding {
|
||||
return ((CraftWorld) location.getWorld()).spawn(location, type.getEntityClass(), null, reason);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean registerDimension(String name, IrisDimension dimension) {
|
||||
var registry = registry(Registry.DIMENSION_TYPE_REGISTRY);
|
||||
var baseLocation = switch (dimension.getEnvironment()) {
|
||||
case NORMAL -> new ResourceLocation("minecraft", "overworld");
|
||||
case NETHER -> new ResourceLocation("minecraft", "the_nether");
|
||||
case THE_END -> new ResourceLocation("minecraft", "the_end");
|
||||
case CUSTOM -> throw new IllegalArgumentException("Cannot register custom dimension");
|
||||
};
|
||||
var base = registry.getHolder(ResourceKey.create(Registry.DIMENSION_TYPE_REGISTRY, baseLocation)).orElse(null);
|
||||
if (base == null) return false;
|
||||
var json = encode(DimensionType.CODEC, base).orElse(null);
|
||||
if (json == null) return false;
|
||||
var object = json.getAsJsonObject();
|
||||
var height = dimension.getDimensionHeight();
|
||||
object.addProperty("min_y", height.getMin());
|
||||
object.addProperty("height", height.getMax() - height.getMin());
|
||||
object.addProperty("logical_height", dimension.getLogicalHeight());
|
||||
var value = decode(DimensionType.CODEC, object.toString()).map(Holder::value).orElse(null);
|
||||
if (value == null) return false;
|
||||
return register(Registry.DIMENSION_TYPE_REGISTRY, new ResourceLocation("iris", name), value, true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean registerBiome(String dimensionId, IrisBiomeCustom biome, boolean replace) {
|
||||
var biomeBase = decode(net.minecraft.world.level.biome.Biome.CODEC, biome.generateJson()).map(Holder::value).orElse(null);
|
||||
if (biomeBase == null) return false;
|
||||
return register(Registry.BIOME_REGISTRY, new ResourceLocation(dimensionId, biome.getId()), biomeBase, replace);
|
||||
}
|
||||
|
||||
private <T> Optional<T> decode(Codec<T> codec, String json) {
|
||||
return codec.decode(JsonOps.INSTANCE, GsonHelper.parse(json)).get().left().map(Pair::getFirst);
|
||||
}
|
||||
|
||||
private <T> Optional<JsonElement> encode(Codec<T> codec, T value) {
|
||||
return codec.encode(value, JsonOps.INSTANCE, new JsonObject()).result();
|
||||
}
|
||||
|
||||
private <T> boolean register(ResourceKey<Registry<T>> registryKey, ResourceLocation location, T value, boolean replace) {
|
||||
Preconditions.checkArgument(registryKey != null, "The registry cannot be null!");
|
||||
Preconditions.checkArgument(location != null, "The location cannot be null!");
|
||||
Preconditions.checkArgument(value != null, "The value cannot be null!");
|
||||
var registry = registry(registryKey);
|
||||
var key = ResourceKey.create(registryKey, location);
|
||||
try {
|
||||
if (registry.containsKey(key)) {
|
||||
if (!replace) return false;
|
||||
return replace(registryKey, location, value);
|
||||
}
|
||||
Field field = getField(MappedRegistry.class, boolean.class);
|
||||
field.setAccessible(true);
|
||||
boolean frozen = field.getBoolean(registry);
|
||||
field.setBoolean(registry, false);
|
||||
Field valueField = getField(Holder.Reference.class, "T");
|
||||
valueField.setAccessible(true);
|
||||
|
||||
try {
|
||||
var holder = registry.register(key, value, Lifecycle.stable());
|
||||
if (frozen) valueField.set(holder, value);
|
||||
return true;
|
||||
} finally {
|
||||
field.setBoolean(registry, frozen);
|
||||
}
|
||||
} catch (Throwable e) {
|
||||
throw new IllegalStateException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private <T> boolean replace(ResourceKey<Registry<T>> registryKey, ResourceLocation location, T value) {
|
||||
Preconditions.checkArgument(registryKey != null, "The registryKey cannot be null!");
|
||||
Preconditions.checkArgument(location != null, "The location cannot be null!");
|
||||
Preconditions.checkArgument(value != null, "The value cannot be null!");
|
||||
var registry = registry(registryKey);
|
||||
var key = ResourceKey.create(registryKey, location);
|
||||
try {
|
||||
var holder = registry.getHolder(key).orElse(null);
|
||||
if (holder == null) return false;
|
||||
var oldValue = holder.value();
|
||||
Field valueField = getField(Holder.Reference.class, "T");
|
||||
valueField.setAccessible(true);
|
||||
Field toIdField = getField(MappedRegistry.class, buildType(Reference2IntMap.class, "T"));
|
||||
toIdField.setAccessible(true);
|
||||
Field byValueField = getField(MappedRegistry.class, buildType(Map.class, "T", buildType(Holder.Reference.class, "T")));
|
||||
byValueField.setAccessible(true);
|
||||
Field lifecyclesField = getField(MappedRegistry.class, buildType(Map.class, "T", Lifecycle.class.getName()));
|
||||
lifecyclesField.setAccessible(true);
|
||||
var toId = (Reference2IntMap<T>) toIdField.get(registry);
|
||||
var byValue = (Map<T, Holder.Reference<T>>) byValueField.get(registry);
|
||||
var lifecycles = (Map<T, Lifecycle>) lifecyclesField.get(registry);
|
||||
|
||||
valueField.set(holder, value);
|
||||
toId.put(value, toId.removeInt(oldValue));
|
||||
byValue.put(value, byValue.remove(oldValue));
|
||||
lifecycles.put(value, lifecycles.remove(oldValue));
|
||||
return true;
|
||||
} catch (Throwable e) {
|
||||
throw new IllegalStateException(e);
|
||||
}
|
||||
}
|
||||
|
||||
private <T> MappedRegistry<T> registry(ResourceKey<Registry<T>> registryKey) {
|
||||
var rawRegistry = registry().registry(registryKey).orElse(null);
|
||||
if (!(rawRegistry instanceof MappedRegistry<T> registry))
|
||||
throw new IllegalStateException("The Registry is not a mapped Registry!");
|
||||
return registry;
|
||||
}
|
||||
|
||||
private static String buildType(Class<?> clazz, String... parameterTypes) {
|
||||
if (parameterTypes.length == 0) return clazz.getName();
|
||||
var builder = new StringBuilder(clazz.getName())
|
||||
.append("<");
|
||||
for (int i = 0; i < parameterTypes.length; i++) {
|
||||
builder.append(parameterTypes[i]).append(parameterTypes.length - 1 == i ? ">" : ", ");
|
||||
}
|
||||
return builder.toString();
|
||||
}
|
||||
|
||||
private static Field getField(Class<?> clazz, String type) throws NoSuchFieldException {
|
||||
try {
|
||||
for (Field f : clazz.getDeclaredFields()) {
|
||||
if (f.getGenericType().getTypeName().equals(type))
|
||||
return f;
|
||||
}
|
||||
throw new NoSuchFieldException(type);
|
||||
} catch (NoSuchFieldException e) {
|
||||
Class<?> superClass = clazz.getSuperclass();
|
||||
if (superClass == null) throw e;
|
||||
return getField(superClass, type);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private static Field getField(Class<?> clazz, Class<?> fieldType) throws NoSuchFieldException {
|
||||
try {
|
||||
for (Field f : clazz.getDeclaredFields()) {
|
||||
@@ -533,4 +710,76 @@ public class NMSBinding implements INMSBinding {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void injectBukkit() {
|
||||
try {
|
||||
Iris.info("Injecting Bukkit");
|
||||
new ByteBuddy()
|
||||
.redefine(CraftServer.class)
|
||||
.visit(Advice.to(CraftServerAdvice.class).on(ElementMatchers.isMethod().and(ElementMatchers.takesArguments(WorldCreator.class))))
|
||||
.make()
|
||||
.load(CraftServer.class.getClassLoader(), ClassReloadingStrategy.fromInstalledAgent());
|
||||
new ByteBuddy()
|
||||
.redefine(ServerLevel.class)
|
||||
.visit(Advice.to(ServerLevelAdvice.class).on(ElementMatchers.isConstructor().and(ElementMatchers.takesArguments(MinecraftServer.class, Executor.class, LevelStorageSource.LevelStorageAccess.class,
|
||||
PrimaryLevelData.class, ResourceKey.class, LevelStem.class, ChunkProgressListener.class, boolean.class, long.class,
|
||||
List.class, boolean.class, World.Environment.class, ChunkGenerator.class, BiomeProvider.class))))
|
||||
.make()
|
||||
.load(ServerLevel.class.getClassLoader(), ClassReloadingStrategy.fromInstalledAgent());
|
||||
Iris.info("Injected Bukkit Successfully!");
|
||||
} catch (Exception e) {
|
||||
Iris.info(C.RED + "Failed to Inject Bukkit!");
|
||||
e.printStackTrace();
|
||||
Iris.reportError(e);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private static class ServerLevelAdvice {
|
||||
@Advice.OnMethodEnter
|
||||
static void enter(@Advice.Argument(0) MinecraftServer server, @Advice.Argument(2) LevelStorageSource.LevelStorageAccess access, @Advice.Argument(4) ResourceKey<Level> key, @Advice.Argument(value = 5, readOnly = false) LevelStem levelStem) {
|
||||
File iris = new File(access.levelDirectory.path().toFile(), "iris");
|
||||
if (!iris.exists() && !key.location().getPath().startsWith("iris/")) return;
|
||||
var logger = MinecraftServer.LOGGER;
|
||||
ResourceKey<DimensionType> typeKey = ResourceKey.create(Registry.DIMENSION_TYPE_REGISTRY, new ResourceLocation("iris", key.location().getPath()));
|
||||
RegistryAccess registryAccess = server.registryAccess();
|
||||
Registry<DimensionType> registry = registryAccess.registry(Registry.DIMENSION_TYPE_REGISTRY).orElse(null);
|
||||
if (registry == null) throw new IllegalStateException("Unable to find registry for dimension type " + typeKey);
|
||||
Holder<DimensionType> holder = registry.getHolder(typeKey).orElse(null);
|
||||
if (holder == null) throw new IllegalStateException("Unable to find dimension type " + typeKey);
|
||||
levelStem = new LevelStem(holder, levelStem.generator());
|
||||
}
|
||||
}
|
||||
|
||||
private static class CraftServerAdvice {
|
||||
@Advice.OnMethodEnter(skipOn = Advice.OnNonDefaultValue.class)
|
||||
static boolean enter(@Advice.This CraftServer self, @Advice.Argument(0) WorldCreator creator) {
|
||||
File isIrisWorld = new File(self.getWorldContainer(), creator.name() + "/iris");
|
||||
boolean isFromIris = false;
|
||||
StackTraceElement[] stackTrace = Thread.currentThread().getStackTrace();
|
||||
for (StackTraceElement stack : stackTrace) {
|
||||
if (stack.getClassName().contains("Iris")) {
|
||||
isFromIris = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (isIrisWorld.exists() && !isFromIris) {
|
||||
var logger = Logger.getLogger("Iris");
|
||||
logger.warning("detected another Plugin trying to load " + creator.name() + ". This is not supported and will be ignored.");
|
||||
|
||||
if (System.getProperty("iris.debug", "false").equals("true")) {
|
||||
new RuntimeException().printStackTrace();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Advice.OnMethodExit
|
||||
static void exit(@Advice.Enter boolean bool, @Advice.Return(readOnly = false) World returned) {
|
||||
if (bool) {
|
||||
returned = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2,6 +2,7 @@ package com.volmit.iris.core.nms.v1_19_R2;
|
||||
|
||||
import com.mojang.serialization.Codec;
|
||||
import com.volmit.iris.Iris;
|
||||
import com.volmit.iris.core.nms.INMS;
|
||||
import com.volmit.iris.engine.data.cache.AtomicCache;
|
||||
import com.volmit.iris.engine.framework.Engine;
|
||||
import com.volmit.iris.engine.object.IrisBiome;
|
||||
@@ -12,6 +13,7 @@ import net.minecraft.core.Holder;
|
||||
import net.minecraft.core.Registry;
|
||||
import net.minecraft.core.RegistryAccess;
|
||||
import net.minecraft.core.registries.Registries;
|
||||
import net.minecraft.resources.ResourceKey;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
import net.minecraft.world.level.biome.Biome;
|
||||
import net.minecraft.world.level.biome.BiomeSource;
|
||||
@@ -26,6 +28,7 @@ import java.lang.reflect.Field;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
public class CustomBiomeSource extends BiomeSource {
|
||||
|
||||
@@ -120,8 +123,28 @@ public class CustomBiomeSource extends BiomeSource {
|
||||
for (IrisBiome i : engine.getAllBiomes()) {
|
||||
if (i.isCustom()) {
|
||||
for (IrisBiomeCustom j : i.getCustomDerivitives()) {
|
||||
m.put(j.getId(), customRegistry.getHolder(customRegistry.getResourceKey(customRegistry
|
||||
.get(new ResourceLocation(engine.getDimension().getLoadKey() + ":" + j.getId()))).get()).get());
|
||||
ResourceLocation location = new ResourceLocation(engine.getDimension().getLoadKey() + ":" + j.getId());
|
||||
Biome biome = customRegistry.get(location);
|
||||
if (biome == null) {
|
||||
INMS.get().registerBiome(location.getNamespace(), j, false);
|
||||
biome = customRegistry.get(location);
|
||||
if (biome == null) {
|
||||
Iris.error("Cannot find biome for IrisBiomeCustom " + j.getId() + " from engine " + engine.getName());
|
||||
continue;
|
||||
}
|
||||
}
|
||||
Optional<ResourceKey<Biome>> optionalBiomeKey = customRegistry.getResourceKey(biome);
|
||||
if (optionalBiomeKey.isEmpty()) {
|
||||
Iris.error("Cannot find biome for IrisBiomeCustom " + j.getId() + " from engine " + engine.getName());
|
||||
continue;
|
||||
}
|
||||
ResourceKey<Biome> biomeKey = optionalBiomeKey.get();
|
||||
Optional<Holder.Reference<Biome>> optionalReferenceHolder = customRegistry.getHolder(biomeKey);
|
||||
if (optionalReferenceHolder.isEmpty()) {
|
||||
Iris.error("Cannot find reference to biome " + biomeKey + " for engine " + engine.getName());
|
||||
continue;
|
||||
}
|
||||
m.put(j.getId(), optionalReferenceHolder.get());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,14 +4,44 @@ import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.DataInputStream;
|
||||
import java.io.DataOutputStream;
|
||||
import java.io.File;
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.Method;
|
||||
import java.lang.reflect.Modifier;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.Vector;
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import com.google.common.base.Preconditions;
|
||||
import com.google.gson.JsonElement;
|
||||
import com.google.gson.JsonObject;
|
||||
import com.mojang.datafixers.util.Pair;
|
||||
import com.mojang.serialization.Codec;
|
||||
import com.mojang.serialization.JsonOps;
|
||||
import com.mojang.serialization.Lifecycle;
|
||||
import com.volmit.iris.engine.object.IrisBiomeCustom;
|
||||
import com.volmit.iris.engine.object.IrisDimension;
|
||||
import com.volmit.iris.util.format.C;
|
||||
import it.unimi.dsi.fastutil.objects.Reference2IntMap;
|
||||
import net.bytebuddy.ByteBuddy;
|
||||
import net.bytebuddy.asm.Advice;
|
||||
import net.bytebuddy.dynamic.loading.ClassReloadingStrategy;
|
||||
import net.bytebuddy.matcher.ElementMatchers;
|
||||
import net.minecraft.core.MappedRegistry;
|
||||
import net.minecraft.resources.ResourceKey;
|
||||
import net.minecraft.server.MinecraftServer;
|
||||
import net.minecraft.server.level.progress.ChunkProgressListener;
|
||||
import net.minecraft.util.GsonHelper;
|
||||
import net.minecraft.world.level.Level;
|
||||
import net.minecraft.world.level.dimension.DimensionType;
|
||||
import net.minecraft.world.level.dimension.LevelStem;
|
||||
import net.minecraft.world.level.storage.LevelStorageSource;
|
||||
import net.minecraft.world.level.storage.PrimaryLevelData;
|
||||
import org.bukkit.*;
|
||||
import org.bukkit.block.Biome;
|
||||
import org.bukkit.block.data.BlockData;
|
||||
@@ -25,6 +55,7 @@ import org.bukkit.craftbukkit.v1_19_R2.inventory.CraftItemStack;
|
||||
import org.bukkit.entity.Dolphin;
|
||||
import org.bukkit.entity.Entity;
|
||||
import org.bukkit.event.entity.CreatureSpawnEvent;
|
||||
import org.bukkit.generator.BiomeProvider;
|
||||
import org.bukkit.generator.ChunkGenerator;
|
||||
import org.bukkit.inventory.ItemStack;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
@@ -171,6 +202,17 @@ public class NMSBinding implements INMSBinding {
|
||||
return null;
|
||||
}
|
||||
|
||||
private RegistryAccess getRegistryAccess(World world) {
|
||||
try {
|
||||
var field = getField(Level.class, RegistryAccess.class);
|
||||
field.setAccessible(true);
|
||||
return (RegistryAccess) field.get(((CraftWorld) world).getHandle());
|
||||
} catch (Throwable e) {
|
||||
e.printStackTrace();
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void deserializeTile(CompoundTag c, Location pos) {
|
||||
((CraftWorld) pos.getWorld()).getHandle().getChunkAt(new BlockPos(pos.getBlockX(), 0, pos.getBlockZ())).setBlockEntityNbt(convert(c));
|
||||
@@ -254,8 +296,7 @@ public class NMSBinding implements INMSBinding {
|
||||
|
||||
@Override
|
||||
public Object getBiomeBase(World world, Biome biome) {
|
||||
return CraftBlock.biomeToBiomeBase(((CraftWorld) world).getHandle()
|
||||
.registryAccess().registry(Registries.BIOME).orElse(null), biome);
|
||||
return CraftBlock.biomeToBiomeBase(getRegistryAccess(world).registry(Registries.BIOME).orElse(null), biome);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -292,8 +333,11 @@ public class NMSBinding implements INMSBinding {
|
||||
public int getBiomeId(Biome biome) {
|
||||
for (World i : Bukkit.getWorlds()) {
|
||||
if (i.getEnvironment().equals(World.Environment.NORMAL)) {
|
||||
Registry<net.minecraft.world.level.biome.Biome> registry = ((CraftWorld) i).getHandle().registryAccess().registry(Registries.BIOME).orElse(null);
|
||||
return registry.getId((net.minecraft.world.level.biome.Biome) getBiomeBase(registry, biome));
|
||||
var registry = getRegistryAccess(i).registry(Registries.BIOME).orElse(null);
|
||||
if (registry != null) {
|
||||
var holder = (Holder<net.minecraft.world.level.biome.Biome>) getBiomeBase(registry, biome);
|
||||
return registry.getId(holder.value());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -519,6 +563,138 @@ public class NMSBinding implements INMSBinding {
|
||||
return ((CraftWorld) location.getWorld()).spawn(location, type.getEntityClass(), null, reason);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean registerDimension(String name, IrisDimension dimension) {
|
||||
var registry = registry(Registries.DIMENSION_TYPE);
|
||||
var baseLocation = switch (dimension.getEnvironment()) {
|
||||
case NORMAL -> new ResourceLocation("minecraft", "overworld");
|
||||
case NETHER -> new ResourceLocation("minecraft", "the_nether");
|
||||
case THE_END -> new ResourceLocation("minecraft", "the_end");
|
||||
case CUSTOM -> throw new IllegalArgumentException("Cannot register custom dimension");
|
||||
};
|
||||
var base = registry.getHolder(ResourceKey.create(Registries.DIMENSION_TYPE, baseLocation)).orElse(null);
|
||||
if (base == null) return false;
|
||||
var json = encode(DimensionType.CODEC, base).orElse(null);
|
||||
if (json == null) return false;
|
||||
var object = json.getAsJsonObject();
|
||||
var height = dimension.getDimensionHeight();
|
||||
object.addProperty("min_y", height.getMin());
|
||||
object.addProperty("height", height.getMax() - height.getMin());
|
||||
object.addProperty("logical_height", dimension.getLogicalHeight());
|
||||
var value = decode(DimensionType.CODEC, object.toString()).map(Holder::value).orElse(null);
|
||||
if (value == null) return false;
|
||||
return register(Registries.DIMENSION_TYPE, new ResourceLocation("iris", name), value, true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean registerBiome(String dimensionId, IrisBiomeCustom biome, boolean replace) {
|
||||
var biomeBase = decode(net.minecraft.world.level.biome.Biome.CODEC, biome.generateJson()).map(Holder::value).orElse(null);
|
||||
if (biomeBase == null) return false;
|
||||
return register(Registries.BIOME, new ResourceLocation(dimensionId, biome.getId()), biomeBase, replace);
|
||||
}
|
||||
|
||||
private <T> Optional<T> decode(Codec<T> codec, String json) {
|
||||
return codec.decode(JsonOps.INSTANCE, GsonHelper.parse(json)).get().left().map(Pair::getFirst);
|
||||
}
|
||||
|
||||
private <T> Optional<JsonElement> encode(Codec<T> codec, T value) {
|
||||
return codec.encode(value, JsonOps.INSTANCE, new JsonObject()).result();
|
||||
}
|
||||
|
||||
private <T> boolean register(ResourceKey<Registry<T>> registryKey, ResourceLocation location, T value, boolean replace) {
|
||||
Preconditions.checkArgument(registryKey != null, "The registry cannot be null!");
|
||||
Preconditions.checkArgument(location != null, "The location cannot be null!");
|
||||
Preconditions.checkArgument(value != null, "The value cannot be null!");
|
||||
var registry = registry(registryKey);
|
||||
var key = ResourceKey.create(registryKey, location);
|
||||
try {
|
||||
if (registry.containsKey(key)) {
|
||||
if (!replace) return false;
|
||||
return replace(registryKey, location, value);
|
||||
}
|
||||
Field field = getField(MappedRegistry.class, boolean.class);
|
||||
field.setAccessible(true);
|
||||
boolean frozen = field.getBoolean(registry);
|
||||
field.setBoolean(registry, false);
|
||||
Field valueField = getField(Holder.Reference.class, "T");
|
||||
valueField.setAccessible(true);
|
||||
|
||||
try {
|
||||
var holder = registry.register(key, value, Lifecycle.stable());
|
||||
if (frozen) valueField.set(holder, value);
|
||||
return true;
|
||||
} finally {
|
||||
field.setBoolean(registry, frozen);
|
||||
}
|
||||
} catch (Throwable e) {
|
||||
throw new IllegalStateException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private <T> boolean replace(ResourceKey<Registry<T>> registryKey, ResourceLocation location, T value) {
|
||||
Preconditions.checkArgument(registryKey != null, "The registryKey cannot be null!");
|
||||
Preconditions.checkArgument(location != null, "The location cannot be null!");
|
||||
Preconditions.checkArgument(value != null, "The value cannot be null!");
|
||||
var registry = registry(registryKey);
|
||||
var key = ResourceKey.create(registryKey, location);
|
||||
try {
|
||||
var holder = registry.getHolder(key).orElse(null);
|
||||
if (holder == null) return false;
|
||||
var oldValue = holder.value();
|
||||
Field valueField = getField(Holder.Reference.class, "T");
|
||||
valueField.setAccessible(true);
|
||||
Field toIdField = getField(MappedRegistry.class, buildType(Reference2IntMap.class, "T"));
|
||||
toIdField.setAccessible(true);
|
||||
Field byValueField = getField(MappedRegistry.class, buildType(Map.class, "T", buildType(Holder.Reference.class, "T")));
|
||||
byValueField.setAccessible(true);
|
||||
Field lifecyclesField = getField(MappedRegistry.class, buildType(Map.class, "T", Lifecycle.class.getName()));
|
||||
lifecyclesField.setAccessible(true);
|
||||
var toId = (Reference2IntMap<T>) toIdField.get(registry);
|
||||
var byValue = (Map<T, Holder.Reference<T>>) byValueField.get(registry);
|
||||
var lifecycles = (Map<T, Lifecycle>) lifecyclesField.get(registry);
|
||||
|
||||
valueField.set(holder, value);
|
||||
toId.put(value, toId.removeInt(oldValue));
|
||||
byValue.put(value, byValue.remove(oldValue));
|
||||
lifecycles.put(value, lifecycles.remove(oldValue));
|
||||
return true;
|
||||
} catch (Throwable e) {
|
||||
throw new IllegalStateException(e);
|
||||
}
|
||||
}
|
||||
|
||||
private <T> MappedRegistry<T> registry(ResourceKey<Registry<T>> registryKey) {
|
||||
var rawRegistry = registry().registry(registryKey).orElse(null);
|
||||
if (!(rawRegistry instanceof MappedRegistry<T> registry))
|
||||
throw new IllegalStateException("The Registry is not a mapped Registry!");
|
||||
return registry;
|
||||
}
|
||||
|
||||
private static String buildType(Class<?> clazz, String... parameterTypes) {
|
||||
if (parameterTypes.length == 0) return clazz.getName();
|
||||
var builder = new StringBuilder(clazz.getName())
|
||||
.append("<");
|
||||
for (int i = 0; i < parameterTypes.length; i++) {
|
||||
builder.append(parameterTypes[i]).append(parameterTypes.length - 1 == i ? ">" : ", ");
|
||||
}
|
||||
return builder.toString();
|
||||
}
|
||||
|
||||
private static Field getField(Class<?> clazz, String type) throws NoSuchFieldException {
|
||||
try {
|
||||
for (Field f : clazz.getDeclaredFields()) {
|
||||
if (f.getGenericType().getTypeName().equals(type))
|
||||
return f;
|
||||
}
|
||||
throw new NoSuchFieldException(type);
|
||||
} catch (NoSuchFieldException e) {
|
||||
Class<?> superClass = clazz.getSuperclass();
|
||||
if (superClass == null) throw e;
|
||||
return getField(superClass, type);
|
||||
}
|
||||
}
|
||||
|
||||
private static Field getField(Class<?> clazz, Class<?> fieldType) throws NoSuchFieldException {
|
||||
try {
|
||||
for (Field f : clazz.getDeclaredFields()) {
|
||||
@@ -535,4 +711,75 @@ public class NMSBinding implements INMSBinding {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void injectBukkit() {
|
||||
try {
|
||||
Iris.info("Injecting Bukkit");
|
||||
new ByteBuddy()
|
||||
.redefine(CraftServer.class)
|
||||
.visit(Advice.to(CraftServerAdvice.class).on(ElementMatchers.isMethod().and(ElementMatchers.takesArguments(WorldCreator.class))))
|
||||
.make()
|
||||
.load(CraftServer.class.getClassLoader(), ClassReloadingStrategy.fromInstalledAgent());
|
||||
new ByteBuddy()
|
||||
.redefine(ServerLevel.class)
|
||||
.visit(Advice.to(ServerLevelAdvice.class).on(ElementMatchers.isConstructor().and(ElementMatchers.takesArguments(MinecraftServer.class, Executor.class, LevelStorageSource.LevelStorageAccess.class,
|
||||
PrimaryLevelData.class, ResourceKey.class, LevelStem.class, ChunkProgressListener.class, boolean.class, long.class,
|
||||
List.class, boolean.class, World.Environment.class, ChunkGenerator.class, BiomeProvider.class))))
|
||||
.make()
|
||||
.load(ServerLevel.class.getClassLoader(), ClassReloadingStrategy.fromInstalledAgent());
|
||||
Iris.info("Injected Bukkit Successfully!");
|
||||
} catch (Exception e) {
|
||||
Iris.info(C.RED + "Failed to Inject Bukkit!");
|
||||
e.printStackTrace();
|
||||
Iris.reportError(e);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private static class ServerLevelAdvice {
|
||||
@Advice.OnMethodEnter
|
||||
static void enter(@Advice.Argument(0) MinecraftServer server, @Advice.Argument(2) LevelStorageSource.LevelStorageAccess access, @Advice.Argument(4) ResourceKey<Level> key, @Advice.Argument(value = 5, readOnly = false) LevelStem levelStem) {
|
||||
File iris = new File(access.levelDirectory.path().toFile(), "iris");
|
||||
if (!iris.exists() && !key.location().getPath().startsWith("iris/")) return;
|
||||
ResourceKey<DimensionType> typeKey = ResourceKey.create(Registries.DIMENSION_TYPE, new ResourceLocation("iris", key.location().getPath()));
|
||||
RegistryAccess registryAccess = server.registryAccess();
|
||||
Registry<DimensionType> registry = registryAccess.registry(Registries.DIMENSION_TYPE).orElse(null);
|
||||
if (registry == null) throw new IllegalStateException("Unable to find registry for dimension type " + typeKey);
|
||||
Holder<DimensionType> holder = registry.getHolder(typeKey).orElse(null);
|
||||
if (holder == null) throw new IllegalStateException("Unable to find dimension type " + typeKey);
|
||||
levelStem = new LevelStem(holder, levelStem.generator());
|
||||
}
|
||||
}
|
||||
|
||||
private static class CraftServerAdvice {
|
||||
@Advice.OnMethodEnter(skipOn = Advice.OnNonDefaultValue.class)
|
||||
static boolean enter(@Advice.This CraftServer self, @Advice.Argument(0) WorldCreator creator) {
|
||||
File isIrisWorld = new File(self.getWorldContainer(), creator.name() + "/iris");
|
||||
boolean isFromIris = false;
|
||||
StackTraceElement[] stackTrace = Thread.currentThread().getStackTrace();
|
||||
for (StackTraceElement stack : stackTrace) {
|
||||
if (stack.getClassName().contains("Iris")) {
|
||||
isFromIris = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (isIrisWorld.exists() && !isFromIris) {
|
||||
var logger = Logger.getLogger("Iris");
|
||||
logger.warning("detected another Plugin trying to load " + creator.name() + ". This is not supported and will be ignored.");
|
||||
|
||||
if (System.getProperty("iris.debug", "false").equals("true")) {
|
||||
new RuntimeException().printStackTrace();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Advice.OnMethodExit
|
||||
static void exit(@Advice.Enter boolean bool, @Advice.Return(readOnly = false) World returned) {
|
||||
if (bool) {
|
||||
returned = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2,6 +2,7 @@ package com.volmit.iris.core.nms.v1_19_R3;
|
||||
|
||||
import com.mojang.serialization.Codec;
|
||||
import com.volmit.iris.Iris;
|
||||
import com.volmit.iris.core.nms.INMS;
|
||||
import com.volmit.iris.engine.data.cache.AtomicCache;
|
||||
import com.volmit.iris.engine.framework.Engine;
|
||||
import com.volmit.iris.engine.object.IrisBiome;
|
||||
@@ -125,8 +126,16 @@ public class CustomBiomeSource extends BiomeSource {
|
||||
for (IrisBiome i : engine.getAllBiomes()) {
|
||||
if (i.isCustom()) {
|
||||
for (IrisBiomeCustom j : i.getCustomDerivitives()) {
|
||||
ResourceLocation resourceLocation = new ResourceLocation(engine.getDimension().getLoadKey() + ":" + j.getId());
|
||||
Biome biome = customRegistry.get(resourceLocation);
|
||||
ResourceLocation location = new ResourceLocation(engine.getDimension().getLoadKey() + ":" + j.getId());
|
||||
Biome biome = customRegistry.get(location);
|
||||
if (biome == null) {
|
||||
INMS.get().registerBiome(location.getNamespace(), j, false);
|
||||
biome = customRegistry.get(location);
|
||||
if (biome == null) {
|
||||
Iris.error("Cannot find biome for IrisBiomeCustom " + j.getId() + " from engine " + engine.getName());
|
||||
continue;
|
||||
}
|
||||
}
|
||||
Optional<ResourceKey<Biome>> optionalBiomeKey = customRegistry.getResourceKey(biome);
|
||||
if (optionalBiomeKey.isEmpty()) {
|
||||
Iris.error("Cannot find biome for IrisBiomeCustom " + j.getId() + " from engine " + engine.getName());
|
||||
|
||||
@@ -4,14 +4,44 @@ import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.DataInputStream;
|
||||
import java.io.DataOutputStream;
|
||||
import java.io.File;
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.Method;
|
||||
import java.lang.reflect.Modifier;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.Vector;
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import com.google.common.base.Preconditions;
|
||||
import com.google.gson.JsonElement;
|
||||
import com.google.gson.JsonObject;
|
||||
import com.mojang.datafixers.util.Pair;
|
||||
import com.mojang.serialization.Codec;
|
||||
import com.mojang.serialization.JsonOps;
|
||||
import com.mojang.serialization.Lifecycle;
|
||||
import com.volmit.iris.engine.object.IrisBiomeCustom;
|
||||
import com.volmit.iris.engine.object.IrisDimension;
|
||||
import com.volmit.iris.util.format.C;
|
||||
import it.unimi.dsi.fastutil.objects.Reference2IntMap;
|
||||
import net.bytebuddy.ByteBuddy;
|
||||
import net.bytebuddy.asm.Advice;
|
||||
import net.bytebuddy.dynamic.loading.ClassReloadingStrategy;
|
||||
import net.bytebuddy.matcher.ElementMatchers;
|
||||
import net.minecraft.core.MappedRegistry;
|
||||
import net.minecraft.resources.ResourceKey;
|
||||
import net.minecraft.server.MinecraftServer;
|
||||
import net.minecraft.server.level.progress.ChunkProgressListener;
|
||||
import net.minecraft.util.GsonHelper;
|
||||
import net.minecraft.world.level.Level;
|
||||
import net.minecraft.world.level.dimension.DimensionType;
|
||||
import net.minecraft.world.level.dimension.LevelStem;
|
||||
import net.minecraft.world.level.storage.LevelStorageSource;
|
||||
import net.minecraft.world.level.storage.PrimaryLevelData;
|
||||
import org.bukkit.*;
|
||||
import org.bukkit.block.Biome;
|
||||
import org.bukkit.block.data.BlockData;
|
||||
@@ -25,6 +55,7 @@ import org.bukkit.craftbukkit.v1_19_R3.inventory.CraftItemStack;
|
||||
import org.bukkit.entity.Dolphin;
|
||||
import org.bukkit.entity.Entity;
|
||||
import org.bukkit.event.entity.CreatureSpawnEvent;
|
||||
import org.bukkit.generator.BiomeProvider;
|
||||
import org.bukkit.generator.ChunkGenerator;
|
||||
import org.bukkit.inventory.ItemStack;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
@@ -172,6 +203,17 @@ public class NMSBinding implements INMSBinding {
|
||||
return null;
|
||||
}
|
||||
|
||||
private RegistryAccess getRegistryAccess(World world) {
|
||||
try {
|
||||
var field = getField(Level.class, RegistryAccess.class);
|
||||
field.setAccessible(true);
|
||||
return (RegistryAccess) field.get(((CraftWorld) world).getHandle());
|
||||
} catch (Throwable e) {
|
||||
e.printStackTrace();
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void deserializeTile(CompoundTag c, Location pos) {
|
||||
((CraftWorld) pos.getWorld()).getHandle().getChunkAt(new BlockPos(pos.getBlockX(), 0, pos.getBlockZ())).setBlockEntityNbt(convert(c));
|
||||
@@ -255,8 +297,7 @@ public class NMSBinding implements INMSBinding {
|
||||
|
||||
@Override
|
||||
public Object getBiomeBase(World world, Biome biome) {
|
||||
return CraftBlock.biomeToBiomeBase(((CraftWorld) world).getHandle()
|
||||
.registryAccess().registry(Registries.BIOME).orElse(null), biome);
|
||||
return CraftBlock.biomeToBiomeBase(getRegistryAccess(world).registry(Registries.BIOME).orElse(null), biome);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -294,8 +335,11 @@ public class NMSBinding implements INMSBinding {
|
||||
public int getBiomeId(Biome biome) {
|
||||
for (World i : Bukkit.getWorlds()) {
|
||||
if (i.getEnvironment().equals(World.Environment.NORMAL)) {
|
||||
Registry<net.minecraft.world.level.biome.Biome> registry = ((CraftWorld) i).getHandle().registryAccess().registry(Registries.BIOME).orElse(null);
|
||||
return registry.getId((net.minecraft.world.level.biome.Biome) getBiomeBase(registry, biome));
|
||||
var registry = getRegistryAccess(i).registry(Registries.BIOME).orElse(null);
|
||||
if (registry != null) {
|
||||
var holder = (Holder<net.minecraft.world.level.biome.Biome>) getBiomeBase(registry, biome);
|
||||
return registry.getId(holder.value());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -523,6 +567,138 @@ public class NMSBinding implements INMSBinding {
|
||||
return ((CraftWorld) location.getWorld()).spawn(location, type.getEntityClass(), null, reason);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean registerDimension(String name, IrisDimension dimension) {
|
||||
var registry = registry(Registries.DIMENSION_TYPE);
|
||||
var baseLocation = switch (dimension.getEnvironment()) {
|
||||
case NORMAL -> new ResourceLocation("minecraft", "overworld");
|
||||
case NETHER -> new ResourceLocation("minecraft", "the_nether");
|
||||
case THE_END -> new ResourceLocation("minecraft", "the_end");
|
||||
case CUSTOM -> throw new IllegalArgumentException("Cannot register custom dimension");
|
||||
};
|
||||
var base = registry.getHolder(ResourceKey.create(Registries.DIMENSION_TYPE, baseLocation)).orElse(null);
|
||||
if (base == null) return false;
|
||||
var json = encode(DimensionType.CODEC, base).orElse(null);
|
||||
if (json == null) return false;
|
||||
var object = json.getAsJsonObject();
|
||||
var height = dimension.getDimensionHeight();
|
||||
object.addProperty("min_y", height.getMin());
|
||||
object.addProperty("height", height.getMax() - height.getMin());
|
||||
object.addProperty("logical_height", dimension.getLogicalHeight());
|
||||
var value = decode(DimensionType.CODEC, object.toString()).map(Holder::value).orElse(null);
|
||||
if (value == null) return false;
|
||||
return register(Registries.DIMENSION_TYPE, new ResourceLocation("iris", name), value, true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean registerBiome(String dimensionId, IrisBiomeCustom biome, boolean replace) {
|
||||
var biomeBase = decode(net.minecraft.world.level.biome.Biome.CODEC, biome.generateJson()).map(Holder::value).orElse(null);
|
||||
if (biomeBase == null) return false;
|
||||
return register(Registries.BIOME, new ResourceLocation(dimensionId, biome.getId()), biomeBase, replace);
|
||||
}
|
||||
|
||||
private <T> Optional<T> decode(Codec<T> codec, String json) {
|
||||
return codec.decode(JsonOps.INSTANCE, GsonHelper.parse(json)).get().left().map(Pair::getFirst);
|
||||
}
|
||||
|
||||
private <T> Optional<JsonElement> encode(Codec<T> codec, T value) {
|
||||
return codec.encode(value, JsonOps.INSTANCE, new JsonObject()).result();
|
||||
}
|
||||
|
||||
private <T> boolean register(ResourceKey<Registry<T>> registryKey, ResourceLocation location, T value, boolean replace) {
|
||||
Preconditions.checkArgument(registryKey != null, "The registry cannot be null!");
|
||||
Preconditions.checkArgument(location != null, "The location cannot be null!");
|
||||
Preconditions.checkArgument(value != null, "The value cannot be null!");
|
||||
var registry = registry(registryKey);
|
||||
var key = ResourceKey.create(registryKey, location);
|
||||
try {
|
||||
if (registry.containsKey(key)) {
|
||||
if (!replace) return false;
|
||||
return replace(registryKey, location, value);
|
||||
}
|
||||
Field field = getField(MappedRegistry.class, boolean.class);
|
||||
field.setAccessible(true);
|
||||
boolean frozen = field.getBoolean(registry);
|
||||
field.setBoolean(registry, false);
|
||||
Field valueField = getField(Holder.Reference.class, "T");
|
||||
valueField.setAccessible(true);
|
||||
|
||||
try {
|
||||
var holder = registry.register(key, value, Lifecycle.stable());
|
||||
if (frozen) valueField.set(holder, value);
|
||||
return true;
|
||||
} finally {
|
||||
field.setBoolean(registry, frozen);
|
||||
}
|
||||
} catch (Throwable e) {
|
||||
throw new IllegalStateException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private <T> boolean replace(ResourceKey<Registry<T>> registryKey, ResourceLocation location, T value) {
|
||||
Preconditions.checkArgument(registryKey != null, "The registryKey cannot be null!");
|
||||
Preconditions.checkArgument(location != null, "The location cannot be null!");
|
||||
Preconditions.checkArgument(value != null, "The value cannot be null!");
|
||||
var registry = registry(registryKey);
|
||||
var key = ResourceKey.create(registryKey, location);
|
||||
try {
|
||||
var holder = registry.getHolder(key).orElse(null);
|
||||
if (holder == null) return false;
|
||||
var oldValue = holder.value();
|
||||
Field valueField = getField(Holder.Reference.class, "T");
|
||||
valueField.setAccessible(true);
|
||||
Field toIdField = getField(MappedRegistry.class, buildType(Reference2IntMap.class, "T"));
|
||||
toIdField.setAccessible(true);
|
||||
Field byValueField = getField(MappedRegistry.class, buildType(Map.class, "T", buildType(Holder.Reference.class, "T")));
|
||||
byValueField.setAccessible(true);
|
||||
Field lifecyclesField = getField(MappedRegistry.class, buildType(Map.class, "T", Lifecycle.class.getName()));
|
||||
lifecyclesField.setAccessible(true);
|
||||
var toId = (Reference2IntMap<T>) toIdField.get(registry);
|
||||
var byValue = (Map<T, Holder.Reference<T>>) byValueField.get(registry);
|
||||
var lifecycles = (Map<T, Lifecycle>) lifecyclesField.get(registry);
|
||||
|
||||
valueField.set(holder, value);
|
||||
toId.put(value, toId.removeInt(oldValue));
|
||||
byValue.put(value, byValue.remove(oldValue));
|
||||
lifecycles.put(value, lifecycles.remove(oldValue));
|
||||
return true;
|
||||
} catch (Throwable e) {
|
||||
throw new IllegalStateException(e);
|
||||
}
|
||||
}
|
||||
|
||||
private <T> MappedRegistry<T> registry(ResourceKey<Registry<T>> registryKey) {
|
||||
var rawRegistry = registry().registry(registryKey).orElse(null);
|
||||
if (!(rawRegistry instanceof MappedRegistry<T> registry))
|
||||
throw new IllegalStateException("The Registry is not a mapped Registry!");
|
||||
return registry;
|
||||
}
|
||||
|
||||
private static String buildType(Class<?> clazz, String... parameterTypes) {
|
||||
if (parameterTypes.length == 0) return clazz.getName();
|
||||
var builder = new StringBuilder(clazz.getName())
|
||||
.append("<");
|
||||
for (int i = 0; i < parameterTypes.length; i++) {
|
||||
builder.append(parameterTypes[i]).append(parameterTypes.length - 1 == i ? ">" : ", ");
|
||||
}
|
||||
return builder.toString();
|
||||
}
|
||||
|
||||
private static Field getField(Class<?> clazz, String type) throws NoSuchFieldException {
|
||||
try {
|
||||
for (Field f : clazz.getDeclaredFields()) {
|
||||
if (f.getGenericType().getTypeName().equals(type))
|
||||
return f;
|
||||
}
|
||||
throw new NoSuchFieldException(type);
|
||||
} catch (NoSuchFieldException e) {
|
||||
Class<?> superClass = clazz.getSuperclass();
|
||||
if (superClass == null) throw e;
|
||||
return getField(superClass, type);
|
||||
}
|
||||
}
|
||||
|
||||
private static Field getField(Class<?> clazz, Class<?> fieldType) throws NoSuchFieldException {
|
||||
try {
|
||||
for (Field f : clazz.getDeclaredFields()) {
|
||||
@@ -539,4 +715,75 @@ public class NMSBinding implements INMSBinding {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void injectBukkit() {
|
||||
try {
|
||||
Iris.info("Injecting Bukkit");
|
||||
new ByteBuddy()
|
||||
.redefine(CraftServer.class)
|
||||
.visit(Advice.to(CraftServerAdvice.class).on(ElementMatchers.isMethod().and(ElementMatchers.takesArguments(WorldCreator.class))))
|
||||
.make()
|
||||
.load(CraftServer.class.getClassLoader(), ClassReloadingStrategy.fromInstalledAgent());
|
||||
new ByteBuddy()
|
||||
.redefine(ServerLevel.class)
|
||||
.visit(Advice.to(ServerLevelAdvice.class).on(ElementMatchers.isConstructor().and(ElementMatchers.takesArguments(MinecraftServer.class, Executor.class, LevelStorageSource.LevelStorageAccess.class,
|
||||
PrimaryLevelData.class, ResourceKey.class, LevelStem.class, ChunkProgressListener.class, boolean.class, long.class,
|
||||
List.class, boolean.class, World.Environment.class, ChunkGenerator.class, BiomeProvider.class))))
|
||||
.make()
|
||||
.load(ServerLevel.class.getClassLoader(), ClassReloadingStrategy.fromInstalledAgent());
|
||||
Iris.info("Injected Bukkit Successfully!");
|
||||
} catch (Exception e) {
|
||||
Iris.info(C.RED + "Failed to Inject Bukkit!");
|
||||
e.printStackTrace();
|
||||
Iris.reportError(e);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private static class ServerLevelAdvice {
|
||||
@Advice.OnMethodEnter
|
||||
static void enter(@Advice.Argument(0) MinecraftServer server, @Advice.Argument(2) LevelStorageSource.LevelStorageAccess access, @Advice.Argument(4) ResourceKey<Level> key, @Advice.Argument(value = 5, readOnly = false) LevelStem levelStem) {
|
||||
File iris = new File(access.levelDirectory.path().toFile(), "iris");
|
||||
if (!iris.exists() && !key.location().getPath().startsWith("iris/")) return;
|
||||
ResourceKey<DimensionType> typeKey = ResourceKey.create(Registries.DIMENSION_TYPE, new ResourceLocation("iris", key.location().getPath()));
|
||||
RegistryAccess registryAccess = server.registryAccess();
|
||||
Registry<DimensionType> registry = registryAccess.registry(Registries.DIMENSION_TYPE).orElse(null);
|
||||
if (registry == null) throw new IllegalStateException("Unable to find registry for dimension type " + typeKey);
|
||||
Holder<DimensionType> holder = registry.getHolder(typeKey).orElse(null);
|
||||
if (holder == null) throw new IllegalStateException("Unable to find dimension type " + typeKey);
|
||||
levelStem = new LevelStem(holder, levelStem.generator());
|
||||
}
|
||||
}
|
||||
|
||||
private static class CraftServerAdvice {
|
||||
@Advice.OnMethodEnter(skipOn = Advice.OnNonDefaultValue.class)
|
||||
static boolean enter(@Advice.This CraftServer self, @Advice.Argument(0) WorldCreator creator) {
|
||||
File isIrisWorld = new File(self.getWorldContainer(), creator.name() + "/iris");
|
||||
boolean isFromIris = false;
|
||||
StackTraceElement[] stackTrace = Thread.currentThread().getStackTrace();
|
||||
for (StackTraceElement stack : stackTrace) {
|
||||
if (stack.getClassName().contains("Iris")) {
|
||||
isFromIris = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (isIrisWorld.exists() && !isFromIris) {
|
||||
var logger = Logger.getLogger("Iris");
|
||||
logger.warning("detected another Plugin trying to load " + creator.name() + ". This is not supported and will be ignored.");
|
||||
|
||||
if (System.getProperty("iris.debug", "false").equals("true")) {
|
||||
new RuntimeException().printStackTrace();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Advice.OnMethodExit
|
||||
static void exit(@Advice.Enter boolean bool, @Advice.Return(readOnly = false) World returned) {
|
||||
if (bool) {
|
||||
returned = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ package com.volmit.iris.core.nms.v1_20_R1;
|
||||
|
||||
import com.mojang.serialization.Codec;
|
||||
import com.volmit.iris.Iris;
|
||||
import com.volmit.iris.core.nms.INMS;
|
||||
import com.volmit.iris.engine.data.cache.AtomicCache;
|
||||
import com.volmit.iris.engine.framework.Engine;
|
||||
import com.volmit.iris.engine.object.IrisBiome;
|
||||
@@ -125,8 +126,16 @@ public class CustomBiomeSource extends BiomeSource {
|
||||
for (IrisBiome i : engine.getAllBiomes()) {
|
||||
if (i.isCustom()) {
|
||||
for (IrisBiomeCustom j : i.getCustomDerivitives()) {
|
||||
ResourceLocation resourceLocation = new ResourceLocation(engine.getDimension().getLoadKey() + ":" + j.getId());
|
||||
Biome biome = customRegistry.get(resourceLocation);
|
||||
ResourceLocation location = new ResourceLocation(engine.getDimension().getLoadKey() + ":" + j.getId());
|
||||
Biome biome = customRegistry.get(location);
|
||||
if (biome == null) {
|
||||
INMS.get().registerBiome(location.getNamespace(), j, false);
|
||||
biome = customRegistry.get(location);
|
||||
if (biome == null) {
|
||||
Iris.error("Cannot find biome for IrisBiomeCustom " + j.getId() + " from engine " + engine.getName());
|
||||
continue;
|
||||
}
|
||||
}
|
||||
Optional<ResourceKey<Biome>> optionalBiomeKey = customRegistry.getResourceKey(biome);
|
||||
if (optionalBiomeKey.isEmpty()) {
|
||||
Iris.error("Cannot find biome for IrisBiomeCustom " + j.getId() + " from engine " + engine.getName());
|
||||
|
||||
@@ -1,12 +1,22 @@
|
||||
package com.volmit.iris.core.nms.v1_20_R1;
|
||||
|
||||
import com.google.common.base.Preconditions;
|
||||
import com.google.gson.JsonElement;
|
||||
import com.google.gson.JsonObject;
|
||||
import com.mojang.brigadier.exceptions.CommandSyntaxException;
|
||||
import com.mojang.datafixers.util.Pair;
|
||||
import com.mojang.serialization.Codec;
|
||||
import com.mojang.serialization.JsonOps;
|
||||
import com.mojang.serialization.Lifecycle;
|
||||
import com.volmit.iris.Iris;
|
||||
import com.volmit.iris.core.nms.INMSBinding;
|
||||
import com.volmit.iris.engine.data.cache.AtomicCache;
|
||||
import com.volmit.iris.engine.framework.Engine;
|
||||
import com.volmit.iris.engine.object.IrisBiomeCustom;
|
||||
import com.volmit.iris.engine.object.IrisDimension;
|
||||
import com.volmit.iris.util.collection.KList;
|
||||
import com.volmit.iris.util.collection.KMap;
|
||||
import com.volmit.iris.util.format.C;
|
||||
import com.volmit.iris.util.hunk.Hunk;
|
||||
import com.volmit.iris.util.json.JSONObject;
|
||||
import com.volmit.iris.util.mantle.Mantle;
|
||||
@@ -17,16 +27,27 @@ import com.volmit.iris.util.nbt.mca.NBTWorld;
|
||||
import com.volmit.iris.util.nbt.mca.palette.*;
|
||||
import com.volmit.iris.util.nbt.tag.CompoundTag;
|
||||
import it.unimi.dsi.fastutil.objects.Object2IntMap;
|
||||
import it.unimi.dsi.fastutil.objects.Reference2IntMap;
|
||||
import net.bytebuddy.ByteBuddy;
|
||||
import net.bytebuddy.asm.Advice;
|
||||
import net.bytebuddy.dynamic.loading.ClassReloadingStrategy;
|
||||
import net.bytebuddy.matcher.ElementMatchers;
|
||||
import net.minecraft.core.BlockPos;
|
||||
import net.minecraft.core.Holder;
|
||||
import net.minecraft.core.MappedRegistry;
|
||||
import net.minecraft.core.Registry;
|
||||
import net.minecraft.core.RegistryAccess;
|
||||
import net.minecraft.core.registries.Registries;
|
||||
import net.minecraft.nbt.NbtIo;
|
||||
import net.minecraft.nbt.TagParser;
|
||||
import net.minecraft.resources.ResourceKey;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
import net.minecraft.server.MinecraftServer;
|
||||
import net.minecraft.server.level.ServerLevel;
|
||||
import net.minecraft.world.entity.EntityDimensions;
|
||||
import net.minecraft.server.level.progress.ChunkProgressListener;
|
||||
import net.minecraft.util.GsonHelper;
|
||||
import net.minecraft.world.RandomSequences;
|
||||
import net.minecraft.world.level.Level;
|
||||
import net.minecraft.world.level.biome.BiomeSource;
|
||||
import net.minecraft.world.level.block.Block;
|
||||
import net.minecraft.world.level.block.entity.BlockEntity;
|
||||
@@ -34,6 +55,10 @@ import net.minecraft.world.level.block.state.BlockState;
|
||||
import net.minecraft.world.level.chunk.ChunkAccess;
|
||||
import net.minecraft.world.level.chunk.ChunkStatus;
|
||||
import net.minecraft.world.level.chunk.LevelChunk;
|
||||
import net.minecraft.world.level.dimension.DimensionType;
|
||||
import net.minecraft.world.level.dimension.LevelStem;
|
||||
import net.minecraft.world.level.storage.LevelStorageSource;
|
||||
import net.minecraft.world.level.storage.PrimaryLevelData;
|
||||
import org.bukkit.*;
|
||||
import org.bukkit.block.Biome;
|
||||
import org.bukkit.block.data.BlockData;
|
||||
@@ -47,9 +72,8 @@ import org.bukkit.craftbukkit.v1_20_R1.entity.CraftDolphin;
|
||||
import org.bukkit.craftbukkit.v1_20_R1.inventory.CraftItemStack;
|
||||
import org.bukkit.entity.Dolphin;
|
||||
import org.bukkit.entity.Entity;
|
||||
import org.bukkit.entity.EntityType;
|
||||
import org.bukkit.event.entity.CreatureSpawnEvent;
|
||||
import org.bukkit.entity.EntityType;
|
||||
import org.bukkit.generator.BiomeProvider;
|
||||
import org.bukkit.generator.ChunkGenerator;
|
||||
import org.bukkit.inventory.ItemStack;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
@@ -59,13 +83,18 @@ import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.DataInputStream;
|
||||
import java.io.DataOutputStream;
|
||||
import java.io.File;
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.Method;
|
||||
import java.lang.reflect.Modifier;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.Vector;
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
public class NMSBinding implements INMSBinding {
|
||||
|
||||
@@ -175,6 +204,17 @@ public class NMSBinding implements INMSBinding {
|
||||
return null;
|
||||
}
|
||||
|
||||
private RegistryAccess getRegistryAccess(World world) {
|
||||
try {
|
||||
var field = getField(Level.class, RegistryAccess.class);
|
||||
field.setAccessible(true);
|
||||
return (RegistryAccess) field.get(((CraftWorld) world).getHandle());
|
||||
} catch (Throwable e) {
|
||||
e.printStackTrace();
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void deserializeTile(CompoundTag c, Location pos) {
|
||||
((CraftWorld) pos.getWorld()).getHandle().getChunkAt(new BlockPos(pos.getBlockX(), 0, pos.getBlockZ())).setBlockEntityNbt(convert(c));
|
||||
@@ -258,8 +298,7 @@ public class NMSBinding implements INMSBinding {
|
||||
|
||||
@Override
|
||||
public Object getBiomeBase(World world, Biome biome) {
|
||||
return CraftBlock.biomeToBiomeBase(((CraftWorld) world).getHandle()
|
||||
.registryAccess().registry(Registries.BIOME).orElse(null), biome);
|
||||
return CraftBlock.biomeToBiomeBase(getRegistryAccess(world).registry(Registries.BIOME).orElse(null), biome);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -296,8 +335,11 @@ public class NMSBinding implements INMSBinding {
|
||||
public int getBiomeId(Biome biome) {
|
||||
for (World i : Bukkit.getWorlds()) {
|
||||
if (i.getEnvironment().equals(World.Environment.NORMAL)) {
|
||||
Registry<net.minecraft.world.level.biome.Biome> registry = ((CraftWorld) i).getHandle().registryAccess().registry(Registries.BIOME).orElse(null);
|
||||
return registry.getId((net.minecraft.world.level.biome.Biome) getBiomeBase(registry, biome));
|
||||
var registry = getRegistryAccess(i).registry(Registries.BIOME).orElse(null);
|
||||
if (registry != null) {
|
||||
var holder = (Holder<net.minecraft.world.level.biome.Biome>) getBiomeBase(registry, biome);
|
||||
return registry.getId(holder.value());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -509,6 +551,138 @@ public class NMSBinding implements INMSBinding {
|
||||
return ((CraftWorld) location.getWorld()).spawn(location, type.getEntityClass(), null, reason);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean registerDimension(String name, IrisDimension dimension) {
|
||||
var registry = registry(Registries.DIMENSION_TYPE);
|
||||
var baseLocation = switch (dimension.getEnvironment()) {
|
||||
case NORMAL -> new ResourceLocation("minecraft", "overworld");
|
||||
case NETHER -> new ResourceLocation("minecraft", "the_nether");
|
||||
case THE_END -> new ResourceLocation("minecraft", "the_end");
|
||||
case CUSTOM -> throw new IllegalArgumentException("Cannot register custom dimension");
|
||||
};
|
||||
var base = registry.getHolder(ResourceKey.create(Registries.DIMENSION_TYPE, baseLocation)).orElse(null);
|
||||
if (base == null) return false;
|
||||
var json = encode(DimensionType.CODEC, base).orElse(null);
|
||||
if (json == null) return false;
|
||||
var object = json.getAsJsonObject();
|
||||
var height = dimension.getDimensionHeight();
|
||||
object.addProperty("min_y", height.getMin());
|
||||
object.addProperty("height", height.getMax() - height.getMin());
|
||||
object.addProperty("logical_height", dimension.getLogicalHeight());
|
||||
var value = decode(DimensionType.CODEC, object.toString()).map(Holder::value).orElse(null);
|
||||
if (value == null) return false;
|
||||
return register(Registries.DIMENSION_TYPE, new ResourceLocation("iris", name), value, true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean registerBiome(String dimensionId, IrisBiomeCustom biome, boolean replace) {
|
||||
var biomeBase = decode(net.minecraft.world.level.biome.Biome.CODEC, biome.generateJson()).map(Holder::value).orElse(null);
|
||||
if (biomeBase == null) return false;
|
||||
return register(Registries.BIOME, new ResourceLocation(dimensionId, biome.getId()), biomeBase, replace);
|
||||
}
|
||||
|
||||
private <T> Optional<T> decode(Codec<T> codec, String json) {
|
||||
return codec.decode(JsonOps.INSTANCE, GsonHelper.parse(json)).get().left().map(Pair::getFirst);
|
||||
}
|
||||
|
||||
private <T> Optional<JsonElement> encode(Codec<T> codec, T value) {
|
||||
return codec.encode(value, JsonOps.INSTANCE, new JsonObject()).result();
|
||||
}
|
||||
|
||||
private <T> boolean register(ResourceKey<Registry<T>> registryKey, ResourceLocation location, T value, boolean replace) {
|
||||
Preconditions.checkArgument(registryKey != null, "The registry cannot be null!");
|
||||
Preconditions.checkArgument(location != null, "The location cannot be null!");
|
||||
Preconditions.checkArgument(value != null, "The value cannot be null!");
|
||||
var registry = registry(registryKey);
|
||||
var key = ResourceKey.create(registryKey, location);
|
||||
try {
|
||||
if (registry.containsKey(key)) {
|
||||
if (!replace) return false;
|
||||
return replace(registryKey, location, value);
|
||||
}
|
||||
Field field = getField(MappedRegistry.class, boolean.class);
|
||||
field.setAccessible(true);
|
||||
boolean frozen = field.getBoolean(registry);
|
||||
field.setBoolean(registry, false);
|
||||
Field valueField = getField(Holder.Reference.class, "T");
|
||||
valueField.setAccessible(true);
|
||||
|
||||
try {
|
||||
var holder = registry.register(key, value, Lifecycle.stable());
|
||||
if (frozen) valueField.set(holder, value);
|
||||
return true;
|
||||
} finally {
|
||||
field.setBoolean(registry, frozen);
|
||||
}
|
||||
} catch (Throwable e) {
|
||||
throw new IllegalStateException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private <T> boolean replace(ResourceKey<Registry<T>> registryKey, ResourceLocation location, T value) {
|
||||
Preconditions.checkArgument(registryKey != null, "The registryKey cannot be null!");
|
||||
Preconditions.checkArgument(location != null, "The location cannot be null!");
|
||||
Preconditions.checkArgument(value != null, "The value cannot be null!");
|
||||
var registry = registry(registryKey);
|
||||
var key = ResourceKey.create(registryKey, location);
|
||||
try {
|
||||
var holder = registry.getHolder(key).orElse(null);
|
||||
if (holder == null) return false;
|
||||
var oldValue = holder.value();
|
||||
Field valueField = getField(Holder.Reference.class, "T");
|
||||
valueField.setAccessible(true);
|
||||
Field toIdField = getField(MappedRegistry.class, buildType(Reference2IntMap.class, "T"));
|
||||
toIdField.setAccessible(true);
|
||||
Field byValueField = getField(MappedRegistry.class, buildType(Map.class, "T", buildType(Holder.Reference.class, "T")));
|
||||
byValueField.setAccessible(true);
|
||||
Field lifecyclesField = getField(MappedRegistry.class, buildType(Map.class, "T", Lifecycle.class.getName()));
|
||||
lifecyclesField.setAccessible(true);
|
||||
var toId = (Reference2IntMap<T>) toIdField.get(registry);
|
||||
var byValue = (Map<T, Holder.Reference<T>>) byValueField.get(registry);
|
||||
var lifecycles = (Map<T, Lifecycle>) lifecyclesField.get(registry);
|
||||
|
||||
valueField.set(holder, value);
|
||||
toId.put(value, toId.removeInt(oldValue));
|
||||
byValue.put(value, byValue.remove(oldValue));
|
||||
lifecycles.put(value, lifecycles.remove(oldValue));
|
||||
return true;
|
||||
} catch (Throwable e) {
|
||||
throw new IllegalStateException(e);
|
||||
}
|
||||
}
|
||||
|
||||
private <T> MappedRegistry<T> registry(ResourceKey<Registry<T>> registryKey) {
|
||||
var rawRegistry = registry().registry(registryKey).orElse(null);
|
||||
if (!(rawRegistry instanceof MappedRegistry<T> registry))
|
||||
throw new IllegalStateException("The Registry is not a mapped Registry!");
|
||||
return registry;
|
||||
}
|
||||
|
||||
private static String buildType(Class<?> clazz, String... parameterTypes) {
|
||||
if (parameterTypes.length == 0) return clazz.getName();
|
||||
var builder = new StringBuilder(clazz.getName())
|
||||
.append("<");
|
||||
for (int i = 0; i < parameterTypes.length; i++) {
|
||||
builder.append(parameterTypes[i]).append(parameterTypes.length - 1 == i ? ">" : ", ");
|
||||
}
|
||||
return builder.toString();
|
||||
}
|
||||
|
||||
private static Field getField(Class<?> clazz, String type) throws NoSuchFieldException {
|
||||
try {
|
||||
for (Field f : clazz.getDeclaredFields()) {
|
||||
if (f.getGenericType().getTypeName().equals(type))
|
||||
return f;
|
||||
}
|
||||
throw new NoSuchFieldException(type);
|
||||
} catch (NoSuchFieldException e) {
|
||||
Class<?> superClass = clazz.getSuperclass();
|
||||
if (superClass == null) throw e;
|
||||
return getField(superClass, type);
|
||||
}
|
||||
}
|
||||
|
||||
public void inject(long seed, Engine engine, World world) throws NoSuchFieldException, IllegalAccessException {
|
||||
ServerLevel serverLevel = ((CraftWorld)world).getHandle();
|
||||
Class<?> clazz = serverLevel.getChunkSource().chunkMap.generator.getClass();
|
||||
@@ -538,4 +712,75 @@ public class NMSBinding implements INMSBinding {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void injectBukkit() {
|
||||
try {
|
||||
Iris.info("Injecting Bukkit");
|
||||
new ByteBuddy()
|
||||
.redefine(CraftServer.class)
|
||||
.visit(Advice.to(CraftServerAdvice.class).on(ElementMatchers.isMethod().and(ElementMatchers.takesArguments(WorldCreator.class))))
|
||||
.make()
|
||||
.load(CraftServer.class.getClassLoader(), ClassReloadingStrategy.fromInstalledAgent());
|
||||
new ByteBuddy()
|
||||
.redefine(ServerLevel.class)
|
||||
.visit(Advice.to(ServerLevelAdvice.class).on(ElementMatchers.isConstructor().and(ElementMatchers.takesArguments(MinecraftServer.class, Executor.class, LevelStorageSource.LevelStorageAccess.class,
|
||||
PrimaryLevelData.class, ResourceKey.class, LevelStem.class, ChunkProgressListener.class, boolean.class, long.class,
|
||||
List.class, boolean.class, RandomSequences.class, World.Environment.class, ChunkGenerator.class, BiomeProvider.class))))
|
||||
.make()
|
||||
.load(ServerLevel.class.getClassLoader(), ClassReloadingStrategy.fromInstalledAgent());
|
||||
Iris.info("Injected Bukkit Successfully!");
|
||||
} catch (Exception e) {
|
||||
Iris.info(C.RED + "Failed to Inject Bukkit!");
|
||||
e.printStackTrace();
|
||||
Iris.reportError(e);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private static class ServerLevelAdvice {
|
||||
@Advice.OnMethodEnter
|
||||
static void enter(@Advice.Argument(0) MinecraftServer server, @Advice.Argument(2) LevelStorageSource.LevelStorageAccess access, @Advice.Argument(4) ResourceKey<Level> key, @Advice.Argument(value = 5, readOnly = false) LevelStem levelStem) {
|
||||
File iris = new File(access.levelDirectory.path().toFile(), "iris");
|
||||
if (!iris.exists() && !key.location().getPath().startsWith("iris/")) return;
|
||||
ResourceKey<DimensionType> typeKey = ResourceKey.create(Registries.DIMENSION_TYPE, new ResourceLocation("iris", key.location().getPath()));
|
||||
RegistryAccess registryAccess = server.registryAccess();
|
||||
Registry<DimensionType> registry = registryAccess.registry(Registries.DIMENSION_TYPE).orElse(null);
|
||||
if (registry == null) throw new IllegalStateException("Unable to find registry for dimension type " + typeKey);
|
||||
Holder<DimensionType> holder = registry.getHolder(typeKey).orElse(null);
|
||||
if (holder == null) throw new IllegalStateException("Unable to find dimension type " + typeKey);
|
||||
levelStem = new LevelStem(holder, levelStem.generator());
|
||||
}
|
||||
}
|
||||
|
||||
private static class CraftServerAdvice {
|
||||
@Advice.OnMethodEnter(skipOn = Advice.OnNonDefaultValue.class)
|
||||
static boolean enter(@Advice.This CraftServer self, @Advice.Argument(0) WorldCreator creator) {
|
||||
File isIrisWorld = new File(self.getWorldContainer(), creator.name() + "/iris");
|
||||
boolean isFromIris = false;
|
||||
StackTraceElement[] stackTrace = Thread.currentThread().getStackTrace();
|
||||
for (StackTraceElement stack : stackTrace) {
|
||||
if (stack.getClassName().contains("Iris")) {
|
||||
isFromIris = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (isIrisWorld.exists() && !isFromIris) {
|
||||
var logger = Logger.getLogger("Iris");
|
||||
logger.warning("detected another Plugin trying to load " + creator.name() + ". This is not supported and will be ignored.");
|
||||
|
||||
if (System.getProperty("iris.debug", "false").equals("true")) {
|
||||
new RuntimeException().printStackTrace();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Advice.OnMethodExit
|
||||
static void exit(@Advice.Enter boolean bool, @Advice.Return(readOnly = false) World returned) {
|
||||
if (bool) {
|
||||
returned = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ package com.volmit.iris.core.nms.v1_20_R2;
|
||||
|
||||
import com.mojang.serialization.Codec;
|
||||
import com.volmit.iris.Iris;
|
||||
import com.volmit.iris.core.nms.INMS;
|
||||
import com.volmit.iris.engine.data.cache.AtomicCache;
|
||||
import com.volmit.iris.engine.framework.Engine;
|
||||
import com.volmit.iris.engine.object.IrisBiome;
|
||||
@@ -124,8 +125,16 @@ public class CustomBiomeSource extends BiomeSource {
|
||||
for (IrisBiome i : engine.getAllBiomes()) {
|
||||
if (i.isCustom()) {
|
||||
for (IrisBiomeCustom j : i.getCustomDerivitives()) {
|
||||
ResourceLocation resourceLocation = new ResourceLocation(engine.getDimension().getLoadKey() + ":" + j.getId());
|
||||
Biome biome = customRegistry.get(resourceLocation);
|
||||
ResourceLocation location = new ResourceLocation(engine.getDimension().getLoadKey() + ":" + j.getId());
|
||||
Biome biome = customRegistry.get(location);
|
||||
if (biome == null) {
|
||||
INMS.get().registerBiome(location.getNamespace(), j, false);
|
||||
biome = customRegistry.get(location);
|
||||
if (biome == null) {
|
||||
Iris.error("Cannot find biome for IrisBiomeCustom " + j.getId() + " from engine " + engine.getName());
|
||||
continue;
|
||||
}
|
||||
}
|
||||
Optional<ResourceKey<Biome>> optionalBiomeKey = customRegistry.getResourceKey(biome);
|
||||
if (optionalBiomeKey.isEmpty()) {
|
||||
Iris.error("Cannot find biome for IrisBiomeCustom " + j.getId() + " from engine " + engine.getName());
|
||||
|
||||
@@ -4,14 +4,44 @@ import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.DataInputStream;
|
||||
import java.io.DataOutputStream;
|
||||
import java.io.File;
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.Method;
|
||||
import java.lang.reflect.Modifier;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.Vector;
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import com.google.common.base.Preconditions;
|
||||
import com.google.gson.JsonElement;
|
||||
import com.google.gson.JsonObject;
|
||||
import com.mojang.datafixers.util.Pair;
|
||||
import com.mojang.serialization.Codec;
|
||||
import com.mojang.serialization.JsonOps;
|
||||
import com.mojang.serialization.Lifecycle;
|
||||
import com.volmit.iris.engine.object.IrisBiomeCustom;
|
||||
import com.volmit.iris.engine.object.IrisDimension;
|
||||
import com.volmit.iris.util.format.C;
|
||||
import it.unimi.dsi.fastutil.objects.Reference2IntMap;
|
||||
import net.bytebuddy.ByteBuddy;
|
||||
import net.bytebuddy.asm.Advice;
|
||||
import net.bytebuddy.dynamic.loading.ClassReloadingStrategy;
|
||||
import net.bytebuddy.matcher.ElementMatchers;
|
||||
import net.minecraft.core.MappedRegistry;
|
||||
import net.minecraft.server.MinecraftServer;
|
||||
import net.minecraft.server.level.progress.ChunkProgressListener;
|
||||
import net.minecraft.util.GsonHelper;
|
||||
import net.minecraft.world.RandomSequences;
|
||||
import net.minecraft.world.level.Level;
|
||||
import net.minecraft.world.level.dimension.DimensionType;
|
||||
import net.minecraft.world.level.dimension.LevelStem;
|
||||
import net.minecraft.world.level.storage.LevelStorageSource;
|
||||
import net.minecraft.world.level.storage.PrimaryLevelData;
|
||||
import org.bukkit.*;
|
||||
import org.bukkit.block.Biome;
|
||||
import org.bukkit.block.data.BlockData;
|
||||
@@ -25,6 +55,7 @@ import org.bukkit.craftbukkit.v1_20_R2.util.CraftNamespacedKey;
|
||||
import org.bukkit.entity.Dolphin;
|
||||
import org.bukkit.entity.Entity;
|
||||
import org.bukkit.event.entity.CreatureSpawnEvent;
|
||||
import org.bukkit.generator.BiomeProvider;
|
||||
import org.bukkit.generator.ChunkGenerator;
|
||||
import org.bukkit.inventory.ItemStack;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
@@ -173,6 +204,17 @@ public class NMSBinding implements INMSBinding {
|
||||
return null;
|
||||
}
|
||||
|
||||
private RegistryAccess getRegistryAccess(World world) {
|
||||
try {
|
||||
var field = getField(Level.class, RegistryAccess.class);
|
||||
field.setAccessible(true);
|
||||
return (RegistryAccess) field.get(((CraftWorld) world).getHandle());
|
||||
} catch (Throwable e) {
|
||||
e.printStackTrace();
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void deserializeTile(CompoundTag c, Location pos) {
|
||||
((CraftWorld) pos.getWorld()).getHandle().getChunkAt(new BlockPos(pos.getBlockX(), 0, pos.getBlockZ())).setBlockEntityNbt(convert(c));
|
||||
@@ -256,8 +298,7 @@ public class NMSBinding implements INMSBinding {
|
||||
|
||||
@Override
|
||||
public Object getBiomeBase(World world, Biome biome) {
|
||||
return biomeToBiomeBase(((CraftWorld) world).getHandle()
|
||||
.registryAccess().registry(Registries.BIOME).orElse(null), biome);
|
||||
return biomeToBiomeBase(getRegistryAccess(world).registry(Registries.BIOME).orElse(null), biome);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -294,8 +335,11 @@ public class NMSBinding implements INMSBinding {
|
||||
public int getBiomeId(Biome biome) {
|
||||
for (World i : Bukkit.getWorlds()) {
|
||||
if (i.getEnvironment().equals(World.Environment.NORMAL)) {
|
||||
Registry<net.minecraft.world.level.biome.Biome> registry = ((CraftWorld) i).getHandle().registryAccess().registry(Registries.BIOME).orElse(null);
|
||||
return registry.getId((net.minecraft.world.level.biome.Biome) getBiomeBase(registry, biome));
|
||||
var registry = getRegistryAccess(i).registry(Registries.BIOME).orElse(null);
|
||||
if (registry != null) {
|
||||
var holder = (Holder<net.minecraft.world.level.biome.Biome>) getBiomeBase(registry, biome);
|
||||
return registry.getId(holder.value());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -520,6 +564,139 @@ public class NMSBinding implements INMSBinding {
|
||||
return ((CraftWorld) location.getWorld()).spawn(location, type.getEntityClass(), null, reason);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean registerDimension(String name, IrisDimension dimension) {
|
||||
var registry = registry(Registries.DIMENSION_TYPE);
|
||||
var baseLocation = switch (dimension.getEnvironment()) {
|
||||
case NORMAL -> new ResourceLocation("minecraft", "overworld");
|
||||
case NETHER -> new ResourceLocation("minecraft", "the_nether");
|
||||
case THE_END -> new ResourceLocation("minecraft", "the_end");
|
||||
case CUSTOM -> throw new IllegalArgumentException("Cannot register custom dimension");
|
||||
};
|
||||
var base = registry.getHolder(ResourceKey.create(Registries.DIMENSION_TYPE, baseLocation)).orElse(null);
|
||||
if (base == null) return false;
|
||||
var json = encode(DimensionType.CODEC, base).orElse(null);
|
||||
if (json == null) return false;
|
||||
var object = json.getAsJsonObject();
|
||||
var height = dimension.getDimensionHeight();
|
||||
object.addProperty("min_y", height.getMin());
|
||||
object.addProperty("height", height.getMax() - height.getMin());
|
||||
object.addProperty("logical_height", dimension.getLogicalHeight());
|
||||
var value = decode(DimensionType.CODEC, object.toString()).map(Holder::value).orElse(null);
|
||||
if (value == null) return false;
|
||||
return register(Registries.DIMENSION_TYPE, new ResourceLocation("iris", name), value, true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean registerBiome(String dimensionId, IrisBiomeCustom biome, boolean replace) {
|
||||
var biomeBase = decode(net.minecraft.world.level.biome.Biome.CODEC, biome.generateJson()).map(Holder::value).orElse(null);
|
||||
if (biomeBase == null) return false;
|
||||
return register(Registries.BIOME, new ResourceLocation(dimensionId, biome.getId()), biomeBase, replace);
|
||||
}
|
||||
|
||||
private <T> Optional<T> decode(Codec<T> codec, String json) {
|
||||
return codec.decode(JsonOps.INSTANCE, GsonHelper.parse(json)).get().left().map(Pair::getFirst);
|
||||
}
|
||||
|
||||
private <T> Optional<JsonElement> encode(Codec<T> codec, T value) {
|
||||
return codec.encode(value, JsonOps.INSTANCE, new JsonObject()).result();
|
||||
}
|
||||
|
||||
private <T> boolean register(ResourceKey<Registry<T>> registryKey, ResourceLocation location, T value, boolean replace) {
|
||||
Preconditions.checkArgument(registryKey != null, "The registry cannot be null!");
|
||||
Preconditions.checkArgument(location != null, "The location cannot be null!");
|
||||
Preconditions.checkArgument(value != null, "The value cannot be null!");
|
||||
var registry = registry(registryKey);
|
||||
var key = ResourceKey.create(registryKey, location);
|
||||
try {
|
||||
if (registry.containsKey(key)) {
|
||||
if (!replace) return false;
|
||||
return replace(registryKey, location, value);
|
||||
}
|
||||
Field field = getField(MappedRegistry.class, boolean.class);
|
||||
field.setAccessible(true);
|
||||
boolean frozen = field.getBoolean(registry);
|
||||
field.setBoolean(registry, false);
|
||||
Field valueField = getField(Holder.Reference.class, "T");
|
||||
valueField.setAccessible(true);
|
||||
|
||||
try {
|
||||
var holder = registry.register(key, value, Lifecycle.stable());
|
||||
if (frozen) valueField.set(holder, value);
|
||||
return true;
|
||||
} finally {
|
||||
field.setBoolean(registry, frozen);
|
||||
}
|
||||
} catch (Throwable e) {
|
||||
throw new IllegalStateException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private <T> boolean replace(ResourceKey<Registry<T>> registryKey, ResourceLocation location, T value) {
|
||||
Preconditions.checkArgument(registryKey != null, "The registryKey cannot be null!");
|
||||
Preconditions.checkArgument(location != null, "The location cannot be null!");
|
||||
Preconditions.checkArgument(value != null, "The value cannot be null!");
|
||||
var registry = registry(registryKey);
|
||||
var key = ResourceKey.create(registryKey, location);
|
||||
try {
|
||||
var holder = registry.getHolder(key).orElse(null);
|
||||
if (holder == null) return false;
|
||||
var oldValue = holder.value();
|
||||
Field valueField = getField(Holder.Reference.class, "T");
|
||||
valueField.setAccessible(true);
|
||||
Field toIdField = getField(MappedRegistry.class, buildType(Reference2IntMap.class, "T"));
|
||||
toIdField.setAccessible(true);
|
||||
Field byValueField = getField(MappedRegistry.class, buildType(Map.class, "T", buildType(Holder.Reference.class, "T")));
|
||||
byValueField.setAccessible(true);
|
||||
Field lifecyclesField = getField(MappedRegistry.class, buildType(Map.class, "T", Lifecycle.class.getName()));
|
||||
lifecyclesField.setAccessible(true);
|
||||
var toId = (Reference2IntMap<T>) toIdField.get(registry);
|
||||
var byValue = (Map<T, Holder.Reference<T>>) byValueField.get(registry);
|
||||
var lifecycles = (Map<T, Lifecycle>) lifecyclesField.get(registry);
|
||||
|
||||
valueField.set(holder, value);
|
||||
toId.put(value, toId.removeInt(oldValue));
|
||||
byValue.put(value, byValue.remove(oldValue));
|
||||
lifecycles.put(value, lifecycles.remove(oldValue));
|
||||
return true;
|
||||
} catch (Throwable e) {
|
||||
throw new IllegalStateException(e);
|
||||
}
|
||||
}
|
||||
|
||||
private <T> MappedRegistry<T> registry(ResourceKey<Registry<T>> registryKey) {
|
||||
var rawRegistry = registry().registry(registryKey).orElse(null);
|
||||
if (!(rawRegistry instanceof MappedRegistry<T> registry))
|
||||
throw new IllegalStateException("The Registry is not a mapped Registry!");
|
||||
return registry;
|
||||
}
|
||||
|
||||
private static String buildType(Class<?> clazz, String... parameterTypes) {
|
||||
if (parameterTypes.length == 0) return clazz.getName();
|
||||
var builder = new StringBuilder(clazz.getName())
|
||||
.append("<");
|
||||
for (int i = 0; i < parameterTypes.length; i++) {
|
||||
builder.append(parameterTypes[i]).append(parameterTypes.length - 1 == i ? ">" : ", ");
|
||||
}
|
||||
return builder.toString();
|
||||
}
|
||||
|
||||
private static Field getField(Class<?> clazz, String type) throws NoSuchFieldException {
|
||||
try {
|
||||
for (Field f : clazz.getDeclaredFields()) {
|
||||
if (f.getGenericType().getTypeName().equals(type))
|
||||
return f;
|
||||
}
|
||||
throw new NoSuchFieldException(type);
|
||||
} catch (NoSuchFieldException e) {
|
||||
Class<?> superClass = clazz.getSuperclass();
|
||||
if (superClass == null) throw e;
|
||||
return getField(superClass, type);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private static Field getField(Class<?> clazz, Class<?> fieldType) throws NoSuchFieldException {
|
||||
try {
|
||||
for (Field f : clazz.getDeclaredFields()) {
|
||||
@@ -540,4 +717,75 @@ public class NMSBinding implements INMSBinding {
|
||||
public static Holder<net.minecraft.world.level.biome.Biome> biomeToBiomeBase(Registry<net.minecraft.world.level.biome.Biome> registry, Biome biome) {
|
||||
return registry.getHolderOrThrow(ResourceKey.create(Registries.BIOME, CraftNamespacedKey.toMinecraft(biome.getKey())));
|
||||
}
|
||||
|
||||
public void injectBukkit() {
|
||||
try {
|
||||
Iris.info("Injecting Bukkit");
|
||||
new ByteBuddy()
|
||||
.redefine(CraftServer.class)
|
||||
.visit(Advice.to(CraftServerAdvice.class).on(ElementMatchers.isMethod().and(ElementMatchers.takesArguments(WorldCreator.class))))
|
||||
.make()
|
||||
.load(CraftServer.class.getClassLoader(), ClassReloadingStrategy.fromInstalledAgent());
|
||||
new ByteBuddy()
|
||||
.redefine(ServerLevel.class)
|
||||
.visit(Advice.to(ServerLevelAdvice.class).on(ElementMatchers.isConstructor().and(ElementMatchers.takesArguments(MinecraftServer.class, Executor.class, LevelStorageSource.LevelStorageAccess.class,
|
||||
PrimaryLevelData.class, ResourceKey.class, LevelStem.class, ChunkProgressListener.class, boolean.class, long.class,
|
||||
List.class, boolean.class, RandomSequences.class, World.Environment.class, ChunkGenerator.class, BiomeProvider.class))))
|
||||
.make()
|
||||
.load(ServerLevel.class.getClassLoader(), ClassReloadingStrategy.fromInstalledAgent());
|
||||
Iris.info("Injected Bukkit Successfully!");
|
||||
} catch (Exception e) {
|
||||
Iris.info(C.RED + "Failed to Inject Bukkit!");
|
||||
e.printStackTrace();
|
||||
Iris.reportError(e);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private static class ServerLevelAdvice {
|
||||
@Advice.OnMethodEnter
|
||||
static void enter(@Advice.Argument(0) MinecraftServer server, @Advice.Argument(2) LevelStorageSource.LevelStorageAccess access, @Advice.Argument(4) ResourceKey<Level> key, @Advice.Argument(value = 5, readOnly = false) LevelStem levelStem) {
|
||||
File iris = new File(access.levelDirectory.path().toFile(), "iris");
|
||||
if (!iris.exists() && !key.location().getPath().startsWith("iris/")) return;
|
||||
ResourceKey<DimensionType> typeKey = ResourceKey.create(Registries.DIMENSION_TYPE, new ResourceLocation("iris", key.location().getPath()));
|
||||
RegistryAccess registryAccess = server.registryAccess();
|
||||
Registry<DimensionType> registry = registryAccess.registry(Registries.DIMENSION_TYPE).orElse(null);
|
||||
if (registry == null) throw new IllegalStateException("Unable to find registry for dimension type " + typeKey);
|
||||
Holder<DimensionType> holder = registry.getHolder(typeKey).orElse(null);
|
||||
if (holder == null) throw new IllegalStateException("Unable to find dimension type " + typeKey);
|
||||
levelStem = new LevelStem(holder, levelStem.generator());
|
||||
}
|
||||
}
|
||||
|
||||
private static class CraftServerAdvice {
|
||||
@Advice.OnMethodEnter(skipOn = Advice.OnNonDefaultValue.class)
|
||||
static boolean enter(@Advice.This CraftServer self, @Advice.Argument(0) WorldCreator creator) {
|
||||
File isIrisWorld = new File(self.getWorldContainer(), creator.name() + "/iris");
|
||||
boolean isFromIris = false;
|
||||
StackTraceElement[] stackTrace = Thread.currentThread().getStackTrace();
|
||||
for (StackTraceElement stack : stackTrace) {
|
||||
if (stack.getClassName().contains("Iris")) {
|
||||
isFromIris = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (isIrisWorld.exists() && !isFromIris) {
|
||||
var logger = Logger.getLogger("Iris");
|
||||
logger.warning("detected another Plugin trying to load " + creator.name() + ". This is not supported and will be ignored.");
|
||||
|
||||
if (System.getProperty("iris.debug", "false").equals("true")) {
|
||||
new RuntimeException().printStackTrace();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Advice.OnMethodExit
|
||||
static void exit(@Advice.Enter boolean bool, @Advice.Return(readOnly = false) World returned) {
|
||||
if (bool) {
|
||||
returned = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ package com.volmit.iris.core.nms.v1_20_R3;
|
||||
|
||||
import com.mojang.serialization.Codec;
|
||||
import com.volmit.iris.Iris;
|
||||
import com.volmit.iris.core.nms.INMS;
|
||||
import com.volmit.iris.engine.data.cache.AtomicCache;
|
||||
import com.volmit.iris.engine.framework.Engine;
|
||||
import com.volmit.iris.engine.object.IrisBiome;
|
||||
@@ -124,8 +125,16 @@ public class CustomBiomeSource extends BiomeSource {
|
||||
for (IrisBiome i : engine.getAllBiomes()) {
|
||||
if (i.isCustom()) {
|
||||
for (IrisBiomeCustom j : i.getCustomDerivitives()) {
|
||||
ResourceLocation resourceLocation = new ResourceLocation(engine.getDimension().getLoadKey() + ":" + j.getId());
|
||||
Biome biome = customRegistry.get(resourceLocation);
|
||||
ResourceLocation location = new ResourceLocation(engine.getDimension().getLoadKey() + ":" + j.getId());
|
||||
Biome biome = customRegistry.get(location);
|
||||
if (biome == null) {
|
||||
INMS.get().registerBiome(location.getNamespace(), j, false);
|
||||
biome = customRegistry.get(location);
|
||||
if (biome == null) {
|
||||
Iris.error("Cannot find biome for IrisBiomeCustom " + j.getId() + " from engine " + engine.getName());
|
||||
continue;
|
||||
}
|
||||
}
|
||||
Optional<ResourceKey<Biome>> optionalBiomeKey = customRegistry.getResourceKey(biome);
|
||||
if (optionalBiomeKey.isEmpty()) {
|
||||
Iris.error("Cannot find biome for IrisBiomeCustom " + j.getId() + " from engine " + engine.getName());
|
||||
|
||||
@@ -0,0 +1,256 @@
|
||||
package com.volmit.iris.core.nms.v1_20_R3;
|
||||
|
||||
import com.volmit.iris.Iris;
|
||||
import com.volmit.iris.core.nms.BiomeBaseInjector;
|
||||
import com.volmit.iris.core.nms.IHeadless;
|
||||
import com.volmit.iris.core.nms.v1_20_R3.mca.MCATerrainChunk;
|
||||
import com.volmit.iris.core.nms.v1_20_R3.mca.RegionFileStorage;
|
||||
import com.volmit.iris.core.pregenerator.PregenListener;
|
||||
import com.volmit.iris.engine.framework.Engine;
|
||||
import com.volmit.iris.engine.framework.EngineStage;
|
||||
import com.volmit.iris.engine.framework.WrongEngineBroException;
|
||||
import com.volmit.iris.engine.object.IrisBiome;
|
||||
import com.volmit.iris.util.collection.KMap;
|
||||
import com.volmit.iris.util.context.ChunkContext;
|
||||
import com.volmit.iris.util.context.IrisContext;
|
||||
import com.volmit.iris.util.documentation.BlockCoordinates;
|
||||
import com.volmit.iris.util.documentation.RegionCoordinates;
|
||||
import com.volmit.iris.util.hunk.Hunk;
|
||||
import com.volmit.iris.util.hunk.view.BiomeGridHunkHolder;
|
||||
import com.volmit.iris.util.hunk.view.ChunkDataHunkHolder;
|
||||
import com.volmit.iris.util.mantle.MantleFlag;
|
||||
import com.volmit.iris.util.math.RNG;
|
||||
import com.volmit.iris.util.parallel.MultiBurst;
|
||||
import com.volmit.iris.util.scheduling.Looper;
|
||||
import com.volmit.iris.util.scheduling.PrecisionStopwatch;
|
||||
import net.minecraft.core.Holder;
|
||||
import net.minecraft.nbt.CompoundTag;
|
||||
import net.minecraft.world.level.ChunkPos;
|
||||
import net.minecraft.world.level.LevelHeightAccessor;
|
||||
import net.minecraft.world.level.biome.Biome;
|
||||
import net.minecraft.world.level.chunk.ChunkAccess;
|
||||
import net.minecraft.world.level.chunk.ChunkStatus;
|
||||
import net.minecraft.world.level.chunk.ProtoChunk;
|
||||
import org.bukkit.Material;
|
||||
import org.bukkit.NamespacedKey;
|
||||
import org.bukkit.block.data.BlockData;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayDeque;
|
||||
import java.util.Queue;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.concurrent.locks.ReentrantLock;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
public class Headless implements IHeadless, LevelHeightAccessor {
|
||||
private final NMSBinding binding;
|
||||
private final Engine engine;
|
||||
private final RegionFileStorage storage;
|
||||
private final Queue<ProtoChunk> chunkQueue = new ArrayDeque<>();
|
||||
private final ReentrantLock saveLock = new ReentrantLock();
|
||||
private final KMap<String, Holder<Biome>> customBiomes = new KMap<>();
|
||||
private final KMap<NamespacedKey, Holder<Biome>> minecraftBiomes = new KMap<>();
|
||||
private boolean closed = false;
|
||||
|
||||
public Headless(NMSBinding binding, Engine engine) {
|
||||
this.binding = binding;
|
||||
this.engine = engine;
|
||||
this.storage = new RegionFileStorage(new File(engine.getWorld().worldFolder(), "region").toPath(), false);
|
||||
var queueLooper = new Looper() {
|
||||
@Override
|
||||
protected long loop() {
|
||||
save();
|
||||
return closed ? -1 : 100;
|
||||
}
|
||||
};
|
||||
queueLooper.setName("Region Save Looper");
|
||||
queueLooper.start();
|
||||
|
||||
var dimKey = engine.getDimension().getLoadKey();
|
||||
for (var biome : engine.getAllBiomes()) {
|
||||
if (!biome.isCustom()) continue;
|
||||
for (var custom : biome.getCustomDerivitives()) {
|
||||
binding.registerBiome(dimKey, custom, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean exists(int x, int z) {
|
||||
if (closed) return false;
|
||||
try {
|
||||
CompoundTag tag = storage.read(new ChunkPos(x, z));
|
||||
return tag != null && !"empty".equals(tag.getString("Status"));
|
||||
} catch (IOException e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void save() {
|
||||
if (closed) return;
|
||||
saveLock.lock();
|
||||
try {
|
||||
while (!chunkQueue.isEmpty()) {
|
||||
ChunkAccess chunk = chunkQueue.poll();
|
||||
if (chunk == null) break;
|
||||
try {
|
||||
storage.write(chunk.getPos(), binding.serializeChunk(chunk, this));
|
||||
} catch (Throwable e) {
|
||||
Iris.error("Failed to save chunk " + chunk.getPos().x + ", " + chunk.getPos().z);
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
saveLock.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void generateRegion(MultiBurst burst, int x, int z, PregenListener listener) {
|
||||
if (closed) return;
|
||||
boolean listening = listener != null;
|
||||
if (listening) listener.onRegionGenerating(x, z);
|
||||
CountDownLatch latch = new CountDownLatch(1024);
|
||||
iterateRegion(x, z, pos -> burst.complete(() -> {
|
||||
if (listening) listener.onChunkGenerating(pos.x, pos.z);
|
||||
generateChunk(pos.x, pos.z);
|
||||
if (listening) listener.onChunkGenerated(pos.x, pos.z);
|
||||
latch.countDown();
|
||||
}));
|
||||
try {
|
||||
latch.await();
|
||||
} catch (InterruptedException ignored) {}
|
||||
if (listening) listener.onRegionGenerated(x, z);
|
||||
}
|
||||
|
||||
@RegionCoordinates
|
||||
private static void iterateRegion(int x, int z, Consumer<ChunkPos> chunkPos) {
|
||||
int cX = x << 5;
|
||||
int cZ = z << 5;
|
||||
for (int xx = 0; xx < 32; xx++) {
|
||||
for (int zz = 0; zz < 32; zz++) {
|
||||
chunkPos.accept(new ChunkPos(cX + xx, cZ + zz));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void generateChunk(int x, int z) {
|
||||
if (closed || exists(x, z)) return;
|
||||
try {
|
||||
var pos = new ChunkPos(x, z);
|
||||
ProtoChunk chunk = binding.createProtoChunk(pos, this);
|
||||
var tc = new MCATerrainChunk(chunk);
|
||||
|
||||
ChunkDataHunkHolder blocks = new ChunkDataHunkHolder(tc);
|
||||
BiomeGridHunkHolder biomes = new BiomeGridHunkHolder(tc, tc.getMinHeight(), tc.getMaxHeight());
|
||||
ChunkContext ctx = generate(engine, pos.x << 4, pos.z << 4, blocks, biomes);
|
||||
blocks.apply();
|
||||
biomes.apply();
|
||||
|
||||
inject(engine, tc.getBiomeBaseInjector(), chunk, ctx); //TODO improve
|
||||
chunk.setStatus(ChunkStatus.FULL);
|
||||
chunkQueue.add(chunk);
|
||||
} catch (Throwable e) {
|
||||
Iris.error("Failed to generate " + x + ", " + z);
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
@BlockCoordinates
|
||||
private ChunkContext generate(Engine engine, int x, int z, Hunk<BlockData> vblocks, Hunk<org.bukkit.block.Biome> vbiomes) throws WrongEngineBroException {
|
||||
if (engine.isClosed()) {
|
||||
throw new WrongEngineBroException();
|
||||
}
|
||||
|
||||
engine.getContext().touch();
|
||||
engine.getEngineData().getStatistics().generatedChunk();
|
||||
ChunkContext ctx = null;
|
||||
try {
|
||||
PrecisionStopwatch p = PrecisionStopwatch.start();
|
||||
Hunk<BlockData> blocks = vblocks.listen((xx, y, zz, t) -> engine.catchBlockUpdates(x + xx, y + engine.getMinHeight(), z + zz, t));
|
||||
|
||||
var dimension = engine.getDimension();
|
||||
if (dimension.isDebugChunkCrossSections() && ((x >> 4) % dimension.getDebugCrossSectionsMod() == 0 || (z >> 4) % dimension.getDebugCrossSectionsMod() == 0)) {
|
||||
for (int i = 0; i < 16; i++) {
|
||||
for (int j = 0; j < 16; j++) {
|
||||
blocks.set(i, 0, j, Material.CRYING_OBSIDIAN.createBlockData());
|
||||
}
|
||||
}
|
||||
} else {
|
||||
ctx = new ChunkContext(x, z, engine.getComplex());
|
||||
IrisContext.getOr(engine).setChunkContext(ctx);
|
||||
|
||||
for (EngineStage i : engine.getMode().getStages()) {
|
||||
i.generate(x, z, blocks, vbiomes, false, ctx);
|
||||
}
|
||||
}
|
||||
|
||||
engine.getMantle().getMantle().flag(x >> 4, z >> 4, MantleFlag.REAL, true);
|
||||
engine.getMetrics().getTotal().put(p.getMilliseconds());
|
||||
engine.addGenerated();
|
||||
|
||||
} catch (Throwable e) {
|
||||
Iris.reportError(e);
|
||||
engine.fail("Failed to generate " + x + ", " + z, e);
|
||||
}
|
||||
return ctx;
|
||||
}
|
||||
|
||||
private void inject(Engine engine, BiomeBaseInjector injector, ChunkAccess chunk, ChunkContext ctx) {
|
||||
var pos = chunk.getPos();
|
||||
for (int y = engine.getMinHeight(); y < engine.getMaxHeight(); y++) {
|
||||
for (int x = 0; x < 16; x++) {
|
||||
for (int z = 0; z < 16; z++) {
|
||||
int wX = pos.getBlockX(x);
|
||||
int wZ = pos.getBlockZ(z);
|
||||
try {
|
||||
injector.setBiome(x, y, z, getNoiseBiome(engine, ctx, x, z, wX, y, wZ));
|
||||
} catch (Throwable e) {
|
||||
Iris.error("Failed to inject biome for " + wX + ", " + y + ", " + wZ);
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private Holder<Biome> getNoiseBiome(Engine engine, ChunkContext ctx, int rX, int rZ, int x, int y, int z) {
|
||||
RNG rng = new RNG(engine.getSeedManager().getBiome());
|
||||
int m = (y - engine.getMinHeight()) << 2;
|
||||
IrisBiome ib = ctx == null ?
|
||||
engine.getComplex().getTrueBiomeStream().get(x << 2, z << 2) :
|
||||
ctx.getBiome().get(rX, rZ);
|
||||
if (ib.isCustom()) {
|
||||
return customBiomes.computeIfAbsent(ib.getCustomBiome(rng, x << 2, m, z << 2).getId(),
|
||||
id -> binding.getBiomeHolder(engine.getDimension().getLoadKey(), id));
|
||||
} else {
|
||||
return minecraftBiomes.computeIfAbsent(ib.getSkyBiome(rng, x << 2, m, z << 2).getKey(),
|
||||
id -> binding.getBiomeHolder(id.getNamespace(), id.getKey()));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() throws IOException {
|
||||
if (closed) return;
|
||||
try {
|
||||
storage.close();
|
||||
} finally {
|
||||
closed = true;
|
||||
customBiomes.clear();
|
||||
minecraftBiomes.clear();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getHeight() {
|
||||
return engine.getHeight();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getMinBuildHeight() {
|
||||
return engine.getMinHeight();
|
||||
}
|
||||
}
|
||||
@@ -4,14 +4,52 @@ import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.DataInputStream;
|
||||
import java.io.DataOutputStream;
|
||||
import java.io.File;
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.Method;
|
||||
import java.lang.reflect.Modifier;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Vector;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import com.google.common.base.Preconditions;
|
||||
import com.google.gson.JsonElement;
|
||||
import com.google.gson.JsonObject;
|
||||
import com.mojang.datafixers.util.Pair;
|
||||
import com.mojang.serialization.Codec;
|
||||
import com.mojang.serialization.JsonOps;
|
||||
import com.mojang.serialization.Lifecycle;
|
||||
import com.volmit.iris.core.nms.IHeadless;
|
||||
import com.volmit.iris.core.nms.v1_20_R3.mca.ChunkSerializer;
|
||||
import com.volmit.iris.engine.object.IrisBiomeCustom;
|
||||
import com.volmit.iris.engine.object.IrisDimension;
|
||||
import com.volmit.iris.util.format.C;
|
||||
import it.unimi.dsi.fastutil.objects.Reference2IntMap;
|
||||
import net.bytebuddy.ByteBuddy;
|
||||
import net.bytebuddy.asm.Advice;
|
||||
import net.bytebuddy.description.method.MethodDescription;
|
||||
import net.bytebuddy.description.type.TypeDescription;
|
||||
import net.bytebuddy.dynamic.loading.ClassReloadingStrategy;
|
||||
import net.bytebuddy.implementation.bytecode.StackManipulation;
|
||||
import net.bytebuddy.implementation.bytecode.assign.Assigner;
|
||||
import net.bytebuddy.implementation.bytecode.member.MethodInvocation;
|
||||
import net.bytebuddy.matcher.ElementMatchers;
|
||||
import net.minecraft.core.IdMapper;
|
||||
import net.minecraft.core.MappedRegistry;
|
||||
import net.minecraft.server.MinecraftServer;
|
||||
import net.minecraft.server.level.progress.ChunkProgressListener;
|
||||
import net.minecraft.util.GsonHelper;
|
||||
import net.minecraft.world.RandomSequences;
|
||||
import net.minecraft.world.level.ChunkPos;
|
||||
import net.minecraft.world.level.Level;
|
||||
import net.minecraft.world.level.LevelHeightAccessor;
|
||||
import net.minecraft.world.level.chunk.ProtoChunk;
|
||||
import net.minecraft.world.level.chunk.UpgradeData;
|
||||
import net.minecraft.world.level.dimension.DimensionType;
|
||||
import net.minecraft.world.level.dimension.LevelStem;
|
||||
import net.minecraft.world.level.storage.LevelStorageSource;
|
||||
import net.minecraft.world.level.storage.PrimaryLevelData;
|
||||
import org.bukkit.*;
|
||||
import org.bukkit.block.Biome;
|
||||
import org.bukkit.block.data.BlockData;
|
||||
@@ -25,6 +63,7 @@ import org.bukkit.craftbukkit.v1_20_R3.util.CraftNamespacedKey;
|
||||
import org.bukkit.entity.Dolphin;
|
||||
import org.bukkit.entity.Entity;
|
||||
import org.bukkit.event.entity.CreatureSpawnEvent;
|
||||
import org.bukkit.generator.BiomeProvider;
|
||||
import org.bukkit.generator.ChunkGenerator;
|
||||
import org.bukkit.inventory.ItemStack;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
@@ -67,6 +106,8 @@ import net.minecraft.world.level.chunk.ChunkStatus;
|
||||
import net.minecraft.world.level.chunk.LevelChunk;
|
||||
import sun.misc.Unsafe;
|
||||
|
||||
import static net.bytebuddy.matcher.ElementMatchers.takesArguments;
|
||||
|
||||
public class NMSBinding implements INMSBinding {
|
||||
private final KMap<Biome, Object> baseBiomeCache = new KMap<>();
|
||||
private final BlockData AIR = Material.AIR.createBlockData();
|
||||
@@ -173,6 +214,17 @@ public class NMSBinding implements INMSBinding {
|
||||
return null;
|
||||
}
|
||||
|
||||
private RegistryAccess getRegistryAccess(World world) {
|
||||
try {
|
||||
var field = getField(Level.class, RegistryAccess.class);
|
||||
field.setAccessible(true);
|
||||
return (RegistryAccess) field.get(((CraftWorld) world).getHandle());
|
||||
} catch (Throwable e) {
|
||||
e.printStackTrace();
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void deserializeTile(CompoundTag c, Location pos) {
|
||||
((CraftWorld) pos.getWorld()).getHandle().getChunkAt(new BlockPos(pos.getBlockX(), 0, pos.getBlockZ())).setBlockEntityNbt(convert(c));
|
||||
@@ -256,8 +308,7 @@ public class NMSBinding implements INMSBinding {
|
||||
|
||||
@Override
|
||||
public Object getBiomeBase(World world, Biome biome) {
|
||||
return biomeToBiomeBase(((CraftWorld) world).getHandle()
|
||||
.registryAccess().registry(Registries.BIOME).orElse(null), biome);
|
||||
return biomeToBiomeBase(getRegistryAccess(world).registry(Registries.BIOME).orElse(null), biome);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -294,8 +345,11 @@ public class NMSBinding implements INMSBinding {
|
||||
public int getBiomeId(Biome biome) {
|
||||
for (World i : Bukkit.getWorlds()) {
|
||||
if (i.getEnvironment().equals(World.Environment.NORMAL)) {
|
||||
Registry<net.minecraft.world.level.biome.Biome> registry = ((CraftWorld) i).getHandle().registryAccess().registry(Registries.BIOME).orElse(null);
|
||||
return registry.getId((net.minecraft.world.level.biome.Biome) getBiomeBase(registry, biome));
|
||||
var registry = getRegistryAccess(i).registry(Registries.BIOME).orElse(null);
|
||||
if (registry != null) {
|
||||
var holder = (Holder<net.minecraft.world.level.biome.Biome>) getBiomeBase(registry, biome);
|
||||
return registry.getId(holder.value());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -415,13 +469,13 @@ public class NMSBinding implements INMSBinding {
|
||||
@Override
|
||||
public MCAPaletteAccess createPalette() {
|
||||
MCAIdMapper<BlockState> registry = registryCache.aquireNasty(() -> {
|
||||
Field cf = net.minecraft.core.IdMapper.class.getDeclaredField("tToId");
|
||||
Field df = net.minecraft.core.IdMapper.class.getDeclaredField("idToT");
|
||||
Field bf = net.minecraft.core.IdMapper.class.getDeclaredField("nextId");
|
||||
Field cf = IdMapper.class.getDeclaredField("tToId");
|
||||
Field df = IdMapper.class.getDeclaredField("idToT");
|
||||
Field bf = IdMapper.class.getDeclaredField("nextId");
|
||||
cf.setAccessible(true);
|
||||
df.setAccessible(true);
|
||||
bf.setAccessible(true);
|
||||
net.minecraft.core.IdMapper<BlockState> blockData = Block.BLOCK_STATE_REGISTRY;
|
||||
IdMapper<BlockState> blockData = Block.BLOCK_STATE_REGISTRY;
|
||||
int b = bf.getInt(blockData);
|
||||
Object2IntMap<BlockState> c = (Object2IntMap<BlockState>) cf.get(blockData);
|
||||
List<BlockState> d = (List<BlockState>) df.get(blockData);
|
||||
@@ -515,12 +569,143 @@ public class NMSBinding implements INMSBinding {
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public Entity spawnEntity(Location location, org.bukkit.entity.EntityType type, CreatureSpawnEvent.SpawnReason reason) {
|
||||
return ((CraftWorld) location.getWorld()).spawn(location, type.getEntityClass(), null, reason);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean registerDimension(String name, IrisDimension dimension) {
|
||||
var registry = registry(Registries.DIMENSION_TYPE);
|
||||
var baseLocation = switch (dimension.getEnvironment()) {
|
||||
case NORMAL -> new ResourceLocation("minecraft", "overworld");
|
||||
case NETHER -> new ResourceLocation("minecraft", "the_nether");
|
||||
case THE_END -> new ResourceLocation("minecraft", "the_end");
|
||||
case CUSTOM -> throw new IllegalArgumentException("Cannot register custom dimension");
|
||||
};
|
||||
var base = registry.getHolder(ResourceKey.create(Registries.DIMENSION_TYPE, baseLocation)).orElse(null);
|
||||
if (base == null) return false;
|
||||
var json = encode(DimensionType.CODEC, base).orElse(null);
|
||||
if (json == null) return false;
|
||||
var object = json.getAsJsonObject();
|
||||
var height = dimension.getDimensionHeight();
|
||||
object.addProperty("min_y", height.getMin());
|
||||
object.addProperty("height", height.getMax() - height.getMin());
|
||||
object.addProperty("logical_height", dimension.getLogicalHeight());
|
||||
var value = decode(DimensionType.CODEC, object.toString()).map(Holder::value).orElse(null);
|
||||
if (value == null) return false;
|
||||
return register(Registries.DIMENSION_TYPE, new ResourceLocation("iris", name), value, true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean registerBiome(String dimensionId, IrisBiomeCustom biome, boolean replace) {
|
||||
var biomeBase = decode(net.minecraft.world.level.biome.Biome.CODEC, biome.generateJson()).map(Holder::value).orElse(null);
|
||||
if (biomeBase == null) return false;
|
||||
return register(Registries.BIOME, new ResourceLocation(dimensionId, biome.getId()), biomeBase, replace);
|
||||
}
|
||||
|
||||
private <T> Optional<T> decode(Codec<T> codec, String json) {
|
||||
return codec.decode(JsonOps.INSTANCE, GsonHelper.parse(json)).get().left().map(Pair::getFirst);
|
||||
}
|
||||
|
||||
private <T> Optional<JsonElement> encode(Codec<T> codec, T value) {
|
||||
return codec.encode(value, JsonOps.INSTANCE, new JsonObject()).result();
|
||||
}
|
||||
|
||||
private <T> boolean register(ResourceKey<Registry<T>> registryKey, ResourceLocation location, T value, boolean replace) {
|
||||
Preconditions.checkArgument(registryKey != null, "The registry cannot be null!");
|
||||
Preconditions.checkArgument(location != null, "The location cannot be null!");
|
||||
Preconditions.checkArgument(value != null, "The value cannot be null!");
|
||||
var registry = registry(registryKey);
|
||||
var key = ResourceKey.create(registryKey, location);
|
||||
try {
|
||||
if (registry.containsKey(key)) {
|
||||
if (!replace) return false;
|
||||
return replace(registryKey, location, value);
|
||||
}
|
||||
Field field = getField(MappedRegistry.class, boolean.class);
|
||||
field.setAccessible(true);
|
||||
boolean frozen = field.getBoolean(registry);
|
||||
field.setBoolean(registry, false);
|
||||
Field valueField = getField(Holder.Reference.class, "T");
|
||||
valueField.setAccessible(true);
|
||||
|
||||
try {
|
||||
var holder = registry.register(key, value, Lifecycle.stable());
|
||||
if (frozen) valueField.set(holder, value);
|
||||
return true;
|
||||
} finally {
|
||||
field.setBoolean(registry, frozen);
|
||||
}
|
||||
} catch (Throwable e) {
|
||||
throw new IllegalStateException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private <T> boolean replace(ResourceKey<Registry<T>> registryKey, ResourceLocation location, T value) {
|
||||
Preconditions.checkArgument(registryKey != null, "The registryKey cannot be null!");
|
||||
Preconditions.checkArgument(location != null, "The location cannot be null!");
|
||||
Preconditions.checkArgument(value != null, "The value cannot be null!");
|
||||
var registry = registry(registryKey);
|
||||
var key = ResourceKey.create(registryKey, location);
|
||||
try {
|
||||
var holder = registry.getHolder(key).orElse(null);
|
||||
if (holder == null) return false;
|
||||
var oldValue = holder.value();
|
||||
Field valueField = getField(Holder.Reference.class, "T");
|
||||
valueField.setAccessible(true);
|
||||
Field toIdField = getField(MappedRegistry.class, buildType(Reference2IntMap.class, "T"));
|
||||
toIdField.setAccessible(true);
|
||||
Field byValueField = getField(MappedRegistry.class, buildType(Map.class, "T", buildType(Holder.Reference.class, "T")));
|
||||
byValueField.setAccessible(true);
|
||||
Field lifecyclesField = getField(MappedRegistry.class, buildType(Map.class, "T", Lifecycle.class.getName()));
|
||||
lifecyclesField.setAccessible(true);
|
||||
var toId = (Reference2IntMap<T>) toIdField.get(registry);
|
||||
var byValue = (Map<T, Holder.Reference<T>>) byValueField.get(registry);
|
||||
var lifecycles = (Map<T, Lifecycle>) lifecyclesField.get(registry);
|
||||
|
||||
valueField.set(holder, value);
|
||||
toId.put(value, toId.removeInt(oldValue));
|
||||
byValue.put(value, byValue.remove(oldValue));
|
||||
lifecycles.put(value, lifecycles.remove(oldValue));
|
||||
return true;
|
||||
} catch (Throwable e) {
|
||||
throw new IllegalStateException(e);
|
||||
}
|
||||
}
|
||||
|
||||
private <T> MappedRegistry<T> registry(ResourceKey<Registry<T>> registryKey) {
|
||||
var rawRegistry = registry().registry(registryKey).orElse(null);
|
||||
if (!(rawRegistry instanceof MappedRegistry<T> registry))
|
||||
throw new IllegalStateException("The Registry is not a mapped Registry!");
|
||||
return registry;
|
||||
}
|
||||
|
||||
private static String buildType(Class<?> clazz, String... parameterTypes) {
|
||||
if (parameterTypes.length == 0) return clazz.getName();
|
||||
var builder = new StringBuilder(clazz.getName())
|
||||
.append("<");
|
||||
for (int i = 0; i < parameterTypes.length; i++) {
|
||||
builder.append(parameterTypes[i]).append(parameterTypes.length - 1 == i ? ">" : ", ");
|
||||
}
|
||||
return builder.toString();
|
||||
}
|
||||
|
||||
private static Field getField(Class<?> clazz, String type) throws NoSuchFieldException {
|
||||
try {
|
||||
for (Field f : clazz.getDeclaredFields()) {
|
||||
if (f.getGenericType().getTypeName().equals(type))
|
||||
return f;
|
||||
}
|
||||
throw new NoSuchFieldException(type);
|
||||
} catch (NoSuchFieldException e) {
|
||||
Class<?> superClass = clazz.getSuperclass();
|
||||
if (superClass == null) throw e;
|
||||
return getField(superClass, type);
|
||||
}
|
||||
}
|
||||
|
||||
private static Field getField(Class<?> clazz, Class<?> fieldType) throws NoSuchFieldException {
|
||||
try {
|
||||
for (Field f : clazz.getDeclaredFields()) {
|
||||
@@ -541,4 +726,92 @@ public class NMSBinding implements INMSBinding {
|
||||
public static Holder<net.minecraft.world.level.biome.Biome> biomeToBiomeBase(Registry<net.minecraft.world.level.biome.Biome> registry, Biome biome) {
|
||||
return registry.getHolderOrThrow(ResourceKey.create(Registries.BIOME, CraftNamespacedKey.toMinecraft(biome.getKey())));
|
||||
}
|
||||
|
||||
public void injectBukkit() {
|
||||
try {
|
||||
Iris.info("Injecting Bukkit");
|
||||
new ByteBuddy()
|
||||
.redefine(CraftServer.class)
|
||||
.visit(Advice.to(CraftServerAdvice.class).on(ElementMatchers.isMethod().and(ElementMatchers.takesArguments(WorldCreator.class))))
|
||||
.make()
|
||||
.load(CraftServer.class.getClassLoader(), ClassReloadingStrategy.fromInstalledAgent());
|
||||
new ByteBuddy()
|
||||
.redefine(ServerLevel.class)
|
||||
.visit(Advice.to(ServerLevelAdvice.class).on(ElementMatchers.isConstructor().and(ElementMatchers.takesArguments(MinecraftServer.class, Executor.class, LevelStorageSource.LevelStorageAccess.class,
|
||||
PrimaryLevelData.class, ResourceKey.class, LevelStem.class, ChunkProgressListener.class, boolean.class, long.class,
|
||||
List.class, boolean.class, RandomSequences.class, World.Environment.class, ChunkGenerator.class, BiomeProvider.class))))
|
||||
.make()
|
||||
.load(ServerLevel.class.getClassLoader(), ClassReloadingStrategy.fromInstalledAgent());
|
||||
Iris.info("Injected Bukkit Successfully!");
|
||||
} catch (Exception e) {
|
||||
Iris.info(C.RED + "Failed to Inject Bukkit!");
|
||||
e.printStackTrace();
|
||||
Iris.reportError(e);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public IHeadless createHeadless(Engine engine) {
|
||||
return new Headless(this, engine);
|
||||
}
|
||||
|
||||
private static class ServerLevelAdvice {
|
||||
@Advice.OnMethodEnter
|
||||
static void enter(@Advice.Argument(0) MinecraftServer server, @Advice.Argument(2) LevelStorageSource.LevelStorageAccess access, @Advice.Argument(4) ResourceKey<Level> key, @Advice.Argument(value = 5, readOnly = false) LevelStem levelStem) {
|
||||
File iris = new File(access.levelDirectory.path().toFile(), "iris");
|
||||
if (!iris.exists() && !key.location().getPath().startsWith("iris/")) return;
|
||||
ResourceKey<DimensionType> typeKey = ResourceKey.create(Registries.DIMENSION_TYPE, new ResourceLocation("iris", key.location().getPath()));
|
||||
RegistryAccess registryAccess = server.registryAccess();
|
||||
Registry<DimensionType> registry = registryAccess.registry(Registries.DIMENSION_TYPE).orElse(null);
|
||||
if (registry == null) throw new IllegalStateException("Unable to find registry for dimension type " + typeKey);
|
||||
Holder<DimensionType> holder = registry.getHolder(typeKey).orElse(null);
|
||||
if (holder == null) throw new IllegalStateException("Unable to find dimension type " + typeKey);
|
||||
levelStem = new LevelStem(holder, levelStem.generator());
|
||||
}
|
||||
}
|
||||
|
||||
private static class CraftServerAdvice {
|
||||
@Advice.OnMethodEnter(skipOn = Advice.OnNonDefaultValue.class)
|
||||
static boolean enter(@Advice.This CraftServer self, @Advice.Argument(0) WorldCreator creator) {
|
||||
File isIrisWorld = new File(self.getWorldContainer(), creator.name() + "/iris");
|
||||
boolean isFromIris = false;
|
||||
StackTraceElement[] stackTrace = Thread.currentThread().getStackTrace();
|
||||
for (StackTraceElement stack : stackTrace) {
|
||||
if (stack.getClassName().contains("Iris")) {
|
||||
isFromIris = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (isIrisWorld.exists() && !isFromIris) {
|
||||
var logger = Logger.getLogger("Iris");
|
||||
logger.warning("detected another Plugin trying to load " + creator.name() + ". This is not supported and will be ignored.");
|
||||
|
||||
if (System.getProperty("iris.debug", "false").equals("true")) {
|
||||
new RuntimeException().printStackTrace();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Advice.OnMethodExit
|
||||
static void exit(@Advice.Enter boolean bool, @Advice.Return(readOnly = false) World returned) {
|
||||
if (bool) {
|
||||
returned = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Holder.Reference<net.minecraft.world.level.biome.Biome> getBiomeHolder(String namespace, String id) {
|
||||
return getCustomBiomeRegistry().getHolder(ResourceKey.create(Registries.BIOME, new ResourceLocation(namespace, id))).orElse(null);
|
||||
}
|
||||
|
||||
ProtoChunk createProtoChunk(ChunkPos pos, LevelHeightAccessor heightAccessor) {
|
||||
return new ProtoChunk(pos, UpgradeData.EMPTY, heightAccessor, getCustomBiomeRegistry(), null);
|
||||
}
|
||||
|
||||
net.minecraft.nbt.CompoundTag serializeChunk(ChunkAccess chunkAccess, LevelHeightAccessor heightAccessor) {
|
||||
return ChunkSerializer.write(chunkAccess, heightAccessor, getCustomBiomeRegistry());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,181 @@
|
||||
package com.volmit.iris.core.nms.v1_20_R3.mca;
|
||||
|
||||
import com.mojang.logging.LogUtils;
|
||||
import com.mojang.serialization.Codec;
|
||||
import com.mojang.serialization.DataResult;
|
||||
import it.unimi.dsi.fastutil.shorts.ShortList;
|
||||
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.Map.Entry;
|
||||
|
||||
import net.minecraft.core.BlockPos;
|
||||
import net.minecraft.core.Holder;
|
||||
import net.minecraft.core.Registry;
|
||||
import net.minecraft.core.registries.BuiltInRegistries;
|
||||
import net.minecraft.nbt.CompoundTag;
|
||||
import net.minecraft.nbt.ListTag;
|
||||
import net.minecraft.nbt.LongArrayTag;
|
||||
import net.minecraft.nbt.NbtOps;
|
||||
import net.minecraft.nbt.NbtUtils;
|
||||
import net.minecraft.nbt.ShortTag;
|
||||
import net.minecraft.nbt.Tag;
|
||||
import net.minecraft.world.level.ChunkPos;
|
||||
import net.minecraft.world.level.LevelHeightAccessor;
|
||||
import net.minecraft.world.level.biome.Biome;
|
||||
import net.minecraft.world.level.chunk.CarvingMask;
|
||||
import net.minecraft.world.level.chunk.ChunkAccess;
|
||||
import net.minecraft.world.level.chunk.LevelChunkSection;
|
||||
import net.minecraft.world.level.chunk.PalettedContainerRO;
|
||||
import net.minecraft.world.level.chunk.ProtoChunk;
|
||||
import net.minecraft.world.level.chunk.UpgradeData;
|
||||
import net.minecraft.world.level.chunk.ChunkAccess.TicksToSave;
|
||||
import net.minecraft.world.level.levelgen.BelowZeroRetrogen;
|
||||
import net.minecraft.world.level.levelgen.Heightmap;
|
||||
import net.minecraft.world.level.levelgen.GenerationStep.Carving;
|
||||
import net.minecraft.world.level.levelgen.Heightmap.Types;
|
||||
import net.minecraft.world.level.levelgen.blending.BlendingData;
|
||||
import org.slf4j.Logger;
|
||||
|
||||
import static net.minecraft.world.level.chunk.storage.ChunkSerializer.BLOCK_STATE_CODEC;
|
||||
|
||||
public class ChunkSerializer {
|
||||
private static final Logger LOGGER = LogUtils.getLogger();
|
||||
private static Method CODEC = null;
|
||||
|
||||
static {
|
||||
for (Method method : net.minecraft.world.level.chunk.storage.ChunkSerializer.class.getDeclaredMethods()) {
|
||||
if (method.getReturnType().equals(Codec.class) && method.getParameterCount() == 1) {
|
||||
CODEC = method;
|
||||
CODEC.setAccessible(true);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static CompoundTag write(ChunkAccess chunk, LevelHeightAccessor heightAccessor, Registry<Biome> biomeRegistry) {
|
||||
ChunkPos pos = chunk.getPos();
|
||||
CompoundTag data = NbtUtils.addCurrentDataVersion(new CompoundTag());
|
||||
data.putInt("xPos", pos.x);
|
||||
data.putInt("yPos", ((LevelHeightAccessor) chunk).getMinSection());
|
||||
data.putInt("zPos", pos.z);
|
||||
data.putLong("LastUpdate", 0L);
|
||||
data.putLong("InhabitedTime", chunk.getInhabitedTime());
|
||||
data.putString("Status", BuiltInRegistries.CHUNK_STATUS.getKey(chunk.getStatus()).toString());
|
||||
BlendingData blendingdata = chunk.getBlendingData();
|
||||
if (blendingdata != null) {
|
||||
DataResult<Tag> dataResult = BlendingData.CODEC.encodeStart(NbtOps.INSTANCE, blendingdata);
|
||||
dataResult.resultOrPartial(LOGGER::error).ifPresent(base -> data.put("blending_data", base));
|
||||
}
|
||||
|
||||
BelowZeroRetrogen belowzeroretrogen = chunk.getBelowZeroRetrogen();
|
||||
if (belowzeroretrogen != null) {
|
||||
DataResult<Tag> dataResult = BelowZeroRetrogen.CODEC.encodeStart(NbtOps.INSTANCE, belowzeroretrogen);
|
||||
dataResult.resultOrPartial(LOGGER::error).ifPresent(base -> data.put("below_zero_retrogen", base));
|
||||
}
|
||||
|
||||
UpgradeData upgradeData = chunk.getUpgradeData();
|
||||
if (!upgradeData.isEmpty()) {
|
||||
data.put("UpgradeData", upgradeData.write());
|
||||
}
|
||||
|
||||
LevelChunkSection[] chunkSections = chunk.getSections();
|
||||
ListTag sections = new ListTag();
|
||||
Codec<PalettedContainerRO<Holder<Biome>>> codec;
|
||||
try {
|
||||
codec = (Codec<PalettedContainerRO<Holder<Biome>>>) CODEC.invoke(null, biomeRegistry);
|
||||
} catch (IllegalAccessException | InvocationTargetException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
|
||||
int minLightSection = heightAccessor.getMinSection() - 1;
|
||||
int maxLightSection = minLightSection + heightAccessor.getSectionsCount() + 2;
|
||||
for (int y = minLightSection; y < maxLightSection; y++) {
|
||||
int i = ((LevelHeightAccessor) chunk).getSectionIndexFromSectionY(y);
|
||||
if (i >= 0 && i < chunkSections.length) {
|
||||
CompoundTag section = new CompoundTag();
|
||||
LevelChunkSection chunkSection = chunkSections[i];
|
||||
DataResult<Tag> dataResult = BLOCK_STATE_CODEC.encodeStart(NbtOps.INSTANCE, chunkSection.getStates());
|
||||
section.put("block_states", dataResult.getOrThrow(false, LOGGER::error));
|
||||
dataResult = codec.encodeStart(NbtOps.INSTANCE, chunkSection.getBiomes());
|
||||
section.put("biomes", dataResult.getOrThrow(false, LOGGER::error));
|
||||
|
||||
if (!section.isEmpty()) {
|
||||
section.putByte("Y", (byte)y);
|
||||
sections.add(section);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
data.put("sections", sections);
|
||||
if (chunk.isLightCorrect()) {
|
||||
data.putBoolean("isLightOn", true);
|
||||
}
|
||||
|
||||
ListTag blockEntities = new ListTag();
|
||||
for (BlockPos blockPos : chunk.getBlockEntitiesPos()) {
|
||||
CompoundTag blockEntityNbt = chunk.getBlockEntityNbtForSaving(blockPos);
|
||||
if (blockEntityNbt != null) {
|
||||
blockEntities.add(blockEntityNbt);
|
||||
}
|
||||
}
|
||||
data.put("block_entities", blockEntities);
|
||||
|
||||
if (chunk instanceof ProtoChunk protoChunk) {
|
||||
ListTag entities = new ListTag();
|
||||
entities.addAll(protoChunk.getEntities());
|
||||
data.put("entities", entities);
|
||||
|
||||
CompoundTag carvingMasks = new CompoundTag();
|
||||
for (Carving carving : Carving.values()) {
|
||||
CarvingMask carvingMask = protoChunk.getCarvingMask(carving);
|
||||
if (carvingMask != null) {
|
||||
carvingMasks.putLongArray(carving.toString(), carvingMask.toArray());
|
||||
}
|
||||
}
|
||||
data.put("CarvingMasks", carvingMasks);
|
||||
}
|
||||
|
||||
saveTicks(data, chunk.getTicksForSerialization());
|
||||
data.put("PostProcessing", packOffsets(chunk.getPostProcessing()));
|
||||
CompoundTag heightmaps = new CompoundTag();
|
||||
|
||||
for (Entry<Types, Heightmap> entry : chunk.getHeightmaps()) {
|
||||
if (chunk.getStatus().heightmapsAfter().contains(entry.getKey())) {
|
||||
heightmaps.put(entry.getKey().getSerializationKey(), new LongArrayTag(entry.getValue().getRawData()));
|
||||
}
|
||||
}
|
||||
|
||||
data.put("Heightmaps", heightmaps);
|
||||
|
||||
CompoundTag structures = new CompoundTag();
|
||||
structures.put("starts", new CompoundTag());
|
||||
structures.put("References", new CompoundTag());
|
||||
data.put("structures", structures);
|
||||
if (!chunk.persistentDataContainer.isEmpty()) {
|
||||
data.put("ChunkBukkitValues", chunk.persistentDataContainer.toTagCompound());
|
||||
}
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
private static void saveTicks(CompoundTag compoundTag, TicksToSave ticksToSave) {
|
||||
compoundTag.put("block_ticks", ticksToSave.blocks().save(0, block -> BuiltInRegistries.BLOCK.getKey(block).toString()));
|
||||
compoundTag.put("fluid_ticks", ticksToSave.fluids().save(0, fluid -> BuiltInRegistries.FLUID.getKey(fluid).toString()));
|
||||
}
|
||||
|
||||
public static ListTag packOffsets(ShortList[] offsets) {
|
||||
ListTag tags = new ListTag();
|
||||
for (ShortList shorts : offsets) {
|
||||
ListTag listTag = new ListTag();
|
||||
if (shorts != null) {
|
||||
for (Short s : shorts) {
|
||||
listTag.add(ShortTag.valueOf(s));
|
||||
}
|
||||
}
|
||||
tags.add(listTag);
|
||||
}
|
||||
|
||||
return tags;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,159 @@
|
||||
package com.volmit.iris.core.nms.v1_20_R3.mca;
|
||||
|
||||
import com.volmit.iris.Iris;
|
||||
import com.volmit.iris.core.nms.BiomeBaseInjector;
|
||||
import com.volmit.iris.engine.data.chunk.TerrainChunk;
|
||||
import com.volmit.iris.util.data.IrisBlockData;
|
||||
import net.minecraft.core.BlockPos;
|
||||
import net.minecraft.core.Holder;
|
||||
import net.minecraft.world.level.LevelHeightAccessor;
|
||||
import net.minecraft.world.level.block.state.BlockState;
|
||||
import net.minecraft.world.level.chunk.ChunkAccess;
|
||||
import org.bukkit.Material;
|
||||
import org.bukkit.block.Biome;
|
||||
import org.bukkit.block.data.BlockData;
|
||||
import org.bukkit.craftbukkit.v1_20_R3.block.CraftBiome;
|
||||
import org.bukkit.craftbukkit.v1_20_R3.block.data.CraftBlockData;
|
||||
import org.bukkit.generator.ChunkGenerator;
|
||||
import org.bukkit.material.MaterialData;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
public record MCATerrainChunk(ChunkAccess chunk) implements TerrainChunk {
|
||||
|
||||
@Override
|
||||
public BiomeBaseInjector getBiomeBaseInjector() {
|
||||
return (x, y, z, biomeBase) -> chunk.setBiome(x, y, z, (Holder<net.minecraft.world.level.biome.Biome>) biomeBase);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Biome getBiome(int x, int z) {
|
||||
return Biome.THE_VOID;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Biome getBiome(int x, int y, int z) {
|
||||
return Biome.THE_VOID;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setBiome(int x, int z, Biome bio) {
|
||||
setBiome(x, 0, z, bio);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setBiome(int x, int y, int z, Biome bio) {
|
||||
if (y < 0) return;
|
||||
y += getMinHeight();
|
||||
if (y > getMaxHeight()) return;
|
||||
chunk.setBiome(x & 15, y, z & 15, CraftBiome.bukkitToMinecraftHolder(bio));
|
||||
}
|
||||
|
||||
private LevelHeightAccessor height() {
|
||||
return chunk;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getMinHeight() {
|
||||
return height().getMinBuildHeight();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getMaxHeight() {
|
||||
return height().getMaxBuildHeight();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setBlock(int x, int y, int z, BlockData blockData) {
|
||||
if (y < 0) return;
|
||||
y += getMinHeight();
|
||||
if (y > getMaxHeight()) return;
|
||||
|
||||
if (blockData == null) {
|
||||
Iris.error("NULL BD");
|
||||
}
|
||||
if (blockData instanceof IrisBlockData data)
|
||||
blockData = data.getBase();
|
||||
if (!(blockData instanceof CraftBlockData craftBlockData))
|
||||
throw new IllegalArgumentException("Expected CraftBlockData, got " + blockData.getClass().getSimpleName() + " instead");
|
||||
chunk.setBlockState(new BlockPos(x & 15, y, z & 15), craftBlockData.getState(), false);
|
||||
}
|
||||
|
||||
private BlockState getBlockState(int x, int y, int z) {
|
||||
if (y < 0) {
|
||||
y = 0;
|
||||
}
|
||||
y += getMinHeight();
|
||||
if (y > getMaxHeight()) {
|
||||
y = getMaxHeight();
|
||||
}
|
||||
|
||||
return chunk.getBlockState(new BlockPos(x & 15, y, z & 15));
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public org.bukkit.block.data.BlockData getBlockData(int x, int y, int z) {
|
||||
return CraftBlockData.fromData(getBlockState(x, y, z));
|
||||
}
|
||||
|
||||
@Override
|
||||
public ChunkGenerator.ChunkData getRaw() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setRaw(ChunkGenerator.ChunkData data) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
@Deprecated
|
||||
public void inject(ChunkGenerator.BiomeGrid biome) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setBlock(int x, int y, int z, @NotNull Material material) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
@Deprecated
|
||||
public void setBlock(int x, int y, int z, @NotNull MaterialData material) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setRegion(int xMin, int yMin, int zMin, int xMax, int yMax, int zMax, @NotNull Material material) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
@Deprecated
|
||||
public void setRegion(int xMin, int yMin, int zMin, int xMax, int yMax, int zMax, @NotNull MaterialData material) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setRegion(int xMin, int yMin, int zMin, int xMax, int yMax, int zMax, @NotNull BlockData blockData) {
|
||||
|
||||
}
|
||||
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public Material getType(int x, int y, int z) {
|
||||
return getBlockData(x, y, z).getMaterial();
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public MaterialData getTypeAndData(int x, int y, int z) {
|
||||
return getBlockData(x, y, z).createBlockState().getData();
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte getData(int x, int y, int z) {
|
||||
return getTypeAndData(x, y, z).getData();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,109 @@
|
||||
package com.volmit.iris.core.nms.v1_20_R3.mca;
|
||||
|
||||
import com.google.common.base.Preconditions;
|
||||
import it.unimi.dsi.fastutil.longs.Long2ObjectLinkedOpenHashMap;
|
||||
import java.io.DataInputStream;
|
||||
import java.io.DataOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import javax.annotation.Nullable;
|
||||
import net.minecraft.FileUtil;
|
||||
import net.minecraft.nbt.CompoundTag;
|
||||
import net.minecraft.nbt.NbtAccounter;
|
||||
import net.minecraft.nbt.NbtIo;
|
||||
import net.minecraft.nbt.StreamTagVisitor;
|
||||
import net.minecraft.util.ExceptionCollector;
|
||||
import net.minecraft.world.level.ChunkPos;
|
||||
import net.minecraft.world.level.chunk.storage.RegionFile;
|
||||
|
||||
public final class RegionFileStorage implements AutoCloseable {
|
||||
public final Long2ObjectLinkedOpenHashMap<RegionFile> regionCache = new Long2ObjectLinkedOpenHashMap<>();
|
||||
private final Path folder;
|
||||
private final boolean sync;
|
||||
|
||||
public RegionFileStorage(Path folder, boolean sync) {
|
||||
this.folder = folder;
|
||||
this.sync = sync;
|
||||
}
|
||||
|
||||
public RegionFile getRegionFile(ChunkPos chunkPos, boolean existingOnly) throws IOException {
|
||||
long id = ChunkPos.asLong(chunkPos.getRegionX(), chunkPos.getRegionZ());
|
||||
RegionFile regionFile = this.regionCache.getAndMoveToFirst(id);
|
||||
if (regionFile != null) {
|
||||
return regionFile;
|
||||
} else {
|
||||
if (this.regionCache.size() >= 256) {
|
||||
this.regionCache.removeLast().close();
|
||||
}
|
||||
|
||||
FileUtil.createDirectoriesSafe(this.folder);
|
||||
Path path = folder.resolve("r." + chunkPos.getRegionX() + "." + chunkPos.getRegionZ() + ".mca");
|
||||
if (existingOnly && !Files.exists(path)) {
|
||||
return null;
|
||||
} else {
|
||||
regionFile = new RegionFile(path, this.folder, this.sync);
|
||||
this.regionCache.putAndMoveToFirst(id, regionFile);
|
||||
return regionFile;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public CompoundTag read(ChunkPos chunkPos) throws IOException {
|
||||
RegionFile regionFile = this.getRegionFile(chunkPos, true);
|
||||
if (regionFile != null) {
|
||||
try (DataInputStream datainputstream = regionFile.getChunkDataInputStream(chunkPos)) {
|
||||
if (datainputstream != null) {
|
||||
return NbtIo.read(datainputstream);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public void scanChunk(ChunkPos chunkPos, StreamTagVisitor visitor) throws IOException {
|
||||
RegionFile regionFile = this.getRegionFile(chunkPos, true);
|
||||
if (regionFile != null) {
|
||||
try (DataInputStream din = regionFile.getChunkDataInputStream(chunkPos)) {
|
||||
if (din != null) {
|
||||
NbtIo.parse(din, visitor, NbtAccounter.unlimitedHeap());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void write(ChunkPos chunkPos, @Nullable CompoundTag compound) throws IOException {
|
||||
RegionFile regionFile = this.getRegionFile(chunkPos, false);
|
||||
Preconditions.checkArgument(regionFile != null, "Failed to find region file for chunk %s", chunkPos);
|
||||
if (compound == null) {
|
||||
regionFile.clear(chunkPos);
|
||||
} else {
|
||||
try (DataOutputStream dos = regionFile.getChunkDataOutputStream(chunkPos)) {
|
||||
NbtIo.write(compound, dos);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() throws IOException {
|
||||
ExceptionCollector<IOException> collector = new ExceptionCollector<>();
|
||||
|
||||
for (RegionFile regionFile : this.regionCache.values()) {
|
||||
try {
|
||||
regionFile.close();
|
||||
} catch (IOException e) {
|
||||
collector.add(e);
|
||||
}
|
||||
}
|
||||
|
||||
collector.throwIfPresent();
|
||||
}
|
||||
|
||||
public void flush() throws IOException {
|
||||
for (RegionFile regionfile : this.regionCache.values()) {
|
||||
regionfile.flush();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2,6 +2,7 @@ package com.volmit.iris.core.nms.v1_20_R4;
|
||||
|
||||
import com.mojang.serialization.MapCodec;
|
||||
import com.volmit.iris.Iris;
|
||||
import com.volmit.iris.core.nms.INMS;
|
||||
import com.volmit.iris.engine.data.cache.AtomicCache;
|
||||
import com.volmit.iris.engine.framework.Engine;
|
||||
import com.volmit.iris.engine.object.IrisBiome;
|
||||
@@ -124,8 +125,16 @@ public class CustomBiomeSource extends BiomeSource {
|
||||
for (IrisBiome i : engine.getAllBiomes()) {
|
||||
if (i.isCustom()) {
|
||||
for (IrisBiomeCustom j : i.getCustomDerivitives()) {
|
||||
ResourceLocation resourceLocation = new ResourceLocation(engine.getDimension().getLoadKey() + ":" + j.getId());
|
||||
Biome biome = customRegistry.get(resourceLocation);
|
||||
ResourceLocation location = new ResourceLocation(engine.getDimension().getLoadKey() + ":" + j.getId());
|
||||
Biome biome = customRegistry.get(location);
|
||||
if (biome == null) {
|
||||
INMS.get().registerBiome(location.getNamespace(), j, false);
|
||||
biome = customRegistry.get(location);
|
||||
if (biome == null) {
|
||||
Iris.error("Cannot find biome for IrisBiomeCustom " + j.getId() + " from engine " + engine.getName());
|
||||
continue;
|
||||
}
|
||||
}
|
||||
Optional<ResourceKey<Biome>> optionalBiomeKey = customRegistry.getResourceKey(biome);
|
||||
if (optionalBiomeKey.isEmpty()) {
|
||||
Iris.error("Cannot find biome for IrisBiomeCustom " + j.getId() + " from engine " + engine.getName());
|
||||
|
||||
@@ -4,18 +4,48 @@ import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.DataInputStream;
|
||||
import java.io.DataOutputStream;
|
||||
import java.io.File;
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.Method;
|
||||
import java.lang.reflect.Modifier;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.Vector;
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import com.google.common.base.Preconditions;
|
||||
import com.google.gson.JsonElement;
|
||||
import com.google.gson.JsonObject;
|
||||
import com.mojang.datafixers.util.Pair;
|
||||
import com.mojang.serialization.Codec;
|
||||
import com.mojang.serialization.JsonOps;
|
||||
import com.volmit.iris.core.nms.datapack.DataVersion;
|
||||
import com.volmit.iris.engine.object.IrisBiomeCustom;
|
||||
import com.volmit.iris.engine.object.IrisDimension;
|
||||
import com.volmit.iris.util.format.C;
|
||||
import it.unimi.dsi.fastutil.objects.Reference2IntMap;
|
||||
import net.bytebuddy.ByteBuddy;
|
||||
import net.bytebuddy.asm.Advice;
|
||||
import net.bytebuddy.dynamic.loading.ClassReloadingStrategy;
|
||||
import net.bytebuddy.matcher.ElementMatchers;
|
||||
import net.minecraft.core.MappedRegistry;
|
||||
import net.minecraft.core.RegistrationInfo;
|
||||
import net.minecraft.core.component.DataComponents;
|
||||
import net.minecraft.server.MinecraftServer;
|
||||
import net.minecraft.server.level.progress.ChunkProgressListener;
|
||||
import net.minecraft.util.GsonHelper;
|
||||
import net.minecraft.world.RandomSequences;
|
||||
import net.minecraft.world.item.component.CustomData;
|
||||
import net.minecraft.world.level.Level;
|
||||
import net.minecraft.world.level.chunk.status.ChunkStatus;
|
||||
import net.minecraft.world.level.dimension.DimensionType;
|
||||
import net.minecraft.world.level.dimension.LevelStem;
|
||||
import net.minecraft.world.level.storage.LevelStorageSource;
|
||||
import net.minecraft.world.level.storage.PrimaryLevelData;
|
||||
import org.bukkit.*;
|
||||
import org.bukkit.block.Biome;
|
||||
import org.bukkit.block.data.BlockData;
|
||||
@@ -29,6 +59,7 @@ import org.bukkit.craftbukkit.v1_20_R4.util.CraftNamespacedKey;
|
||||
import org.bukkit.entity.Dolphin;
|
||||
import org.bukkit.entity.Entity;
|
||||
import org.bukkit.event.entity.CreatureSpawnEvent;
|
||||
import org.bukkit.generator.BiomeProvider;
|
||||
import org.bukkit.generator.ChunkGenerator;
|
||||
import org.bukkit.inventory.ItemStack;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
@@ -549,4 +580,203 @@ public class NMSBinding implements INMSBinding {
|
||||
public DataVersion getDataVersion() {
|
||||
return DataVersion.V1205;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean registerDimension(String name, IrisDimension dimension) {
|
||||
var registry = registry(Registries.DIMENSION_TYPE);
|
||||
var baseLocation = switch (dimension.getEnvironment()) {
|
||||
case NORMAL -> new ResourceLocation("minecraft", "overworld");
|
||||
case NETHER -> new ResourceLocation("minecraft", "the_nether");
|
||||
case THE_END -> new ResourceLocation("minecraft", "the_end");
|
||||
case CUSTOM -> throw new IllegalArgumentException("Cannot register custom dimension");
|
||||
};
|
||||
var base = registry.getHolder(ResourceKey.create(Registries.DIMENSION_TYPE, baseLocation)).orElse(null);
|
||||
if (base == null) return false;
|
||||
var json = encode(DimensionType.CODEC, base).orElse(null);
|
||||
if (json == null) return false;
|
||||
var object = json.getAsJsonObject();
|
||||
var height = dimension.getDimensionHeight();
|
||||
object.addProperty("min_y", height.getMin());
|
||||
object.addProperty("height", height.getMax() - height.getMin());
|
||||
object.addProperty("logical_height", dimension.getLogicalHeight());
|
||||
var value = decode(DimensionType.CODEC, object.toString()).map(Holder::value).orElse(null);
|
||||
if (value == null) return false;
|
||||
return register(Registries.DIMENSION_TYPE, new ResourceLocation("iris", name), value, true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean registerBiome(String dimensionId, IrisBiomeCustom biome, boolean replace) {
|
||||
var biomeBase = decode(net.minecraft.world.level.biome.Biome.CODEC, biome.generateJson()).map(Holder::value).orElse(null);
|
||||
if (biomeBase == null) return false;
|
||||
return register(Registries.BIOME, new ResourceLocation(dimensionId, biome.getId()), biomeBase, replace);
|
||||
}
|
||||
|
||||
private <T> Optional<T> decode(Codec<T> codec, String json) {
|
||||
return codec.decode(JsonOps.INSTANCE, GsonHelper.parse(json)).result().map(Pair::getFirst);
|
||||
}
|
||||
|
||||
private <T> Optional<JsonElement> encode(Codec<T> codec, T value) {
|
||||
return codec.encode(value, JsonOps.INSTANCE, new JsonObject()).result();
|
||||
}
|
||||
|
||||
private <T> boolean register(ResourceKey<Registry<T>> registryKey, ResourceLocation location, T value, boolean replace) {
|
||||
Preconditions.checkArgument(registryKey != null, "The registry cannot be null!");
|
||||
Preconditions.checkArgument(location != null, "The location cannot be null!");
|
||||
Preconditions.checkArgument(value != null, "The value cannot be null!");
|
||||
var registry = registry(registryKey);
|
||||
var key = ResourceKey.create(registryKey, location);
|
||||
try {
|
||||
if (registry.containsKey(key)) {
|
||||
if (!replace) return false;
|
||||
return replace(registryKey, location, value);
|
||||
}
|
||||
Field field = getField(MappedRegistry.class, boolean.class);
|
||||
field.setAccessible(true);
|
||||
boolean frozen = field.getBoolean(registry);
|
||||
field.setBoolean(registry, false);
|
||||
Field valueField = getField(Holder.Reference.class, "T");
|
||||
valueField.setAccessible(true);
|
||||
|
||||
try {
|
||||
var holder = registry.register(key, value, RegistrationInfo.BUILT_IN);
|
||||
if (frozen) valueField.set(holder, value);
|
||||
return true;
|
||||
} finally {
|
||||
field.setBoolean(registry, frozen);
|
||||
}
|
||||
} catch (Throwable e) {
|
||||
throw new IllegalStateException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private <T> boolean replace(ResourceKey<Registry<T>> registryKey, ResourceLocation location, T value) {
|
||||
Preconditions.checkArgument(registryKey != null, "The registryKey cannot be null!");
|
||||
Preconditions.checkArgument(location != null, "The location cannot be null!");
|
||||
Preconditions.checkArgument(value != null, "The value cannot be null!");
|
||||
var registry = registry(registryKey);
|
||||
var key = ResourceKey.create(registryKey, location);
|
||||
try {
|
||||
var holder = registry.getHolder(key).orElse(null);
|
||||
if (holder == null) return false;
|
||||
var oldValue = holder.value();
|
||||
Field valueField = getField(Holder.Reference.class, "T");
|
||||
valueField.setAccessible(true);
|
||||
Field toIdField = getField(MappedRegistry.class, buildType(Reference2IntMap.class, "T"));
|
||||
toIdField.setAccessible(true);
|
||||
Field byValueField = getField(MappedRegistry.class, buildType(Map.class, "T", buildType(Holder.Reference.class, "T")));
|
||||
byValueField.setAccessible(true);
|
||||
var toId = (Reference2IntMap<T>) toIdField.get(registry);
|
||||
var byValue = (Map<T, Holder.Reference<T>>) byValueField.get(registry);
|
||||
|
||||
valueField.set(holder, value);
|
||||
toId.put(value, toId.removeInt(oldValue));
|
||||
byValue.put(value, byValue.remove(oldValue));
|
||||
return true;
|
||||
} catch (Throwable e) {
|
||||
throw new IllegalStateException(e);
|
||||
}
|
||||
}
|
||||
|
||||
private <T> MappedRegistry<T> registry(ResourceKey<Registry<T>> registryKey) {
|
||||
var rawRegistry = registry().registry(registryKey).orElse(null);
|
||||
if (!(rawRegistry instanceof MappedRegistry<T> registry))
|
||||
throw new IllegalStateException("The Registry is not a mapped Registry!");
|
||||
return registry;
|
||||
}
|
||||
|
||||
private static String buildType(Class<?> clazz, String... parameterTypes) {
|
||||
if (parameterTypes.length == 0) return clazz.getName();
|
||||
var builder = new StringBuilder(clazz.getName())
|
||||
.append("<");
|
||||
for (int i = 0; i < parameterTypes.length; i++) {
|
||||
builder.append(parameterTypes[i]).append(parameterTypes.length - 1 == i ? ">" : ", ");
|
||||
}
|
||||
return builder.toString();
|
||||
}
|
||||
|
||||
private static Field getField(Class<?> clazz, String type) throws NoSuchFieldException {
|
||||
try {
|
||||
for (Field f : clazz.getDeclaredFields()) {
|
||||
if (f.getGenericType().getTypeName().equals(type))
|
||||
return f;
|
||||
}
|
||||
throw new NoSuchFieldException(type);
|
||||
} catch (NoSuchFieldException e) {
|
||||
Class<?> superClass = clazz.getSuperclass();
|
||||
if (superClass == null) throw e;
|
||||
return getField(superClass, type);
|
||||
}
|
||||
}
|
||||
|
||||
public void injectBukkit() {
|
||||
try {
|
||||
Iris.info("Injecting Bukkit");
|
||||
new ByteBuddy()
|
||||
.redefine(CraftServer.class)
|
||||
.visit(Advice.to(CraftServerAdvice.class).on(ElementMatchers.isMethod().and(ElementMatchers.takesArguments(WorldCreator.class))))
|
||||
.make()
|
||||
.load(CraftServer.class.getClassLoader(), ClassReloadingStrategy.fromInstalledAgent());
|
||||
new ByteBuddy()
|
||||
.redefine(ServerLevel.class)
|
||||
.visit(Advice.to(ServerLevelAdvice.class).on(ElementMatchers.isConstructor().and(ElementMatchers.takesArguments(MinecraftServer.class, Executor.class, LevelStorageSource.LevelStorageAccess.class,
|
||||
PrimaryLevelData.class, ResourceKey.class, LevelStem.class, ChunkProgressListener.class, boolean.class, long.class,
|
||||
List.class, boolean.class, RandomSequences.class, World.Environment.class, ChunkGenerator.class, BiomeProvider.class))))
|
||||
.make()
|
||||
.load(ServerLevel.class.getClassLoader(), ClassReloadingStrategy.fromInstalledAgent());
|
||||
Iris.info("Injected Bukkit Successfully!");
|
||||
} catch (Exception e) {
|
||||
Iris.info(C.RED + "Failed to Inject Bukkit!");
|
||||
e.printStackTrace();
|
||||
Iris.reportError(e);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private static class ServerLevelAdvice {
|
||||
@Advice.OnMethodEnter
|
||||
static void enter(@Advice.Argument(0) MinecraftServer server, @Advice.Argument(2) LevelStorageSource.LevelStorageAccess access, @Advice.Argument(4) ResourceKey<Level> key, @Advice.Argument(value = 5, readOnly = false) LevelStem levelStem) {
|
||||
File iris = new File(access.levelDirectory.path().toFile(), "iris");
|
||||
if (!iris.exists() && !key.location().getPath().startsWith("iris/")) return;
|
||||
ResourceKey<DimensionType> typeKey = ResourceKey.create(Registries.DIMENSION_TYPE, new ResourceLocation("iris", key.location().getPath()));
|
||||
RegistryAccess registryAccess = server.registryAccess();
|
||||
Registry<DimensionType> registry = registryAccess.registry(Registries.DIMENSION_TYPE).orElse(null);
|
||||
if (registry == null) throw new IllegalStateException("Unable to find registry for dimension type " + typeKey);
|
||||
Holder<DimensionType> holder = registry.getHolder(typeKey).orElse(null);
|
||||
if (holder == null) throw new IllegalStateException("Unable to find dimension type " + typeKey);
|
||||
levelStem = new LevelStem(holder, levelStem.generator());
|
||||
}
|
||||
}
|
||||
|
||||
private static class CraftServerAdvice {
|
||||
@Advice.OnMethodEnter(skipOn = Advice.OnNonDefaultValue.class)
|
||||
static boolean enter(@Advice.This CraftServer self, @Advice.Argument(0) WorldCreator creator) {
|
||||
File isIrisWorld = new File(self.getWorldContainer(), creator.name() + "/iris");
|
||||
boolean isFromIris = false;
|
||||
StackTraceElement[] stackTrace = Thread.currentThread().getStackTrace();
|
||||
for (StackTraceElement stack : stackTrace) {
|
||||
if (stack.getClassName().contains("Iris")) {
|
||||
isFromIris = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (isIrisWorld.exists() && !isFromIris) {
|
||||
var logger = Logger.getLogger("Iris");
|
||||
logger.warning("detected another Plugin trying to load " + creator.name() + ". This is not supported and will be ignored.");
|
||||
|
||||
if (System.getProperty("iris.debug", "false").equals("true")) {
|
||||
new RuntimeException().printStackTrace();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Advice.OnMethodExit
|
||||
static void exit(@Advice.Enter boolean bool, @Advice.Return(readOnly = false) World returned) {
|
||||
if (bool) {
|
||||
returned = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,7 +23,7 @@ pluginManagement {
|
||||
}
|
||||
}
|
||||
rootProject.name = 'Iris'
|
||||
|
||||
//include 'app', 'com.volmit.gui'
|
||||
include(':core')
|
||||
include(
|
||||
':nms:v1_20_R4',
|
||||
|
||||
Reference in New Issue
Block a user