mirror of
https://github.com/VolmitSoftware/Iris.git
synced 2026-04-24 08:58:19 +00:00
Merge branch 'mca' of https://github.com/RePixelatedMC/Iris into PixelatedDev
This commit is contained in:
@@ -69,7 +69,6 @@ IrisAccess access=IrisToolbelt.createWorld() // If you like builders...
|
|||||||
.name("myWorld") // The world name
|
.name("myWorld") // The world name
|
||||||
.dimension("terrifyinghands")
|
.dimension("terrifyinghands")
|
||||||
.seed(69133742) // The world seed
|
.seed(69133742) // The world seed
|
||||||
.headless(true) // Headless make gen go fast
|
|
||||||
.pregen(PregenTask // Define a pregen job to run
|
.pregen(PregenTask // Define a pregen job to run
|
||||||
.builder()
|
.builder()
|
||||||
.center(new Position2(0,0)) // REGION coords (1 region = 32x32 chunks)
|
.center(new Position2(0,0)) // REGION coords (1 region = 32x32 chunks)
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ plugins {
|
|||||||
id "de.undercouch.download" version "5.0.1"
|
id "de.undercouch.download" version "5.0.1"
|
||||||
}
|
}
|
||||||
|
|
||||||
version '3.2.4-1.19.2-1.20.4'
|
version '3.2.6-1.19.2-1.20.4'
|
||||||
def specialSourceVersion = '1.11.0' //[NMS]
|
def specialSourceVersion = '1.11.0' //[NMS]
|
||||||
|
|
||||||
// ADD YOURSELF AS A NEW LINE IF YOU WANT YOUR OWN BUILD TASK GENERATED
|
// ADD YOURSELF AS A NEW LINE IF YOU WANT YOUR OWN BUILD TASK GENERATED
|
||||||
|
|||||||
@@ -728,6 +728,11 @@ public class Iris extends VolmitPlugin implements Listener {
|
|||||||
Iris.info("Resolved missing dimension, proceeding with generation.");
|
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());
|
Iris.debug("Assuming IrisDimension: " + dim.getName());
|
||||||
|
|
||||||
@@ -747,6 +752,9 @@ public class Iris extends VolmitPlugin implements Listener {
|
|||||||
ff.mkdirs();
|
ff.mkdirs();
|
||||||
service(StudioSVC.class).installIntoWorld(getSender(), dim.getLoadKey(), w.worldFolder());
|
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);
|
return new BukkitChunkGenerator(w, false, ff, dim.getLoadKey(), false);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -135,30 +135,6 @@ public class ServerConfigurator {
|
|||||||
public static void installDataPacks(boolean fullInstall) {
|
public static void installDataPacks(boolean fullInstall) {
|
||||||
Iris.info("Checking Data Packs...");
|
Iris.info("Checking Data Packs...");
|
||||||
File packs = new File("plugins/Iris/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()) {
|
if (packs.exists()) {
|
||||||
for (File i : packs.listFiles()) {
|
for (File i : packs.listFiles()) {
|
||||||
if (i.isDirectory()) {
|
if (i.isDirectory()) {
|
||||||
@@ -177,7 +153,7 @@ public class ServerConfigurator {
|
|||||||
|
|
||||||
Iris.verbose(" Checking Dimension " + dim.getLoadFile().getPath());
|
Iris.verbose(" Checking Dimension " + dim.getLoadFile().getPath());
|
||||||
for (File dpack : getDatapacksFolder()) {
|
for (File dpack : getDatapacksFolder()) {
|
||||||
dim.installDataPack(() -> data, dpack, ultimateMaxHeight, ultimateMinHeight);
|
dim.installDataPack(() -> data, dpack);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -228,7 +204,7 @@ public class ServerConfigurator {
|
|||||||
Iris.info( "Hotloading all Datapacks!");
|
Iris.info( "Hotloading all Datapacks!");
|
||||||
if (INMS.get().supportsDataPacks()) {
|
if (INMS.get().supportsDataPacks()) {
|
||||||
for (File folder : getDatapacksFolder()) {
|
for (File folder : getDatapacksFolder()) {
|
||||||
INMS.get().loadDatapack(folder);
|
INMS.get().loadDatapack(folder, false);
|
||||||
}
|
}
|
||||||
Iris.info("Datapacks Hotloaded!");
|
Iris.info("Datapacks Hotloaded!");
|
||||||
Iris.info(C.YELLOW + "============================================================================");
|
Iris.info(C.YELLOW + "============================================================================");
|
||||||
|
|||||||
@@ -20,9 +20,11 @@ package com.volmit.iris.core.commands;
|
|||||||
|
|
||||||
import com.volmit.iris.Iris;
|
import com.volmit.iris.Iris;
|
||||||
import com.volmit.iris.core.IrisSettings;
|
import com.volmit.iris.core.IrisSettings;
|
||||||
|
import com.volmit.iris.core.ServerConfigurator;
|
||||||
import com.volmit.iris.core.gui.NoiseExplorerGUI;
|
import com.volmit.iris.core.gui.NoiseExplorerGUI;
|
||||||
import com.volmit.iris.core.gui.VisionGUI;
|
import com.volmit.iris.core.gui.VisionGUI;
|
||||||
import com.volmit.iris.core.loader.IrisData;
|
import com.volmit.iris.core.loader.IrisData;
|
||||||
|
import com.volmit.iris.core.nms.INMS;
|
||||||
import com.volmit.iris.core.project.IrisProject;
|
import com.volmit.iris.core.project.IrisProject;
|
||||||
import com.volmit.iris.core.service.ConversionSVC;
|
import com.volmit.iris.core.service.ConversionSVC;
|
||||||
import com.volmit.iris.core.service.StudioSVC;
|
import com.volmit.iris.core.service.StudioSVC;
|
||||||
@@ -279,13 +281,23 @@ public class CommandStudio implements DecreeExecutor {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Decree(description = "Hotload a studio", aliases = {"reload", "h"})
|
@Decree(description = "Hotload a studio", aliases = {"reload", "h"})
|
||||||
public void hotload() {
|
public void hotload(@Param(defaultValue = "false") boolean reloadDataPack) {
|
||||||
if (!Iris.service(StudioSVC.class).isProjectOpen()) {
|
if (!Iris.service(StudioSVC.class).isProjectOpen()) {
|
||||||
sender().sendMessage(C.RED + "No studio world open!");
|
sender().sendMessage(C.RED + "No studio world open!");
|
||||||
return;
|
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");
|
sender().sendMessage(C.GREEN + "Hotloaded");
|
||||||
|
if (reloadDataPack) {
|
||||||
|
var world = provider.getTarget().getWorld().realWorld();
|
||||||
|
if (world == null) {
|
||||||
|
sender().sendMessage(C.RED + "Failed to reload datapacks.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
boolean success = INMS.get().loadDatapack(new File(world.getWorldFolder(), "datapacks"), true);
|
||||||
|
sender().sendMessage(success ? C.GREEN + "Reloaded datapacks." : C.RED + "Failed to reload datapacks.");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Decree(description = "Show loot if a chest were right here", origin = DecreeOrigin.PLAYER, sync = true)
|
@Decree(description = "Show loot if a chest were right here", origin = DecreeOrigin.PLAYER, sync = true)
|
||||||
|
|||||||
@@ -19,6 +19,7 @@
|
|||||||
package com.volmit.iris.core.nms;
|
package com.volmit.iris.core.nms;
|
||||||
|
|
||||||
import com.volmit.iris.engine.framework.Engine;
|
import com.volmit.iris.engine.framework.Engine;
|
||||||
|
import com.volmit.iris.engine.object.IrisDimension;
|
||||||
import com.volmit.iris.util.collection.KList;
|
import com.volmit.iris.util.collection.KList;
|
||||||
import com.volmit.iris.util.collection.KMap;
|
import com.volmit.iris.util.collection.KMap;
|
||||||
import com.volmit.iris.util.mantle.Mantle;
|
import com.volmit.iris.util.mantle.Mantle;
|
||||||
@@ -113,7 +114,9 @@ public interface INMSBinding {
|
|||||||
|
|
||||||
Entity spawnEntity(Location location, EntityType type, CreatureSpawnEvent.SpawnReason reason);
|
Entity spawnEntity(Location location, EntityType type, CreatureSpawnEvent.SpawnReason reason);
|
||||||
|
|
||||||
boolean loadDatapack(File datapackFolder);
|
boolean loadDatapack(File datapackFolder, boolean replace);
|
||||||
|
|
||||||
|
boolean registerDimension(String name, IrisDimension dimension);
|
||||||
|
|
||||||
void injectBukkit();
|
void injectBukkit();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ import com.volmit.iris.Iris;
|
|||||||
import com.volmit.iris.core.nms.INMSBinding;
|
import com.volmit.iris.core.nms.INMSBinding;
|
||||||
import com.volmit.iris.core.nms.container.BlockPos;
|
import com.volmit.iris.core.nms.container.BlockPos;
|
||||||
import com.volmit.iris.engine.framework.Engine;
|
import com.volmit.iris.engine.framework.Engine;
|
||||||
|
import com.volmit.iris.engine.object.IrisDimension;
|
||||||
import com.volmit.iris.util.collection.KList;
|
import com.volmit.iris.util.collection.KList;
|
||||||
import com.volmit.iris.util.collection.KMap;
|
import com.volmit.iris.util.collection.KMap;
|
||||||
import com.volmit.iris.util.format.C;
|
import com.volmit.iris.util.format.C;
|
||||||
@@ -107,7 +108,12 @@ public class NMSBinding1X implements INMSBinding {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean loadDatapack(File datapackFolder) {
|
public boolean loadDatapack(File datapackFolder, boolean replace) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean registerDimension(String name, IrisDimension dimension) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -19,6 +19,7 @@
|
|||||||
package com.volmit.iris.core.tools;
|
package com.volmit.iris.core.tools;
|
||||||
|
|
||||||
import com.volmit.iris.core.loader.IrisData;
|
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.object.*;
|
||||||
import com.volmit.iris.engine.platform.BukkitChunkGenerator;
|
import com.volmit.iris.engine.platform.BukkitChunkGenerator;
|
||||||
import org.bukkit.Bukkit;
|
import org.bukkit.Bukkit;
|
||||||
@@ -84,6 +85,9 @@ public class IrisWorldCreator {
|
|||||||
? dim.getLoader().getDataFolder() :
|
? dim.getLoader().getDataFolder() :
|
||||||
new File(w.worldFolder(), "iris/pack"), dimensionName, smartVanillaHeight);
|
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)
|
return new WorldCreator(name)
|
||||||
.environment(findEnvironment())
|
.environment(findEnvironment())
|
||||||
|
|||||||
@@ -56,73 +56,6 @@ import java.io.IOException;
|
|||||||
public class IrisDimension extends IrisRegistrant {
|
public class IrisDimension extends IrisRegistrant {
|
||||||
public static final BlockData STONE = Material.STONE.createBlockData();
|
public static final BlockData STONE = Material.STONE.createBlockData();
|
||||||
public static final BlockData WATER = Material.WATER.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<Position2> parallaxSize = new AtomicCache<>();
|
||||||
private final transient AtomicCache<CNG> rockLayerGenerator = new AtomicCache<>();
|
private final transient AtomicCache<CNG> rockLayerGenerator = new AtomicCache<>();
|
||||||
private final transient AtomicCache<CNG> fluidLayerGenerator = new AtomicCache<>();
|
private final transient AtomicCache<CNG> fluidLayerGenerator = new AtomicCache<>();
|
||||||
@@ -140,10 +73,6 @@ public class IrisDimension extends IrisRegistrant {
|
|||||||
@MaxNumber(2032)
|
@MaxNumber(2032)
|
||||||
@Desc("Maximum height at which players can be teleported to through gameplay.")
|
@Desc("Maximum height at which players can be teleported to through gameplay.")
|
||||||
private int logicalHeight = 256;
|
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)
|
@RegistryListResource(IrisJigsawStructure.class)
|
||||||
@Desc("If defined, Iris will place the given jigsaw structure where minecraft should place the overworld stronghold.")
|
@Desc("If defined, Iris will place the given jigsaw structure where minecraft should place the overworld stronghold.")
|
||||||
private String stronghold;
|
private String stronghold;
|
||||||
@@ -230,10 +159,6 @@ public class IrisDimension extends IrisRegistrant {
|
|||||||
private int fluidHeight = 63;
|
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.")
|
@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);
|
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")
|
@Desc("Enable smart vanilla height")
|
||||||
private boolean smartVanillaHeight = false;
|
private boolean smartVanillaHeight = false;
|
||||||
@RegistryListResource(IrisBiome.class)
|
@RegistryListResource(IrisBiome.class)
|
||||||
@@ -376,6 +301,14 @@ public class IrisDimension extends IrisRegistrant {
|
|||||||
return environment;
|
return environment;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public IrisRange getDimensionHeight() {
|
||||||
|
return smartVanillaHeight ? new IrisRange(-64, 320) : dimensionHeight;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getLogicalHeight() {
|
||||||
|
return smartVanillaHeight ? 256 : logicalHeight;
|
||||||
|
}
|
||||||
|
|
||||||
public boolean hasFocusRegion() {
|
public boolean hasFocusRegion() {
|
||||||
return !focusRegion.equals("");
|
return !focusRegion.equals("");
|
||||||
}
|
}
|
||||||
@@ -447,7 +380,7 @@ public class IrisDimension extends IrisRegistrant {
|
|||||||
return landBiomeStyle;
|
return landBiomeStyle;
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean installDataPack(DataProvider data, File datapacks, double ultimateMaxHeight, double ultimateMinHeight) {
|
public boolean installDataPack(DataProvider data, File datapacks) {
|
||||||
boolean write = false;
|
boolean write = false;
|
||||||
boolean changed = false;
|
boolean changed = false;
|
||||||
|
|
||||||
@@ -476,13 +409,6 @@ public class IrisDimension extends IrisRegistrant {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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(changed, datapacks);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (write) {
|
if (write) {
|
||||||
File mcm = new File(datapacks, "iris/pack.mcmeta");
|
File mcm = new File(datapacks, "iris/pack.mcmeta");
|
||||||
try {
|
try {
|
||||||
@@ -518,67 +444,4 @@ public class IrisDimension extends IrisRegistrant {
|
|||||||
public void scanForErrors(JSONObject p, VolmitSender sender) {
|
public void scanForErrors(JSONObject p, VolmitSender sender) {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean writeDimensionType(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());
|
|
||||||
} 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());
|
|
||||||
} 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());
|
|
||||||
} catch (IOException e) {
|
|
||||||
Iris.reportError(e);
|
|
||||||
e.printStackTrace();
|
|
||||||
}
|
|
||||||
|
|
||||||
return changed;
|
|
||||||
}
|
|
||||||
|
|
||||||
private String generateDatapackJsonOverworld() {
|
|
||||||
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 obj.toString(4);
|
|
||||||
}
|
|
||||||
|
|
||||||
private String generateDatapackJsonNether() {
|
|
||||||
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 obj.toString(4);
|
|
||||||
}
|
|
||||||
|
|
||||||
private String generateDatapackJsonEnd() {
|
|
||||||
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 obj.toString(4);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import java.io.DataInputStream;
|
|||||||
import java.io.DataOutputStream;
|
import java.io.DataOutputStream;
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.FilenameFilter;
|
import java.io.FilenameFilter;
|
||||||
|
import java.io.IOException;
|
||||||
import java.lang.reflect.Field;
|
import java.lang.reflect.Field;
|
||||||
import java.lang.reflect.Method;
|
import java.lang.reflect.Method;
|
||||||
import java.lang.reflect.Modifier;
|
import java.lang.reflect.Modifier;
|
||||||
@@ -13,13 +14,19 @@ import java.util.IdentityHashMap;
|
|||||||
import java.util.Iterator;
|
import java.util.Iterator;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
import java.util.Optional;
|
||||||
import java.util.Vector;
|
import java.util.Vector;
|
||||||
|
import java.util.concurrent.Executor;
|
||||||
import java.util.concurrent.atomic.AtomicInteger;
|
import java.util.concurrent.atomic.AtomicInteger;
|
||||||
|
|
||||||
import com.google.common.base.Preconditions;
|
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.datafixers.util.Pair;
|
||||||
|
import com.mojang.serialization.Codec;
|
||||||
import com.mojang.serialization.JsonOps;
|
import com.mojang.serialization.JsonOps;
|
||||||
import com.mojang.serialization.Lifecycle;
|
import com.mojang.serialization.Lifecycle;
|
||||||
|
import com.volmit.iris.engine.object.IrisDimension;
|
||||||
import com.volmit.iris.util.format.C;
|
import com.volmit.iris.util.format.C;
|
||||||
import com.volmit.iris.util.io.IO;
|
import com.volmit.iris.util.io.IO;
|
||||||
import it.unimi.dsi.fastutil.objects.Reference2IntMap;
|
import it.unimi.dsi.fastutil.objects.Reference2IntMap;
|
||||||
@@ -29,9 +36,14 @@ import net.bytebuddy.dynamic.loading.ClassReloadingStrategy;
|
|||||||
import net.bytebuddy.matcher.ElementMatchers;
|
import net.bytebuddy.matcher.ElementMatchers;
|
||||||
import net.minecraft.core.MappedRegistry;
|
import net.minecraft.core.MappedRegistry;
|
||||||
import net.minecraft.resources.ResourceKey;
|
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.util.GsonHelper;
|
||||||
import net.minecraft.world.level.Level;
|
import net.minecraft.world.level.Level;
|
||||||
import net.minecraft.world.level.dimension.DimensionType;
|
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.*;
|
||||||
import org.bukkit.block.Biome;
|
import org.bukkit.block.Biome;
|
||||||
import org.bukkit.block.data.BlockData;
|
import org.bukkit.block.data.BlockData;
|
||||||
@@ -45,6 +57,7 @@ import org.bukkit.craftbukkit.v1_19_R1.inventory.CraftItemStack;
|
|||||||
import org.bukkit.entity.Dolphin;
|
import org.bukkit.entity.Dolphin;
|
||||||
import org.bukkit.entity.Entity;
|
import org.bukkit.entity.Entity;
|
||||||
import org.bukkit.event.entity.CreatureSpawnEvent;
|
import org.bukkit.event.entity.CreatureSpawnEvent;
|
||||||
|
import org.bukkit.generator.BiomeProvider;
|
||||||
import org.bukkit.generator.ChunkGenerator;
|
import org.bukkit.generator.ChunkGenerator;
|
||||||
import org.bukkit.inventory.ItemStack;
|
import org.bukkit.inventory.ItemStack;
|
||||||
import org.jetbrains.annotations.NotNull;
|
import org.jetbrains.annotations.NotNull;
|
||||||
@@ -551,25 +564,34 @@ public class NMSBinding implements INMSBinding {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean loadDatapack(File folder) {
|
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 loadDatapack(File folder, boolean replace) {
|
||||||
var data = new File(folder, "iris/data");
|
var data = new File(folder, "iris/data");
|
||||||
if (!data.exists() || !data.isDirectory()) return false;
|
if (!data.exists() || !data.isDirectory()) return false;
|
||||||
FilenameFilter jsonFilter = (dir, name) -> new File(dir, name).isFile() && name.toLowerCase().endsWith(".json");
|
FilenameFilter jsonFilter = (dir, name) -> new File(dir, name).isFile() && name.toLowerCase().endsWith(".json");
|
||||||
|
|
||||||
var dimensionFolder = new File(data, "minecraft/dimension_type");
|
|
||||||
if (dimensionFolder.exists()) {
|
|
||||||
var files = dimensionFolder.listFiles(jsonFilter);
|
|
||||||
if (files != null) {
|
|
||||||
for (File file : files) {
|
|
||||||
try {
|
|
||||||
modifyDimension(file);
|
|
||||||
} catch (Throwable e) {
|
|
||||||
Iris.error("Unable to modify dimension!");
|
|
||||||
e.printStackTrace();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
var files = data.listFiles((dir, name) -> new File(dir, name).isDirectory());
|
var files = data.listFiles((dir, name) -> new File(dir, name).isDirectory());
|
||||||
if (files == null) return false;
|
if (files == null) return false;
|
||||||
for (File file : files) {
|
for (File file : files) {
|
||||||
@@ -578,8 +600,26 @@ public class NMSBinding implements INMSBinding {
|
|||||||
var biomeFiles = biome.listFiles(jsonFilter);
|
var biomeFiles = biome.listFiles(jsonFilter);
|
||||||
if (biomeFiles == null) continue;
|
if (biomeFiles == null) continue;
|
||||||
for (File biomeFile : biomeFiles) {
|
for (File biomeFile : biomeFiles) {
|
||||||
|
String json = null;
|
||||||
|
int tries = 10;
|
||||||
|
while (json == null && tries-- > 0) {
|
||||||
|
try {
|
||||||
|
json = IO.readAll(biomeFile);
|
||||||
|
} catch (IOException e) {
|
||||||
|
Iris.error("Failed to read biome " + file.getName() + ":" + biomeFile.getName() + " tries left: " + tries);
|
||||||
|
if (tries == 0) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
Thread.sleep(100);
|
||||||
|
} catch (InterruptedException ignored) {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (json == null) continue;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
registerBiome(file.getName(), biomeFile);
|
var value = decode(net.minecraft.world.level.biome.Biome.CODEC, json).map(Holder::value).orElse(null);
|
||||||
|
register(Registry.BIOME_REGISTRY, from(file.getName(), biomeFile), value, replace);
|
||||||
} catch (Throwable e) {
|
} catch (Throwable e) {
|
||||||
Iris.error("Failed to register biome " + file.getName() + ":" + biomeFile.getName());
|
Iris.error("Failed to register biome " + file.getName() + ":" + biomeFile.getName());
|
||||||
e.printStackTrace();
|
e.printStackTrace();
|
||||||
@@ -594,75 +634,82 @@ public class NMSBinding implements INMSBinding {
|
|||||||
return new ResourceLocation(namespace, name.substring(0, name.lastIndexOf('.')));
|
return new ResourceLocation(namespace, name.substring(0, name.lastIndexOf('.')));
|
||||||
}
|
}
|
||||||
|
|
||||||
private void registerBiome(String namespace, File file) throws Throwable {
|
private <T> Optional<T> decode(Codec<T> codec, String json) {
|
||||||
var rawRegistry = registry().registry(Registry.BIOME_REGISTRY).orElse(null);
|
return codec.decode(JsonOps.INSTANCE, GsonHelper.parse(json)).get().left().map(Pair::getFirst);
|
||||||
var key = ResourceKey.create(Registry.BIOME_REGISTRY, from(namespace, file));
|
}
|
||||||
if (!(rawRegistry instanceof MappedRegistry<net.minecraft.world.level.biome.Biome> registry))
|
|
||||||
throw new IllegalStateException("The Biome Registry is not a mapped Registry!");
|
|
||||||
if (registry.containsKey(key)) return;
|
|
||||||
Field field = getField(MappedRegistry.class, boolean.class);
|
|
||||||
field.setAccessible(true);
|
|
||||||
boolean frozen = field.getBoolean(registry);
|
|
||||||
field.setBoolean(registry, false);
|
|
||||||
Field holdersField = null;
|
|
||||||
boolean holders = false;
|
|
||||||
for (Field f : MappedRegistry.class.getDeclaredFields()) {
|
|
||||||
if (!f.getGenericType().getTypeName().startsWith("java.util.Map<T, "))
|
|
||||||
continue;
|
|
||||||
holdersField = f;
|
|
||||||
}
|
|
||||||
if (holdersField != null) {
|
|
||||||
holdersField.setAccessible(true);
|
|
||||||
holders = holdersField.get(registry) == null;
|
|
||||||
if (holders) holdersField.set(registry, new IdentityHashMap<>());
|
|
||||||
}
|
|
||||||
|
|
||||||
|
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 {
|
try {
|
||||||
var biome = net.minecraft.world.level.biome.Biome.CODEC.decode(JsonOps.INSTANCE, GsonHelper.parse(IO.readAll(file)))
|
if (registry.containsKey(key)) {
|
||||||
.get().left().map(Pair::getFirst).map(Holder::value).orElse(null);
|
if (!replace) return false;
|
||||||
if (biome == null)
|
return replace(registryKey, location, value);
|
||||||
throw new IllegalStateException("Failed to decode biome " + file.getName());
|
|
||||||
|
|
||||||
registry.createIntrusiveHolder(biome);
|
|
||||||
registry.register(key, biome, Lifecycle.stable());
|
|
||||||
} finally {
|
|
||||||
field.setBoolean(registry, frozen);
|
|
||||||
if (holders) {
|
|
||||||
holdersField.set(registry, null);
|
|
||||||
}
|
}
|
||||||
|
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")
|
@SuppressWarnings("unchecked")
|
||||||
private void modifyDimension(File file) throws Throwable {
|
private <T> boolean replace(ResourceKey<Registry<T>> registryKey, ResourceLocation location, T value) {
|
||||||
var key = ResourceKey.create(Registry.DIMENSION_TYPE_REGISTRY, from("minecraft", file));
|
Preconditions.checkArgument(registryKey != null, "The registryKey cannot be null!");
|
||||||
var rawRegistry = registry().registry(Registry.DIMENSION_TYPE_REGISTRY).orElse(null);
|
Preconditions.checkArgument(location != null, "The location cannot be null!");
|
||||||
if (!(rawRegistry instanceof MappedRegistry<DimensionType> registry))
|
Preconditions.checkArgument(value != null, "The value cannot be null!");
|
||||||
throw new IllegalStateException("The Dimension Registry is not a mapped Registry!");
|
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);
|
||||||
|
|
||||||
var holder = registry.getHolder(key).orElseThrow(() -> new IllegalStateException("Unknown dimension type: " + key));
|
valueField.set(holder, value);
|
||||||
var oldValue = holder.value();
|
toId.put(value, toId.removeInt(oldValue));
|
||||||
Field valueField = getField(Holder.Reference.class, "T");
|
byValue.put(value, byValue.remove(oldValue));
|
||||||
valueField.setAccessible(true);
|
lifecycles.put(value, lifecycles.remove(oldValue));
|
||||||
Field toIdField = getField(MappedRegistry.class, buildType(Reference2IntMap.class, "T"));
|
return true;
|
||||||
toIdField.setAccessible(true);
|
} catch (Throwable e) {
|
||||||
Field byValueField = getField(MappedRegistry.class, buildType(Map.class, "T", buildType(Holder.Reference.class, "T")));
|
throw new IllegalStateException(e);
|
||||||
byValueField.setAccessible(true);
|
}
|
||||||
Field lifecyclesField = getField(MappedRegistry.class, buildType(Map.class, "T", Lifecycle.class.getName()));
|
}
|
||||||
lifecyclesField.setAccessible(true);
|
|
||||||
var toId = (Reference2IntMap<DimensionType>) toIdField.get(registry);
|
|
||||||
var byValue = (Map<DimensionType, Holder.Reference<DimensionType>>) byValueField.get(registry);
|
|
||||||
var lifecycles = (Map<DimensionType, Lifecycle>) lifecyclesField.get(registry);
|
|
||||||
|
|
||||||
var newValue = DimensionType.CODEC.decode(JsonOps.INSTANCE, GsonHelper.parse(IO.readAll(file)))
|
private <T> MappedRegistry<T> registry(ResourceKey<Registry<T>> registryKey) {
|
||||||
.get().left().map(Pair::getFirst).map(Holder::value).orElse(null);
|
var rawRegistry = registry().registry(registryKey).orElse(null);
|
||||||
if (newValue == null)
|
if (!(rawRegistry instanceof MappedRegistry<T> registry))
|
||||||
throw new IllegalArgumentException("Failed to parse dimension type " + key.location() + " from " + file);
|
throw new IllegalStateException("The Registry is not a mapped Registry!");
|
||||||
|
return registry;
|
||||||
valueField.set(holder, newValue);
|
|
||||||
toId.put(newValue, toId.removeInt(oldValue));
|
|
||||||
byValue.put(newValue, byValue.remove(oldValue));
|
|
||||||
lifecycles.put(newValue, lifecycles.remove(oldValue));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static String buildType(Class<?> clazz, String... parameterTypes) {
|
private static String buildType(Class<?> clazz, String... parameterTypes) {
|
||||||
@@ -715,6 +762,13 @@ public class NMSBinding implements INMSBinding {
|
|||||||
.visit(Advice.to(WorldCreatorAdvice.class).on(ElementMatchers.isConstructor().and(ElementMatchers.takesArguments(String.class))))
|
.visit(Advice.to(WorldCreatorAdvice.class).on(ElementMatchers.isConstructor().and(ElementMatchers.takesArguments(String.class))))
|
||||||
.make()
|
.make()
|
||||||
.load(WorldCreator.class.getClassLoader(), ClassReloadingStrategy.fromInstalledAgent());
|
.load(WorldCreator.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!");
|
Iris.info("Injected Bukkit Successfully!");
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
Iris.info(C.RED + "Failed to Inject Bukkit!");
|
Iris.info(C.RED + "Failed to Inject Bukkit!");
|
||||||
@@ -724,6 +778,22 @@ public class NMSBinding implements INMSBinding {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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 WorldCreatorAdvice {
|
private static class WorldCreatorAdvice {
|
||||||
@Advice.OnMethodEnter
|
@Advice.OnMethodEnter
|
||||||
static void enter(@Advice.Argument(0) String name) {
|
static void enter(@Advice.Argument(0) String name) {
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import java.io.DataInputStream;
|
|||||||
import java.io.DataOutputStream;
|
import java.io.DataOutputStream;
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.FilenameFilter;
|
import java.io.FilenameFilter;
|
||||||
|
import java.io.IOException;
|
||||||
import java.lang.reflect.Field;
|
import java.lang.reflect.Field;
|
||||||
import java.lang.reflect.Method;
|
import java.lang.reflect.Method;
|
||||||
import java.lang.reflect.Modifier;
|
import java.lang.reflect.Modifier;
|
||||||
@@ -13,13 +14,19 @@ import java.util.IdentityHashMap;
|
|||||||
import java.util.Iterator;
|
import java.util.Iterator;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
import java.util.Optional;
|
||||||
import java.util.Vector;
|
import java.util.Vector;
|
||||||
|
import java.util.concurrent.Executor;
|
||||||
import java.util.concurrent.atomic.AtomicInteger;
|
import java.util.concurrent.atomic.AtomicInteger;
|
||||||
|
|
||||||
import com.google.common.base.Preconditions;
|
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.datafixers.util.Pair;
|
||||||
|
import com.mojang.serialization.Codec;
|
||||||
import com.mojang.serialization.JsonOps;
|
import com.mojang.serialization.JsonOps;
|
||||||
import com.mojang.serialization.Lifecycle;
|
import com.mojang.serialization.Lifecycle;
|
||||||
|
import com.volmit.iris.engine.object.IrisDimension;
|
||||||
import com.volmit.iris.util.format.C;
|
import com.volmit.iris.util.format.C;
|
||||||
import com.volmit.iris.util.io.IO;
|
import com.volmit.iris.util.io.IO;
|
||||||
import it.unimi.dsi.fastutil.objects.Reference2IntMap;
|
import it.unimi.dsi.fastutil.objects.Reference2IntMap;
|
||||||
@@ -29,9 +36,14 @@ import net.bytebuddy.dynamic.loading.ClassReloadingStrategy;
|
|||||||
import net.bytebuddy.matcher.ElementMatchers;
|
import net.bytebuddy.matcher.ElementMatchers;
|
||||||
import net.minecraft.core.MappedRegistry;
|
import net.minecraft.core.MappedRegistry;
|
||||||
import net.minecraft.resources.ResourceKey;
|
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.util.GsonHelper;
|
||||||
import net.minecraft.world.level.Level;
|
import net.minecraft.world.level.Level;
|
||||||
import net.minecraft.world.level.dimension.DimensionType;
|
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.*;
|
||||||
import org.bukkit.block.Biome;
|
import org.bukkit.block.Biome;
|
||||||
import org.bukkit.block.data.BlockData;
|
import org.bukkit.block.data.BlockData;
|
||||||
@@ -45,6 +57,7 @@ import org.bukkit.craftbukkit.v1_19_R2.inventory.CraftItemStack;
|
|||||||
import org.bukkit.entity.Dolphin;
|
import org.bukkit.entity.Dolphin;
|
||||||
import org.bukkit.entity.Entity;
|
import org.bukkit.entity.Entity;
|
||||||
import org.bukkit.event.entity.CreatureSpawnEvent;
|
import org.bukkit.event.entity.CreatureSpawnEvent;
|
||||||
|
import org.bukkit.generator.BiomeProvider;
|
||||||
import org.bukkit.generator.ChunkGenerator;
|
import org.bukkit.generator.ChunkGenerator;
|
||||||
import org.bukkit.inventory.ItemStack;
|
import org.bukkit.inventory.ItemStack;
|
||||||
import org.jetbrains.annotations.NotNull;
|
import org.jetbrains.annotations.NotNull;
|
||||||
@@ -553,25 +566,34 @@ public class NMSBinding implements INMSBinding {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean loadDatapack(File folder) {
|
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 loadDatapack(File folder, boolean replace) {
|
||||||
var data = new File(folder, "iris/data");
|
var data = new File(folder, "iris/data");
|
||||||
if (!data.exists() || !data.isDirectory()) return false;
|
if (!data.exists() || !data.isDirectory()) return false;
|
||||||
FilenameFilter jsonFilter = (dir, name) -> new File(dir, name).isFile() && name.toLowerCase().endsWith(".json");
|
FilenameFilter jsonFilter = (dir, name) -> new File(dir, name).isFile() && name.toLowerCase().endsWith(".json");
|
||||||
|
|
||||||
var dimensionFolder = new File(data, "minecraft/dimension_type");
|
|
||||||
if (dimensionFolder.exists()) {
|
|
||||||
var files = dimensionFolder.listFiles(jsonFilter);
|
|
||||||
if (files != null) {
|
|
||||||
for (File file : files) {
|
|
||||||
try {
|
|
||||||
modifyDimension(file);
|
|
||||||
} catch (Throwable e) {
|
|
||||||
Iris.error("Unable to modify dimension!");
|
|
||||||
e.printStackTrace();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
var files = data.listFiles((dir, name) -> new File(dir, name).isDirectory());
|
var files = data.listFiles((dir, name) -> new File(dir, name).isDirectory());
|
||||||
if (files == null) return false;
|
if (files == null) return false;
|
||||||
for (File file : files) {
|
for (File file : files) {
|
||||||
@@ -580,8 +602,26 @@ public class NMSBinding implements INMSBinding {
|
|||||||
var biomeFiles = biome.listFiles(jsonFilter);
|
var biomeFiles = biome.listFiles(jsonFilter);
|
||||||
if (biomeFiles == null) continue;
|
if (biomeFiles == null) continue;
|
||||||
for (File biomeFile : biomeFiles) {
|
for (File biomeFile : biomeFiles) {
|
||||||
|
String json = null;
|
||||||
|
int tries = 10;
|
||||||
|
while (json == null && tries-- > 0) {
|
||||||
|
try {
|
||||||
|
json = IO.readAll(biomeFile);
|
||||||
|
} catch (IOException e) {
|
||||||
|
Iris.error("Failed to read biome " + file.getName() + ":" + biomeFile.getName() + " tries left: " + tries);
|
||||||
|
if (tries == 0) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
Thread.sleep(100);
|
||||||
|
} catch (InterruptedException ignored) {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (json == null) continue;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
registerBiome(file.getName(), biomeFile);
|
var value = decode(net.minecraft.world.level.biome.Biome.CODEC, json).map(Holder::value).orElse(null);
|
||||||
|
register(Registries.BIOME, from(file.getName(), biomeFile), value, replace);
|
||||||
} catch (Throwable e) {
|
} catch (Throwable e) {
|
||||||
Iris.error("Failed to register biome " + file.getName() + ":" + biomeFile.getName());
|
Iris.error("Failed to register biome " + file.getName() + ":" + biomeFile.getName());
|
||||||
e.printStackTrace();
|
e.printStackTrace();
|
||||||
@@ -596,75 +636,82 @@ public class NMSBinding implements INMSBinding {
|
|||||||
return new ResourceLocation(namespace, name.substring(0, name.lastIndexOf('.')));
|
return new ResourceLocation(namespace, name.substring(0, name.lastIndexOf('.')));
|
||||||
}
|
}
|
||||||
|
|
||||||
private void registerBiome(String namespace, File file) throws Throwable {
|
private <T> Optional<T> decode(Codec<T> codec, String json) {
|
||||||
var rawRegistry = registry().registry(Registries.BIOME).orElse(null);
|
return codec.decode(JsonOps.INSTANCE, GsonHelper.parse(json)).get().left().map(Pair::getFirst);
|
||||||
var key = ResourceKey.create(Registries.BIOME, from(namespace, file));
|
}
|
||||||
if (!(rawRegistry instanceof MappedRegistry<net.minecraft.world.level.biome.Biome> registry))
|
|
||||||
throw new IllegalStateException("The Biome Registry is not a mapped Registry!");
|
|
||||||
if (registry.containsKey(key)) return;
|
|
||||||
Field field = getField(MappedRegistry.class, boolean.class);
|
|
||||||
field.setAccessible(true);
|
|
||||||
boolean frozen = field.getBoolean(registry);
|
|
||||||
field.setBoolean(registry, false);
|
|
||||||
Field holdersField = null;
|
|
||||||
boolean holders = false;
|
|
||||||
for (Field f : MappedRegistry.class.getDeclaredFields()) {
|
|
||||||
if (!f.getGenericType().getTypeName().startsWith("java.util.Map<T, "))
|
|
||||||
continue;
|
|
||||||
holdersField = f;
|
|
||||||
}
|
|
||||||
if (holdersField != null) {
|
|
||||||
holdersField.setAccessible(true);
|
|
||||||
holders = holdersField.get(registry) == null;
|
|
||||||
if (holders) holdersField.set(registry, new IdentityHashMap<>());
|
|
||||||
}
|
|
||||||
|
|
||||||
|
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 {
|
try {
|
||||||
var biome = net.minecraft.world.level.biome.Biome.CODEC.decode(JsonOps.INSTANCE, GsonHelper.parse(IO.readAll(file)))
|
if (registry.containsKey(key)) {
|
||||||
.get().left().map(Pair::getFirst).map(Holder::value).orElse(null);
|
if (!replace) return false;
|
||||||
if (biome == null)
|
return replace(registryKey, location, value);
|
||||||
throw new IllegalStateException("Failed to decode biome " + file.getName());
|
|
||||||
|
|
||||||
registry.createIntrusiveHolder(biome);
|
|
||||||
registry.register(key, biome, Lifecycle.stable());
|
|
||||||
} finally {
|
|
||||||
field.setBoolean(registry, frozen);
|
|
||||||
if (holders) {
|
|
||||||
holdersField.set(registry, null);
|
|
||||||
}
|
}
|
||||||
|
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")
|
@SuppressWarnings("unchecked")
|
||||||
private void modifyDimension(File file) throws Throwable {
|
private <T> boolean replace(ResourceKey<Registry<T>> registryKey, ResourceLocation location, T value) {
|
||||||
var key = ResourceKey.create(Registries.DIMENSION_TYPE, from("minecraft", file));
|
Preconditions.checkArgument(registryKey != null, "The registryKey cannot be null!");
|
||||||
var rawRegistry = registry().registry(Registries.DIMENSION_TYPE).orElse(null);
|
Preconditions.checkArgument(location != null, "The location cannot be null!");
|
||||||
if (!(rawRegistry instanceof MappedRegistry<DimensionType> registry))
|
Preconditions.checkArgument(value != null, "The value cannot be null!");
|
||||||
throw new IllegalStateException("The Dimension Registry is not a mapped Registry!");
|
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);
|
||||||
|
|
||||||
var holder = registry.getHolder(key).orElseThrow(() -> new IllegalStateException("Unknown dimension type: " + key));
|
valueField.set(holder, value);
|
||||||
var oldValue = holder.value();
|
toId.put(value, toId.removeInt(oldValue));
|
||||||
Field valueField = getField(Holder.Reference.class, "T");
|
byValue.put(value, byValue.remove(oldValue));
|
||||||
valueField.setAccessible(true);
|
lifecycles.put(value, lifecycles.remove(oldValue));
|
||||||
Field toIdField = getField(MappedRegistry.class, buildType(Reference2IntMap.class, "T"));
|
return true;
|
||||||
toIdField.setAccessible(true);
|
} catch (Throwable e) {
|
||||||
Field byValueField = getField(MappedRegistry.class, buildType(Map.class, "T", buildType(Holder.Reference.class, "T")));
|
throw new IllegalStateException(e);
|
||||||
byValueField.setAccessible(true);
|
}
|
||||||
Field lifecyclesField = getField(MappedRegistry.class, buildType(Map.class, "T", Lifecycle.class.getName()));
|
}
|
||||||
lifecyclesField.setAccessible(true);
|
|
||||||
var toId = (Reference2IntMap<DimensionType>) toIdField.get(registry);
|
|
||||||
var byValue = (Map<DimensionType, Holder.Reference<DimensionType>>) byValueField.get(registry);
|
|
||||||
var lifecycles = (Map<DimensionType, Lifecycle>) lifecyclesField.get(registry);
|
|
||||||
|
|
||||||
var newValue = DimensionType.CODEC.decode(JsonOps.INSTANCE, GsonHelper.parse(IO.readAll(file)))
|
private <T> MappedRegistry<T> registry(ResourceKey<Registry<T>> registryKey) {
|
||||||
.get().left().map(Pair::getFirst).map(Holder::value).orElse(null);
|
var rawRegistry = registry().registry(registryKey).orElse(null);
|
||||||
if (newValue == null)
|
if (!(rawRegistry instanceof MappedRegistry<T> registry))
|
||||||
throw new IllegalArgumentException("Failed to parse dimension type " + key.location() + " from " + file);
|
throw new IllegalStateException("The Registry is not a mapped Registry!");
|
||||||
|
return registry;
|
||||||
valueField.set(holder, newValue);
|
|
||||||
toId.put(newValue, toId.removeInt(oldValue));
|
|
||||||
byValue.put(newValue, byValue.remove(oldValue));
|
|
||||||
lifecycles.put(newValue, lifecycles.remove(oldValue));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static String buildType(Class<?> clazz, String... parameterTypes) {
|
private static String buildType(Class<?> clazz, String... parameterTypes) {
|
||||||
@@ -716,6 +763,13 @@ public class NMSBinding implements INMSBinding {
|
|||||||
.visit(Advice.to(WorldCreatorAdvice.class).on(ElementMatchers.isConstructor().and(ElementMatchers.takesArguments(String.class))))
|
.visit(Advice.to(WorldCreatorAdvice.class).on(ElementMatchers.isConstructor().and(ElementMatchers.takesArguments(String.class))))
|
||||||
.make()
|
.make()
|
||||||
.load(WorldCreator.class.getClassLoader(), ClassReloadingStrategy.fromInstalledAgent());
|
.load(WorldCreator.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!");
|
Iris.info("Injected Bukkit Successfully!");
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
Iris.info(C.RED + "Failed to Inject Bukkit!");
|
Iris.info(C.RED + "Failed to Inject Bukkit!");
|
||||||
@@ -725,6 +779,21 @@ public class NMSBinding implements INMSBinding {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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 WorldCreatorAdvice {
|
private static class WorldCreatorAdvice {
|
||||||
@Advice.OnMethodEnter
|
@Advice.OnMethodEnter
|
||||||
static void enter(@Advice.Argument(0) String name) {
|
static void enter(@Advice.Argument(0) String name) {
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import java.io.DataInputStream;
|
|||||||
import java.io.DataOutputStream;
|
import java.io.DataOutputStream;
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.FilenameFilter;
|
import java.io.FilenameFilter;
|
||||||
|
import java.io.IOException;
|
||||||
import java.lang.reflect.Field;
|
import java.lang.reflect.Field;
|
||||||
import java.lang.reflect.Method;
|
import java.lang.reflect.Method;
|
||||||
import java.lang.reflect.Modifier;
|
import java.lang.reflect.Modifier;
|
||||||
@@ -13,13 +14,19 @@ import java.util.IdentityHashMap;
|
|||||||
import java.util.Iterator;
|
import java.util.Iterator;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
import java.util.Optional;
|
||||||
import java.util.Vector;
|
import java.util.Vector;
|
||||||
|
import java.util.concurrent.Executor;
|
||||||
import java.util.concurrent.atomic.AtomicInteger;
|
import java.util.concurrent.atomic.AtomicInteger;
|
||||||
|
|
||||||
import com.google.common.base.Preconditions;
|
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.datafixers.util.Pair;
|
||||||
|
import com.mojang.serialization.Codec;
|
||||||
import com.mojang.serialization.JsonOps;
|
import com.mojang.serialization.JsonOps;
|
||||||
import com.mojang.serialization.Lifecycle;
|
import com.mojang.serialization.Lifecycle;
|
||||||
|
import com.volmit.iris.engine.object.IrisDimension;
|
||||||
import com.volmit.iris.util.format.C;
|
import com.volmit.iris.util.format.C;
|
||||||
import com.volmit.iris.util.io.IO;
|
import com.volmit.iris.util.io.IO;
|
||||||
import it.unimi.dsi.fastutil.objects.Reference2IntMap;
|
import it.unimi.dsi.fastutil.objects.Reference2IntMap;
|
||||||
@@ -29,9 +36,14 @@ import net.bytebuddy.dynamic.loading.ClassReloadingStrategy;
|
|||||||
import net.bytebuddy.matcher.ElementMatchers;
|
import net.bytebuddy.matcher.ElementMatchers;
|
||||||
import net.minecraft.core.MappedRegistry;
|
import net.minecraft.core.MappedRegistry;
|
||||||
import net.minecraft.resources.ResourceKey;
|
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.util.GsonHelper;
|
||||||
import net.minecraft.world.level.Level;
|
import net.minecraft.world.level.Level;
|
||||||
import net.minecraft.world.level.dimension.DimensionType;
|
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.*;
|
||||||
import org.bukkit.block.Biome;
|
import org.bukkit.block.Biome;
|
||||||
import org.bukkit.block.data.BlockData;
|
import org.bukkit.block.data.BlockData;
|
||||||
@@ -45,6 +57,7 @@ import org.bukkit.craftbukkit.v1_19_R3.inventory.CraftItemStack;
|
|||||||
import org.bukkit.entity.Dolphin;
|
import org.bukkit.entity.Dolphin;
|
||||||
import org.bukkit.entity.Entity;
|
import org.bukkit.entity.Entity;
|
||||||
import org.bukkit.event.entity.CreatureSpawnEvent;
|
import org.bukkit.event.entity.CreatureSpawnEvent;
|
||||||
|
import org.bukkit.generator.BiomeProvider;
|
||||||
import org.bukkit.generator.ChunkGenerator;
|
import org.bukkit.generator.ChunkGenerator;
|
||||||
import org.bukkit.inventory.ItemStack;
|
import org.bukkit.inventory.ItemStack;
|
||||||
import org.jetbrains.annotations.NotNull;
|
import org.jetbrains.annotations.NotNull;
|
||||||
@@ -557,25 +570,34 @@ public class NMSBinding implements INMSBinding {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean loadDatapack(File folder) {
|
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 loadDatapack(File folder, boolean replace) {
|
||||||
var data = new File(folder, "iris/data");
|
var data = new File(folder, "iris/data");
|
||||||
if (!data.exists() || !data.isDirectory()) return false;
|
if (!data.exists() || !data.isDirectory()) return false;
|
||||||
FilenameFilter jsonFilter = (dir, name) -> new File(dir, name).isFile() && name.toLowerCase().endsWith(".json");
|
FilenameFilter jsonFilter = (dir, name) -> new File(dir, name).isFile() && name.toLowerCase().endsWith(".json");
|
||||||
|
|
||||||
var dimensionFolder = new File(data, "minecraft/dimension_type");
|
|
||||||
if (dimensionFolder.exists()) {
|
|
||||||
var files = dimensionFolder.listFiles(jsonFilter);
|
|
||||||
if (files != null) {
|
|
||||||
for (File file : files) {
|
|
||||||
try {
|
|
||||||
modifyDimension(file);
|
|
||||||
} catch (Throwable e) {
|
|
||||||
Iris.error("Unable to modify dimension!");
|
|
||||||
e.printStackTrace();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
var files = data.listFiles((dir, name) -> new File(dir, name).isDirectory());
|
var files = data.listFiles((dir, name) -> new File(dir, name).isDirectory());
|
||||||
if (files == null) return false;
|
if (files == null) return false;
|
||||||
for (File file : files) {
|
for (File file : files) {
|
||||||
@@ -584,8 +606,26 @@ public class NMSBinding implements INMSBinding {
|
|||||||
var biomeFiles = biome.listFiles(jsonFilter);
|
var biomeFiles = biome.listFiles(jsonFilter);
|
||||||
if (biomeFiles == null) continue;
|
if (biomeFiles == null) continue;
|
||||||
for (File biomeFile : biomeFiles) {
|
for (File biomeFile : biomeFiles) {
|
||||||
|
String json = null;
|
||||||
|
int tries = 10;
|
||||||
|
while (json == null && tries-- > 0) {
|
||||||
|
try {
|
||||||
|
json = IO.readAll(biomeFile);
|
||||||
|
} catch (IOException e) {
|
||||||
|
Iris.error("Failed to read biome " + file.getName() + ":" + biomeFile.getName() + " tries left: " + tries);
|
||||||
|
if (tries == 0) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
Thread.sleep(100);
|
||||||
|
} catch (InterruptedException ignored) {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (json == null) continue;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
registerBiome(file.getName(), biomeFile);
|
var value = decode(net.minecraft.world.level.biome.Biome.CODEC, json).map(Holder::value).orElse(null);
|
||||||
|
register(Registries.BIOME, from(file.getName(), biomeFile), value, replace);
|
||||||
} catch (Throwable e) {
|
} catch (Throwable e) {
|
||||||
Iris.error("Failed to register biome " + file.getName() + ":" + biomeFile.getName());
|
Iris.error("Failed to register biome " + file.getName() + ":" + biomeFile.getName());
|
||||||
e.printStackTrace();
|
e.printStackTrace();
|
||||||
@@ -600,75 +640,82 @@ public class NMSBinding implements INMSBinding {
|
|||||||
return new ResourceLocation(namespace, name.substring(0, name.lastIndexOf('.')));
|
return new ResourceLocation(namespace, name.substring(0, name.lastIndexOf('.')));
|
||||||
}
|
}
|
||||||
|
|
||||||
private void registerBiome(String namespace, File file) throws Throwable {
|
private <T> Optional<T> decode(Codec<T> codec, String json) {
|
||||||
var rawRegistry = registry().registry(Registries.BIOME).orElse(null);
|
return codec.decode(JsonOps.INSTANCE, GsonHelper.parse(json)).get().left().map(Pair::getFirst);
|
||||||
var key = ResourceKey.create(Registries.BIOME, from(namespace, file));
|
}
|
||||||
if (!(rawRegistry instanceof MappedRegistry<net.minecraft.world.level.biome.Biome> registry))
|
|
||||||
throw new IllegalStateException("The Biome Registry is not a mapped Registry!");
|
|
||||||
if (registry.containsKey(key)) return;
|
|
||||||
Field field = getField(MappedRegistry.class, boolean.class);
|
|
||||||
field.setAccessible(true);
|
|
||||||
boolean frozen = field.getBoolean(registry);
|
|
||||||
field.setBoolean(registry, false);
|
|
||||||
Field holdersField = null;
|
|
||||||
boolean holders = false;
|
|
||||||
for (Field f : MappedRegistry.class.getDeclaredFields()) {
|
|
||||||
if (!f.getGenericType().getTypeName().startsWith("java.util.Map<T, "))
|
|
||||||
continue;
|
|
||||||
holdersField = f;
|
|
||||||
}
|
|
||||||
if (holdersField != null) {
|
|
||||||
holdersField.setAccessible(true);
|
|
||||||
holders = holdersField.get(registry) == null;
|
|
||||||
if (holders) holdersField.set(registry, new IdentityHashMap<>());
|
|
||||||
}
|
|
||||||
|
|
||||||
|
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 {
|
try {
|
||||||
var biome = net.minecraft.world.level.biome.Biome.CODEC.decode(JsonOps.INSTANCE, GsonHelper.parse(IO.readAll(file)))
|
if (registry.containsKey(key)) {
|
||||||
.get().left().map(Pair::getFirst).map(Holder::value).orElse(null);
|
if (!replace) return false;
|
||||||
if (biome == null)
|
return replace(registryKey, location, value);
|
||||||
throw new IllegalStateException("Failed to decode biome " + file.getName());
|
|
||||||
|
|
||||||
registry.createIntrusiveHolder(biome);
|
|
||||||
registry.register(key, biome, Lifecycle.stable());
|
|
||||||
} finally {
|
|
||||||
field.setBoolean(registry, frozen);
|
|
||||||
if (holders) {
|
|
||||||
holdersField.set(registry, null);
|
|
||||||
}
|
}
|
||||||
|
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")
|
@SuppressWarnings("unchecked")
|
||||||
private void modifyDimension(File file) throws Throwable {
|
private <T> boolean replace(ResourceKey<Registry<T>> registryKey, ResourceLocation location, T value) {
|
||||||
var key = ResourceKey.create(Registries.DIMENSION_TYPE, from("minecraft", file));
|
Preconditions.checkArgument(registryKey != null, "The registryKey cannot be null!");
|
||||||
var rawRegistry = registry().registry(Registries.DIMENSION_TYPE).orElse(null);
|
Preconditions.checkArgument(location != null, "The location cannot be null!");
|
||||||
if (!(rawRegistry instanceof MappedRegistry<DimensionType> registry))
|
Preconditions.checkArgument(value != null, "The value cannot be null!");
|
||||||
throw new IllegalStateException("The Dimension Registry is not a mapped Registry!");
|
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);
|
||||||
|
|
||||||
var holder = registry.getHolder(key).orElseThrow(() -> new IllegalStateException("Unknown dimension type: " + key));
|
valueField.set(holder, value);
|
||||||
var oldValue = holder.value();
|
toId.put(value, toId.removeInt(oldValue));
|
||||||
Field valueField = getField(Holder.Reference.class, "T");
|
byValue.put(value, byValue.remove(oldValue));
|
||||||
valueField.setAccessible(true);
|
lifecycles.put(value, lifecycles.remove(oldValue));
|
||||||
Field toIdField = getField(MappedRegistry.class, buildType(Reference2IntMap.class, "T"));
|
return true;
|
||||||
toIdField.setAccessible(true);
|
} catch (Throwable e) {
|
||||||
Field byValueField = getField(MappedRegistry.class, buildType(Map.class, "T", buildType(Holder.Reference.class, "T")));
|
throw new IllegalStateException(e);
|
||||||
byValueField.setAccessible(true);
|
}
|
||||||
Field lifecyclesField = getField(MappedRegistry.class, buildType(Map.class, "T", Lifecycle.class.getName()));
|
}
|
||||||
lifecyclesField.setAccessible(true);
|
|
||||||
var toId = (Reference2IntMap<DimensionType>) toIdField.get(registry);
|
|
||||||
var byValue = (Map<DimensionType, Holder.Reference<DimensionType>>) byValueField.get(registry);
|
|
||||||
var lifecycles = (Map<DimensionType, Lifecycle>) lifecyclesField.get(registry);
|
|
||||||
|
|
||||||
var newValue = DimensionType.CODEC.decode(JsonOps.INSTANCE, GsonHelper.parse(IO.readAll(file)))
|
private <T> MappedRegistry<T> registry(ResourceKey<Registry<T>> registryKey) {
|
||||||
.get().left().map(Pair::getFirst).map(Holder::value).orElse(null);
|
var rawRegistry = registry().registry(registryKey).orElse(null);
|
||||||
if (newValue == null)
|
if (!(rawRegistry instanceof MappedRegistry<T> registry))
|
||||||
throw new IllegalArgumentException("Failed to parse dimension type " + key.location() + " from " + file);
|
throw new IllegalStateException("The Registry is not a mapped Registry!");
|
||||||
|
return registry;
|
||||||
valueField.set(holder, newValue);
|
|
||||||
toId.put(newValue, toId.removeInt(oldValue));
|
|
||||||
byValue.put(newValue, byValue.remove(oldValue));
|
|
||||||
lifecycles.put(newValue, lifecycles.remove(oldValue));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static String buildType(Class<?> clazz, String... parameterTypes) {
|
private static String buildType(Class<?> clazz, String... parameterTypes) {
|
||||||
@@ -720,6 +767,13 @@ public class NMSBinding implements INMSBinding {
|
|||||||
.visit(Advice.to(WorldCreatorAdvice.class).on(ElementMatchers.isConstructor().and(ElementMatchers.takesArguments(String.class))))
|
.visit(Advice.to(WorldCreatorAdvice.class).on(ElementMatchers.isConstructor().and(ElementMatchers.takesArguments(String.class))))
|
||||||
.make()
|
.make()
|
||||||
.load(WorldCreator.class.getClassLoader(), ClassReloadingStrategy.fromInstalledAgent());
|
.load(WorldCreator.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!");
|
Iris.info("Injected Bukkit Successfully!");
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
Iris.info(C.RED + "Failed to Inject Bukkit!");
|
Iris.info(C.RED + "Failed to Inject Bukkit!");
|
||||||
@@ -729,6 +783,21 @@ public class NMSBinding implements INMSBinding {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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 WorldCreatorAdvice {
|
private static class WorldCreatorAdvice {
|
||||||
@Advice.OnMethodEnter
|
@Advice.OnMethodEnter
|
||||||
static void enter(@Advice.Argument(0) String name) {
|
static void enter(@Advice.Argument(0) String name) {
|
||||||
|
|||||||
@@ -1,14 +1,18 @@
|
|||||||
package com.volmit.iris.core.nms.v1_20_R1;
|
package com.volmit.iris.core.nms.v1_20_R1;
|
||||||
|
|
||||||
import com.google.common.base.Preconditions;
|
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.brigadier.exceptions.CommandSyntaxException;
|
||||||
import com.mojang.datafixers.util.Pair;
|
import com.mojang.datafixers.util.Pair;
|
||||||
|
import com.mojang.serialization.Codec;
|
||||||
import com.mojang.serialization.JsonOps;
|
import com.mojang.serialization.JsonOps;
|
||||||
import com.mojang.serialization.Lifecycle;
|
import com.mojang.serialization.Lifecycle;
|
||||||
import com.volmit.iris.Iris;
|
import com.volmit.iris.Iris;
|
||||||
import com.volmit.iris.core.nms.INMSBinding;
|
import com.volmit.iris.core.nms.INMSBinding;
|
||||||
import com.volmit.iris.engine.data.cache.AtomicCache;
|
import com.volmit.iris.engine.data.cache.AtomicCache;
|
||||||
import com.volmit.iris.engine.framework.Engine;
|
import com.volmit.iris.engine.framework.Engine;
|
||||||
|
import com.volmit.iris.engine.object.IrisDimension;
|
||||||
import com.volmit.iris.util.collection.KList;
|
import com.volmit.iris.util.collection.KList;
|
||||||
import com.volmit.iris.util.collection.KMap;
|
import com.volmit.iris.util.collection.KMap;
|
||||||
import com.volmit.iris.util.format.C;
|
import com.volmit.iris.util.format.C;
|
||||||
@@ -38,8 +42,11 @@ import net.minecraft.nbt.NbtIo;
|
|||||||
import net.minecraft.nbt.TagParser;
|
import net.minecraft.nbt.TagParser;
|
||||||
import net.minecraft.resources.ResourceKey;
|
import net.minecraft.resources.ResourceKey;
|
||||||
import net.minecraft.resources.ResourceLocation;
|
import net.minecraft.resources.ResourceLocation;
|
||||||
|
import net.minecraft.server.MinecraftServer;
|
||||||
import net.minecraft.server.level.ServerLevel;
|
import net.minecraft.server.level.ServerLevel;
|
||||||
|
import net.minecraft.server.level.progress.ChunkProgressListener;
|
||||||
import net.minecraft.util.GsonHelper;
|
import net.minecraft.util.GsonHelper;
|
||||||
|
import net.minecraft.world.RandomSequences;
|
||||||
import net.minecraft.world.entity.EntityDimensions;
|
import net.minecraft.world.entity.EntityDimensions;
|
||||||
import net.minecraft.world.level.Level;
|
import net.minecraft.world.level.Level;
|
||||||
import net.minecraft.world.level.biome.BiomeSource;
|
import net.minecraft.world.level.biome.BiomeSource;
|
||||||
@@ -50,6 +57,9 @@ import net.minecraft.world.level.chunk.ChunkAccess;
|
|||||||
import net.minecraft.world.level.chunk.ChunkStatus;
|
import net.minecraft.world.level.chunk.ChunkStatus;
|
||||||
import net.minecraft.world.level.chunk.LevelChunk;
|
import net.minecraft.world.level.chunk.LevelChunk;
|
||||||
import net.minecraft.world.level.dimension.DimensionType;
|
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.*;
|
||||||
import org.bukkit.block.Biome;
|
import org.bukkit.block.Biome;
|
||||||
import org.bukkit.block.data.BlockData;
|
import org.bukkit.block.data.BlockData;
|
||||||
@@ -66,6 +76,7 @@ import org.bukkit.entity.Entity;
|
|||||||
import org.bukkit.entity.EntityType;
|
import org.bukkit.entity.EntityType;
|
||||||
import org.bukkit.event.entity.CreatureSpawnEvent;
|
import org.bukkit.event.entity.CreatureSpawnEvent;
|
||||||
import org.bukkit.entity.EntityType;
|
import org.bukkit.entity.EntityType;
|
||||||
|
import org.bukkit.generator.BiomeProvider;
|
||||||
import org.bukkit.generator.ChunkGenerator;
|
import org.bukkit.generator.ChunkGenerator;
|
||||||
import org.bukkit.inventory.ItemStack;
|
import org.bukkit.inventory.ItemStack;
|
||||||
import org.jetbrains.annotations.NotNull;
|
import org.jetbrains.annotations.NotNull;
|
||||||
@@ -77,6 +88,7 @@ import java.io.DataInputStream;
|
|||||||
import java.io.DataOutputStream;
|
import java.io.DataOutputStream;
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.FilenameFilter;
|
import java.io.FilenameFilter;
|
||||||
|
import java.io.IOException;
|
||||||
import java.lang.reflect.Field;
|
import java.lang.reflect.Field;
|
||||||
import java.lang.reflect.Method;
|
import java.lang.reflect.Method;
|
||||||
import java.lang.reflect.Modifier;
|
import java.lang.reflect.Modifier;
|
||||||
@@ -84,7 +96,9 @@ import java.util.IdentityHashMap;
|
|||||||
import java.util.Iterator;
|
import java.util.Iterator;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
import java.util.Optional;
|
||||||
import java.util.Vector;
|
import java.util.Vector;
|
||||||
|
import java.util.concurrent.Executor;
|
||||||
import java.util.concurrent.atomic.AtomicInteger;
|
import java.util.concurrent.atomic.AtomicInteger;
|
||||||
|
|
||||||
public class NMSBinding implements INMSBinding {
|
public class NMSBinding implements INMSBinding {
|
||||||
@@ -543,25 +557,34 @@ public class NMSBinding implements INMSBinding {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean loadDatapack(File folder) {
|
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 loadDatapack(File folder, boolean replace) {
|
||||||
var data = new File(folder, "iris/data");
|
var data = new File(folder, "iris/data");
|
||||||
if (!data.exists() || !data.isDirectory()) return false;
|
if (!data.exists() || !data.isDirectory()) return false;
|
||||||
FilenameFilter jsonFilter = (dir, name) -> new File(dir, name).isFile() && name.toLowerCase().endsWith(".json");
|
FilenameFilter jsonFilter = (dir, name) -> new File(dir, name).isFile() && name.toLowerCase().endsWith(".json");
|
||||||
|
|
||||||
var dimensionFolder = new File(data, "minecraft/dimension_type");
|
|
||||||
if (dimensionFolder.exists()) {
|
|
||||||
var files = dimensionFolder.listFiles(jsonFilter);
|
|
||||||
if (files != null) {
|
|
||||||
for (File file : files) {
|
|
||||||
try {
|
|
||||||
modifyDimension(file);
|
|
||||||
} catch (Throwable e) {
|
|
||||||
Iris.error("Unable to modify dimension!");
|
|
||||||
e.printStackTrace();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
var files = data.listFiles((dir, name) -> new File(dir, name).isDirectory());
|
var files = data.listFiles((dir, name) -> new File(dir, name).isDirectory());
|
||||||
if (files == null) return false;
|
if (files == null) return false;
|
||||||
for (File file : files) {
|
for (File file : files) {
|
||||||
@@ -570,8 +593,26 @@ public class NMSBinding implements INMSBinding {
|
|||||||
var biomeFiles = biome.listFiles(jsonFilter);
|
var biomeFiles = biome.listFiles(jsonFilter);
|
||||||
if (biomeFiles == null) continue;
|
if (biomeFiles == null) continue;
|
||||||
for (File biomeFile : biomeFiles) {
|
for (File biomeFile : biomeFiles) {
|
||||||
|
String json = null;
|
||||||
|
int tries = 10;
|
||||||
|
while (json == null && tries-- > 0) {
|
||||||
|
try {
|
||||||
|
json = IO.readAll(biomeFile);
|
||||||
|
} catch (IOException e) {
|
||||||
|
Iris.error("Failed to read biome " + file.getName() + ":" + biomeFile.getName() + " tries left: " + tries);
|
||||||
|
if (tries == 0) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
Thread.sleep(100);
|
||||||
|
} catch (InterruptedException ignored) {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (json == null) continue;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
registerBiome(file.getName(), biomeFile);
|
var value = decode(net.minecraft.world.level.biome.Biome.CODEC, json).map(Holder::value).orElse(null);
|
||||||
|
register(Registries.BIOME, from(file.getName(), biomeFile), value, replace);
|
||||||
} catch (Throwable e) {
|
} catch (Throwable e) {
|
||||||
Iris.error("Failed to register biome " + file.getName() + ":" + biomeFile.getName());
|
Iris.error("Failed to register biome " + file.getName() + ":" + biomeFile.getName());
|
||||||
e.printStackTrace();
|
e.printStackTrace();
|
||||||
@@ -586,75 +627,82 @@ public class NMSBinding implements INMSBinding {
|
|||||||
return new ResourceLocation(namespace, name.substring(0, name.lastIndexOf('.')));
|
return new ResourceLocation(namespace, name.substring(0, name.lastIndexOf('.')));
|
||||||
}
|
}
|
||||||
|
|
||||||
private void registerBiome(String namespace, File file) throws Throwable {
|
private <T> Optional<T> decode(Codec<T> codec, String json) {
|
||||||
var rawRegistry = registry().registry(Registries.BIOME).orElse(null);
|
return codec.decode(JsonOps.INSTANCE, GsonHelper.parse(json)).get().left().map(Pair::getFirst);
|
||||||
var key = ResourceKey.create(Registries.BIOME, from(namespace, file));
|
}
|
||||||
if (!(rawRegistry instanceof MappedRegistry<net.minecraft.world.level.biome.Biome> registry))
|
|
||||||
throw new IllegalStateException("The Biome Registry is not a mapped Registry!");
|
|
||||||
if (registry.containsKey(key)) return;
|
|
||||||
Field field = getField(MappedRegistry.class, boolean.class);
|
|
||||||
field.setAccessible(true);
|
|
||||||
boolean frozen = field.getBoolean(registry);
|
|
||||||
field.setBoolean(registry, false);
|
|
||||||
Field holdersField = null;
|
|
||||||
boolean holders = false;
|
|
||||||
for (Field f : MappedRegistry.class.getDeclaredFields()) {
|
|
||||||
if (!f.getGenericType().getTypeName().startsWith("java.util.Map<T, "))
|
|
||||||
continue;
|
|
||||||
holdersField = f;
|
|
||||||
}
|
|
||||||
if (holdersField != null) {
|
|
||||||
holdersField.setAccessible(true);
|
|
||||||
holders = holdersField.get(registry) == null;
|
|
||||||
if (holders) holdersField.set(registry, new IdentityHashMap<>());
|
|
||||||
}
|
|
||||||
|
|
||||||
|
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 {
|
try {
|
||||||
var biome = net.minecraft.world.level.biome.Biome.CODEC.decode(JsonOps.INSTANCE, GsonHelper.parse(IO.readAll(file)))
|
if (registry.containsKey(key)) {
|
||||||
.get().left().map(Pair::getFirst).map(Holder::value).orElse(null);
|
if (!replace) return false;
|
||||||
if (biome == null)
|
return replace(registryKey, location, value);
|
||||||
throw new IllegalStateException("Failed to decode biome " + file.getName());
|
|
||||||
|
|
||||||
registry.createIntrusiveHolder(biome);
|
|
||||||
registry.register(key, biome, Lifecycle.stable());
|
|
||||||
} finally {
|
|
||||||
field.setBoolean(registry, frozen);
|
|
||||||
if (holders) {
|
|
||||||
holdersField.set(registry, null);
|
|
||||||
}
|
}
|
||||||
|
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")
|
@SuppressWarnings("unchecked")
|
||||||
private void modifyDimension(File file) throws Throwable {
|
private <T> boolean replace(ResourceKey<Registry<T>> registryKey, ResourceLocation location, T value) {
|
||||||
var key = ResourceKey.create(Registries.DIMENSION_TYPE, from("minecraft", file));
|
Preconditions.checkArgument(registryKey != null, "The registryKey cannot be null!");
|
||||||
var rawRegistry = registry().registry(Registries.DIMENSION_TYPE).orElse(null);
|
Preconditions.checkArgument(location != null, "The location cannot be null!");
|
||||||
if (!(rawRegistry instanceof MappedRegistry<DimensionType> registry))
|
Preconditions.checkArgument(value != null, "The value cannot be null!");
|
||||||
throw new IllegalStateException("The Dimension Registry is not a mapped Registry!");
|
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);
|
||||||
|
|
||||||
var holder = registry.getHolder(key).orElseThrow(() -> new IllegalStateException("Unknown dimension type: " + key));
|
valueField.set(holder, value);
|
||||||
var oldValue = holder.value();
|
toId.put(value, toId.removeInt(oldValue));
|
||||||
Field valueField = getField(Holder.Reference.class, "T");
|
byValue.put(value, byValue.remove(oldValue));
|
||||||
valueField.setAccessible(true);
|
lifecycles.put(value, lifecycles.remove(oldValue));
|
||||||
Field toIdField = getField(MappedRegistry.class, buildType(Reference2IntMap.class, "T"));
|
return true;
|
||||||
toIdField.setAccessible(true);
|
} catch (Throwable e) {
|
||||||
Field byValueField = getField(MappedRegistry.class, buildType(Map.class, "T", buildType(Holder.Reference.class, "T")));
|
throw new IllegalStateException(e);
|
||||||
byValueField.setAccessible(true);
|
}
|
||||||
Field lifecyclesField = getField(MappedRegistry.class, buildType(Map.class, "T", Lifecycle.class.getName()));
|
}
|
||||||
lifecyclesField.setAccessible(true);
|
|
||||||
var toId = (Reference2IntMap<DimensionType>) toIdField.get(registry);
|
|
||||||
var byValue = (Map<DimensionType, Holder.Reference<DimensionType>>) byValueField.get(registry);
|
|
||||||
var lifecycles = (Map<DimensionType, Lifecycle>) lifecyclesField.get(registry);
|
|
||||||
|
|
||||||
var newValue = DimensionType.CODEC.decode(JsonOps.INSTANCE, GsonHelper.parse(IO.readAll(file)))
|
private <T> MappedRegistry<T> registry(ResourceKey<Registry<T>> registryKey) {
|
||||||
.get().left().map(Pair::getFirst).map(Holder::value).orElse(null);
|
var rawRegistry = registry().registry(registryKey).orElse(null);
|
||||||
if (newValue == null)
|
if (!(rawRegistry instanceof MappedRegistry<T> registry))
|
||||||
throw new IllegalArgumentException("Failed to parse dimension type " + key.location() + " from " + file);
|
throw new IllegalStateException("The Registry is not a mapped Registry!");
|
||||||
|
return registry;
|
||||||
valueField.set(holder, newValue);
|
|
||||||
toId.put(newValue, toId.removeInt(oldValue));
|
|
||||||
byValue.put(newValue, byValue.remove(oldValue));
|
|
||||||
lifecycles.put(newValue, lifecycles.remove(oldValue));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static String buildType(Class<?> clazz, String... parameterTypes) {
|
private static String buildType(Class<?> clazz, String... parameterTypes) {
|
||||||
@@ -719,6 +767,13 @@ public class NMSBinding implements INMSBinding {
|
|||||||
.visit(Advice.to(WorldCreatorAdvice.class).on(ElementMatchers.isConstructor().and(ElementMatchers.takesArguments(String.class))))
|
.visit(Advice.to(WorldCreatorAdvice.class).on(ElementMatchers.isConstructor().and(ElementMatchers.takesArguments(String.class))))
|
||||||
.make()
|
.make()
|
||||||
.load(WorldCreator.class.getClassLoader(), ClassReloadingStrategy.fromInstalledAgent());
|
.load(WorldCreator.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!");
|
Iris.info("Injected Bukkit Successfully!");
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
Iris.info(C.RED + "Failed to Inject Bukkit!");
|
Iris.info(C.RED + "Failed to Inject Bukkit!");
|
||||||
@@ -728,6 +783,21 @@ public class NMSBinding implements INMSBinding {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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 WorldCreatorAdvice {
|
private static class WorldCreatorAdvice {
|
||||||
@Advice.OnMethodEnter
|
@Advice.OnMethodEnter
|
||||||
static void enter(@Advice.Argument(0) String name) {
|
static void enter(@Advice.Argument(0) String name) {
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import java.io.DataInputStream;
|
|||||||
import java.io.DataOutputStream;
|
import java.io.DataOutputStream;
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.FilenameFilter;
|
import java.io.FilenameFilter;
|
||||||
|
import java.io.IOException;
|
||||||
import java.lang.reflect.Field;
|
import java.lang.reflect.Field;
|
||||||
import java.lang.reflect.Method;
|
import java.lang.reflect.Method;
|
||||||
import java.lang.reflect.Modifier;
|
import java.lang.reflect.Modifier;
|
||||||
@@ -13,13 +14,19 @@ import java.util.IdentityHashMap;
|
|||||||
import java.util.Iterator;
|
import java.util.Iterator;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
import java.util.Optional;
|
||||||
import java.util.Vector;
|
import java.util.Vector;
|
||||||
|
import java.util.concurrent.Executor;
|
||||||
import java.util.concurrent.atomic.AtomicInteger;
|
import java.util.concurrent.atomic.AtomicInteger;
|
||||||
|
|
||||||
import com.google.common.base.Preconditions;
|
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.datafixers.util.Pair;
|
||||||
|
import com.mojang.serialization.Codec;
|
||||||
import com.mojang.serialization.JsonOps;
|
import com.mojang.serialization.JsonOps;
|
||||||
import com.mojang.serialization.Lifecycle;
|
import com.mojang.serialization.Lifecycle;
|
||||||
|
import com.volmit.iris.engine.object.IrisDimension;
|
||||||
import com.volmit.iris.util.format.C;
|
import com.volmit.iris.util.format.C;
|
||||||
import com.volmit.iris.util.io.IO;
|
import com.volmit.iris.util.io.IO;
|
||||||
import it.unimi.dsi.fastutil.objects.Reference2IntMap;
|
import it.unimi.dsi.fastutil.objects.Reference2IntMap;
|
||||||
@@ -28,9 +35,15 @@ import net.bytebuddy.asm.Advice;
|
|||||||
import net.bytebuddy.dynamic.loading.ClassReloadingStrategy;
|
import net.bytebuddy.dynamic.loading.ClassReloadingStrategy;
|
||||||
import net.bytebuddy.matcher.ElementMatchers;
|
import net.bytebuddy.matcher.ElementMatchers;
|
||||||
import net.minecraft.core.MappedRegistry;
|
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.util.GsonHelper;
|
||||||
|
import net.minecraft.world.RandomSequences;
|
||||||
import net.minecraft.world.level.Level;
|
import net.minecraft.world.level.Level;
|
||||||
import net.minecraft.world.level.dimension.DimensionType;
|
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.*;
|
||||||
import org.bukkit.block.Biome;
|
import org.bukkit.block.Biome;
|
||||||
import org.bukkit.block.data.BlockData;
|
import org.bukkit.block.data.BlockData;
|
||||||
@@ -44,6 +57,7 @@ import org.bukkit.craftbukkit.v1_20_R2.util.CraftNamespacedKey;
|
|||||||
import org.bukkit.entity.Dolphin;
|
import org.bukkit.entity.Dolphin;
|
||||||
import org.bukkit.entity.Entity;
|
import org.bukkit.entity.Entity;
|
||||||
import org.bukkit.event.entity.CreatureSpawnEvent;
|
import org.bukkit.event.entity.CreatureSpawnEvent;
|
||||||
|
import org.bukkit.generator.BiomeProvider;
|
||||||
import org.bukkit.generator.ChunkGenerator;
|
import org.bukkit.generator.ChunkGenerator;
|
||||||
import org.bukkit.inventory.ItemStack;
|
import org.bukkit.inventory.ItemStack;
|
||||||
import org.jetbrains.annotations.NotNull;
|
import org.jetbrains.annotations.NotNull;
|
||||||
@@ -553,25 +567,34 @@ public class NMSBinding implements INMSBinding {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean loadDatapack(File folder) {
|
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 loadDatapack(File folder, boolean replace) {
|
||||||
var data = new File(folder, "iris/data");
|
var data = new File(folder, "iris/data");
|
||||||
if (!data.exists() || !data.isDirectory()) return false;
|
if (!data.exists() || !data.isDirectory()) return false;
|
||||||
FilenameFilter jsonFilter = (dir, name) -> new File(dir, name).isFile() && name.toLowerCase().endsWith(".json");
|
FilenameFilter jsonFilter = (dir, name) -> new File(dir, name).isFile() && name.toLowerCase().endsWith(".json");
|
||||||
|
|
||||||
var dimensionFolder = new File(data, "minecraft/dimension_type");
|
|
||||||
if (dimensionFolder.exists()) {
|
|
||||||
var files = dimensionFolder.listFiles(jsonFilter);
|
|
||||||
if (files != null) {
|
|
||||||
for (File file : files) {
|
|
||||||
try {
|
|
||||||
modifyDimension(file);
|
|
||||||
} catch (Throwable e) {
|
|
||||||
Iris.error("Unable to modify dimension!");
|
|
||||||
e.printStackTrace();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
var files = data.listFiles((dir, name) -> new File(dir, name).isDirectory());
|
var files = data.listFiles((dir, name) -> new File(dir, name).isDirectory());
|
||||||
if (files == null) return false;
|
if (files == null) return false;
|
||||||
for (File file : files) {
|
for (File file : files) {
|
||||||
@@ -580,8 +603,26 @@ public class NMSBinding implements INMSBinding {
|
|||||||
var biomeFiles = biome.listFiles(jsonFilter);
|
var biomeFiles = biome.listFiles(jsonFilter);
|
||||||
if (biomeFiles == null) continue;
|
if (biomeFiles == null) continue;
|
||||||
for (File biomeFile : biomeFiles) {
|
for (File biomeFile : biomeFiles) {
|
||||||
|
String json = null;
|
||||||
|
int tries = 10;
|
||||||
|
while (json == null && tries-- > 0) {
|
||||||
|
try {
|
||||||
|
json = IO.readAll(biomeFile);
|
||||||
|
} catch (IOException e) {
|
||||||
|
Iris.error("Failed to read biome " + file.getName() + ":" + biomeFile.getName() + " tries left: " + tries);
|
||||||
|
if (tries == 0) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
Thread.sleep(100);
|
||||||
|
} catch (InterruptedException ignored) {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (json == null) continue;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
registerBiome(file.getName(), biomeFile);
|
var value = decode(net.minecraft.world.level.biome.Biome.CODEC, json).map(Holder::value).orElse(null);
|
||||||
|
register(Registries.BIOME, from(file.getName(), biomeFile), value, replace);
|
||||||
} catch (Throwable e) {
|
} catch (Throwable e) {
|
||||||
Iris.error("Failed to register biome " + file.getName() + ":" + biomeFile.getName());
|
Iris.error("Failed to register biome " + file.getName() + ":" + biomeFile.getName());
|
||||||
e.printStackTrace();
|
e.printStackTrace();
|
||||||
@@ -596,75 +637,82 @@ public class NMSBinding implements INMSBinding {
|
|||||||
return new ResourceLocation(namespace, name.substring(0, name.lastIndexOf('.')));
|
return new ResourceLocation(namespace, name.substring(0, name.lastIndexOf('.')));
|
||||||
}
|
}
|
||||||
|
|
||||||
private void registerBiome(String namespace, File file) throws Throwable {
|
private <T> Optional<T> decode(Codec<T> codec, String json) {
|
||||||
var rawRegistry = registry().registry(Registries.BIOME).orElse(null);
|
return codec.decode(JsonOps.INSTANCE, GsonHelper.parse(json)).get().left().map(Pair::getFirst);
|
||||||
var key = ResourceKey.create(Registries.BIOME, from(namespace, file));
|
}
|
||||||
if (!(rawRegistry instanceof MappedRegistry<net.minecraft.world.level.biome.Biome> registry))
|
|
||||||
throw new IllegalStateException("The Biome Registry is not a mapped Registry!");
|
|
||||||
if (registry.containsKey(key)) return;
|
|
||||||
Field field = getField(MappedRegistry.class, boolean.class);
|
|
||||||
field.setAccessible(true);
|
|
||||||
boolean frozen = field.getBoolean(registry);
|
|
||||||
field.setBoolean(registry, false);
|
|
||||||
Field holdersField = null;
|
|
||||||
boolean holders = false;
|
|
||||||
for (Field f : MappedRegistry.class.getDeclaredFields()) {
|
|
||||||
if (!f.getGenericType().getTypeName().startsWith("java.util.Map<T, "))
|
|
||||||
continue;
|
|
||||||
holdersField = f;
|
|
||||||
}
|
|
||||||
if (holdersField != null) {
|
|
||||||
holdersField.setAccessible(true);
|
|
||||||
holders = holdersField.get(registry) == null;
|
|
||||||
if (holders) holdersField.set(registry, new IdentityHashMap<>());
|
|
||||||
}
|
|
||||||
|
|
||||||
|
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 {
|
try {
|
||||||
var biome = net.minecraft.world.level.biome.Biome.CODEC.decode(JsonOps.INSTANCE, GsonHelper.parse(IO.readAll(file)))
|
if (registry.containsKey(key)) {
|
||||||
.get().left().map(Pair::getFirst).map(Holder::value).orElse(null);
|
if (!replace) return false;
|
||||||
if (biome == null)
|
return replace(registryKey, location, value);
|
||||||
throw new IllegalStateException("Failed to decode biome " + file.getName());
|
|
||||||
|
|
||||||
registry.createIntrusiveHolder(biome);
|
|
||||||
registry.register(key, biome, Lifecycle.stable());
|
|
||||||
} finally {
|
|
||||||
field.setBoolean(registry, frozen);
|
|
||||||
if (holders) {
|
|
||||||
holdersField.set(registry, null);
|
|
||||||
}
|
}
|
||||||
|
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")
|
@SuppressWarnings("unchecked")
|
||||||
private void modifyDimension(File file) throws Throwable {
|
private <T> boolean replace(ResourceKey<Registry<T>> registryKey, ResourceLocation location, T value) {
|
||||||
var key = ResourceKey.create(Registries.DIMENSION_TYPE, from("minecraft", file));
|
Preconditions.checkArgument(registryKey != null, "The registryKey cannot be null!");
|
||||||
var rawRegistry = registry().registry(Registries.DIMENSION_TYPE).orElse(null);
|
Preconditions.checkArgument(location != null, "The location cannot be null!");
|
||||||
if (!(rawRegistry instanceof MappedRegistry<DimensionType> registry))
|
Preconditions.checkArgument(value != null, "The value cannot be null!");
|
||||||
throw new IllegalStateException("The Dimension Registry is not a mapped Registry!");
|
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);
|
||||||
|
|
||||||
var holder = registry.getHolder(key).orElseThrow(() -> new IllegalStateException("Unknown dimension type: " + key));
|
valueField.set(holder, value);
|
||||||
var oldValue = holder.value();
|
toId.put(value, toId.removeInt(oldValue));
|
||||||
Field valueField = getField(Holder.Reference.class, "T");
|
byValue.put(value, byValue.remove(oldValue));
|
||||||
valueField.setAccessible(true);
|
lifecycles.put(value, lifecycles.remove(oldValue));
|
||||||
Field toIdField = getField(MappedRegistry.class, buildType(Reference2IntMap.class, "T"));
|
return true;
|
||||||
toIdField.setAccessible(true);
|
} catch (Throwable e) {
|
||||||
Field byValueField = getField(MappedRegistry.class, buildType(Map.class, "T", buildType(Holder.Reference.class, "T")));
|
throw new IllegalStateException(e);
|
||||||
byValueField.setAccessible(true);
|
}
|
||||||
Field lifecyclesField = getField(MappedRegistry.class, buildType(Map.class, "T", Lifecycle.class.getName()));
|
}
|
||||||
lifecyclesField.setAccessible(true);
|
|
||||||
var toId = (Reference2IntMap<DimensionType>) toIdField.get(registry);
|
|
||||||
var byValue = (Map<DimensionType, Holder.Reference<DimensionType>>) byValueField.get(registry);
|
|
||||||
var lifecycles = (Map<DimensionType, Lifecycle>) lifecyclesField.get(registry);
|
|
||||||
|
|
||||||
var newValue = DimensionType.CODEC.decode(JsonOps.INSTANCE, GsonHelper.parse(IO.readAll(file)))
|
private <T> MappedRegistry<T> registry(ResourceKey<Registry<T>> registryKey) {
|
||||||
.get().left().map(Pair::getFirst).map(Holder::value).orElse(null);
|
var rawRegistry = registry().registry(registryKey).orElse(null);
|
||||||
if (newValue == null)
|
if (!(rawRegistry instanceof MappedRegistry<T> registry))
|
||||||
throw new IllegalArgumentException("Failed to parse dimension type " + key.location() + " from " + file);
|
throw new IllegalStateException("The Registry is not a mapped Registry!");
|
||||||
|
return registry;
|
||||||
valueField.set(holder, newValue);
|
|
||||||
toId.put(newValue, toId.removeInt(oldValue));
|
|
||||||
byValue.put(newValue, byValue.remove(oldValue));
|
|
||||||
lifecycles.put(newValue, lifecycles.remove(oldValue));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static String buildType(Class<?> clazz, String... parameterTypes) {
|
private static String buildType(Class<?> clazz, String... parameterTypes) {
|
||||||
@@ -721,6 +769,13 @@ public class NMSBinding implements INMSBinding {
|
|||||||
.visit(Advice.to(WorldCreatorAdvice.class).on(ElementMatchers.isConstructor().and(ElementMatchers.takesArguments(String.class))))
|
.visit(Advice.to(WorldCreatorAdvice.class).on(ElementMatchers.isConstructor().and(ElementMatchers.takesArguments(String.class))))
|
||||||
.make()
|
.make()
|
||||||
.load(WorldCreator.class.getClassLoader(), ClassReloadingStrategy.fromInstalledAgent());
|
.load(WorldCreator.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!");
|
Iris.info("Injected Bukkit Successfully!");
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
Iris.info(C.RED + "Failed to Inject Bukkit!");
|
Iris.info(C.RED + "Failed to Inject Bukkit!");
|
||||||
@@ -730,6 +785,21 @@ public class NMSBinding implements INMSBinding {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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 WorldCreatorAdvice {
|
private static class WorldCreatorAdvice {
|
||||||
@Advice.OnMethodEnter
|
@Advice.OnMethodEnter
|
||||||
static void enter(@Advice.Argument(0) String name) {
|
static void enter(@Advice.Argument(0) String name) {
|
||||||
|
|||||||
@@ -6,16 +6,22 @@ import java.io.DataInputStream;
|
|||||||
import java.io.DataOutputStream;
|
import java.io.DataOutputStream;
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.FilenameFilter;
|
import java.io.FilenameFilter;
|
||||||
|
import java.io.IOException;
|
||||||
import java.lang.reflect.Field;
|
import java.lang.reflect.Field;
|
||||||
import java.lang.reflect.Method;
|
import java.lang.reflect.Method;
|
||||||
import java.lang.reflect.Modifier;
|
import java.lang.reflect.Modifier;
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
|
import java.util.concurrent.Executor;
|
||||||
import java.util.concurrent.atomic.AtomicInteger;
|
import java.util.concurrent.atomic.AtomicInteger;
|
||||||
|
|
||||||
import com.google.common.base.Preconditions;
|
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.datafixers.util.Pair;
|
||||||
|
import com.mojang.serialization.Codec;
|
||||||
import com.mojang.serialization.JsonOps;
|
import com.mojang.serialization.JsonOps;
|
||||||
import com.mojang.serialization.Lifecycle;
|
import com.mojang.serialization.Lifecycle;
|
||||||
|
import com.volmit.iris.engine.object.IrisDimension;
|
||||||
import com.volmit.iris.util.format.C;
|
import com.volmit.iris.util.format.C;
|
||||||
import com.volmit.iris.util.io.IO;
|
import com.volmit.iris.util.io.IO;
|
||||||
import it.unimi.dsi.fastutil.objects.Reference2IntMap;
|
import it.unimi.dsi.fastutil.objects.Reference2IntMap;
|
||||||
@@ -23,10 +29,17 @@ import net.bytebuddy.ByteBuddy;
|
|||||||
import net.bytebuddy.asm.Advice;
|
import net.bytebuddy.asm.Advice;
|
||||||
import net.bytebuddy.dynamic.loading.ClassReloadingStrategy;
|
import net.bytebuddy.dynamic.loading.ClassReloadingStrategy;
|
||||||
import net.bytebuddy.matcher.ElementMatchers;
|
import net.bytebuddy.matcher.ElementMatchers;
|
||||||
|
import net.minecraft.core.IdMapper;
|
||||||
import net.minecraft.core.MappedRegistry;
|
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.util.GsonHelper;
|
||||||
|
import net.minecraft.world.RandomSequences;
|
||||||
import net.minecraft.world.level.Level;
|
import net.minecraft.world.level.Level;
|
||||||
import net.minecraft.world.level.dimension.DimensionType;
|
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.*;
|
||||||
import org.bukkit.block.Biome;
|
import org.bukkit.block.Biome;
|
||||||
import org.bukkit.block.data.BlockData;
|
import org.bukkit.block.data.BlockData;
|
||||||
@@ -40,6 +53,7 @@ import org.bukkit.craftbukkit.v1_20_R3.util.CraftNamespacedKey;
|
|||||||
import org.bukkit.entity.Dolphin;
|
import org.bukkit.entity.Dolphin;
|
||||||
import org.bukkit.entity.Entity;
|
import org.bukkit.entity.Entity;
|
||||||
import org.bukkit.event.entity.CreatureSpawnEvent;
|
import org.bukkit.event.entity.CreatureSpawnEvent;
|
||||||
|
import org.bukkit.generator.BiomeProvider;
|
||||||
import org.bukkit.generator.ChunkGenerator;
|
import org.bukkit.generator.ChunkGenerator;
|
||||||
import org.bukkit.inventory.ItemStack;
|
import org.bukkit.inventory.ItemStack;
|
||||||
import org.jetbrains.annotations.NotNull;
|
import org.jetbrains.annotations.NotNull;
|
||||||
@@ -445,13 +459,13 @@ public class NMSBinding implements INMSBinding {
|
|||||||
@Override
|
@Override
|
||||||
public MCAPaletteAccess createPalette() {
|
public MCAPaletteAccess createPalette() {
|
||||||
MCAIdMapper<BlockState> registry = registryCache.aquireNasty(() -> {
|
MCAIdMapper<BlockState> registry = registryCache.aquireNasty(() -> {
|
||||||
Field cf = net.minecraft.core.IdMapper.class.getDeclaredField("tToId");
|
Field cf = IdMapper.class.getDeclaredField("tToId");
|
||||||
Field df = net.minecraft.core.IdMapper.class.getDeclaredField("idToT");
|
Field df = IdMapper.class.getDeclaredField("idToT");
|
||||||
Field bf = net.minecraft.core.IdMapper.class.getDeclaredField("nextId");
|
Field bf = IdMapper.class.getDeclaredField("nextId");
|
||||||
cf.setAccessible(true);
|
cf.setAccessible(true);
|
||||||
df.setAccessible(true);
|
df.setAccessible(true);
|
||||||
bf.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);
|
int b = bf.getInt(blockData);
|
||||||
Object2IntMap<BlockState> c = (Object2IntMap<BlockState>) cf.get(blockData);
|
Object2IntMap<BlockState> c = (Object2IntMap<BlockState>) cf.get(blockData);
|
||||||
List<BlockState> d = (List<BlockState>) df.get(blockData);
|
List<BlockState> d = (List<BlockState>) df.get(blockData);
|
||||||
@@ -551,25 +565,34 @@ public class NMSBinding implements INMSBinding {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean loadDatapack(File folder) {
|
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 loadDatapack(File folder, boolean replace) {
|
||||||
var data = new File(folder, "iris/data");
|
var data = new File(folder, "iris/data");
|
||||||
if (!data.exists() || !data.isDirectory()) return false;
|
if (!data.exists() || !data.isDirectory()) return false;
|
||||||
FilenameFilter jsonFilter = (dir, name) -> new File(dir, name).isFile() && name.toLowerCase().endsWith(".json");
|
FilenameFilter jsonFilter = (dir, name) -> new File(dir, name).isFile() && name.toLowerCase().endsWith(".json");
|
||||||
|
|
||||||
var dimensionFolder = new File(data, "minecraft/dimension_type");
|
|
||||||
if (dimensionFolder.exists()) {
|
|
||||||
var files = dimensionFolder.listFiles(jsonFilter);
|
|
||||||
if (files != null) {
|
|
||||||
for (File file : files) {
|
|
||||||
try {
|
|
||||||
modifyDimension(file);
|
|
||||||
} catch (Throwable e) {
|
|
||||||
Iris.error("Unable to modify dimension!");
|
|
||||||
e.printStackTrace();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
var files = data.listFiles((dir, name) -> new File(dir, name).isDirectory());
|
var files = data.listFiles((dir, name) -> new File(dir, name).isDirectory());
|
||||||
if (files == null) return false;
|
if (files == null) return false;
|
||||||
for (File file : files) {
|
for (File file : files) {
|
||||||
@@ -578,8 +601,26 @@ public class NMSBinding implements INMSBinding {
|
|||||||
var biomeFiles = biome.listFiles(jsonFilter);
|
var biomeFiles = biome.listFiles(jsonFilter);
|
||||||
if (biomeFiles == null) continue;
|
if (biomeFiles == null) continue;
|
||||||
for (File biomeFile : biomeFiles) {
|
for (File biomeFile : biomeFiles) {
|
||||||
|
String json = null;
|
||||||
|
int tries = 10;
|
||||||
|
while (json == null && tries-- > 0) {
|
||||||
|
try {
|
||||||
|
json = IO.readAll(biomeFile);
|
||||||
|
} catch (IOException e) {
|
||||||
|
Iris.error("Failed to read biome " + file.getName() + ":" + biomeFile.getName() + " tries left: " + tries);
|
||||||
|
if (tries == 0) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
Thread.sleep(100);
|
||||||
|
} catch (InterruptedException ignored) {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (json == null) continue;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
registerBiome(file.getName(), biomeFile);
|
var value = decode(net.minecraft.world.level.biome.Biome.CODEC, json).map(Holder::value).orElse(null);
|
||||||
|
register(Registries.BIOME, from(file.getName(), biomeFile), value, replace);
|
||||||
} catch (Throwable e) {
|
} catch (Throwable e) {
|
||||||
Iris.error("Failed to register biome " + file.getName() + ":" + biomeFile.getName());
|
Iris.error("Failed to register biome " + file.getName() + ":" + biomeFile.getName());
|
||||||
e.printStackTrace();
|
e.printStackTrace();
|
||||||
@@ -594,75 +635,82 @@ public class NMSBinding implements INMSBinding {
|
|||||||
return new ResourceLocation(namespace, name.substring(0, name.lastIndexOf('.')));
|
return new ResourceLocation(namespace, name.substring(0, name.lastIndexOf('.')));
|
||||||
}
|
}
|
||||||
|
|
||||||
private void registerBiome(String namespace, File file) throws Throwable {
|
private <T> Optional<T> decode(Codec<T> codec, String json) {
|
||||||
var rawRegistry = registry().registry(Registries.BIOME).orElse(null);
|
return codec.decode(JsonOps.INSTANCE, GsonHelper.parse(json)).get().left().map(Pair::getFirst);
|
||||||
var key = ResourceKey.create(Registries.BIOME, from(namespace, file));
|
}
|
||||||
if (!(rawRegistry instanceof MappedRegistry<net.minecraft.world.level.biome.Biome> registry))
|
|
||||||
throw new IllegalStateException("The Biome Registry is not a mapped Registry!");
|
|
||||||
if (registry.containsKey(key)) return;
|
|
||||||
Field field = getField(MappedRegistry.class, boolean.class);
|
|
||||||
field.setAccessible(true);
|
|
||||||
boolean frozen = field.getBoolean(registry);
|
|
||||||
field.setBoolean(registry, false);
|
|
||||||
Field holdersField = null;
|
|
||||||
boolean holders = false;
|
|
||||||
for (Field f : MappedRegistry.class.getDeclaredFields()) {
|
|
||||||
if (!f.getGenericType().getTypeName().startsWith("java.util.Map<T, "))
|
|
||||||
continue;
|
|
||||||
holdersField = f;
|
|
||||||
}
|
|
||||||
if (holdersField != null) {
|
|
||||||
holdersField.setAccessible(true);
|
|
||||||
holders = holdersField.get(registry) == null;
|
|
||||||
if (holders) holdersField.set(registry, new IdentityHashMap<>());
|
|
||||||
}
|
|
||||||
|
|
||||||
|
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 {
|
try {
|
||||||
var biome = net.minecraft.world.level.biome.Biome.CODEC.decode(JsonOps.INSTANCE, GsonHelper.parse(IO.readAll(file)))
|
if (registry.containsKey(key)) {
|
||||||
.get().left().map(Pair::getFirst).map(Holder::value).orElse(null);
|
if (!replace) return false;
|
||||||
if (biome == null)
|
return replace(registryKey, location, value);
|
||||||
throw new IllegalStateException("Failed to decode biome " + file.getName());
|
|
||||||
|
|
||||||
registry.createIntrusiveHolder(biome);
|
|
||||||
registry.register(key, biome, Lifecycle.stable());
|
|
||||||
} finally {
|
|
||||||
field.setBoolean(registry, frozen);
|
|
||||||
if (holders) {
|
|
||||||
holdersField.set(registry, null);
|
|
||||||
}
|
}
|
||||||
|
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")
|
@SuppressWarnings("unchecked")
|
||||||
private void modifyDimension(File file) throws Throwable {
|
private <T> boolean replace(ResourceKey<Registry<T>> registryKey, ResourceLocation location, T value) {
|
||||||
var key = ResourceKey.create(Registries.DIMENSION_TYPE, from("minecraft", file));
|
Preconditions.checkArgument(registryKey != null, "The registryKey cannot be null!");
|
||||||
var rawRegistry = registry().registry(Registries.DIMENSION_TYPE).orElse(null);
|
Preconditions.checkArgument(location != null, "The location cannot be null!");
|
||||||
if (!(rawRegistry instanceof MappedRegistry<DimensionType> registry))
|
Preconditions.checkArgument(value != null, "The value cannot be null!");
|
||||||
throw new IllegalStateException("The Dimension Registry is not a mapped Registry!");
|
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);
|
||||||
|
|
||||||
var holder = registry.getHolder(key).orElseThrow(() -> new IllegalStateException("Unknown dimension type: " + key));
|
valueField.set(holder, value);
|
||||||
var oldValue = holder.value();
|
toId.put(value, toId.removeInt(oldValue));
|
||||||
Field valueField = getField(Holder.Reference.class, "T");
|
byValue.put(value, byValue.remove(oldValue));
|
||||||
valueField.setAccessible(true);
|
lifecycles.put(value, lifecycles.remove(oldValue));
|
||||||
Field toIdField = getField(MappedRegistry.class, buildType(Reference2IntMap.class, "T"));
|
return true;
|
||||||
toIdField.setAccessible(true);
|
} catch (Throwable e) {
|
||||||
Field byValueField = getField(MappedRegistry.class, buildType(Map.class, "T", buildType(Holder.Reference.class, "T")));
|
throw new IllegalStateException(e);
|
||||||
byValueField.setAccessible(true);
|
}
|
||||||
Field lifecyclesField = getField(MappedRegistry.class, buildType(Map.class, "T", Lifecycle.class.getName()));
|
}
|
||||||
lifecyclesField.setAccessible(true);
|
|
||||||
var toId = (Reference2IntMap<DimensionType>) toIdField.get(registry);
|
|
||||||
var byValue = (Map<DimensionType, Holder.Reference<DimensionType>>) byValueField.get(registry);
|
|
||||||
var lifecycles = (Map<DimensionType, Lifecycle>) lifecyclesField.get(registry);
|
|
||||||
|
|
||||||
var newValue = DimensionType.CODEC.decode(JsonOps.INSTANCE, GsonHelper.parse(IO.readAll(file)))
|
private <T> MappedRegistry<T> registry(ResourceKey<Registry<T>> registryKey) {
|
||||||
.get().left().map(Pair::getFirst).map(Holder::value).orElse(null);
|
var rawRegistry = registry().registry(registryKey).orElse(null);
|
||||||
if (newValue == null)
|
if (!(rawRegistry instanceof MappedRegistry<T> registry))
|
||||||
throw new IllegalArgumentException("Failed to parse dimension type " + key.location() + " from " + file);
|
throw new IllegalStateException("The Registry is not a mapped Registry!");
|
||||||
|
return registry;
|
||||||
valueField.set(holder, newValue);
|
|
||||||
toId.put(newValue, toId.removeInt(oldValue));
|
|
||||||
byValue.put(newValue, byValue.remove(oldValue));
|
|
||||||
lifecycles.put(newValue, lifecycles.remove(oldValue));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static String buildType(Class<?> clazz, String... parameterTypes) {
|
private static String buildType(Class<?> clazz, String... parameterTypes) {
|
||||||
@@ -718,6 +766,13 @@ public class NMSBinding implements INMSBinding {
|
|||||||
.visit(Advice.to(WorldCreatorAdvice.class).on(ElementMatchers.isConstructor().and(ElementMatchers.takesArguments(String.class))))
|
.visit(Advice.to(WorldCreatorAdvice.class).on(ElementMatchers.isConstructor().and(ElementMatchers.takesArguments(String.class))))
|
||||||
.make()
|
.make()
|
||||||
.load(WorldCreator.class.getClassLoader(), ClassReloadingStrategy.fromInstalledAgent());
|
.load(WorldCreator.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!");
|
Iris.info("Injected Bukkit Successfully!");
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
Iris.info(C.RED + "Failed to Inject Bukkit!");
|
Iris.info(C.RED + "Failed to Inject Bukkit!");
|
||||||
@@ -727,6 +782,21 @@ public class NMSBinding implements INMSBinding {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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 WorldCreatorAdvice {
|
private static class WorldCreatorAdvice {
|
||||||
@Advice.OnMethodEnter
|
@Advice.OnMethodEnter
|
||||||
static void enter(@Advice.Argument(0) String name) {
|
static void enter(@Advice.Argument(0) String name) {
|
||||||
|
|||||||
Reference in New Issue
Block a user