mirror of
https://github.com/VolmitSoftware/Iris.git
synced 2025-12-20 15:39:31 +00:00
Compare commits
85 Commits
old/iris4
...
old/v3.4.3
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b279bb7716 | ||
|
|
2976390e78 | ||
|
|
578070dffe | ||
|
|
1f9c72d093 | ||
|
|
a1495a10e5 | ||
|
|
a0d91dbc74 | ||
|
|
eb80ae62b1 | ||
|
|
11ae48bea1 | ||
|
|
b0f4b29a2d | ||
|
|
c38bb1cd01 | ||
|
|
7faa727bd2 | ||
|
|
a8751c80ab | ||
|
|
fd38f0d127 | ||
|
|
537b0f8dd6 | ||
|
|
560f2f4fdc | ||
|
|
7c725a9436 | ||
|
|
6532715490 | ||
|
|
b1bb01665a | ||
|
|
6c69c69868 | ||
|
|
9cd7ad6770 | ||
|
|
b70c9fd4d8 | ||
|
|
412f592e1c | ||
|
|
6c0fa9361f | ||
|
|
b7dcdd2921 | ||
|
|
1d8acd2d62 | ||
|
|
05bf92ca50 | ||
|
|
dd598d18bf | ||
|
|
cc6de4a46d | ||
|
|
9950551ecb | ||
|
|
dfd27ecbff | ||
|
|
ac5d5793ab | ||
|
|
2133a03c21 | ||
|
|
8755449c13 | ||
|
|
9e40259ca2 | ||
|
|
94a7692735 | ||
|
|
86ce02789e | ||
|
|
773c8129c9 | ||
|
|
dbe9f81091 | ||
|
|
288e556f2a | ||
|
|
70822e37de | ||
|
|
8b803a87f0 | ||
|
|
3ae6e92eaf | ||
|
|
0f3c52a5aa | ||
|
|
747be7aa09 | ||
|
|
3f994c18ff | ||
|
|
36b99ae4b0 | ||
|
|
25dbc4dfd6 | ||
|
|
c35366caa4 | ||
|
|
76365e7875 | ||
|
|
57649a9ec5 | ||
|
|
03582751c5 | ||
|
|
13369bbf32 | ||
|
|
f3b1109804 | ||
|
|
7f530a4b32 | ||
|
|
eda44a8ace | ||
|
|
f6a354b890 | ||
|
|
db14861b40 | ||
|
|
457b691add | ||
|
|
d651f204f8 | ||
|
|
a61787ecfe | ||
|
|
093d77bf8a | ||
|
|
0101130d7a | ||
|
|
a3b2a17e2d | ||
|
|
ff0a68c4f6 | ||
|
|
e0034dd718 | ||
|
|
7c6df58c15 | ||
|
|
5d5c5ba9f4 | ||
|
|
85aefcd206 | ||
|
|
34a67dc781 | ||
|
|
1a96128321 | ||
|
|
1b0f4e6af6 | ||
|
|
9eb01dd6ca | ||
|
|
bb6778dc63 | ||
|
|
9a24627ceb | ||
|
|
7583b91d46 | ||
|
|
c418962411 | ||
|
|
8e8366b318 | ||
|
|
a4ad83d462 | ||
|
|
ddf0e79a7e | ||
|
|
2bd3ac7a9b | ||
|
|
5e437b34e3 | ||
|
|
a5ef89a128 | ||
|
|
558b8e4eca | ||
|
|
0a001b8a63 | ||
|
|
6c6c9654c1 |
39
build.gradle
39
build.gradle
@@ -43,7 +43,8 @@ registerCustomOutputTask('Coco', 'D://mcsm/plugins')
|
||||
registerCustomOutputTask('Strange', 'D://Servers/1.17 Test Server/plugins')
|
||||
registerCustomOutputTask('Vatuu', 'D://Minecraft/Servers/1.19.4/plugins')
|
||||
registerCustomOutputTask('CrazyDev22', 'C://Users/Julian/Desktop/server/plugins')
|
||||
registerCustomOutputTask('Pixel', 'C://Users/repix/Iris Dimension Engine/1.20.4 - Development/plugins')
|
||||
registerCustomOutputTask('Pixel', 'D://Iris Dimension Engine/1.20.4 - Development/plugins')
|
||||
registerCustomOutputTask('PixelFury', 'C://Users/RePixelatedMC/Iris/1.21 - Development-v3/plugins')
|
||||
// ========================== UNIX ==============================
|
||||
registerCustomOutputTaskUnix('CyberpwnLT', '/Users/danielmills/development/server/plugins')
|
||||
registerCustomOutputTaskUnix('PsychoLT', '/Users/brianfopiano/Developer/RemoteGit/Server/plugins')
|
||||
@@ -52,7 +53,7 @@ registerCustomOutputTaskUnix('CrazyDev22LT', '/home/julian/Desktop/server/plugin
|
||||
// ==============================================================
|
||||
|
||||
def NMS_BINDINGS = Map.of(
|
||||
"v1_21_R1", "1.21-R0.1-SNAPSHOT",
|
||||
"v1_21_R1", "1.21.1-R0.1-SNAPSHOT",
|
||||
"v1_20_R4", "1.20.6-R0.1-SNAPSHOT",
|
||||
"v1_20_R3", "1.20.4-R0.1-SNAPSHOT",
|
||||
"v1_20_R2", "1.20.2-R0.1-SNAPSHOT",
|
||||
@@ -109,10 +110,10 @@ allprojects {
|
||||
|
||||
repositories {
|
||||
mavenCentral()
|
||||
maven { url "https://repo.papermc.io/repository/maven-public/"}
|
||||
maven { url "https://repo.papermc.io/repository/maven-public/" }
|
||||
maven { url "https://repo.codemc.org/repository/maven-public" }
|
||||
maven { url "https://mvn.lumine.io/repository/maven-public/" }
|
||||
maven { url "https://jitpack.io"}
|
||||
maven { url "https://jitpack.io" }
|
||||
|
||||
maven { url "https://s01.oss.sonatype.org/content/repositories/snapshots" }
|
||||
maven { url "https://mvn.lumine.io/repository/maven/" }
|
||||
@@ -136,6 +137,7 @@ allprojects {
|
||||
//implementation 'org.bytedeco:javacpp:1.5.10'
|
||||
//implementation 'org.bytedeco:cuda-platform:12.3-8.9-1.5.10'
|
||||
compileOnly 'io.lumine:Mythic-Dist:5.2.1'
|
||||
compileOnly 'io.lumine:MythicCrucible-Dist:2.0.0'
|
||||
|
||||
// Dynamically Loaded
|
||||
compileOnly 'io.timeandspace:smoothie-map:2.0.2'
|
||||
@@ -158,6 +160,21 @@ allprojects {
|
||||
options.compilerArgs << '-parameters'
|
||||
options.encoding = "UTF-8"
|
||||
}
|
||||
|
||||
javadoc {
|
||||
options.encoding = "UTF-8"
|
||||
options.addStringOption('Xdoclint:none', '-quiet')
|
||||
}
|
||||
|
||||
task sourcesJar(type: Jar, dependsOn: classes) {
|
||||
archiveClassifier.set('sources')
|
||||
from sourceSets.main.allSource
|
||||
}
|
||||
|
||||
task javadocJar(type: Jar, dependsOn: javadoc) {
|
||||
archiveClassifier.set('javadoc')
|
||||
from javadoc.destinationDir
|
||||
}
|
||||
}
|
||||
|
||||
if (JavaVersion.current().toString() != "17") {
|
||||
@@ -185,6 +202,20 @@ task iris(type: Copy) {
|
||||
dependsOn(build)
|
||||
}
|
||||
|
||||
// with classifier: 'javadoc' and 'sources'
|
||||
task irisDev(type: Copy) {
|
||||
group "iris"
|
||||
from("core/build/libs/core-javadoc.jar", "core/build/libs/core-sources.jar")
|
||||
rename { String fileName ->
|
||||
fileName.replace("core", "Iris-${version}")
|
||||
}
|
||||
into layout.buildDirectory.asFile.get()
|
||||
dependsOn(iris)
|
||||
dependsOn("core:sourcesJar")
|
||||
dependsOn("core:javadocJar")
|
||||
}
|
||||
|
||||
|
||||
def registerCustomOutputTask(name, path) {
|
||||
if (!System.properties['os.name'].toLowerCase().contains('windows')) {
|
||||
return;
|
||||
|
||||
@@ -55,7 +55,7 @@ dependencies {
|
||||
compileOnly 'org.spigotmc:spigot-api:1.20.1-R0.1-SNAPSHOT'
|
||||
compileOnly 'org.apache.logging.log4j:log4j-api:2.19.0'
|
||||
compileOnly 'org.apache.logging.log4j:log4j-core:2.19.0'
|
||||
compileOnly 'commons-io:commons-io:2.13.0'
|
||||
compileOnly 'commons-io:commons-io:2.14.0'
|
||||
compileOnly 'commons-lang:commons-lang:2.6'
|
||||
compileOnly 'com.github.oshi:oshi-core:5.8.5'
|
||||
compileOnly 'org.lz4:lz4-java:1.8.0'
|
||||
|
||||
@@ -91,6 +91,7 @@ import java.io.*;
|
||||
import java.lang.annotation.Annotation;
|
||||
import java.lang.management.ManagementFactory;
|
||||
import java.lang.management.OperatingSystemMXBean;
|
||||
import java.lang.management.ThreadMXBean;
|
||||
import java.net.URL;
|
||||
import java.util.Date;
|
||||
import java.util.Map;
|
||||
@@ -811,6 +812,22 @@ public class Iris extends VolmitPlugin implements Listener {
|
||||
Iris.info("Server type & version: " + C.RED + Bukkit.getVersion());
|
||||
} else { Iris.info("Server type & version: " + Bukkit.getVersion()); }
|
||||
Iris.info("Java: " + getJava());
|
||||
try {
|
||||
if (getCPUModel().contains("Intel")) {
|
||||
Iris.info("Server Cpu: " + C.BLUE + getCPUModel());
|
||||
}
|
||||
if (getCPUModel().contains("Ryzen")) {
|
||||
Iris.info("Server Cpu: " + C.RED + getCPUModel());
|
||||
}
|
||||
if (!getCPUModel().contains("Ryzen") && !getCPUModel().contains("Intel")) {
|
||||
Iris.info("Server Cpu: " + C.GRAY + getCPUModel());
|
||||
}
|
||||
|
||||
} catch (Exception e){
|
||||
Iris.info("Server Cpu: " + C.DARK_RED + "Failed");
|
||||
}
|
||||
|
||||
Iris.info("Threads: " + C.GRAY + Runtime.getRuntime().availableProcessors());
|
||||
if (!instance.getServer().getVersion().contains("Purpur")) {
|
||||
if (instance.getServer().getVersion().contains("Spigot") && instance.getServer().getVersion().contains("Bukkit")) {
|
||||
Iris.info(C.RED + " Iris requires paper or above to function properly..");
|
||||
|
||||
@@ -25,7 +25,9 @@ import com.volmit.iris.util.json.JSONException;
|
||||
import com.volmit.iris.util.json.JSONObject;
|
||||
import com.volmit.iris.util.plugin.VolmitSender;
|
||||
import com.volmit.iris.util.scheduling.ChronoLatch;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
@@ -42,8 +44,11 @@ public class IrisSettings {
|
||||
private IrisSettingsConcurrency concurrency = new IrisSettingsConcurrency();
|
||||
private IrisSettingsStudio studio = new IrisSettingsStudio();
|
||||
private IrisSettingsPerformance performance = new IrisSettingsPerformance();
|
||||
private IrisSettingsUpdater updater = new IrisSettingsUpdater();
|
||||
|
||||
public static int getThreadCount(int c) {
|
||||
if (System.getProperty("os.name").toLowerCase().contains("win"))
|
||||
return Runtime.getRuntime().availableProcessors();
|
||||
return switch (c) {
|
||||
case -1, -2, -4 -> Runtime.getRuntime().availableProcessors() / -c;
|
||||
case 0, 1, 2 -> 1;
|
||||
@@ -132,6 +137,7 @@ public class IrisSettings {
|
||||
@Data
|
||||
public static class IrisSettingsConcurrency {
|
||||
public int parallelism = -1;
|
||||
public boolean windowsFullPerformance = true;
|
||||
}
|
||||
|
||||
@Data
|
||||
@@ -144,6 +150,30 @@ public class IrisSettings {
|
||||
public int scriptLoaderCacheSize = 512;
|
||||
}
|
||||
|
||||
@Data
|
||||
public static class IrisSettingsUpdater {
|
||||
public double threadMultiplier = 2;
|
||||
public double chunkLoadSensitivity = 0.7;
|
||||
public MsRange emptyMsRange = new MsRange(80, 100);
|
||||
public MsRange defaultMsRange = new MsRange(20, 40);
|
||||
|
||||
public double getThreadMultiplier() {
|
||||
return Math.min(Math.abs(threadMultiplier), 0.1);
|
||||
}
|
||||
|
||||
public double getChunkLoadSensitivity() {
|
||||
return Math.min(chunkLoadSensitivity, 0.9);
|
||||
}
|
||||
}
|
||||
|
||||
@Data
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public static class MsRange {
|
||||
public int min = 20;
|
||||
public int max = 40;
|
||||
}
|
||||
|
||||
@Data
|
||||
public static class IrisSettingsGeneral {
|
||||
public boolean DoomsdayAnnihilationSelfDestructMode = false;
|
||||
|
||||
@@ -1,134 +0,0 @@
|
||||
/*
|
||||
* Iris is a World Generator for Minecraft Bukkit Servers
|
||||
* Copyright (c) 2022 Arcane Arts (Volmit Software)
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.volmit.iris.core.commands;
|
||||
|
||||
import com.volmit.iris.Iris;
|
||||
import com.volmit.iris.core.pregenerator.DeepSearchPregenerator;
|
||||
import com.volmit.iris.core.pregenerator.PregenTask;
|
||||
import com.volmit.iris.core.pregenerator.TurboPregenerator;
|
||||
import com.volmit.iris.core.tools.IrisToolbelt;
|
||||
import com.volmit.iris.util.data.Dimension;
|
||||
import com.volmit.iris.util.decree.DecreeExecutor;
|
||||
import com.volmit.iris.util.decree.annotations.Decree;
|
||||
import com.volmit.iris.util.decree.annotations.Param;
|
||||
import com.volmit.iris.util.format.C;
|
||||
import com.volmit.iris.util.math.Position2;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.World;
|
||||
import org.bukkit.util.Vector;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
|
||||
@Decree(name = "DeepSearch", aliases = "search", description = "Pregenerate your Iris worlds!")
|
||||
public class CommandDeepSearch implements DecreeExecutor {
|
||||
public String worldName;
|
||||
@Decree(description = "DeepSearch a world")
|
||||
public void start(
|
||||
@Param(description = "The radius of the pregen in blocks", aliases = "size")
|
||||
int radius,
|
||||
@Param(description = "The world to pregen", contextual = true)
|
||||
World world,
|
||||
@Param(aliases = "middle", description = "The center location of the pregen. Use \"me\" for your current location", defaultValue = "0,0")
|
||||
Vector center
|
||||
) {
|
||||
|
||||
worldName = world.getName();
|
||||
File worldDirectory = new File(Bukkit.getWorldContainer(), world.getName());
|
||||
File TurboFile = new File(worldDirectory, "DeepSearch.json");
|
||||
if (TurboFile.exists()) {
|
||||
if (DeepSearchPregenerator.getInstance() != null) {
|
||||
sender().sendMessage(C.BLUE + "DeepSearch is already in progress");
|
||||
Iris.info(C.YELLOW + "DeepSearch is already in progress");
|
||||
return;
|
||||
} else {
|
||||
try {
|
||||
TurboFile.delete();
|
||||
} catch (Exception e){
|
||||
Iris.error("Failed to delete the old instance file of DeepSearch!");
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
if (sender().isPlayer() && access() == null) {
|
||||
sender().sendMessage(C.RED + "The engine access for this world is null!");
|
||||
sender().sendMessage(C.RED + "Please make sure the world is loaded & the engine is initialized. Generate a new chunk, for example.");
|
||||
}
|
||||
|
||||
DeepSearchPregenerator.DeepSearchJob DeepSearchJob = DeepSearchPregenerator.DeepSearchJob.builder()
|
||||
.world(world)
|
||||
.radiusBlocks(radius)
|
||||
.position(0)
|
||||
.build();
|
||||
|
||||
File SearchGenFile = new File(worldDirectory, "DeepSearch.json");
|
||||
DeepSearchPregenerator pregenerator = new DeepSearchPregenerator(DeepSearchJob, SearchGenFile);
|
||||
pregenerator.start();
|
||||
|
||||
String msg = C.GREEN + "DeepSearch started in " + C.GOLD + worldName + C.GREEN + " of " + C.GOLD + (radius * 2) + C.GREEN + " by " + C.GOLD + (radius * 2) + C.GREEN + " blocks from " + C.GOLD + center.getX() + "," + center.getZ();
|
||||
sender().sendMessage(msg);
|
||||
Iris.info(msg);
|
||||
} catch (Throwable e) {
|
||||
sender().sendMessage(C.RED + "Epic fail. See console.");
|
||||
Iris.reportError(e);
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
@Decree(description = "Stop the active DeepSearch task", aliases = "x")
|
||||
public void stop(@Param(aliases = "world", description = "The world to pause") World world) throws IOException {
|
||||
DeepSearchPregenerator DeepSearchInstance = DeepSearchPregenerator.getInstance();
|
||||
File worldDirectory = new File(Bukkit.getWorldContainer(), world.getName());
|
||||
File turboFile = new File(worldDirectory, "DeepSearch.json");
|
||||
|
||||
if (DeepSearchInstance != null) {
|
||||
DeepSearchInstance.shutdownInstance(world);
|
||||
sender().sendMessage(C.LIGHT_PURPLE + "Closed Turbogen instance for " + world.getName());
|
||||
} else if (turboFile.exists() && turboFile.delete()) {
|
||||
sender().sendMessage(C.LIGHT_PURPLE + "Closed Turbogen instance for " + world.getName());
|
||||
} else if (turboFile.exists()) {
|
||||
Iris.error("Failed to delete the old instance file of Turbo Pregen!");
|
||||
} else {
|
||||
sender().sendMessage(C.YELLOW + "No active pregeneration tasks to stop");
|
||||
}
|
||||
}
|
||||
|
||||
@Decree(description = "Pause / continue the active pregeneration task", aliases = {"t", "resume", "unpause"})
|
||||
public void pause(
|
||||
@Param(aliases = "world", description = "The world to pause")
|
||||
World world
|
||||
) {
|
||||
if (TurboPregenerator.getInstance() != null) {
|
||||
TurboPregenerator.setPausedTurbo(world);
|
||||
sender().sendMessage(C.GREEN + "Paused/unpaused Turbo Pregen, now: " + (TurboPregenerator.isPausedTurbo(world) ? "Paused" : "Running") + ".");
|
||||
} else {
|
||||
File worldDirectory = new File(Bukkit.getWorldContainer(), world.getName());
|
||||
File TurboFile = new File(worldDirectory, "DeepSearch.json");
|
||||
if (TurboFile.exists()){
|
||||
TurboPregenerator.loadTurboGenerator(world.getName());
|
||||
sender().sendMessage(C.YELLOW + "Started DeepSearch back up!");
|
||||
} else {
|
||||
sender().sendMessage(C.YELLOW + "No active DeepSearch tasks to pause/unpause.");
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -23,19 +23,12 @@ import com.volmit.iris.core.ServerConfigurator;
|
||||
import com.volmit.iris.core.loader.IrisData;
|
||||
import com.volmit.iris.core.nms.INMS;
|
||||
import com.volmit.iris.core.nms.datapack.DataVersion;
|
||||
import com.volmit.iris.core.nms.v1X.NMSBinding1X;
|
||||
import com.volmit.iris.core.pregenerator.ChunkUpdater;
|
||||
import com.volmit.iris.core.service.IrisEngineSVC;
|
||||
import com.volmit.iris.core.tools.IrisConverter;
|
||||
import com.volmit.iris.core.tools.IrisPackBenchmarking;
|
||||
import com.volmit.iris.core.tools.IrisToolbelt;
|
||||
import com.volmit.iris.core.tools.IrisWorldAnalytics;
|
||||
import com.volmit.iris.engine.framework.Engine;
|
||||
import com.volmit.iris.engine.mantle.components.MantleObjectComponent;
|
||||
import com.volmit.iris.engine.object.IrisBiome;
|
||||
import com.volmit.iris.engine.object.IrisCave;
|
||||
import com.volmit.iris.engine.object.IrisDimension;
|
||||
import com.volmit.iris.engine.object.IrisEntity;
|
||||
import com.volmit.iris.util.data.Dimension;
|
||||
import com.volmit.iris.util.decree.DecreeExecutor;
|
||||
import com.volmit.iris.util.decree.DecreeOrigin;
|
||||
import com.volmit.iris.util.decree.annotations.Decree;
|
||||
@@ -44,24 +37,16 @@ import com.volmit.iris.util.format.C;
|
||||
import com.volmit.iris.util.format.Form;
|
||||
import com.volmit.iris.util.io.IO;
|
||||
import com.volmit.iris.util.mantle.TectonicPlate;
|
||||
import com.volmit.iris.util.math.Spiraler;
|
||||
import com.volmit.iris.util.math.Vector3d;
|
||||
import com.volmit.iris.util.nbt.mca.MCAFile;
|
||||
import com.volmit.iris.util.nbt.mca.MCAUtil;
|
||||
import com.volmit.iris.util.parallel.MultiBurst;
|
||||
import com.volmit.iris.util.plugin.VolmitSender;
|
||||
import io.lumine.mythic.bukkit.adapters.BukkitEntity;
|
||||
import net.jpountz.lz4.LZ4BlockInputStream;
|
||||
import net.jpountz.lz4.LZ4BlockOutputStream;
|
||||
import net.jpountz.lz4.LZ4FrameInputStream;
|
||||
import net.jpountz.lz4.LZ4FrameOutputStream;
|
||||
import org.apache.commons.lang.RandomStringUtils;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.Chunk;
|
||||
import org.bukkit.Location;
|
||||
import org.bukkit.World;
|
||||
import org.bukkit.entity.Creeper;
|
||||
import org.bukkit.entity.EntityType;
|
||||
import org.bukkit.*;
|
||||
|
||||
import java.io.*;
|
||||
import java.net.InetAddress;
|
||||
@@ -75,7 +60,6 @@ import java.util.zip.GZIPOutputStream;
|
||||
|
||||
@Decree(name = "Developer", origin = DecreeOrigin.BOTH, description = "Iris World Manager", aliases = {"dev"})
|
||||
public class CommandDeveloper implements DecreeExecutor {
|
||||
private CommandTurboPregen turboPregen;
|
||||
private CommandUpdater updater;
|
||||
|
||||
@Decree(description = "Get Loaded TectonicPlates Count", origin = DecreeOrigin.BOTH, sync = true)
|
||||
@@ -160,6 +144,17 @@ public class CommandDeveloper implements DecreeExecutor {
|
||||
|
||||
}
|
||||
|
||||
@Decree(description = "gets wg height")
|
||||
public void whatHeight() {
|
||||
Iris.info("test");
|
||||
sender().sendMessage("Height: " + player().getWorld().getHighestBlockAt(player().getLocation(), HeightMap.MOTION_BLOCKING).getY());
|
||||
}
|
||||
|
||||
@Decree(description = "check", aliases = {"ck"} )
|
||||
public void check() {
|
||||
sender().sendMessage("Data Pack Biome: " + INMS.get().getTrueBiomeBaseKey(player().getLocation()) + " (ID: " + INMS.get().getTrueBiomeBaseId(INMS.get().getTrueBiomeBase(player().getLocation())) + ")");
|
||||
}
|
||||
|
||||
@Decree(description = "Upgrade to another Minecraft version")
|
||||
public void upgrade(
|
||||
@Param(description = "The version to upgrade to", defaultValue = "latest") DataVersion version) {
|
||||
@@ -175,6 +170,7 @@ public class CommandDeveloper implements DecreeExecutor {
|
||||
File[] McaFiles = new File(world, "region").listFiles((dir, name) -> name.endsWith(".mca"));
|
||||
for (File mca : McaFiles) {
|
||||
MCAFile MCARegion = MCAUtil.read(mca);
|
||||
int i = 0;
|
||||
}
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
@@ -182,6 +178,19 @@ public class CommandDeveloper implements DecreeExecutor {
|
||||
|
||||
}
|
||||
|
||||
@Decree(description = "test")
|
||||
public void anl (
|
||||
@Param(description = "String") String world) {
|
||||
try {
|
||||
IrisWorldAnalytics a = new IrisWorldAnalytics(world);
|
||||
a.execute();
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
@Decree(description = "UnloadChunks for good reasons.")
|
||||
public void unloadchunks() {
|
||||
List<World> IrisWorlds = new ArrayList<>();
|
||||
|
||||
@@ -73,14 +73,12 @@ import static org.bukkit.Bukkit.getServer;
|
||||
public class CommandIris implements DecreeExecutor {
|
||||
private CommandStudio studio;
|
||||
private CommandPregen pregen;
|
||||
private CommandLazyPregen lazyPregen;
|
||||
private CommandSettings settings;
|
||||
private CommandObject object;
|
||||
private CommandJigsaw jigsaw;
|
||||
private CommandWhat what;
|
||||
private CommandEdit edit;
|
||||
private CommandFind find;
|
||||
private CommandSupport support;
|
||||
private CommandDeveloper developer;
|
||||
public static boolean worldCreation = false;
|
||||
String WorldEngine;
|
||||
|
||||
@@ -60,7 +60,7 @@ public class CommandJigsaw implements DecreeExecutor {
|
||||
try {
|
||||
var world = world();
|
||||
WorldObjectPlacer placer = new WorldObjectPlacer(world);
|
||||
PlannedStructure ps = new PlannedStructure(structure, new IrisPosition(player().getLocation().add(0, world.getMinHeight(), 0)), new RNG());
|
||||
PlannedStructure ps = new PlannedStructure(structure, new IrisPosition(player().getLocation().add(0, world.getMinHeight(), 0)), new RNG(), true);
|
||||
VolmitSender sender = sender();
|
||||
sender.sendMessage(C.GREEN + "Generated " + ps.getPieces().size() + " pieces in " + Form.duration(p.getMilliseconds(), 2));
|
||||
ps.place(placer, failed -> sender.sendMessage(failed ? C.GREEN + "Placed the structure!" : C.RED + "Failed to place the structure!"));
|
||||
|
||||
@@ -1,121 +0,0 @@
|
||||
/*
|
||||
* Iris is a World Generator for Minecraft Bukkit Servers
|
||||
* Copyright (c) 2022 Arcane Arts (Volmit Software)
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.volmit.iris.core.commands;
|
||||
|
||||
import com.volmit.iris.Iris;
|
||||
import com.volmit.iris.core.IrisSettings;
|
||||
import com.volmit.iris.core.gui.PregeneratorJob;
|
||||
import com.volmit.iris.core.pregenerator.LazyPregenerator;
|
||||
import com.volmit.iris.core.pregenerator.PregenTask;
|
||||
import com.volmit.iris.core.tools.IrisToolbelt;
|
||||
import com.volmit.iris.util.decree.DecreeExecutor;
|
||||
import com.volmit.iris.util.decree.annotations.Decree;
|
||||
import com.volmit.iris.util.decree.annotations.Param;
|
||||
import com.volmit.iris.util.format.C;
|
||||
import com.volmit.iris.util.math.Position2;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.World;
|
||||
import org.bukkit.util.Vector;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
|
||||
@Decree(name = "lazypregen", aliases = "lazy", description = "Pregenerate your Iris worlds!")
|
||||
public class CommandLazyPregen implements DecreeExecutor {
|
||||
public String worldName;
|
||||
@Decree(description = "Pregenerate a world")
|
||||
public void start(
|
||||
@Param(description = "The radius of the pregen in blocks", aliases = "size")
|
||||
int radius,
|
||||
@Param(description = "The world to pregen", contextual = true)
|
||||
World world,
|
||||
@Param(aliases = "middle", description = "The center location of the pregen. Use \"me\" for your current location", defaultValue = "0,0")
|
||||
Vector center,
|
||||
@Param(aliases = "maxcpm", description = "Limit the chunks per minute the pregen will generate", defaultValue = "999999999")
|
||||
int cpm,
|
||||
@Param(aliases = "silent", description = "Silent generation", defaultValue = "false")
|
||||
boolean silent
|
||||
) {
|
||||
|
||||
worldName = world.getName();
|
||||
File worldDirectory = new File(Bukkit.getWorldContainer(), world.getName());
|
||||
File lazyFile = new File(worldDirectory, "lazygen.json");
|
||||
if (lazyFile.exists()) {
|
||||
sender().sendMessage(C.BLUE + "Lazy pregen is already in progress");
|
||||
Iris.info(C.YELLOW + "Lazy pregen is already in progress");
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
if (sender().isPlayer() && access() == null) {
|
||||
sender().sendMessage(C.RED + "The engine access for this world is null!");
|
||||
sender().sendMessage(C.RED + "Please make sure the world is loaded & the engine is initialized. Generate a new chunk, for example.");
|
||||
}
|
||||
|
||||
LazyPregenerator.LazyPregenJob pregenJob = LazyPregenerator.LazyPregenJob.builder()
|
||||
.world(worldName)
|
||||
.healingPosition(0)
|
||||
.healing(false)
|
||||
.chunksPerMinute(cpm)
|
||||
.radiusBlocks(radius)
|
||||
.position(0)
|
||||
.silent(silent)
|
||||
.build();
|
||||
|
||||
File lazyGenFile = new File(worldDirectory, "lazygen.json");
|
||||
LazyPregenerator pregenerator = new LazyPregenerator(pregenJob, lazyGenFile);
|
||||
pregenerator.start();
|
||||
|
||||
String msg = C.GREEN + "LazyPregen started in " + C.GOLD + worldName + C.GREEN + " of " + C.GOLD + (radius * 2) + C.GREEN + " by " + C.GOLD + (radius * 2) + C.GREEN + " blocks from " + C.GOLD + center.getX() + "," + center.getZ();
|
||||
sender().sendMessage(msg);
|
||||
Iris.info(msg);
|
||||
} catch (Throwable e) {
|
||||
sender().sendMessage(C.RED + "Epic fail. See console.");
|
||||
Iris.reportError(e);
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
@Decree(description = "Stop the active pregeneration task", aliases = "x")
|
||||
public void stop(
|
||||
@Param(aliases = "world", description = "The world to pause")
|
||||
World world
|
||||
) throws IOException {
|
||||
if (LazyPregenerator.getInstance() != null) {
|
||||
LazyPregenerator.getInstance().shutdownInstance(world);
|
||||
sender().sendMessage(C.LIGHT_PURPLE + "Closed lazygen instance for " + world.getName());
|
||||
} else {
|
||||
sender().sendMessage(C.YELLOW + "No active pregeneration tasks to stop");
|
||||
}
|
||||
}
|
||||
|
||||
@Decree(description = "Pause / continue the active pregeneration task", aliases = {"t", "resume", "unpause"})
|
||||
public void pause(
|
||||
@Param(aliases = "world", description = "The world to pause")
|
||||
World world
|
||||
) {
|
||||
if (LazyPregenerator.getInstance() != null) {
|
||||
LazyPregenerator.getInstance().setPausedLazy(world);
|
||||
sender().sendMessage(C.GREEN + "Paused/unpaused Lazy Pregen, now: " + (LazyPregenerator.getInstance().isPausedLazy(world) ? "Paused" : "Running") + ".");
|
||||
} else {
|
||||
sender().sendMessage(C.YELLOW + "No active Lazy Pregen tasks to pause/unpause.");
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -306,7 +306,7 @@ public class CommandStudio implements DecreeExecutor {
|
||||
Inventory inv = Bukkit.createInventory(null, 27 * 2);
|
||||
|
||||
try {
|
||||
engine().addItems(true, inv, RNG.r, tables, InventorySlotType.STORAGE, player().getLocation().getBlockX(), player().getLocation().getBlockY(), player().getLocation().getBlockZ(), 1);
|
||||
engine().addItems(true, inv, RNG.r, tables, InventorySlotType.STORAGE, player().getWorld(), player().getLocation().getBlockX(), player().getLocation().getBlockY(), player().getLocation().getBlockZ(), 1);
|
||||
} catch (Throwable e) {
|
||||
Iris.reportError(e);
|
||||
sender().sendMessage(C.RED + "Cannot add items to virtual inventory because of: " + e.getMessage());
|
||||
@@ -329,7 +329,7 @@ public class CommandStudio implements DecreeExecutor {
|
||||
inv.clear();
|
||||
}
|
||||
|
||||
engine().addItems(true, inv, new RNG(RNG.r.imax()), tables, InventorySlotType.STORAGE, player().getLocation().getBlockX(), player().getLocation().getBlockY(), player().getLocation().getBlockZ(), 1);
|
||||
engine().addItems(true, inv, new RNG(RNG.r.imax()), tables, InventorySlotType.STORAGE, player().getWorld(), player().getLocation().getBlockX(), player().getLocation().getBlockY(), player().getLocation().getBlockZ(), 1);
|
||||
}, 0, fast ? 5 : 35));
|
||||
|
||||
sender().sendMessage(C.GREEN + "Opening inventory now!");
|
||||
|
||||
@@ -1,82 +0,0 @@
|
||||
/*
|
||||
* Iris is a World Generator for Minecraft Bukkit Servers
|
||||
* Copyright (c) 2022 Arcane Arts (Volmit Software)
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.volmit.iris.core.commands;
|
||||
|
||||
import com.volmit.iris.Iris;
|
||||
import com.volmit.iris.core.loader.IrisData;
|
||||
import com.volmit.iris.core.pregenerator.ChunkUpdater;
|
||||
import com.volmit.iris.core.service.IrisEngineSVC;
|
||||
import com.volmit.iris.core.tools.IrisPackBenchmarking;
|
||||
import com.volmit.iris.core.tools.IrisToolbelt;
|
||||
import com.volmit.iris.engine.framework.Engine;
|
||||
import com.volmit.iris.engine.object.IrisDimension;
|
||||
import com.volmit.iris.util.collection.KList;
|
||||
import com.volmit.iris.util.decree.DecreeExecutor;
|
||||
import com.volmit.iris.util.decree.DecreeOrigin;
|
||||
import com.volmit.iris.util.decree.annotations.Decree;
|
||||
import com.volmit.iris.util.decree.annotations.Param;
|
||||
import com.volmit.iris.util.format.C;
|
||||
import com.volmit.iris.util.format.Form;
|
||||
import com.volmit.iris.util.io.IO;
|
||||
import com.volmit.iris.util.mantle.TectonicPlate;
|
||||
import com.volmit.iris.util.misc.Hastebin;
|
||||
import com.volmit.iris.util.misc.Platform;
|
||||
import com.volmit.iris.util.misc.getHardware;
|
||||
import com.volmit.iris.util.nbt.mca.MCAFile;
|
||||
import com.volmit.iris.util.nbt.mca.MCAUtil;
|
||||
import com.volmit.iris.util.plugin.VolmitSender;
|
||||
import net.jpountz.lz4.LZ4BlockInputStream;
|
||||
import net.jpountz.lz4.LZ4BlockOutputStream;
|
||||
import net.jpountz.lz4.LZ4FrameInputStream;
|
||||
import net.jpountz.lz4.LZ4FrameOutputStream;
|
||||
import org.apache.commons.lang.RandomStringUtils;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.Chunk;
|
||||
import org.bukkit.World;
|
||||
import oshi.SystemInfo;
|
||||
|
||||
import java.io.*;
|
||||
import java.net.InetAddress;
|
||||
import java.net.NetworkInterface;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.zip.GZIPInputStream;
|
||||
import java.util.zip.GZIPOutputStream;
|
||||
|
||||
@Decree(name = "Support", origin = DecreeOrigin.BOTH, description = "Iris World Manager", aliases = {"support"})
|
||||
public class CommandSupport implements DecreeExecutor {
|
||||
|
||||
@Decree(description = "report")
|
||||
public void report() {
|
||||
try {
|
||||
if (sender().isPlayer()) sender().sendMessage(C.GOLD + "Creating report..");
|
||||
if (!sender().isPlayer()) Iris.info(C.GOLD + "Creating report..");
|
||||
Hastebin.enviornment(sender());
|
||||
|
||||
} catch (Exception e) {
|
||||
Iris.info(C.RED + "Something went wrong: ");
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -1,131 +0,0 @@
|
||||
/*
|
||||
* Iris is a World Generator for Minecraft Bukkit Servers
|
||||
* Copyright (c) 2022 Arcane Arts (Volmit Software)
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.volmit.iris.core.commands;
|
||||
|
||||
import com.volmit.iris.Iris;
|
||||
import com.volmit.iris.core.pregenerator.LazyPregenerator;
|
||||
import com.volmit.iris.core.pregenerator.TurboPregenerator;
|
||||
import com.volmit.iris.core.pregenerator.TurboPregenerator;
|
||||
import com.volmit.iris.util.decree.DecreeExecutor;
|
||||
import com.volmit.iris.util.decree.annotations.Decree;
|
||||
import com.volmit.iris.util.decree.annotations.Param;
|
||||
import com.volmit.iris.util.format.C;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.World;
|
||||
import org.bukkit.util.Vector;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
|
||||
@Decree(name = "turbopregen", aliases = "turbo", description = "Pregenerate your Iris worlds!")
|
||||
public class CommandTurboPregen implements DecreeExecutor {
|
||||
public String worldName;
|
||||
@Decree(description = "Pregenerate a world")
|
||||
public void start(
|
||||
@Param(description = "The radius of the pregen in blocks", aliases = "size")
|
||||
int radius,
|
||||
@Param(description = "The world to pregen", contextual = true)
|
||||
World world,
|
||||
@Param(aliases = "middle", description = "The center location of the pregen. Use \"me\" for your current location", defaultValue = "0,0")
|
||||
Vector center
|
||||
) {
|
||||
|
||||
worldName = world.getName();
|
||||
File worldDirectory = new File(Bukkit.getWorldContainer(), world.getName());
|
||||
File TurboFile = new File(worldDirectory, "turbogen.json");
|
||||
if (TurboFile.exists()) {
|
||||
if (TurboPregenerator.getInstance() != null) {
|
||||
sender().sendMessage(C.BLUE + "Turbo pregen is already in progress");
|
||||
Iris.info(C.YELLOW + "Turbo pregen is already in progress");
|
||||
return;
|
||||
} else {
|
||||
try {
|
||||
TurboFile.delete();
|
||||
} catch (Exception e){
|
||||
Iris.error("Failed to delete the old instance file of Turbo Pregen!");
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
if (sender().isPlayer() && access() == null) {
|
||||
sender().sendMessage(C.RED + "The engine access for this world is null!");
|
||||
sender().sendMessage(C.RED + "Please make sure the world is loaded & the engine is initialized. Generate a new chunk, for example.");
|
||||
}
|
||||
|
||||
TurboPregenerator.TurboPregenJob pregenJob = TurboPregenerator.TurboPregenJob.builder()
|
||||
.world(worldName)
|
||||
.radiusBlocks(radius)
|
||||
.position(0)
|
||||
.build();
|
||||
|
||||
File TurboGenFile = new File(worldDirectory, "turbogen.json");
|
||||
TurboPregenerator pregenerator = new TurboPregenerator(pregenJob, TurboGenFile);
|
||||
pregenerator.start();
|
||||
|
||||
String msg = C.GREEN + "TurboPregen started in " + C.GOLD + worldName + C.GREEN + " of " + C.GOLD + (radius * 2) + C.GREEN + " by " + C.GOLD + (radius * 2) + C.GREEN + " blocks from " + C.GOLD + center.getX() + "," + center.getZ();
|
||||
sender().sendMessage(msg);
|
||||
Iris.info(msg);
|
||||
} catch (Throwable e) {
|
||||
sender().sendMessage(C.RED + "Epic fail. See console.");
|
||||
Iris.reportError(e);
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
@Decree(description = "Stop the active pregeneration task", aliases = "x")
|
||||
public void stop(@Param(aliases = "world", description = "The world to pause") World world) throws IOException {
|
||||
TurboPregenerator turboPregenInstance = TurboPregenerator.getInstance();
|
||||
File worldDirectory = new File(Bukkit.getWorldContainer(), world.getName());
|
||||
File turboFile = new File(worldDirectory, "turbogen.json");
|
||||
|
||||
if (turboPregenInstance != null) {
|
||||
turboPregenInstance.shutdownInstance(world);
|
||||
sender().sendMessage(C.LIGHT_PURPLE + "Closed Turbogen instance for " + world.getName());
|
||||
} else if (turboFile.exists() && turboFile.delete()) {
|
||||
sender().sendMessage(C.LIGHT_PURPLE + "Closed Turbogen instance for " + world.getName());
|
||||
} else if (turboFile.exists()) {
|
||||
Iris.error("Failed to delete the old instance file of Turbo Pregen!");
|
||||
} else {
|
||||
sender().sendMessage(C.YELLOW + "No active pregeneration tasks to stop");
|
||||
}
|
||||
}
|
||||
|
||||
@Decree(description = "Pause / continue the active pregeneration task", aliases = {"t", "resume", "unpause"})
|
||||
public void pause(
|
||||
@Param(aliases = "world", description = "The world to pause")
|
||||
World world
|
||||
) {
|
||||
if (TurboPregenerator.getInstance() != null) {
|
||||
TurboPregenerator.setPausedTurbo(world);
|
||||
sender().sendMessage(C.GREEN + "Paused/unpaused Turbo Pregen, now: " + (TurboPregenerator.isPausedTurbo(world) ? "Paused" : "Running") + ".");
|
||||
} else {
|
||||
File worldDirectory = new File(Bukkit.getWorldContainer(), world.getName());
|
||||
File TurboFile = new File(worldDirectory, "turbogen.json");
|
||||
if (TurboFile.exists()){
|
||||
TurboPregenerator.loadTurboGenerator(world.getName());
|
||||
sender().sendMessage(C.YELLOW + "Started Turbo Pregen back up!");
|
||||
} else {
|
||||
sender().sendMessage(C.YELLOW + "No active Turbo Pregen tasks to pause/unpause.");
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -43,6 +43,10 @@ public class CommandUpdater implements DecreeExecutor {
|
||||
sender().sendMessage(C.GOLD + "This is not an Iris world");
|
||||
return;
|
||||
}
|
||||
if (chunkUpdater != null) {
|
||||
chunkUpdater.stop();
|
||||
}
|
||||
|
||||
chunkUpdater = new ChunkUpdater(world);
|
||||
if (sender().isPlayer()) {
|
||||
sender().sendMessage(C.GREEN + "Updating " + world.getName() + C.GRAY + " Total chunks: " + Form.f(chunkUpdater.getChunks()));
|
||||
@@ -53,14 +57,7 @@ public class CommandUpdater implements DecreeExecutor {
|
||||
}
|
||||
|
||||
@Decree(description = "Pause the updater")
|
||||
public void pause(
|
||||
@Param(description = "World to pause the Updater at")
|
||||
World world
|
||||
) {
|
||||
if (!IrisToolbelt.isIrisWorld(world)) {
|
||||
sender().sendMessage(C.GOLD + "This is not an Iris world");
|
||||
return;
|
||||
}
|
||||
public void pause( ) {
|
||||
if (chunkUpdater == null) {
|
||||
sender().sendMessage(C.GOLD + "You cant pause something that doesnt exist?");
|
||||
return;
|
||||
@@ -68,40 +65,32 @@ public class CommandUpdater implements DecreeExecutor {
|
||||
boolean status = chunkUpdater.pause();
|
||||
if (sender().isPlayer()) {
|
||||
if (status) {
|
||||
sender().sendMessage(C.IRIS + "Paused task for: " + C.GRAY + world.getName());
|
||||
sender().sendMessage(C.IRIS + "Paused task for: " + C.GRAY + chunkUpdater.getName());
|
||||
} else {
|
||||
sender().sendMessage(C.IRIS + "Unpause task for: " + C.GRAY + world.getName());
|
||||
sender().sendMessage(C.IRIS + "Unpause task for: " + C.GRAY + chunkUpdater.getName());
|
||||
}
|
||||
} else {
|
||||
if (status) {
|
||||
Iris.info(C.IRIS + "Paused task for: " + C.GRAY + world.getName());
|
||||
Iris.info(C.IRIS + "Paused task for: " + C.GRAY + chunkUpdater.getName());
|
||||
} else {
|
||||
Iris.info(C.IRIS + "Unpause task for: " + C.GRAY + world.getName());
|
||||
Iris.info(C.IRIS + "Unpause task for: " + C.GRAY + chunkUpdater.getName());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Decree(description = "Stops the updater")
|
||||
public void stop(
|
||||
@Param(description = "World to stop the Updater at")
|
||||
World world
|
||||
) {
|
||||
if (!IrisToolbelt.isIrisWorld(world)) {
|
||||
sender().sendMessage(C.GOLD + "This is not an Iris world");
|
||||
return;
|
||||
}
|
||||
public void stop() {
|
||||
if (chunkUpdater == null) {
|
||||
sender().sendMessage(C.GOLD + "You cant stop something that doesnt exist?");
|
||||
return;
|
||||
}
|
||||
if (sender().isPlayer()) {
|
||||
sender().sendMessage("Stopping Updater for: " + C.GRAY + world.getName());
|
||||
sender().sendMessage("Stopping Updater for: " + C.GRAY + chunkUpdater.getName());
|
||||
} else {
|
||||
Iris.info("Stopping Updater for: " + C.GRAY + world.getName());
|
||||
Iris.info("Stopping Updater for: " + C.GRAY + chunkUpdater.getName());
|
||||
}
|
||||
chunkUpdater.stop();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -275,9 +275,9 @@ public class NoiseExplorerGUI extends JPanel implements MouseWheelListener, List
|
||||
n = n > 1 ? 1 : n < 0 ? 0 : n;
|
||||
|
||||
try {
|
||||
//Color color = colorMode ? Color.getHSBColor((float) (n), 1f - (float) (n * n * n * n * n * n), 1f - (float) n) : Color.getHSBColor(0f, 0f, (float) n);
|
||||
Color color = colorMode ? Color.getHSBColor((float) (n), 1f - (float) (n * n * n * n * n * n), 1f - (float) n) : Color.getHSBColor(0f, 0f, (float) n);
|
||||
//Color color = colorMode ? Color.getHSBColor((float) (n), (float) (n * n * n * n * n * n), (float) n) : Color.getHSBColor(0f, 0f, (float) n);
|
||||
Color color = colorMode ? Color.getHSBColor((float) n, (float) (n * n * n * n * n * n), (float) n) : Color.getHSBColor(0f, 0f, (float) n);
|
||||
//Color color = colorMode ? Color.getHSBColor((float) n, (float) (n * n * n * n * n * n), (float) n) : Color.getHSBColor(0f, 0f, (float) n);
|
||||
|
||||
int rgb = color.getRGB();
|
||||
img.setRGB(xx, z, rgb);
|
||||
|
||||
@@ -0,0 +1,166 @@
|
||||
/*
|
||||
* Iris is a World Generator for Minecraft Bukkit Servers
|
||||
* Copyright (c) 2022 Arcane Arts (Volmit Software)
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.volmit.iris.core.link;
|
||||
|
||||
import com.volmit.iris.Iris;
|
||||
import com.volmit.iris.core.nms.INMS;
|
||||
import com.volmit.iris.core.nms.container.BiomeColor;
|
||||
import com.volmit.iris.core.service.ExternalDataSVC;
|
||||
import com.volmit.iris.engine.data.cache.Cache;
|
||||
import com.volmit.iris.engine.framework.Engine;
|
||||
import com.volmit.iris.util.collection.KList;
|
||||
import com.volmit.iris.util.collection.KMap;
|
||||
import com.volmit.iris.util.data.B;
|
||||
import com.volmit.iris.util.data.IrisBlockData;
|
||||
import com.volmit.iris.util.math.RNG;
|
||||
import io.lumine.mythic.bukkit.BukkitAdapter;
|
||||
import io.lumine.mythic.bukkit.utils.serialize.Chroma;
|
||||
import io.lumine.mythiccrucible.MythicCrucible;
|
||||
import io.lumine.mythiccrucible.items.CrucibleItem;
|
||||
import io.lumine.mythiccrucible.items.ItemManager;
|
||||
import io.lumine.mythiccrucible.items.blocks.CustomBlockItemContext;
|
||||
import io.lumine.mythiccrucible.items.furniture.FurnitureItemContext;
|
||||
import org.bukkit.block.Block;
|
||||
import org.bukkit.block.BlockFace;
|
||||
import org.bukkit.block.data.BlockData;
|
||||
import org.bukkit.inventory.ItemStack;
|
||||
|
||||
import java.util.MissingResourceException;
|
||||
import java.util.Optional;
|
||||
|
||||
public class MythicCrucibleDataProvider extends ExternalDataProvider {
|
||||
|
||||
private ItemManager itemManager;
|
||||
|
||||
public MythicCrucibleDataProvider() {
|
||||
super("MythicCrucible");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void init() {
|
||||
Iris.info("Setting up MythicCrucible Link...");
|
||||
try {
|
||||
this.itemManager = MythicCrucible.inst().getItemManager();
|
||||
} catch (Exception e) {
|
||||
Iris.error("Failed to set up MythicCrucible Link: Unable to fetch MythicCrucible instance!");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public BlockData getBlockData(Identifier blockId, KMap<String, String> state) throws MissingResourceException {
|
||||
CrucibleItem crucibleItem = this.itemManager.getItem(blockId.key())
|
||||
.orElseThrow(() -> new MissingResourceException("Failed to find BlockData!", blockId.namespace(), blockId.key()));
|
||||
CustomBlockItemContext blockItemContext = crucibleItem.getBlockData();
|
||||
FurnitureItemContext furnitureItemContext = crucibleItem.getFurnitureData();
|
||||
if (furnitureItemContext != null) {
|
||||
return new IrisBlockData(B.getAir(), ExternalDataSVC.buildState(blockId, state));
|
||||
} else if (blockItemContext != null) {
|
||||
return blockItemContext.getBlockData();
|
||||
}
|
||||
throw new MissingResourceException("Failed to find BlockData!", blockId.namespace(), blockId.key());
|
||||
}
|
||||
|
||||
@Override
|
||||
public ItemStack getItemStack(Identifier itemId, KMap<String, Object> customNbt) throws MissingResourceException {
|
||||
Optional<CrucibleItem> opt = this.itemManager.getItem(itemId.key());
|
||||
return BukkitAdapter.adapt(opt.orElseThrow(() ->
|
||||
new MissingResourceException("Failed to find ItemData!", itemId.namespace(), itemId.key()))
|
||||
.getMythicItem()
|
||||
.generateItemStack(1));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Identifier[] getBlockTypes() {
|
||||
KList<Identifier> names = new KList<>();
|
||||
for (CrucibleItem item : this.itemManager.getItems()) {
|
||||
if (item.getBlockData() == null) continue;
|
||||
try {
|
||||
Identifier key = new Identifier("crucible", item.getInternalName());
|
||||
if (getBlockData(key) != null) {
|
||||
Iris.info("getBlockTypes: Block loaded '" + item.getInternalName() + "'");
|
||||
names.add(key);
|
||||
}
|
||||
} catch (MissingResourceException ignored) {}
|
||||
}
|
||||
return names.toArray(new Identifier[0]);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Identifier[] getItemTypes() {
|
||||
KList<Identifier> names = new KList<>();
|
||||
for (CrucibleItem item : this.itemManager.getItems()) {
|
||||
try {
|
||||
Identifier key = new Identifier("crucible", item.getInternalName());
|
||||
if (getItemStack(key) != null) {
|
||||
Iris.info("getItemTypes: Item loaded '" + item.getInternalName() + "'");
|
||||
names.add(key);
|
||||
}
|
||||
} catch (MissingResourceException ignored) {}
|
||||
}
|
||||
return names.toArray(new Identifier[0]);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void processUpdate(Engine engine, Block block, Identifier blockId) {
|
||||
var pair = ExternalDataSVC.parseState(blockId);
|
||||
var state = pair.getB();
|
||||
blockId = pair.getA();
|
||||
|
||||
Optional<CrucibleItem> item = itemManager.getItem(blockId.key());
|
||||
if (item.isEmpty()) return;
|
||||
FurnitureItemContext furniture = item.get().getFurnitureData();
|
||||
if (furniture == null) return;
|
||||
|
||||
float yaw = 0;
|
||||
BlockFace face = BlockFace.NORTH;
|
||||
long seed = engine.getSeedManager().getSeed() + Cache.key(block.getX(), block.getZ()) + block.getY();
|
||||
RNG rng = new RNG(seed);
|
||||
if ("true".equals(state.get("randomYaw"))) {
|
||||
yaw = rng.f(0, 360);
|
||||
} else if (state.containsKey("yaw")) {
|
||||
yaw = Float.parseFloat(state.get("yaw"));
|
||||
}
|
||||
if ("true".equals(state.get("randomFace"))) {
|
||||
BlockFace[] faces = BlockFace.values();
|
||||
face = faces[rng.i(0, faces.length - 1)];
|
||||
} else if (state.containsKey("face")) {
|
||||
face = BlockFace.valueOf(state.get("face").toUpperCase());
|
||||
}
|
||||
if (face == BlockFace.SELF) {
|
||||
face = BlockFace.NORTH;
|
||||
}
|
||||
|
||||
BiomeColor type = null;
|
||||
Chroma color = null;
|
||||
try {
|
||||
type = BiomeColor.valueOf(state.get("matchBiome").toUpperCase());
|
||||
} catch (NullPointerException | IllegalArgumentException ignored) {}
|
||||
if (type != null) {
|
||||
var biomeColor = INMS.get().getBiomeColor(block.getLocation(), type);
|
||||
if (biomeColor == null) return;
|
||||
color = Chroma.of(biomeColor.getRGB());
|
||||
}
|
||||
furniture.place(block, face, yaw, color);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isValidProvider(Identifier key, boolean isItem) {
|
||||
return key.namespace().equalsIgnoreCase("crucible");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
package com.volmit.iris.core.nms;
|
||||
|
||||
import org.bukkit.Chunk;
|
||||
import org.bukkit.World;
|
||||
import org.bukkit.event.Listener;
|
||||
import org.bukkit.generator.ChunkGenerator;
|
||||
|
||||
public interface IMemoryWorld extends Listener, AutoCloseable {
|
||||
|
||||
World getBukkit();
|
||||
|
||||
Chunk getChunk(int x, int z);
|
||||
|
||||
ChunkGenerator.ChunkData getChunkData(int x, int z);
|
||||
}
|
||||
@@ -18,6 +18,7 @@
|
||||
|
||||
package com.volmit.iris.core.nms;
|
||||
|
||||
import com.volmit.iris.Iris;
|
||||
import com.volmit.iris.core.nms.container.BiomeColor;
|
||||
import com.volmit.iris.core.nms.datapack.DataVersion;
|
||||
import com.volmit.iris.engine.framework.Engine;
|
||||
@@ -30,6 +31,7 @@ import com.volmit.iris.util.nbt.mca.palette.MCAPaletteAccess;
|
||||
import com.volmit.iris.util.nbt.tag.CompoundTag;
|
||||
import org.bukkit.*;
|
||||
import org.bukkit.block.Biome;
|
||||
import org.bukkit.block.data.BlockData;
|
||||
import org.bukkit.entity.Dolphin;
|
||||
import org.bukkit.entity.Entity;
|
||||
import org.bukkit.entity.EntityType;
|
||||
@@ -40,6 +42,7 @@ import org.bukkit.inventory.ItemStack;
|
||||
|
||||
import java.awt.*;
|
||||
import java.awt.Color;
|
||||
import java.io.IOException;
|
||||
|
||||
public interface INMSBinding {
|
||||
boolean hasTile(Material material);
|
||||
@@ -96,6 +99,10 @@ public interface INMSBinding {
|
||||
|
||||
int countCustomBiomes();
|
||||
|
||||
default boolean setBlock(World world, int x, int y, int z, BlockData data, int flag, int updateDepth) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
void forceBiomeInto(int x, int y, int z, Object somethingVeryDirty, ChunkGenerator.BiomeGrid chunk);
|
||||
|
||||
default boolean supportsDataPacks() {
|
||||
@@ -125,4 +132,20 @@ public interface INMSBinding {
|
||||
}
|
||||
|
||||
KList<String> getStructureKeys();
|
||||
|
||||
default BlockData getBlockData(CompoundTag tag) {
|
||||
Iris.error("Unsupported version!");
|
||||
return null;
|
||||
};
|
||||
|
||||
default IMemoryWorld createMemoryWorld(WorldCreator creator) throws IOException {
|
||||
return createMemoryWorld(switch (creator.environment()) {
|
||||
case NORMAL -> NamespacedKey.minecraft("overworld");
|
||||
case NETHER -> NamespacedKey.minecraft("the_nether");
|
||||
case THE_END -> NamespacedKey.minecraft("the_end");
|
||||
default -> throw new IllegalArgumentException("Illegal dimension (" + creator.environment() + ")");
|
||||
}, creator);
|
||||
}
|
||||
|
||||
IMemoryWorld createMemoryWorld(NamespacedKey levelType, WorldCreator creator) throws IOException;
|
||||
}
|
||||
|
||||
@@ -19,6 +19,7 @@
|
||||
package com.volmit.iris.core.nms.v1X;
|
||||
|
||||
import com.volmit.iris.Iris;
|
||||
import com.volmit.iris.core.nms.IMemoryWorld;
|
||||
import com.volmit.iris.core.nms.INMSBinding;
|
||||
import com.volmit.iris.core.nms.container.BiomeColor;
|
||||
import com.volmit.iris.core.nms.container.BlockPos;
|
||||
@@ -32,6 +33,7 @@ import com.volmit.iris.util.nbt.mca.palette.MCAPaletteAccess;
|
||||
import com.volmit.iris.util.nbt.tag.CompoundTag;
|
||||
import org.bukkit.*;
|
||||
import org.bukkit.block.Biome;
|
||||
import org.bukkit.block.data.BlockData;
|
||||
import org.bukkit.entity.Dolphin;
|
||||
import org.bukkit.entity.Entity;
|
||||
import org.bukkit.entity.EntityType;
|
||||
@@ -42,6 +44,7 @@ import org.bukkit.inventory.ItemStack;
|
||||
|
||||
import java.awt.*;
|
||||
import java.awt.Color;
|
||||
import java.io.IOException;
|
||||
|
||||
public class NMSBinding1X implements INMSBinding {
|
||||
private static final boolean supportsCustomHeight = testCustomHeight();
|
||||
@@ -120,6 +123,11 @@ public class NMSBinding1X implements INMSBinding {
|
||||
return new KList<>(list);
|
||||
}
|
||||
|
||||
@Override
|
||||
public IMemoryWorld createMemoryWorld(NamespacedKey levelType, WorldCreator creator) throws IOException {
|
||||
throw new IOException("Unsupported version!");
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompoundTag serializeEntity(Entity location) {
|
||||
return null;
|
||||
|
||||
@@ -1,16 +1,21 @@
|
||||
package com.volmit.iris.core.pregenerator;
|
||||
|
||||
import com.volmit.iris.Iris;
|
||||
import com.volmit.iris.core.IrisSettings;
|
||||
import com.volmit.iris.core.nms.container.Pair;
|
||||
import com.volmit.iris.core.tools.IrisToolbelt;
|
||||
import com.volmit.iris.engine.data.cache.Cache;
|
||||
import com.volmit.iris.engine.framework.Engine;
|
||||
import com.volmit.iris.util.collection.KList;
|
||||
import com.volmit.iris.util.collection.KMap;
|
||||
import com.volmit.iris.util.format.Form;
|
||||
import com.volmit.iris.util.mantle.MantleFlag;
|
||||
import com.volmit.iris.util.math.M;
|
||||
import com.volmit.iris.util.math.Position2;
|
||||
import com.volmit.iris.util.math.RollingSequence;
|
||||
import com.volmit.iris.util.math.Spiraler;
|
||||
import com.volmit.iris.util.profile.LoadBalancer;
|
||||
import com.volmit.iris.util.scheduling.J;
|
||||
import io.papermc.lib.PaperLib;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.Chunk;
|
||||
import org.bukkit.World;
|
||||
|
||||
@@ -23,53 +28,40 @@ import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.concurrent.atomic.AtomicLong;
|
||||
|
||||
public class ChunkUpdater {
|
||||
private AtomicBoolean paused;
|
||||
private AtomicBoolean cancelled;
|
||||
private KMap<Chunk, Long> lastUse;
|
||||
private final RollingSequence chunksPerSecond;
|
||||
private final AtomicInteger worldheightsize;
|
||||
private final AtomicInteger worldwidthsize;
|
||||
private final AtomicInteger totalChunks;
|
||||
private final AtomicInteger totalMaxChunks;
|
||||
private final AtomicInteger totalMcaregions;
|
||||
private final AtomicInteger position;
|
||||
private AtomicInteger chunksProcessed;
|
||||
private AtomicInteger chunksUpdated;
|
||||
private AtomicLong startTime;
|
||||
private ExecutorService executor;
|
||||
private ExecutorService chunkExecutor;
|
||||
private ScheduledExecutorService scheduler;
|
||||
private CompletableFuture future;
|
||||
private CountDownLatch latch;
|
||||
private final Object pauseLock;
|
||||
private final AtomicBoolean paused = new AtomicBoolean();
|
||||
private final AtomicBoolean cancelled = new AtomicBoolean();
|
||||
private final KMap<Long, Pair<Long, AtomicInteger>> lastUse = new KMap<>();
|
||||
private final RollingSequence chunksPerSecond = new RollingSequence(5);
|
||||
private final AtomicInteger totalMaxChunks = new AtomicInteger();
|
||||
private final AtomicInteger chunksProcessed = new AtomicInteger();
|
||||
private final AtomicInteger chunksProcessedLast = new AtomicInteger();
|
||||
private final AtomicInteger chunksUpdated = new AtomicInteger();
|
||||
private final AtomicBoolean serverEmpty = new AtomicBoolean(true);
|
||||
private final AtomicLong lastCpsTime = new AtomicLong(M.ms());
|
||||
private final int coreLimit = (int) Math.max(Runtime.getRuntime().availableProcessors() * IrisSettings.get().getUpdater().getThreadMultiplier(), 1);
|
||||
private final Semaphore semaphore = new Semaphore(256);
|
||||
private final LoadBalancer loadBalancer = new LoadBalancer(semaphore, 256, IrisSettings.get().getUpdater().emptyMsRange);
|
||||
private final AtomicLong startTime = new AtomicLong();
|
||||
private final Dimensions dimensions;
|
||||
private final PregenTask task;
|
||||
private final ExecutorService executor = Executors.newFixedThreadPool(coreLimit);
|
||||
private final ExecutorService chunkExecutor = Executors.newFixedThreadPool(coreLimit);
|
||||
private final ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1);
|
||||
private final CountDownLatch latch;
|
||||
private final Engine engine;
|
||||
private final World world;
|
||||
|
||||
public ChunkUpdater(World world) {
|
||||
this.engine = IrisToolbelt.access(world).getEngine();
|
||||
this.chunksPerSecond = new RollingSequence(5);
|
||||
this.world = world;
|
||||
this.lastUse = new KMap();
|
||||
this.worldheightsize = new AtomicInteger(calculateWorldDimensions(new File(world.getWorldFolder(), "region"), 1));
|
||||
this.worldwidthsize = new AtomicInteger(calculateWorldDimensions(new File(world.getWorldFolder(), "region"), 0));
|
||||
int m = Math.max(worldheightsize.get(), worldwidthsize.get());
|
||||
this.executor = Executors.newFixedThreadPool(Math.max(Runtime.getRuntime().availableProcessors() / 3, 1));
|
||||
this.chunkExecutor = Executors.newFixedThreadPool(Math.max(Runtime.getRuntime().availableProcessors() / 3, 1));
|
||||
this.scheduler = Executors.newScheduledThreadPool(1);
|
||||
this.future = new CompletableFuture<>();
|
||||
this.startTime = new AtomicLong();
|
||||
this.worldheightsize.set(m);
|
||||
this.worldwidthsize.set(m);
|
||||
this.totalMaxChunks = new AtomicInteger((worldheightsize.get() / 16) * (worldwidthsize.get() / 16));
|
||||
this.chunksProcessed = new AtomicInteger();
|
||||
this.chunksUpdated = new AtomicInteger();
|
||||
this.position = new AtomicInteger(0);
|
||||
this.dimensions = calculateWorldDimensions(new File(world.getWorldFolder(), "region"));
|
||||
this.task = dimensions.task();
|
||||
this.totalMaxChunks.set(dimensions.count * 1024);
|
||||
this.latch = new CountDownLatch(totalMaxChunks.get());
|
||||
this.paused = new AtomicBoolean(false);
|
||||
this.pauseLock = new Object();
|
||||
this.cancelled = new AtomicBoolean(false);
|
||||
this.totalChunks = new AtomicInteger(0);
|
||||
this.totalMcaregions = new AtomicInteger(0);
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return world.getName();
|
||||
}
|
||||
|
||||
public int getChunks() {
|
||||
@@ -97,7 +89,6 @@ public class ChunkUpdater {
|
||||
cancelled.set(true);
|
||||
}
|
||||
|
||||
|
||||
private void update() {
|
||||
Iris.info("Updating..");
|
||||
try {
|
||||
@@ -106,11 +97,11 @@ public class ChunkUpdater {
|
||||
try {
|
||||
if (!paused.get()) {
|
||||
long eta = computeETA();
|
||||
long elapsedSeconds = (System.currentTimeMillis() - startTime.get()) / 1000;
|
||||
int processed = chunksProcessed.get();
|
||||
double cps = elapsedSeconds > 0 ? processed / (double) elapsedSeconds : 0;
|
||||
double last = processed - chunksProcessedLast.getAndSet(processed);
|
||||
double cps = last / ((M.ms() - lastCpsTime.getAndSet(M.ms())) / 1000d);
|
||||
chunksPerSecond.put(cps);
|
||||
double percentage = ((double) chunksProcessed.get() / (double) totalMaxChunks.get()) * 100;
|
||||
double percentage = ((double) processed / (double) totalMaxChunks.get()) * 100;
|
||||
if (!cancelled.get()) {
|
||||
Iris.info("Updated: " + Form.f(processed) + " of " + Form.f(totalMaxChunks.get()) + " (%.0f%%) " + Form.f(chunksPerSecond.getAverage()) + "/s, ETA: " + Form.duration(eta,
|
||||
2), percentage);
|
||||
@@ -120,35 +111,20 @@ public class ChunkUpdater {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}, 0, 3, TimeUnit.SECONDS);
|
||||
scheduler.scheduleAtFixedRate(this::unloadChunks, 0, 1, TimeUnit.SECONDS);
|
||||
scheduler.scheduleAtFixedRate(() -> {
|
||||
boolean empty = Bukkit.getOnlinePlayers().isEmpty();
|
||||
if (serverEmpty.getAndSet(empty) == empty)
|
||||
return;
|
||||
loadBalancer.setRange(empty ? IrisSettings.get().getUpdater().emptyMsRange : IrisSettings.get().getUpdater().defaultMsRange);
|
||||
}, 0, 10, TimeUnit.SECONDS);
|
||||
|
||||
CompletableFuture.runAsync(() -> {
|
||||
for (int i = 0; i < totalMaxChunks.get(); i++) {
|
||||
if (paused.get()) {
|
||||
synchronized (pauseLock) {
|
||||
try {
|
||||
pauseLock.wait();
|
||||
} catch (InterruptedException e) {
|
||||
Iris.error("Interrupted while waiting for executor: ");
|
||||
e.printStackTrace();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
executor.submit(() -> {
|
||||
if (!cancelled.get()) {
|
||||
processNextChunk();
|
||||
}
|
||||
latch.countDown();
|
||||
});
|
||||
}
|
||||
}).thenRun(() -> {
|
||||
try {
|
||||
latch.await();
|
||||
close();
|
||||
} catch (Exception e) {
|
||||
Thread.currentThread().interrupt();
|
||||
}
|
||||
});
|
||||
var t = new Thread(() -> {
|
||||
run();
|
||||
close();
|
||||
}, "Iris Chunk Updater - " + world.getName());
|
||||
t.setPriority(Thread.MAX_PRIORITY);
|
||||
t.start();
|
||||
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
@@ -157,14 +133,16 @@ public class ChunkUpdater {
|
||||
|
||||
public void close() {
|
||||
try {
|
||||
unloadAndSaveAllChunks();
|
||||
loadBalancer.close();
|
||||
semaphore.acquire(256);
|
||||
|
||||
executor.shutdown();
|
||||
executor.awaitTermination(5, TimeUnit.SECONDS);
|
||||
chunkExecutor.shutdown();
|
||||
chunkExecutor.awaitTermination(5, TimeUnit.SECONDS);
|
||||
scheduler.shutdownNow();
|
||||
} catch (Exception ignored) {
|
||||
}
|
||||
unloadAndSaveAllChunks();
|
||||
} catch (Exception ignored) {}
|
||||
if (cancelled.get()) {
|
||||
Iris.info("Updated: " + Form.f(chunksUpdated.get()) + " Chunks");
|
||||
Iris.info("Irritated: " + Form.f(chunksProcessed.get()) + " of " + Form.f(totalMaxChunks.get()));
|
||||
@@ -175,18 +153,69 @@ public class ChunkUpdater {
|
||||
}
|
||||
}
|
||||
|
||||
private void processNextChunk() {
|
||||
int pos = position.getAndIncrement();
|
||||
int[] coords = getChunk(pos);
|
||||
if (loadChunksIfGenerated(coords[0], coords[1])) {
|
||||
Chunk c = world.getChunkAt(coords[0], coords[1]);
|
||||
engine.updateChunk(c);
|
||||
chunksUpdated.incrementAndGet();
|
||||
private void run() {
|
||||
task.iterateRegions((rX, rZ) -> {
|
||||
if (cancelled.get())
|
||||
return;
|
||||
|
||||
while (paused.get()) {
|
||||
J.sleep(50);
|
||||
}
|
||||
|
||||
if (rX < dimensions.min.getX() || rX > dimensions.max.getX() || rZ < dimensions.min.getZ() || rZ > dimensions.max.getZ()) {
|
||||
return;
|
||||
}
|
||||
|
||||
PregenTask.iterateRegion(rX, rZ, (x, z) -> {
|
||||
while (paused.get() && !cancelled.get()) {
|
||||
J.sleep(50);
|
||||
}
|
||||
|
||||
try {
|
||||
semaphore.acquire();
|
||||
} catch (InterruptedException ignored) {
|
||||
return;
|
||||
}
|
||||
chunkExecutor.submit(() -> {
|
||||
try {
|
||||
if (!cancelled.get())
|
||||
processChunk(x, z);
|
||||
} finally {
|
||||
latch.countDown();
|
||||
semaphore.release();
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
private void processChunk(int x, int z) {
|
||||
if (!loadChunksIfGenerated(x, z)) {
|
||||
chunksProcessed.getAndIncrement();
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
Chunk c = world.getChunkAt(x, z);
|
||||
engine.getMantle().getMantle().getChunk(c);
|
||||
engine.updateChunk(c);
|
||||
|
||||
for (int xx = -1; xx <= 1; xx++) {
|
||||
for (int zz = -1; zz <= 1; zz++) {
|
||||
var counter = lastUse.get(Cache.key(x + xx, z + zz));
|
||||
if (counter != null) counter.getB().decrementAndGet();
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
chunksUpdated.incrementAndGet();
|
||||
chunksProcessed.getAndIncrement();
|
||||
}
|
||||
chunksProcessed.getAndIncrement();
|
||||
}
|
||||
|
||||
private boolean loadChunksIfGenerated(int x, int z) {
|
||||
if (engine.getMantle().getMantle().hasFlag(x, z, MantleFlag.ETCHED))
|
||||
return false;
|
||||
|
||||
for (int dx = -1; dx <= 1; dx++) {
|
||||
for (int dz = -1; dz <= 1; dz++) {
|
||||
if (!PaperLib.isChunkGenerated(world, x + dx, z + dz)) {
|
||||
@@ -196,45 +225,73 @@ public class ChunkUpdater {
|
||||
}
|
||||
|
||||
AtomicBoolean generated = new AtomicBoolean(true);
|
||||
KList<Future<?>> futures = new KList<>(9);
|
||||
CountDownLatch latch = new CountDownLatch(9);
|
||||
for (int dx = -1; dx <= 1; dx++) {
|
||||
for (int dz = -1; dz <= 1; dz++) {
|
||||
int xx = x + dx;
|
||||
int zz = z + dz;
|
||||
futures.add(chunkExecutor.submit(() -> {
|
||||
Chunk c;
|
||||
executor.submit(() -> {
|
||||
try {
|
||||
c = PaperLib.getChunkAtAsync(world, xx, zz, false).get();
|
||||
} catch (InterruptedException | ExecutionException e) {
|
||||
generated.set(false);
|
||||
return;
|
||||
}
|
||||
if (!c.isLoaded()) {
|
||||
CountDownLatch latch = new CountDownLatch(1);
|
||||
J.s(() -> {
|
||||
c.load(false);
|
||||
latch.countDown();
|
||||
});
|
||||
Chunk c;
|
||||
try {
|
||||
latch.await();
|
||||
} catch (InterruptedException ignored) {}
|
||||
c = PaperLib.getChunkAtAsync(world, xx, zz, false, true)
|
||||
.thenApply(chunk -> {
|
||||
chunk.addPluginChunkTicket(Iris.instance);
|
||||
return chunk;
|
||||
}).get();
|
||||
} catch (InterruptedException | ExecutionException e) {
|
||||
generated.set(false);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!c.isLoaded()) {
|
||||
var future = J.sfut(() -> c.load(false));
|
||||
if (future != null) future.join();
|
||||
}
|
||||
|
||||
if (!c.isGenerated())
|
||||
generated.set(false);
|
||||
|
||||
var pair = lastUse.computeIfAbsent(Cache.key(c), k -> new Pair<>(0L, new AtomicInteger(-1)));
|
||||
pair.setA(M.ms());
|
||||
pair.getB().updateAndGet(i -> i == -1 ? 1 : ++i);
|
||||
} finally {
|
||||
latch.countDown();
|
||||
}
|
||||
if (!c.isGenerated()) {
|
||||
generated.set(false);
|
||||
}
|
||||
lastUse.put(c, M.ms());
|
||||
}));
|
||||
});
|
||||
}
|
||||
}
|
||||
while (!futures.isEmpty()) {
|
||||
futures.removeIf(Future::isDone);
|
||||
try {
|
||||
Thread.sleep(50);
|
||||
} catch (InterruptedException ignored) {}
|
||||
|
||||
try {
|
||||
latch.await();
|
||||
} catch (InterruptedException e) {
|
||||
Iris.info("Interrupted while waiting for chunks to load");
|
||||
}
|
||||
return generated.get();
|
||||
}
|
||||
|
||||
private synchronized void unloadChunks() {
|
||||
for (var key : new ArrayList<>(lastUse.keySet())) {
|
||||
if (key == null) continue;
|
||||
var pair = lastUse.get(key);
|
||||
if (pair == null) continue;
|
||||
var lastUseTime = pair.getA();
|
||||
var counter = pair.getB();
|
||||
if (lastUseTime == null || counter == null)
|
||||
continue;
|
||||
|
||||
if (M.ms() - lastUseTime >= 5000 && counter.get() == 0) {
|
||||
int x = Cache.keyX(key);
|
||||
int z = Cache.keyZ(key);
|
||||
J.s(() -> {
|
||||
world.removePluginChunkTicket(x, z, Iris.instance);
|
||||
world.unloadChunk(x, z);
|
||||
lastUse.remove(key);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void unloadAndSaveAllChunks() {
|
||||
try {
|
||||
J.sfut(() -> {
|
||||
@@ -243,13 +300,7 @@ public class ChunkUpdater {
|
||||
return;
|
||||
}
|
||||
|
||||
for (Chunk i : new ArrayList<>(lastUse.keySet())) {
|
||||
Long lastUseTime = lastUse.get(i);
|
||||
if (lastUseTime != null && M.ms() - lastUseTime >= 5000) {
|
||||
i.unload();
|
||||
lastUse.remove(i);
|
||||
}
|
||||
}
|
||||
unloadChunks();
|
||||
world.save();
|
||||
}).get();
|
||||
} catch (Throwable e) {
|
||||
@@ -266,7 +317,7 @@ public class ChunkUpdater {
|
||||
);
|
||||
}
|
||||
|
||||
public int calculateWorldDimensions(File regionDir, Integer o) {
|
||||
private Dimensions calculateWorldDimensions(File regionDir) {
|
||||
File[] files = regionDir.listFiles((dir, name) -> name.endsWith(".mca"));
|
||||
|
||||
int minX = Integer.MAX_VALUE;
|
||||
@@ -279,40 +330,23 @@ public class ChunkUpdater {
|
||||
int x = Integer.parseInt(parts[1]);
|
||||
int z = Integer.parseInt(parts[2]);
|
||||
|
||||
if (x < minX) minX = x;
|
||||
if (x > maxX) maxX = x;
|
||||
if (z < minZ) minZ = z;
|
||||
if (z > maxZ) maxZ = z;
|
||||
minX = Math.min(minX, x);
|
||||
maxX = Math.max(maxX, x);
|
||||
minZ = Math.min(minZ, z);
|
||||
maxZ = Math.max(maxZ, z);
|
||||
}
|
||||
int oX = minX + ((maxX - minX) / 2);
|
||||
int oZ = minZ + ((maxZ - minZ) / 2);
|
||||
|
||||
int height = (maxX - minX + 1) * 32 * 16;
|
||||
int width = (maxZ - minZ + 1) * 32 * 16;
|
||||
int height = maxX - minX + 1;
|
||||
int width = maxZ - minZ + 1;
|
||||
|
||||
if (o == 1) {
|
||||
return height;
|
||||
}
|
||||
if (o == 0) {
|
||||
return width;
|
||||
}
|
||||
return 0;
|
||||
return new Dimensions(new Position2(minX, minZ), new Position2(maxX, maxZ), height * width, PregenTask.builder()
|
||||
.width((int) Math.ceil(width / 2d))
|
||||
.height((int) Math.ceil(height / 2d))
|
||||
.center(new Position2(oX, oZ))
|
||||
.build());
|
||||
}
|
||||
|
||||
public int[] getChunk(int position) {
|
||||
int p = -1;
|
||||
AtomicInteger xx = new AtomicInteger();
|
||||
AtomicInteger zz = new AtomicInteger();
|
||||
Spiraler s = new Spiraler(worldheightsize.get() * 2, worldwidthsize.get() * 2, (x, z) -> {
|
||||
xx.set(x);
|
||||
zz.set(z);
|
||||
});
|
||||
|
||||
while (s.hasNext() && p++ < position) {
|
||||
s.next();
|
||||
}
|
||||
int[] coords = new int[2];
|
||||
coords[0] = xx.get();
|
||||
coords[1] = zz.get();
|
||||
|
||||
return coords;
|
||||
}
|
||||
private record Dimensions(Position2 min, Position2 max, int count, PregenTask task) { }
|
||||
}
|
||||
|
||||
@@ -1,275 +0,0 @@
|
||||
package com.volmit.iris.core.pregenerator;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import com.volmit.iris.Iris;
|
||||
import com.volmit.iris.core.tools.IrisToolbelt;
|
||||
import com.volmit.iris.engine.framework.Engine;
|
||||
import com.volmit.iris.engine.object.IrisBiome;
|
||||
import com.volmit.iris.util.collection.KList;
|
||||
import com.volmit.iris.util.format.C;
|
||||
import com.volmit.iris.util.format.Form;
|
||||
import com.volmit.iris.util.io.IO;
|
||||
import com.volmit.iris.util.math.M;
|
||||
import com.volmit.iris.util.math.Position2;
|
||||
import com.volmit.iris.util.math.RollingSequence;
|
||||
import com.volmit.iris.util.math.Spiraler;
|
||||
import com.volmit.iris.util.scheduling.ChronoLatch;
|
||||
import com.volmit.iris.util.scheduling.J;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.Getter;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.World;
|
||||
import org.bukkit.event.EventHandler;
|
||||
import org.bukkit.event.Listener;
|
||||
import org.bukkit.event.world.WorldUnloadEvent;
|
||||
import org.bukkit.scheduler.BukkitRunnable;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileWriter;
|
||||
import java.io.IOException;
|
||||
import java.util.HashMap;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.concurrent.atomic.AtomicLong;
|
||||
import java.util.concurrent.locks.ReentrantLock;
|
||||
|
||||
public class DeepSearchPregenerator extends Thread implements Listener {
|
||||
@Getter
|
||||
private static DeepSearchPregenerator instance;
|
||||
private final DeepSearchJob job;
|
||||
private final File destination;
|
||||
private final int maxPosition;
|
||||
private World world;
|
||||
private final ChronoLatch latch;
|
||||
private static AtomicInteger foundChunks;
|
||||
private final AtomicInteger foundLast;
|
||||
private final AtomicInteger foundTotalChunks;
|
||||
private final AtomicLong startTime;
|
||||
private final RollingSequence chunksPerSecond;
|
||||
private final RollingSequence chunksPerMinute;
|
||||
private final AtomicInteger chunkCachePos;
|
||||
private final AtomicInteger chunkCacheSize;
|
||||
private int pos;
|
||||
private final AtomicInteger foundCacheLast;
|
||||
private final AtomicInteger foundCache;
|
||||
private LinkedHashMap<Integer, Position2> chunkCache;
|
||||
private KList<Position2> chunkQueue;
|
||||
private final ReentrantLock cacheLock;
|
||||
|
||||
private static final Map<String, DeepSearchJob> jobs = new HashMap<>();
|
||||
|
||||
public DeepSearchPregenerator(DeepSearchJob job, File destination) {
|
||||
this.job = job;
|
||||
this.chunkCacheSize = new AtomicInteger(); // todo
|
||||
this.chunkCachePos = new AtomicInteger(1000);
|
||||
this.foundCacheLast = new AtomicInteger();
|
||||
this.foundCache = new AtomicInteger();
|
||||
this.cacheLock = new ReentrantLock();
|
||||
this.destination = destination;
|
||||
this.chunkCache = new LinkedHashMap<>();
|
||||
this.maxPosition = new Spiraler(job.getRadiusBlocks() * 2, job.getRadiusBlocks() * 2, (x, z) -> {
|
||||
}).count();
|
||||
this.world = Bukkit.getWorld(job.getWorld().getUID());
|
||||
this.chunkQueue = new KList<>();
|
||||
this.latch = new ChronoLatch(3000);
|
||||
this.startTime = new AtomicLong(M.ms());
|
||||
this.chunksPerSecond = new RollingSequence(10);
|
||||
this.chunksPerMinute = new RollingSequence(10);
|
||||
foundChunks = new AtomicInteger(0);
|
||||
this.foundLast = new AtomicInteger(0);
|
||||
this.foundTotalChunks = new AtomicInteger((int) Math.ceil(Math.pow((2.0 * job.getRadiusBlocks()) / 16, 2)));
|
||||
|
||||
this.pos = 0;
|
||||
jobs.put(job.getWorld().getName(), job);
|
||||
DeepSearchPregenerator.instance = this;
|
||||
}
|
||||
|
||||
@EventHandler
|
||||
public void on(WorldUnloadEvent e) {
|
||||
if (e.getWorld().equals(world)) {
|
||||
interrupt();
|
||||
}
|
||||
}
|
||||
|
||||
public void run() {
|
||||
while (!interrupted()) {
|
||||
tick();
|
||||
}
|
||||
try {
|
||||
saveNow();
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
public void tick() {
|
||||
DeepSearchJob job = jobs.get(world.getName());
|
||||
// chunkCache(); //todo finish this
|
||||
if (latch.flip() && !job.paused) {
|
||||
if (cacheLock.isLocked()) {
|
||||
Iris.info("DeepFinder: Caching: " + chunkCachePos.get() + " Of " + chunkCacheSize.get());
|
||||
} else {
|
||||
long eta = computeETA();
|
||||
save();
|
||||
int secondGenerated = foundChunks.get() - foundLast.get();
|
||||
foundLast.set(foundChunks.get());
|
||||
secondGenerated = secondGenerated / 3;
|
||||
chunksPerSecond.put(secondGenerated);
|
||||
chunksPerMinute.put(secondGenerated * 60);
|
||||
Iris.info("DeepFinder: " + C.IRIS + world.getName() + C.RESET + " Searching: " + Form.f(foundChunks.get()) + " of " + Form.f(foundTotalChunks.get()) + " " + Form.f((int) chunksPerSecond.getAverage()) + "/s ETA: " + Form.duration((double) eta, 2));
|
||||
}
|
||||
|
||||
}
|
||||
if (foundChunks.get() >= foundTotalChunks.get()) {
|
||||
Iris.info("Completed DeepSearch!");
|
||||
interrupt();
|
||||
}
|
||||
}
|
||||
|
||||
private long computeETA() {
|
||||
return (long) ((foundTotalChunks.get() - foundChunks.get()) / chunksPerSecond.getAverage()) * 1000;
|
||||
// todo broken
|
||||
}
|
||||
|
||||
private final ExecutorService executorService = Executors.newSingleThreadExecutor();
|
||||
|
||||
private void queueSystem(Position2 chunk) {
|
||||
if (chunkQueue.isEmpty()) {
|
||||
for (int limit = 512; limit != 0; limit--) {
|
||||
pos = job.getPosition() + 1;
|
||||
chunkQueue.add(getChunk(pos));
|
||||
}
|
||||
} else {
|
||||
//MCAUtil.read();
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
private void findInChunk(World world, int x, int z) throws IOException {
|
||||
int xx = x * 16;
|
||||
int zz = z * 16;
|
||||
Engine engine = IrisToolbelt.access(world).getEngine();
|
||||
for (int i = 0; i < 16; i++) {
|
||||
for (int j = 0; j < 16; j++) {
|
||||
int height = engine.getHeight(xx + i, zz + j);
|
||||
if (height > 300) {
|
||||
File found = new File("plugins" + "iris" + "found.txt");
|
||||
FileWriter writer = new FileWriter(found);
|
||||
if (!found.exists()) {
|
||||
found.createNewFile();
|
||||
}
|
||||
IrisBiome biome = engine.getBiome(xx, engine.getHeight(), zz);
|
||||
Iris.info("Found at! " + xx + ", " + zz + "Biome ID: " + biome.getName() + ", ");
|
||||
writer.write("Biome at: X: " + xx + " Z: " + zz + "Biome ID: " + biome.getName() + ", ");
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public Position2 getChunk(int position) {
|
||||
int p = -1;
|
||||
AtomicInteger xx = new AtomicInteger();
|
||||
AtomicInteger zz = new AtomicInteger();
|
||||
Spiraler s = new Spiraler(job.getRadiusBlocks() * 2, job.getRadiusBlocks() * 2, (x, z) -> {
|
||||
xx.set(x);
|
||||
zz.set(z);
|
||||
});
|
||||
|
||||
while (s.hasNext() && p++ < position) {
|
||||
s.next();
|
||||
}
|
||||
|
||||
return new Position2(xx.get(), zz.get());
|
||||
}
|
||||
|
||||
public void save() {
|
||||
J.a(() -> {
|
||||
try {
|
||||
saveNow();
|
||||
} catch (Throwable e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public static void setPausedDeep(World world) {
|
||||
DeepSearchJob job = jobs.get(world.getName());
|
||||
if (isPausedDeep(world)){
|
||||
job.paused = false;
|
||||
} else {
|
||||
job.paused = true;
|
||||
}
|
||||
|
||||
if ( job.paused) {
|
||||
Iris.info(C.BLUE + "DeepSearch: " + C.IRIS + world.getName() + C.BLUE + " Paused");
|
||||
} else {
|
||||
Iris.info(C.BLUE + "DeepSearch: " + C.IRIS + world.getName() + C.BLUE + " Resumed");
|
||||
}
|
||||
}
|
||||
|
||||
public static boolean isPausedDeep(World world) {
|
||||
DeepSearchJob job = jobs.get(world.getName());
|
||||
return job != null && job.isPaused();
|
||||
}
|
||||
|
||||
public void shutdownInstance(World world) throws IOException {
|
||||
Iris.info("DeepSearch: " + C.IRIS + world.getName() + C.BLUE + " Shutting down..");
|
||||
DeepSearchJob job = jobs.get(world.getName());
|
||||
File worldDirectory = new File(Bukkit.getWorldContainer(), world.getName());
|
||||
File deepFile = new File(worldDirectory, "DeepSearch.json");
|
||||
|
||||
if (job == null) {
|
||||
Iris.error("No DeepSearch job found for world: " + world.getName());
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
if (!job.isPaused()) {
|
||||
job.setPaused(true);
|
||||
}
|
||||
save();
|
||||
jobs.remove(world.getName());
|
||||
new BukkitRunnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
while (deepFile.exists()){
|
||||
deepFile.delete();
|
||||
J.sleep(1000);
|
||||
}
|
||||
Iris.info("DeepSearch: " + C.IRIS + world.getName() + C.BLUE + " File deleted and instance closed.");
|
||||
}
|
||||
}.runTaskLater(Iris.instance, 20L);
|
||||
} catch (Exception e) {
|
||||
Iris.error("Failed to shutdown DeepSearch for " + world.getName());
|
||||
e.printStackTrace();
|
||||
} finally {
|
||||
saveNow();
|
||||
interrupt();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public void saveNow() throws IOException {
|
||||
IO.writeAll(this.destination, new Gson().toJson(job));
|
||||
}
|
||||
|
||||
@Data
|
||||
@Builder
|
||||
public static class DeepSearchJob {
|
||||
private World world;
|
||||
@Builder.Default
|
||||
private int radiusBlocks = 5000;
|
||||
@Builder.Default
|
||||
private int position = 0;
|
||||
@Builder.Default
|
||||
boolean paused = false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,342 +0,0 @@
|
||||
package com.volmit.iris.core.pregenerator;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import com.volmit.iris.Iris;
|
||||
import com.volmit.iris.core.IrisSettings;
|
||||
import com.volmit.iris.util.collection.KList;
|
||||
import com.volmit.iris.util.format.C;
|
||||
import com.volmit.iris.util.format.Form;
|
||||
import com.volmit.iris.util.io.IO;
|
||||
import com.volmit.iris.util.math.M;
|
||||
import com.volmit.iris.util.math.Position2;
|
||||
import com.volmit.iris.util.math.RollingSequence;
|
||||
import com.volmit.iris.util.math.Spiraler;
|
||||
import com.volmit.iris.util.parallel.BurstExecutor;
|
||||
import com.volmit.iris.util.parallel.HyperLock;
|
||||
import com.volmit.iris.util.parallel.MultiBurst;
|
||||
import com.volmit.iris.util.scheduling.ChronoLatch;
|
||||
import com.volmit.iris.util.scheduling.J;
|
||||
import com.volmit.iris.util.scheduling.PrecisionStopwatch;
|
||||
import io.papermc.lib.PaperLib;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.Getter;
|
||||
import org.apache.logging.log4j.core.util.ExecutorServices;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.World;
|
||||
import org.bukkit.event.EventHandler;
|
||||
import org.bukkit.event.Listener;
|
||||
import org.bukkit.event.world.WorldUnloadEvent;
|
||||
import org.bukkit.scheduler.BukkitRunnable;
|
||||
import org.checkerframework.checker.units.qual.N;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.lang.reflect.Array;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.*;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.concurrent.atomic.AtomicLong;
|
||||
import java.util.concurrent.locks.ReentrantLock;
|
||||
import java.util.stream.IntStream;
|
||||
|
||||
public class TurboPregenerator extends Thread implements Listener {
|
||||
@Getter
|
||||
private static TurboPregenerator instance;
|
||||
private final TurboPregenJob job;
|
||||
private final File destination;
|
||||
private final int maxPosition;
|
||||
private World world;
|
||||
private final ChronoLatch latch;
|
||||
private static AtomicInteger turboGeneratedChunks;
|
||||
private final AtomicInteger generatedLast;
|
||||
private final AtomicLong cachedLast;
|
||||
private final RollingSequence cachePerSecond;
|
||||
private final AtomicInteger turboTotalChunks;
|
||||
private final AtomicLong startTime;
|
||||
private final RollingSequence chunksPerSecond;
|
||||
private final RollingSequence chunksPerMinute;
|
||||
private KList<Position2> queue;
|
||||
private ConcurrentHashMap<Integer, Position2> cache;
|
||||
private AtomicInteger maxWaiting;
|
||||
private ReentrantLock cachinglock;
|
||||
private AtomicBoolean caching;
|
||||
private final HyperLock hyperLock;
|
||||
private MultiBurst burst;
|
||||
private static final Map<String, TurboPregenJob> jobs = new HashMap<>();
|
||||
|
||||
public TurboPregenerator(TurboPregenJob job, File destination) {
|
||||
this.job = job;
|
||||
queue = new KList<>(512);
|
||||
this.maxWaiting = new AtomicInteger(128);
|
||||
this.destination = destination;
|
||||
this.maxPosition = new Spiraler(job.getRadiusBlocks() * 2, job.getRadiusBlocks() * 2, (x, z) -> {
|
||||
}).count();
|
||||
this.world = Bukkit.getWorld(job.getWorld());
|
||||
this.latch = new ChronoLatch(3000);
|
||||
this.burst = MultiBurst.burst;
|
||||
this.hyperLock = new HyperLock();
|
||||
this.startTime = new AtomicLong(M.ms());
|
||||
this.cachePerSecond = new RollingSequence(10);
|
||||
this.chunksPerSecond = new RollingSequence(10);
|
||||
this.chunksPerMinute = new RollingSequence(10);
|
||||
turboGeneratedChunks = new AtomicInteger(0);
|
||||
this.generatedLast = new AtomicInteger(0);
|
||||
this.cachedLast = new AtomicLong(0);
|
||||
this.caching = new AtomicBoolean(false);
|
||||
this.turboTotalChunks = new AtomicInteger((int) Math.ceil(Math.pow((2.0 * job.getRadiusBlocks()) / 16, 2)));
|
||||
cache = new ConcurrentHashMap<>(turboTotalChunks.get());
|
||||
this.cachinglock = new ReentrantLock();
|
||||
jobs.put(job.getWorld(), job);
|
||||
TurboPregenerator.instance = this;
|
||||
}
|
||||
|
||||
public TurboPregenerator(File file) throws IOException {
|
||||
this(new Gson().fromJson(IO.readAll(file), TurboPregenerator.TurboPregenJob.class), file);
|
||||
}
|
||||
|
||||
public static void loadTurboGenerator(String i) {
|
||||
World x = Bukkit.getWorld(i);
|
||||
File turbogen = new File(x.getWorldFolder(), "turbogen.json");
|
||||
if (turbogen.exists()) {
|
||||
try {
|
||||
TurboPregenerator p = new TurboPregenerator(turbogen);
|
||||
p.start();
|
||||
Iris.info("Started Turbo Pregenerator: " + p.job);
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@EventHandler
|
||||
public void on(WorldUnloadEvent e) {
|
||||
if (e.getWorld().equals(world)) {
|
||||
interrupt();
|
||||
}
|
||||
}
|
||||
|
||||
public void run() {
|
||||
while (!interrupted()) {
|
||||
tick();
|
||||
}
|
||||
|
||||
try {
|
||||
saveNow();
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
public void tick() {
|
||||
TurboPregenJob job = jobs.get(world.getName());
|
||||
if (!cachinglock.isLocked() && cache.isEmpty() && !caching.get()) {
|
||||
ExecutorService cache = Executors.newFixedThreadPool(1);
|
||||
cache.submit(this::cache);
|
||||
}
|
||||
|
||||
if (latch.flip() && caching.get()) {
|
||||
long secondCached = cache.mappingCount() - cachedLast.get();
|
||||
cachedLast.set(cache.mappingCount());
|
||||
secondCached = secondCached / 3;
|
||||
cachePerSecond.put(secondCached);
|
||||
Iris.info("TurboGen: " + C.IRIS + world.getName() + C.RESET + C.BLUE + " Caching: " + Form.f(cache.mappingCount()) + " of " + Form.f(turboTotalChunks.get()) + " " + Form.f((int) cachePerSecond.getAverage()) + "/s");
|
||||
}
|
||||
|
||||
if (latch.flip() && !job.paused && !cachinglock.isLocked()) {
|
||||
long eta = computeETA();
|
||||
save();
|
||||
int secondGenerated = turboGeneratedChunks.get() - generatedLast.get();
|
||||
generatedLast.set(turboGeneratedChunks.get());
|
||||
secondGenerated = secondGenerated / 3;
|
||||
chunksPerSecond.put(secondGenerated);
|
||||
chunksPerMinute.put(secondGenerated * 60);
|
||||
Iris.info("TurboGen: " + C.IRIS + world.getName() + C.RESET + " RTT: " + Form.f(turboGeneratedChunks.get()) + " of " + Form.f(turboTotalChunks.get()) + " " + Form.f((int) chunksPerSecond.getAverage()) + "/s ETA: " + Form.duration((double) eta, 2));
|
||||
|
||||
}
|
||||
if (turboGeneratedChunks.get() >= turboTotalChunks.get()) {
|
||||
Iris.info("Completed Turbo Gen!");
|
||||
interrupt();
|
||||
} else {
|
||||
if (!cachinglock.isLocked()) {
|
||||
int pos = job.getPosition() + 1;
|
||||
job.setPosition(pos);
|
||||
if (!job.paused) {
|
||||
if (queue.size() < maxWaiting.get()) {
|
||||
Position2 chunk = cache.get(pos);
|
||||
queue.add(chunk);
|
||||
}
|
||||
waitForChunksPartial();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void cache() {
|
||||
if (!cachinglock.isLocked()) {
|
||||
cachinglock.lock();
|
||||
caching.set(true);
|
||||
PrecisionStopwatch p = PrecisionStopwatch.start();
|
||||
BurstExecutor b = MultiBurst.burst.burst(turboTotalChunks.get());
|
||||
b.setMulticore(true);
|
||||
int[] list = IntStream.rangeClosed(0, turboTotalChunks.get()).toArray();
|
||||
AtomicInteger order = new AtomicInteger(turboTotalChunks.get());
|
||||
|
||||
int threads = Runtime.getRuntime().availableProcessors();
|
||||
if (threads > 1) threads--;
|
||||
ExecutorService process = Executors.newFixedThreadPool(threads);
|
||||
|
||||
for (int id : list) {
|
||||
b.queue(() -> {
|
||||
cache.put(id, getChunk(id));
|
||||
order.addAndGet(-1);
|
||||
});
|
||||
}
|
||||
b.complete();
|
||||
|
||||
if (order.get() < 0) {
|
||||
cachinglock.unlock();
|
||||
caching.set(false);
|
||||
Iris.info("Completed Caching in: " + Form.duration(p.getMilliseconds(), 2));
|
||||
}
|
||||
} else {
|
||||
Iris.error("TurboCache is locked!");
|
||||
}
|
||||
}
|
||||
|
||||
private void waitForChunksPartial() {
|
||||
while (!queue.isEmpty() && maxWaiting.get() > queue.size()) {
|
||||
try {
|
||||
for (Position2 c : new KList<>(queue)) {
|
||||
tickGenerate(c);
|
||||
queue.remove(c);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private long computeETA() {
|
||||
return (long) ((turboTotalChunks.get() - turboGeneratedChunks.get()) / chunksPerMinute.getAverage()) * 1000;
|
||||
// todo broken
|
||||
}
|
||||
|
||||
private final ExecutorService executorService = Executors.newFixedThreadPool(10);
|
||||
private void tickGenerate(Position2 chunk) {
|
||||
executorService.submit(() -> {
|
||||
CountDownLatch latch = new CountDownLatch(1);
|
||||
PaperLib.getChunkAtAsync(world, chunk.getX(), chunk.getZ(), true)
|
||||
.thenAccept((i) -> {
|
||||
latch.countDown();
|
||||
});
|
||||
try {
|
||||
latch.await();
|
||||
} catch (InterruptedException ignored) {
|
||||
}
|
||||
turboGeneratedChunks.addAndGet(1);
|
||||
});
|
||||
}
|
||||
|
||||
public Position2 getChunk(int position) {
|
||||
int p = -1;
|
||||
AtomicInteger xx = new AtomicInteger();
|
||||
AtomicInteger zz = new AtomicInteger();
|
||||
Spiraler s = new Spiraler(job.getRadiusBlocks() * 2, job.getRadiusBlocks() * 2, (x, z) -> {
|
||||
xx.set(x);
|
||||
zz.set(z);
|
||||
});
|
||||
|
||||
while (s.hasNext() && p++ < position) {
|
||||
s.next();
|
||||
}
|
||||
|
||||
return new Position2(xx.get(), zz.get());
|
||||
}
|
||||
|
||||
public void save() {
|
||||
J.a(() -> {
|
||||
try {
|
||||
saveNow();
|
||||
} catch (Throwable e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public static void setPausedTurbo(World world) {
|
||||
TurboPregenJob job = jobs.get(world.getName());
|
||||
if (isPausedTurbo(world)) {
|
||||
job.paused = false;
|
||||
} else {
|
||||
job.paused = true;
|
||||
}
|
||||
|
||||
if (job.paused) {
|
||||
Iris.info(C.BLUE + "TurboGen: " + C.IRIS + world.getName() + C.BLUE + " Paused");
|
||||
} else {
|
||||
Iris.info(C.BLUE + "TurboGen: " + C.IRIS + world.getName() + C.BLUE + " Resumed");
|
||||
}
|
||||
}
|
||||
|
||||
public static boolean isPausedTurbo(World world) {
|
||||
TurboPregenJob job = jobs.get(world.getName());
|
||||
return job != null && job.isPaused();
|
||||
}
|
||||
|
||||
public void shutdownInstance(World world) throws IOException {
|
||||
Iris.info("turboGen: " + C.IRIS + world.getName() + C.BLUE + " Shutting down..");
|
||||
TurboPregenJob job = jobs.get(world.getName());
|
||||
File worldDirectory = new File(Bukkit.getWorldContainer(), world.getName());
|
||||
File turboFile = new File(worldDirectory, "turbogen.json");
|
||||
|
||||
if (job == null) {
|
||||
Iris.error("No turbogen job found for world: " + world.getName());
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
if (!job.isPaused()) {
|
||||
job.setPaused(true);
|
||||
}
|
||||
save();
|
||||
jobs.remove(world.getName());
|
||||
new BukkitRunnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
while (turboFile.exists()) {
|
||||
turboFile.delete();
|
||||
J.sleep(1000);
|
||||
}
|
||||
Iris.info("turboGen: " + C.IRIS + world.getName() + C.BLUE + " File deleted and instance closed.");
|
||||
}
|
||||
}.runTaskLater(Iris.instance, 20L);
|
||||
} catch (Exception e) {
|
||||
Iris.error("Failed to shutdown turbogen for " + world.getName());
|
||||
e.printStackTrace();
|
||||
} finally {
|
||||
saveNow();
|
||||
interrupt();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public void saveNow() throws IOException {
|
||||
IO.writeAll(this.destination, new Gson().toJson(job));
|
||||
}
|
||||
|
||||
@Data
|
||||
@Builder
|
||||
public static class TurboPregenJob {
|
||||
private String world;
|
||||
@Builder.Default
|
||||
private int radiusBlocks = 5000;
|
||||
@Builder.Default
|
||||
private int position = 0;
|
||||
@Builder.Default
|
||||
boolean paused = false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -397,6 +397,10 @@ public class SchemaBuilder {
|
||||
description.add(SYMBOL_LIMIT__N + " Requires at least " + t.min() + " entries.");
|
||||
}
|
||||
}
|
||||
if (t.max() > 0) {
|
||||
prop.put("maxItems", t.max());
|
||||
description.add(SYMBOL_LIMIT__N + " Maximum allowed entries are " + t.max() + ".");
|
||||
}
|
||||
|
||||
String arrayType = getType(t.type());
|
||||
|
||||
|
||||
@@ -125,32 +125,19 @@ public class BoardSVC implements IrisService, BoardProvider {
|
||||
int y = player.getLocation().getBlockY() - player.getWorld().getMinHeight();
|
||||
int z = player.getLocation().getBlockZ();
|
||||
|
||||
if(IrisSettings.get().getGeneral().debug){
|
||||
lines.add("&7&m ");
|
||||
lines.add(C.GREEN + "Speed" + C.GRAY + ": " + Form.f(engine.getGeneratedPerSecond(), 0) + "/s " + Form.duration(1000D / engine.getGeneratedPerSecond(), 0));
|
||||
lines.add(C.AQUA + "Cache" + C.GRAY + ": " + Form.f(IrisData.cacheSize()));
|
||||
lines.add(C.AQUA + "Mantle" + C.GRAY + ": " + engine.getMantle().getLoadedRegionCount());
|
||||
lines.add(C.LIGHT_PURPLE + "Carving" + C.GRAY + ": " + engine.getMantle().isCarved(x,y,z));
|
||||
lines.add("&7&m ");
|
||||
lines.add(C.AQUA + "Region" + C.GRAY + ": " + engine.getRegion(x, z).getName());
|
||||
lines.add(C.AQUA + "Biome" + C.GRAY + ": " + engine.getBiomeOrMantle(x, y, z).getName());
|
||||
lines.add(C.AQUA + "Height" + C.GRAY + ": " + Math.round(engine.getHeight(x, z)));
|
||||
lines.add(C.AQUA + "Slope" + C.GRAY + ": " + Form.f(engine.getComplex().getSlopeStream().get(x, z), 2));
|
||||
lines.add(C.AQUA + "BUD/s" + C.GRAY + ": " + Form.f(engine.getBlockUpdatesPerSecond()));
|
||||
lines.add("&7&m ");
|
||||
} else {
|
||||
lines.add("&7&m ");
|
||||
lines.add(C.GREEN + "Speed" + C.GRAY + ": " + Form.f(engine.getGeneratedPerSecond(), 0) + "/s " + Form.duration(1000D / engine.getGeneratedPerSecond(), 0));
|
||||
lines.add(C.AQUA + "Cache" + C.GRAY + ": " + Form.f(IrisData.cacheSize()));
|
||||
lines.add(C.AQUA + "Mantle" + C.GRAY + ": " + engine.getMantle().getLoadedRegionCount());
|
||||
lines.add("&7&m ");
|
||||
lines.add(C.AQUA + "Region" + C.GRAY + ": " + engine.getRegion(x, z).getName());
|
||||
lines.add(C.AQUA + "Biome" + C.GRAY + ": " + engine.getBiomeOrMantle(x, y, z).getName());
|
||||
lines.add(C.AQUA + "Height" + C.GRAY + ": " + Math.round(engine.getHeight(x, z)));
|
||||
lines.add(C.AQUA + "Slope" + C.GRAY + ": " + Form.f(engine.getComplex().getSlopeStream().get(x, z), 2));
|
||||
lines.add(C.AQUA + "BUD/s" + C.GRAY + ": " + Form.f(engine.getBlockUpdatesPerSecond()));
|
||||
lines.add("&7&m ");
|
||||
}
|
||||
|
||||
lines.add("&7&m ");
|
||||
lines.add(C.GREEN + "Speed" + C.GRAY + ": " + Form.f(engine.getGeneratedPerSecond(), 0) + "/s " + Form.duration(1000D / engine.getGeneratedPerSecond(), 0));
|
||||
lines.add(C.AQUA + "Cache" + C.GRAY + ": " + Form.f(IrisData.cacheSize()));
|
||||
lines.add(C.AQUA + "Mantle" + C.GRAY + ": " + engine.getMantle().getLoadedRegionCount());
|
||||
lines.add("&7&m ");
|
||||
lines.add(C.AQUA + "Region" + C.GRAY + ": " + engine.getRegion(x, z).getName());
|
||||
lines.add(C.AQUA + "Biome" + C.GRAY + ": " + engine.getBiomeOrMantle(x, y, z).getName());
|
||||
lines.add(C.AQUA + "Height" + C.GRAY + ": " + Math.round(engine.getHeight(x, z)));
|
||||
lines.add(C.AQUA + "Slope" + C.GRAY + ": " + Form.f(engine.getComplex().getSlopeStream().get(x, z), 2));
|
||||
lines.add(C.AQUA + "BUD/s" + C.GRAY + ": " + Form.f(engine.getBlockUpdatesPerSecond()));
|
||||
lines.add("&7&m ");
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,9 +20,12 @@ package com.volmit.iris.core.service;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import com.volmit.iris.Iris;
|
||||
import com.volmit.iris.core.nms.INMS;
|
||||
import com.volmit.iris.core.nms.v1X.NMSBinding1X;
|
||||
import com.volmit.iris.engine.object.*;
|
||||
import com.volmit.iris.util.collection.KList;
|
||||
import com.volmit.iris.util.collection.KMap;
|
||||
import com.volmit.iris.util.format.C;
|
||||
import com.volmit.iris.util.format.Form;
|
||||
import com.volmit.iris.util.function.Consumer2;
|
||||
import com.volmit.iris.util.io.Converter;
|
||||
@@ -37,13 +40,16 @@ import com.volmit.iris.util.nbt.tag.ListTag;
|
||||
import com.volmit.iris.util.plugin.IrisService;
|
||||
import com.volmit.iris.util.plugin.VolmitSender;
|
||||
import com.volmit.iris.util.scheduling.J;
|
||||
import org.apache.commons.io.FileUtils;
|
||||
import org.bukkit.Material;
|
||||
import org.bukkit.block.data.BlockData;
|
||||
import org.bukkit.block.data.type.Jigsaw;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.Objects;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
public class ConversionSVC implements IrisService {
|
||||
private KList<Converter> converters;
|
||||
@@ -122,7 +128,7 @@ public class ConversionSVC implements IrisService {
|
||||
@SuppressWarnings("unchecked") ListTag<CompoundTag> paletteList = (ListTag<CompoundTag>) compound.getListTag("palette");
|
||||
for (int i = 0; i < paletteList.size(); i++) {
|
||||
CompoundTag cp = paletteList.get(i);
|
||||
palette.add(NBTWorld.getBlockData(cp));
|
||||
palette.add(INMS.get().getBlockData(cp));
|
||||
}
|
||||
IrisJigsawPiece piece = new IrisJigsawPiece();
|
||||
IrisObject object = new IrisObject(w, h, d);
|
||||
@@ -135,20 +141,37 @@ public class ConversionSVC implements IrisService {
|
||||
int z = pos.get(2).asInt();
|
||||
BlockData bd = palette.get(cp.getInt("state")).clone();
|
||||
|
||||
piece.setObject(in.toURI().relativize(folder.toURI()).getPath() + file.getName().split("\\Q.\\E")[0]);
|
||||
if (bd.getMaterial().equals(Material.JIGSAW) && cp.containsKey("nbt")) {
|
||||
piece.setObject(in.toURI().relativize(folder.toURI()).getPath() + file.getName().split("\\Q.\\E")[0]);
|
||||
//.setObject(in.toURI().relativize(folder.toURI()).getPath() + file.getName().split("\\Q.\\E")[0]);
|
||||
IrisPosition spos = new IrisPosition(object.getSigned(x, y, z));
|
||||
CompoundTag nbt = cp.getCompoundTag("nbt");
|
||||
CompoundTag finalState = new CompoundTag();
|
||||
finalState.putString("Name", nbt.getString("final_state"));
|
||||
BlockData jd = bd.clone();
|
||||
bd = NBTWorld.getBlockData(finalState);
|
||||
bd = INMS.get().getBlockData(finalState);
|
||||
String joint = nbt.getString("joint");
|
||||
String pool = nbt.getString("pool");
|
||||
String poolId = toPoolName(pool);
|
||||
String name = nbt.getString("name");
|
||||
String target = nbt.getString("target");
|
||||
pools.computeIfAbsent(poolId, (k) -> new IrisJigsawPool());
|
||||
pools.computeIfAbsent(poolId, (k) -> {
|
||||
IrisJigsawPool irisPool = new IrisJigsawPool();
|
||||
|
||||
String basePath = in.toURI().relativize(folder.toURI()).getPath();
|
||||
File baseFolder = new File(in.toURI().relativize(folder.toURI()).toString());
|
||||
String[] paths = FileUtils.listFiles(folder, null, true)
|
||||
.stream()
|
||||
.map(path -> path.getPath().replaceAll("\\.nbt$", "")).toArray(String[]::new);
|
||||
|
||||
KList<String> poolList = new KList<>();
|
||||
for (int ii = 0; ii < Objects.requireNonNull(paths).length; ii++) {
|
||||
String lastSegment = paths[ii].substring(paths[ii].lastIndexOf("\\") + 1);
|
||||
poolList.add(basePath + lastSegment);
|
||||
}
|
||||
irisPool.setPieces(poolList);
|
||||
return irisPool;
|
||||
});
|
||||
IrisJigsawPieceConnector connector = new IrisJigsawPieceConnector();
|
||||
connector.setName(name);
|
||||
connector.setTargetName(target);
|
||||
@@ -169,10 +192,14 @@ public class ConversionSVC implements IrisService {
|
||||
}
|
||||
}
|
||||
|
||||
if (piece.getObject().isBlank() || piece.getObject().isEmpty()) {
|
||||
Iris.info(C.RED + "Failed Setting object with path: " + in.toURI().relativize(folder.toURI()).getPath() + file.getName().split("\\Q.\\E")[0]);
|
||||
}
|
||||
jpool.getPieces().addIfMissing(id);
|
||||
object.write(new File(destObjects, file.getName().split("\\Q.\\E")[0] + ".iob"));
|
||||
IO.writeAll(new File(destPieces, file.getName().split("\\Q.\\E")[0] + ".json"), new JSONObject(new Gson().toJson(piece)).toString(4));
|
||||
Iris.info("[Jigsaw]: (" + Form.pc((double) at.get() / (double) total.get(), 0) + ") Exported Piece: " + id);
|
||||
Iris.info("[Jigsaw]: (" + Form.pc((double) at.get() / (double) total.get(), 0).replace("%", "%%") + ") Exported Piece: " + id);
|
||||
|
||||
}
|
||||
} catch (Throwable e) {
|
||||
e.printStackTrace();
|
||||
|
||||
@@ -50,6 +50,10 @@ public class ExternalDataSVC implements IrisService {
|
||||
if (Bukkit.getPluginManager().getPlugin("Oraxen") != null) {
|
||||
Iris.info("Oraxen found, loading OraxenDataProvider...");
|
||||
}
|
||||
providers.add(new MythicCrucibleDataProvider());
|
||||
if (Bukkit.getPluginManager().getPlugin("MythicCrucible") != null) {
|
||||
Iris.info("MythicCrucible found, loading MythicCrucibleDataProvider...");
|
||||
}
|
||||
providers.add(new ItemAdderDataProvider());
|
||||
if (Bukkit.getPluginManager().getPlugin("ItemAdder") != null) {
|
||||
Iris.info("ItemAdder found, loading ItemAdderDataProvider...");
|
||||
|
||||
@@ -2,8 +2,12 @@ package com.volmit.iris.core.tools;
|
||||
|
||||
import com.volmit.iris.Iris;
|
||||
import com.volmit.iris.engine.object.*;
|
||||
import com.volmit.iris.util.collection.KList;
|
||||
import com.volmit.iris.util.collection.KMap;
|
||||
import com.volmit.iris.util.format.C;
|
||||
import com.volmit.iris.util.format.Form;
|
||||
import com.volmit.iris.util.function.Consumer2;
|
||||
import com.volmit.iris.util.misc.E;
|
||||
import com.volmit.iris.util.nbt.io.NBTUtil;
|
||||
import com.volmit.iris.util.nbt.io.NamedTag;
|
||||
import com.volmit.iris.util.nbt.tag.*;
|
||||
@@ -30,6 +34,10 @@ import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
public class IrisConverter {
|
||||
/**
|
||||
* Converts all schematics in the convert folder
|
||||
* @param sender
|
||||
*/
|
||||
public static void convertSchematics(VolmitSender sender) {
|
||||
File folder = Iris.instance.getDataFolder("convert");
|
||||
|
||||
@@ -132,6 +140,62 @@ public class IrisConverter {
|
||||
});
|
||||
}
|
||||
|
||||
// /**
|
||||
// *
|
||||
// * @param sender
|
||||
// */
|
||||
// public static void convertJigsawStructure(File in, File out, VolmitSender sender) {
|
||||
// File dataFolder = Iris.instance.getDataFolder("convert");
|
||||
// try {
|
||||
// KMap<String, IrisJigsawPool> pools = new KMap<>();
|
||||
// KList<File> roots = new KList<>();
|
||||
// AtomicInteger total = new AtomicInteger(0);
|
||||
// AtomicInteger at = new AtomicInteger(0);
|
||||
// File destPools = new File(out.getAbsolutePath() + "/jigsaw-pools");
|
||||
// destPools.mkdirs();
|
||||
// findAllNBT(in, (folder, file) -> {
|
||||
// total.getAndIncrement();
|
||||
// if (roots.addIfMissing(folder)) {
|
||||
// String b = in.toURI().relativize(folder.toURI()).getPath();
|
||||
// if (b.startsWith("/")) {
|
||||
// b = b.substring(1);
|
||||
// }
|
||||
//
|
||||
// if (b.endsWith("/")) {
|
||||
// b = b.substring(0, b.length() - 1);
|
||||
// }
|
||||
//
|
||||
// pools.put(b, new IrisJigsawPool());
|
||||
// }
|
||||
// });
|
||||
//
|
||||
// } catch (Exception e) {
|
||||
// Iris.error(C.RED + "Failed to convert: " + in.getPath());
|
||||
// e.printStackTrace();
|
||||
// }
|
||||
//
|
||||
//
|
||||
//
|
||||
// }
|
||||
|
||||
private static void findAllNBT(File path, Consumer2<File, File> inFile) {
|
||||
if (path == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (path.isFile() && path.getName().endsWith(".nbt")) {
|
||||
inFile.accept(path.getParentFile(), path);
|
||||
return;
|
||||
}
|
||||
for (File i : path.listFiles()) {
|
||||
if (i.isDirectory()) {
|
||||
findAllNBT(i, inFile);
|
||||
} else if (i.isFile() && i.getName().endsWith(".nbt")) {
|
||||
inFile.accept(path, i);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,111 @@
|
||||
package com.volmit.iris.core.tools;
|
||||
|
||||
import com.volmit.iris.Iris;
|
||||
import com.volmit.iris.util.format.Form;
|
||||
import com.volmit.iris.util.math.M;
|
||||
import com.volmit.iris.util.math.RollingSequence;
|
||||
import com.volmit.iris.util.nbt.mca.MCAFile;
|
||||
import com.volmit.iris.util.nbt.mca.MCAUtil;
|
||||
import com.volmit.iris.util.parallel.BurstExecutor;
|
||||
import com.volmit.iris.util.parallel.MultiBurst;
|
||||
import com.volmit.iris.util.scheduling.ChronoLatch;
|
||||
import com.volmit.iris.util.scheduling.Looper;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.ScheduledFuture;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.concurrent.atomic.AtomicLong;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
public class IrisWorldAnalytics {
|
||||
private final ChronoLatch latch;
|
||||
private final String world;
|
||||
private final AtomicInteger totalChunks;
|
||||
private final AtomicInteger processed;
|
||||
private final RollingSequence chunksPerSecond;
|
||||
private final AtomicLong startTime;
|
||||
private final Looper ticker;
|
||||
|
||||
public IrisWorldAnalytics(String world) {
|
||||
this.world = world;
|
||||
|
||||
totalChunks = new AtomicInteger();
|
||||
processed = new AtomicInteger(0);
|
||||
latch = new ChronoLatch(3000);
|
||||
chunksPerSecond = new RollingSequence(3000);
|
||||
startTime = new AtomicLong(M.ms());
|
||||
index();
|
||||
ticker = new Looper() {
|
||||
@Override
|
||||
protected long loop() {
|
||||
|
||||
|
||||
|
||||
return 1000;
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
public void execute() {
|
||||
Iris.info("Starting world analyser..");
|
||||
long startTime = System.currentTimeMillis();
|
||||
|
||||
}
|
||||
|
||||
private long computeETA() {
|
||||
return (long) (totalChunks.get() > 1024 ? // Generated chunks exceed 1/8th of total?
|
||||
// If yes, use smooth function (which gets more accurate over time since its less sensitive to outliers)
|
||||
((totalChunks.get() - processed.get()) * ((double) (M.ms() - startTime.get()) / (double) processed.get())) :
|
||||
// If no, use quick function (which is less accurate over time but responds better to the initial delay)
|
||||
((totalChunks.get() - processed.get()) / chunksPerSecond.getAverage()) * 1000
|
||||
);
|
||||
}
|
||||
|
||||
private void index() {
|
||||
try {
|
||||
AtomicInteger chunks = new AtomicInteger();
|
||||
AtomicInteger pr = new AtomicInteger();
|
||||
AtomicInteger pl = new AtomicInteger(0);
|
||||
RollingSequence rps = new RollingSequence(5);
|
||||
ChronoLatch cl = new ChronoLatch(3000);
|
||||
File[] McaFiles = new File(world, "region").listFiles((dir, name) -> name.endsWith(".mca"));
|
||||
Supplier<Long> eta = () -> (long) ((McaFiles.length - pr.get()) / rps.getAverage()) * 1000;
|
||||
ScheduledFuture<?> sc = Executors.newSingleThreadScheduledExecutor().scheduleAtFixedRate(() -> {
|
||||
int sp = pr.get() - pl.get();
|
||||
pl.set(pr.get());
|
||||
rps.put(sp);
|
||||
if (cl.flip()) {
|
||||
double pc = ((double) pr.get() / (double) McaFiles.length) * 100;
|
||||
Iris.info("Indexing: " + Form.f(pr.get()) + " of " + Form.f(McaFiles.length) + " (%.0f%%) " + Form.f((int) rps.getAverage()) + "/s ETA: " + Form.duration(eta.get(), 2), pc);
|
||||
}
|
||||
}, 3,1, TimeUnit.SECONDS);
|
||||
BurstExecutor b = MultiBurst.burst.burst(McaFiles.length);
|
||||
for (File mca : McaFiles) {
|
||||
b.queue(() -> {
|
||||
try {
|
||||
MCAFile region = MCAUtil.read(mca, 0);
|
||||
var array = region.getChunks();
|
||||
for (int i = 0; i < array.length(); i++) {
|
||||
if (array.get(i) != null) {
|
||||
chunks.incrementAndGet();
|
||||
}
|
||||
}
|
||||
pr.incrementAndGet();
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
});
|
||||
}
|
||||
b.complete();
|
||||
sc.cancel(true);
|
||||
totalChunks.set(chunks.get());
|
||||
Iris.info("Indexing completed!");
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
package com.volmit.iris.core.tools;
|
||||
|
||||
import com.volmit.iris.engine.framework.Engine;
|
||||
import com.volmit.iris.util.math.Position2;
|
||||
import com.volmit.iris.util.math.Spiraler;
|
||||
import org.bukkit.Chunk;
|
||||
import org.bukkit.Material;
|
||||
import org.bukkit.World;
|
||||
import org.bukkit.block.Block;
|
||||
|
||||
public class IrisWorldMerger {
|
||||
private Engine engine;
|
||||
private World world;
|
||||
private World selectedWorld;
|
||||
|
||||
/**
|
||||
* @param world > The selected world to get the caves from
|
||||
* @param engine > The engine of the iris world
|
||||
*/
|
||||
public IrisWorldMerger(Engine engine, World world) {
|
||||
this.engine = engine;
|
||||
this.world = this.engine.getWorld().realWorld();
|
||||
this.selectedWorld = world;
|
||||
}
|
||||
|
||||
/**
|
||||
* Merges caves from a selected chunk into the corresponding chunk in the outcome world.
|
||||
*
|
||||
* @param selectedChunk The chunk from the selected world.
|
||||
* @param targetChunk The corresponding chunk in the outcome world.
|
||||
*/
|
||||
private void mergeCavesInChunk(Chunk selectedChunk, Chunk targetChunk) {
|
||||
int baseX = selectedChunk.getX() << 4;
|
||||
int baseZ = selectedChunk.getZ() << 4;
|
||||
|
||||
for (int x = 0; x < 16; x++) {
|
||||
int worldX = baseX + x;
|
||||
for (int z = 0; z < 16; z++) {
|
||||
int worldZ = baseZ + z;
|
||||
int surfaceY = engine.getHeight(worldX, worldZ);
|
||||
for (int y = 0; y <= surfaceY; y++) {
|
||||
Block selectedBlock = selectedChunk.getBlock(x, y, z);
|
||||
if (selectedBlock.getType() == Material.AIR) {
|
||||
Block targetBlock = targetChunk.getBlock(x, y, z);
|
||||
targetBlock.setType(Material.AIR);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Irritates (merges) caves in a spiral pattern around the specified center chunk coordinates.
|
||||
*
|
||||
* @param centerX The X coordinate of the center chunk.
|
||||
* @param centerZ The Z coordinate of the center chunk.
|
||||
* @param radius The radius (in chunks) to merge caves around.
|
||||
*/
|
||||
public void irritateSpiral(int centerX, int centerZ, int radius) {
|
||||
Spiraler spiraler = new Spiraler(radius * 2, radius * 2, (x, z) -> {
|
||||
int chunkX = centerX + x;
|
||||
int chunkZ = centerZ + z;
|
||||
Chunk selectedChunk = selectedWorld.getChunkAt(chunkX, chunkZ);
|
||||
Chunk targetChunk = world.getChunkAt(chunkX, chunkZ);
|
||||
mergeCavesInChunk(selectedChunk, targetChunk);
|
||||
});
|
||||
|
||||
// Execute the spiral iteration
|
||||
while (spiraler.hasNext()) {
|
||||
spiraler.next(); // The spiraler itself runs the callback defined in its constructor
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -21,10 +21,11 @@ package com.volmit.iris.engine;
|
||||
import com.google.common.util.concurrent.AtomicDouble;
|
||||
import com.google.gson.Gson;
|
||||
import com.volmit.iris.Iris;
|
||||
import com.volmit.iris.core.IrisSettings;
|
||||
import com.volmit.iris.core.ServerConfigurator;
|
||||
import com.volmit.iris.core.events.IrisEngineHotloadEvent;
|
||||
import com.volmit.iris.core.gui.PregeneratorJob;
|
||||
import com.volmit.iris.core.nms.IMemoryWorld;
|
||||
import com.volmit.iris.core.nms.INMS;
|
||||
import com.volmit.iris.core.nms.container.BlockPos;
|
||||
import com.volmit.iris.core.nms.container.Pair;
|
||||
import com.volmit.iris.core.project.IrisProject;
|
||||
@@ -53,21 +54,24 @@ import com.volmit.iris.util.scheduling.PrecisionStopwatch;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.ToString;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.apache.commons.lang3.function.Failable;
|
||||
import org.bukkit.Material;
|
||||
import org.bukkit.NamespacedKey;
|
||||
import org.bukkit.WorldCreator;
|
||||
import org.bukkit.block.Biome;
|
||||
import org.bukkit.block.data.BlockData;
|
||||
import org.bukkit.command.CommandSender;
|
||||
import org.bukkit.persistence.PersistentDataContainer;
|
||||
import org.bukkit.persistence.PersistentDataType;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.HashSet;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.concurrent.atomic.AtomicLong;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
@Data
|
||||
@EqualsAndHashCode(exclude = "context")
|
||||
@@ -96,6 +100,8 @@ public class IrisEngine implements Engine {
|
||||
private EngineEffects effects;
|
||||
private EngineExecutionEnvironment execution;
|
||||
private EngineWorldManager worldManager;
|
||||
private IMemoryWorld memoryWorld;
|
||||
private IrisMerger merger;
|
||||
private volatile int parallelism;
|
||||
private volatile int minHeight;
|
||||
private boolean failing;
|
||||
@@ -127,6 +133,9 @@ public class IrisEngine implements Engine {
|
||||
context = new IrisContext(this);
|
||||
cleaning = new AtomicBoolean(false);
|
||||
context.touch();
|
||||
merger = getDimension().getMerger();
|
||||
merger.loadWorld(this);
|
||||
updateMemoryWorld();
|
||||
getData().setEngine(this);
|
||||
getData().loadPrefetch(this);
|
||||
Iris.info("Initializing Engine: " + target.getWorld().name() + "/" + target.getDimension().getLoadKey() + " (" + target.getDimension().getDimensionHeight() + " height) Seed: " + getSeedManager().getSeed());
|
||||
@@ -192,6 +201,34 @@ public class IrisEngine implements Engine {
|
||||
mode = getDimension().getMode().getType().create(this);
|
||||
}
|
||||
|
||||
private void updateMemoryWorld() {
|
||||
try {
|
||||
if(!merger.isUseMemoryWorld() || merger.getGenerator().isEmpty())
|
||||
return;
|
||||
merger = getDimension().getMerger();
|
||||
if (!getDimension().isEnableExperimentalMerger()) return;
|
||||
if (getMerger().getGenerator().isBlank()) return;
|
||||
NamespacedKey dk = NamespacedKey.minecraft("memory_current_creator");
|
||||
PersistentDataContainer per;
|
||||
if (memoryWorld != null) {
|
||||
per = memoryWorld.getBukkit().getPersistentDataContainer();
|
||||
if (Objects.equals(per.get(dk, PersistentDataType.STRING), getMerger().getGenerator()))
|
||||
return;
|
||||
if (memoryWorld != null)
|
||||
memoryWorld.close();
|
||||
}
|
||||
memoryWorld = getMerger().isDatapackMode() ? Failable.get(() ->
|
||||
getMerger().getGenerator() == null ?
|
||||
INMS.get().createMemoryWorld(new WorldCreator("memoryworld").seed(getSeedManager().getSeed())) :
|
||||
INMS.get().createMemoryWorld(NamespacedKey.minecraft(getMerger().getGenerator()), new WorldCreator("memoryworld").seed(getSeedManager().getSeed()))
|
||||
) : null; // todo: experimental
|
||||
per = memoryWorld.getBukkit().getPersistentDataContainer();
|
||||
per.set(dk, PersistentDataType.STRING, getMerger().getGenerator());
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void generateMatter(int x, int z, boolean multicore, ChunkContext context) {
|
||||
getMantle().generateMatter(x, z, multicore, context);
|
||||
@@ -241,6 +278,7 @@ public class IrisEngine implements Engine {
|
||||
getData().clearLists();
|
||||
getTarget().setDimension(getData().getDimensionLoader().load(getDimension().getLoadKey()));
|
||||
prehotload();
|
||||
updateMemoryWorld();
|
||||
setupEngine();
|
||||
J.a(() -> {
|
||||
synchronized (ServerConfigurator.class) {
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
|
||||
package com.volmit.iris.engine.actuator;
|
||||
|
||||
import com.volmit.iris.core.nms.IMemoryWorld;
|
||||
import com.volmit.iris.engine.framework.Engine;
|
||||
import com.volmit.iris.engine.framework.EngineAssignedActuator;
|
||||
import com.volmit.iris.engine.object.IrisBiome;
|
||||
@@ -29,15 +30,18 @@ import com.volmit.iris.util.hunk.Hunk;
|
||||
import com.volmit.iris.util.math.RNG;
|
||||
import com.volmit.iris.util.scheduling.PrecisionStopwatch;
|
||||
import lombok.Getter;
|
||||
import org.bukkit.Material;
|
||||
import org.bukkit.*;
|
||||
import org.bukkit.block.data.BlockData;
|
||||
|
||||
public class IrisTerrainNormalActuator extends EngineAssignedActuator<BlockData> {
|
||||
private static final BlockData AIR = Material.AIR.createBlockData();
|
||||
private static final BlockData BEDROCK = Material.BEDROCK.createBlockData();
|
||||
private static final BlockData DEEPSLATE = Material.DEEPSLATE.createBlockData();
|
||||
private static final BlockData LAVA = Material.LAVA.createBlockData();
|
||||
private static final BlockData GLASS = Material.GLASS.createBlockData();
|
||||
private static final BlockData CAVE_AIR = Material.CAVE_AIR.createBlockData();
|
||||
private static final BlockData FILLER = Material.STONE.createBlockData();
|
||||
private IMemoryWorld memoryWorld;
|
||||
@Getter
|
||||
private final RNG rng;
|
||||
@Getter
|
||||
@@ -51,15 +55,21 @@ public class IrisTerrainNormalActuator extends EngineAssignedActuator<BlockData>
|
||||
@BlockCoordinates
|
||||
@Override
|
||||
public void onActuate(int x, int z, Hunk<BlockData> h, boolean multicore, ChunkContext context) {
|
||||
PrecisionStopwatch p = PrecisionStopwatch.start();
|
||||
try {
|
||||
PrecisionStopwatch p = PrecisionStopwatch.start();
|
||||
|
||||
for (int xf = 0; xf < h.getWidth(); xf++) {
|
||||
terrainSliver(x, z, xf, h, context);
|
||||
for (int xf = 0; xf < h.getWidth(); xf++) {
|
||||
terrainSliver(x, z, xf, h, context);
|
||||
}
|
||||
|
||||
getEngine().getMetrics().getTerrain().put(p.getMilliseconds());
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
//Iris.error("Fatal Error!", e);
|
||||
}
|
||||
|
||||
getEngine().getMetrics().getTerrain().put(p.getMilliseconds());
|
||||
}
|
||||
|
||||
|
||||
private int fluidOrHeight(int height) {
|
||||
return Math.max(getDimension().getFluidHeight(), height);
|
||||
}
|
||||
@@ -138,6 +148,11 @@ public class IrisTerrainNormalActuator extends EngineAssignedActuator<BlockData>
|
||||
continue;
|
||||
}
|
||||
|
||||
if (getDimension().isEnableExperimentalMerger()) {
|
||||
h.set(xf, i, zf, FILLER);
|
||||
continue;
|
||||
}
|
||||
|
||||
BlockData ore = biome.generateOres(realX, i, realZ, rng, getData());
|
||||
ore = ore == null ? region.generateOres(realX, i, realZ, rng, getData()) : ore;
|
||||
ore = ore == null ? getDimension().generateOres(realX, i, realZ, rng, getData()) : ore;
|
||||
@@ -145,7 +160,11 @@ public class IrisTerrainNormalActuator extends EngineAssignedActuator<BlockData>
|
||||
if (ore != null) {
|
||||
h.set(xf, i, zf, ore);
|
||||
} else {
|
||||
h.set(xf, i, zf, context.getRock().get(xf, zf));
|
||||
if (getDimension().isDeepslateLayer() && i < 64) {
|
||||
h.set(xf, i, zf, DEEPSLATE);
|
||||
} else {
|
||||
h.set(xf, i, zf, context.getRock().get(xf, zf));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,8 +25,10 @@ import com.volmit.iris.core.gui.components.Renderer;
|
||||
import com.volmit.iris.core.link.Identifier;
|
||||
import com.volmit.iris.core.loader.IrisData;
|
||||
import com.volmit.iris.core.loader.IrisRegistrant;
|
||||
import com.volmit.iris.core.nms.IMemoryWorld;
|
||||
import com.volmit.iris.core.nms.container.BlockPos;
|
||||
import com.volmit.iris.core.nms.container.Pair;
|
||||
import com.volmit.iris.core.pregenerator.ChunkUpdater;
|
||||
import com.volmit.iris.core.service.ExternalDataSVC;
|
||||
import com.volmit.iris.engine.IrisComplex;
|
||||
import com.volmit.iris.engine.data.cache.Cache;
|
||||
@@ -57,15 +59,13 @@ import com.volmit.iris.util.matter.TileWrapper;
|
||||
import com.volmit.iris.util.matter.slices.container.JigsawPieceContainer;
|
||||
import com.volmit.iris.util.parallel.BurstExecutor;
|
||||
import com.volmit.iris.util.parallel.MultiBurst;
|
||||
import com.volmit.iris.util.reflect.W;
|
||||
import com.volmit.iris.util.scheduling.ChronoLatch;
|
||||
import com.volmit.iris.util.scheduling.J;
|
||||
import com.volmit.iris.util.scheduling.PrecisionStopwatch;
|
||||
import com.volmit.iris.util.stream.ProceduralStream;
|
||||
import io.papermc.lib.PaperLib;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.Chunk;
|
||||
import org.bukkit.Location;
|
||||
import org.bukkit.Material;
|
||||
import org.bukkit.*;
|
||||
import org.bukkit.block.Biome;
|
||||
import org.bukkit.block.Block;
|
||||
import org.bukkit.block.BlockFace;
|
||||
@@ -77,9 +77,11 @@ import org.bukkit.inventory.InventoryHolder;
|
||||
import org.bukkit.inventory.ItemStack;
|
||||
|
||||
import java.awt.*;
|
||||
import java.awt.Color;
|
||||
import java.util.Arrays;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.Semaphore;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.concurrent.atomic.AtomicLong;
|
||||
@@ -110,6 +112,10 @@ public interface Engine extends DataProvider, Fallible, LootProvider, BlockUpdat
|
||||
|
||||
EngineExecutionEnvironment getExecution();
|
||||
|
||||
IMemoryWorld getMemoryWorld();
|
||||
|
||||
IrisMerger getMerger();
|
||||
|
||||
double getMaxBiomeObjectDensity();
|
||||
|
||||
double getMaxBiomeDecoratorDensity();
|
||||
@@ -273,33 +279,43 @@ public interface Engine extends DataProvider, Fallible, LootProvider, BlockUpdat
|
||||
for (int z = -1; z <= 1; z++) {
|
||||
if (c.getWorld().isChunkLoaded(c.getX() + x, c.getZ() + z))
|
||||
continue;
|
||||
Iris.debug("Chunk %s, %s [%s, %s] is not loaded".formatted(c.getX() + x, c.getZ() + z, x, z));
|
||||
var msg = "Chunk %s, %s [%s, %s] is not loaded".formatted(c.getX() + x, c.getZ() + z, x, z);
|
||||
if (W.getStack().getCallerClass().equals(ChunkUpdater.class)) Iris.warn(msg);
|
||||
else Iris.debug(msg);
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (!getMantle().getMantle().isLoaded(c)) {
|
||||
Iris.debug("Mantle Chunk " + c.getX() + c.getX() + " is not loaded");
|
||||
var mantle = getMantle().getMantle();
|
||||
if (!mantle.isLoaded(c)) {
|
||||
var msg = "Mantle Chunk " + c.getX() + c.getX() + " is not loaded";
|
||||
if (W.getStack().getCallerClass().equals(ChunkUpdater.class)) Iris.warn(msg);
|
||||
else Iris.debug(msg);
|
||||
return;
|
||||
}
|
||||
|
||||
getMantle().getMantle().raiseFlag(c.getX(), c.getZ(), MantleFlag.TILE, () -> J.s(() -> {
|
||||
getMantle().getMantle().iterateChunk(c.getX(), c.getZ(), TileWrapper.class, (x, y, z, v) -> {
|
||||
var chunk = mantle.getChunk(c);
|
||||
if (chunk.isFlagged(MantleFlag.ETCHED)) return;
|
||||
chunk.flag(MantleFlag.ETCHED, true);
|
||||
|
||||
Semaphore semaphore = new Semaphore(3);
|
||||
chunk.raiseFlag(MantleFlag.TILE, run(semaphore, () -> J.s(() -> {
|
||||
mantle.iterateChunk(c.getX(), c.getZ(), TileWrapper.class, (x, y, z, v) -> {
|
||||
int betterY = y + getWorld().minHeight();
|
||||
if (!TileData.setTileState(c.getBlock(x, betterY, z), v.getData()))
|
||||
Iris.warn("Failed to set tile entity data at [%d %d %d | %s] for tile %s!", x, betterY, z, c.getBlock(x, betterY, z).getBlockData().getMaterial().getKey(), v.getData().getMaterial().name());
|
||||
});
|
||||
}));
|
||||
getMantle().getMantle().raiseFlag(c.getX(), c.getZ(), MantleFlag.CUSTOM, () -> J.s(() -> {
|
||||
getMantle().getMantle().iterateChunk(c.getX(), c.getZ(), Identifier.class, (x, y, z, v) -> {
|
||||
})));
|
||||
chunk.raiseFlag(MantleFlag.CUSTOM, run(semaphore, () -> J.s(() -> {
|
||||
mantle.iterateChunk(c.getX(), c.getZ(), Identifier.class, (x, y, z, v) -> {
|
||||
Iris.service(ExternalDataSVC.class).processUpdate(this, c.getBlock(x & 15, y + getWorld().minHeight(), z & 15), v);
|
||||
});
|
||||
}));
|
||||
})));
|
||||
|
||||
getMantle().getMantle().raiseFlag(c.getX(), c.getZ(), MantleFlag.UPDATE, () -> J.s(() -> {
|
||||
chunk.raiseFlag(MantleFlag.UPDATE, run(semaphore, () -> J.s(() -> {
|
||||
PrecisionStopwatch p = PrecisionStopwatch.start();
|
||||
KMap<Long, Integer> updates = new KMap<>();
|
||||
RNG r = new RNG(Cache.key(c.getX(), c.getZ()));
|
||||
getMantle().getMantle().iterateChunk(c.getX(), c.getZ(), MatterCavern.class, (x, yf, z, v) -> {
|
||||
mantle.iterateChunk(c.getX(), c.getZ(), MatterCavern.class, (x, yf, z, v) -> {
|
||||
int y = yf + getWorld().minHeight();
|
||||
if (!B.isFluid(c.getBlock(x & 15, y, z & 15).getBlockData())) {
|
||||
return;
|
||||
@@ -329,7 +345,7 @@ public interface Engine extends DataProvider, Fallible, LootProvider, BlockUpdat
|
||||
});
|
||||
|
||||
updates.forEach((k, v) -> update(Cache.keyX(k), v, Cache.keyZ(k), c, r));
|
||||
getMantle().getMantle().iterateChunk(c.getX(), c.getZ(), MatterUpdate.class, (x, yf, z, v) -> {
|
||||
mantle.iterateChunk(c.getX(), c.getZ(), MatterUpdate.class, (x, yf, z, v) -> {
|
||||
int y = yf + getWorld().minHeight();
|
||||
if (v != null && v.isUpdate()) {
|
||||
int vx = x & 15;
|
||||
@@ -340,9 +356,25 @@ public interface Engine extends DataProvider, Fallible, LootProvider, BlockUpdat
|
||||
}
|
||||
}
|
||||
});
|
||||
getMantle().getMantle().deleteChunkSlice(c.getX(), c.getZ(), MatterUpdate.class);
|
||||
mantle.deleteChunkSlice(c.getX(), c.getZ(), MatterUpdate.class);
|
||||
getMetrics().getUpdates().put(p.getMilliseconds());
|
||||
}, RNG.r.i(0, 20)));
|
||||
}, RNG.r.i(0, 20))));
|
||||
|
||||
try {
|
||||
semaphore.acquire(3);
|
||||
} catch (InterruptedException ignored) {}
|
||||
}
|
||||
|
||||
private static Runnable run(Semaphore semaphore, Runnable runnable) {
|
||||
return () -> {
|
||||
if (!semaphore.tryAcquire())
|
||||
return;
|
||||
try {
|
||||
runnable.run();
|
||||
} finally {
|
||||
semaphore.release();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@BlockCoordinates
|
||||
@@ -388,7 +420,7 @@ public interface Engine extends DataProvider, Fallible, LootProvider, BlockUpdat
|
||||
if (tables.isEmpty())
|
||||
return;
|
||||
InventoryHolder m = (InventoryHolder) block.getState();
|
||||
addItems(false, m.getInventory(), rx, tables, slot, x, y, z, 15);
|
||||
addItems(false, m.getInventory(), rx, tables, slot, c.getWorld(), x, y, z, 15);
|
||||
|
||||
} catch (Throwable e) {
|
||||
Iris.reportError(e);
|
||||
@@ -497,7 +529,7 @@ public interface Engine extends DataProvider, Fallible, LootProvider, BlockUpdat
|
||||
}
|
||||
|
||||
@Override
|
||||
default void addItems(boolean debug, Inventory inv, RNG rng, KList<IrisLootTable> tables, InventorySlotType slot, int x, int y, int z, int mgf) {
|
||||
default void addItems(boolean debug, Inventory inv, RNG rng, KList<IrisLootTable> tables, InventorySlotType slot, World world, int x, int y, int z, int mgf) {
|
||||
KList<ItemStack> items = new KList<>();
|
||||
|
||||
int b = 4;
|
||||
@@ -505,7 +537,7 @@ public interface Engine extends DataProvider, Fallible, LootProvider, BlockUpdat
|
||||
if (i == null)
|
||||
continue;
|
||||
b++;
|
||||
items.addAll(i.getLoot(debug, rng, slot, x, y, z));
|
||||
items.addAll(i.getLoot(debug, rng, slot, world, x, y, z));
|
||||
}
|
||||
|
||||
if (PaperLib.isPaper() && getWorld().hasRealWorld()) {
|
||||
|
||||
@@ -3,29 +3,106 @@ package com.volmit.iris.engine.framework;
|
||||
import com.volmit.iris.engine.object.InventorySlotType;
|
||||
import com.volmit.iris.engine.object.IrisLootTable;
|
||||
import com.volmit.iris.util.collection.KList;
|
||||
import com.volmit.iris.util.math.RNG;
|
||||
import lombok.Getter;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.Material;
|
||||
import org.bukkit.NamespacedKey;
|
||||
import org.bukkit.World;
|
||||
import org.bukkit.block.Block;
|
||||
import org.bukkit.event.Event;
|
||||
import org.bukkit.event.HandlerList;
|
||||
import org.bukkit.event.world.LootGenerateEvent;
|
||||
|
||||
import org.bukkit.inventory.Inventory;
|
||||
import org.bukkit.inventory.InventoryHolder;
|
||||
import org.bukkit.inventory.ItemStack;
|
||||
import org.bukkit.loot.LootContext;
|
||||
import org.bukkit.loot.LootTable;
|
||||
import org.bukkit.loot.LootTables;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
|
||||
@Getter
|
||||
public class IrisLootEvent extends Event {
|
||||
private static final HandlerList handlers = new HandlerList();
|
||||
|
||||
private final Engine engine;
|
||||
private final Block block;
|
||||
private final InventorySlotType slot;
|
||||
private final KList<IrisLootTable> tables;
|
||||
private final Mode mode; // New field to represent the mode
|
||||
|
||||
// Define the different modes for the event
|
||||
public enum Mode {
|
||||
NORMAL,
|
||||
BUKKIT_LOOT
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructor for IrisLootEvent with mode selection.
|
||||
*
|
||||
* @param engine The engine instance.
|
||||
* @param block The block associated with the event.
|
||||
* @param slot The inventory slot type.
|
||||
* @param tables The list of IrisLootTables. (mutable*)
|
||||
*/
|
||||
public IrisLootEvent(Engine engine, Block block, InventorySlotType slot, KList<IrisLootTable> tables) {
|
||||
this.engine = engine;
|
||||
this.block = block;
|
||||
this.slot = slot;
|
||||
this.tables = tables;
|
||||
this.mode = Mode.BUKKIT_LOOT;
|
||||
|
||||
if (this.mode == Mode.BUKKIT_LOOT) {
|
||||
triggerBukkitLootEvent();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Triggers the corresponding Bukkit loot event.
|
||||
* This method integrates your custom IrisLootTables with Bukkit's LootGenerateEvent,
|
||||
* allowing other plugins to modify or cancel the loot generation.
|
||||
*/
|
||||
private Inventory triggerBukkitLootEvent() {
|
||||
if (block.getState() instanceof InventoryHolder holder) {
|
||||
Inventory inventory = holder.getInventory();
|
||||
inventory.clear();
|
||||
|
||||
List<ItemStack> loot = new ArrayList<>();
|
||||
RNG rng = new RNG();
|
||||
int x = block.getX(), y = block.getY(), z = block.getZ();
|
||||
|
||||
for (IrisLootTable table : tables)
|
||||
loot.addAll(table.getLoot(false, rng, slot, block.getWorld(), x, y, z));
|
||||
|
||||
LootContext context = new LootContext.Builder(block.getLocation()).build();
|
||||
|
||||
LootTable lootTable = LootTables.EMPTY.getLootTable(); // todo: Correct structure
|
||||
|
||||
LootGenerateEvent bukkitEvent = new LootGenerateEvent(engine.getWorld().realWorld(), null, holder, lootTable, context, loot, true); // todo: Use the iris loottable
|
||||
Bukkit.getServer().getPluginManager().callEvent(bukkitEvent);
|
||||
|
||||
if (!bukkitEvent.isCancelled())
|
||||
inventory.setContents(bukkitEvent.getLoot().toArray(new ItemStack[0]));
|
||||
return inventory;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public HandlerList getHandlers() {
|
||||
return handlers;
|
||||
}
|
||||
|
||||
/**
|
||||
* Required method to get the HandlerList for this event.
|
||||
*
|
||||
* @return The HandlerList.
|
||||
*/
|
||||
public static HandlerList getHandlerList() {
|
||||
return handlers;
|
||||
}
|
||||
|
||||
@@ -23,6 +23,7 @@ import com.volmit.iris.engine.object.IrisLootReference;
|
||||
import com.volmit.iris.engine.object.IrisLootTable;
|
||||
import com.volmit.iris.util.collection.KList;
|
||||
import com.volmit.iris.util.math.RNG;
|
||||
import org.bukkit.World;
|
||||
import org.bukkit.block.Block;
|
||||
import org.bukkit.inventory.Inventory;
|
||||
|
||||
@@ -33,5 +34,5 @@ public interface LootProvider {
|
||||
|
||||
KList<IrisLootTable> getLootTables(RNG rng, Block b);
|
||||
|
||||
void addItems(boolean debug, Inventory inv, RNG rng, KList<IrisLootTable> tables, InventorySlotType slot, int x, int y, int z, int mgf);
|
||||
void addItems(boolean debug, Inventory inv, RNG rng, KList<IrisLootTable> tables, InventorySlotType slot, World world, int x, int y, int z, int mgf);
|
||||
}
|
||||
|
||||
@@ -23,6 +23,7 @@ import org.bukkit.block.Block;
|
||||
import org.bukkit.block.BlockState;
|
||||
import org.bukkit.block.TileState;
|
||||
import org.bukkit.block.data.BlockData;
|
||||
import org.bukkit.inventory.Inventory;
|
||||
import org.bukkit.inventory.InventoryHolder;
|
||||
|
||||
@Getter
|
||||
@@ -64,6 +65,8 @@ public class WorldObjectPlacer implements IObjectPlacer {
|
||||
RNG rx = new RNG(Cache.key(x, z));
|
||||
KList<IrisLootTable> tables = engine.getLootTables(rx, block);
|
||||
|
||||
Inventory inventory = null;
|
||||
|
||||
try {
|
||||
Bukkit.getPluginManager().callEvent(new IrisLootEvent(engine, block, slot, tables));
|
||||
|
||||
@@ -74,7 +77,7 @@ public class WorldObjectPlacer implements IObjectPlacer {
|
||||
if (tables.isEmpty())
|
||||
return;
|
||||
InventoryHolder m = (InventoryHolder) block.getState();
|
||||
engine.addItems(false, m.getInventory(), rx, tables, slot, x, y, z, 15);
|
||||
engine.addItems(false, m.getInventory(), rx, tables, slot, world, x, y, z, 15);
|
||||
} catch (Throwable e) {
|
||||
Iris.reportError(e);
|
||||
}
|
||||
|
||||
@@ -30,6 +30,7 @@ import com.volmit.iris.util.mantle.Mantle;
|
||||
import com.volmit.iris.util.math.Position2;
|
||||
import com.volmit.iris.util.math.RNG;
|
||||
import com.volmit.iris.util.matter.slices.container.JigsawPieceContainer;
|
||||
import com.volmit.iris.util.matter.slices.container.JigsawStructureContainer;
|
||||
import com.volmit.iris.util.matter.slices.container.JigsawStructuresContainer;
|
||||
import com.volmit.iris.util.scheduling.J;
|
||||
import lombok.Data;
|
||||
@@ -50,16 +51,18 @@ public class PlannedStructure {
|
||||
private IrisPosition position;
|
||||
private IrisData data;
|
||||
private RNG rng;
|
||||
private boolean forcePlace;
|
||||
private boolean verbose;
|
||||
private boolean terminating;
|
||||
|
||||
public PlannedStructure(IrisJigsawStructure structure, IrisPosition position, RNG rng) {
|
||||
public PlannedStructure(IrisJigsawStructure structure, IrisPosition position, RNG rng, boolean forcePlace) {
|
||||
terminating = false;
|
||||
verbose = true;
|
||||
this.pieces = new KList<>();
|
||||
this.structure = structure;
|
||||
this.position = position;
|
||||
this.rng = rng;
|
||||
this.forcePlace = forcePlace;
|
||||
this.data = structure.getLoader();
|
||||
generateStartPiece();
|
||||
|
||||
@@ -108,6 +111,9 @@ public class PlannedStructure {
|
||||
} else {
|
||||
options.setMode(i.getPiece().getPlaceMode());
|
||||
}
|
||||
if (forcePlace) {
|
||||
options.setForcePlace(true);
|
||||
}
|
||||
|
||||
IrisObject v = i.getObject();
|
||||
int sx = (v.getW() / 2);
|
||||
@@ -146,10 +152,12 @@ public class PlannedStructure {
|
||||
|
||||
int id = rng.i(0, Integer.MAX_VALUE);
|
||||
JigsawPieceContainer container = JigsawPieceContainer.toContainer(i.getPiece());
|
||||
JigsawStructureContainer structureContainer = JigsawStructureContainer.toContainer(structure);
|
||||
i.setRealPositions(xx, height, zz, placer);
|
||||
return v.place(xx, height, zz, placer, options, rng, (b, data) -> {
|
||||
e.set(b.getX(), b.getY(), b.getZ(), v.getLoadKey() + "@" + id);
|
||||
e.set(b.getX(), b.getY(), b.getZ(), container);
|
||||
e.set(b.getX(), b.getY(), b.getZ(), structureContainer);
|
||||
if (data instanceof IrisBlockData d) {
|
||||
e.set(b.getX(), b.getY(), b.getZ(), d.getCustom());
|
||||
}
|
||||
|
||||
@@ -66,7 +66,7 @@ public class MantleJigsawComponent extends IrisMantleComponent {
|
||||
for (Position2 pos : poss) {
|
||||
if (x == pos.getX() >> 4 && z == pos.getZ() >> 4) {
|
||||
IrisJigsawStructure structure = getData().getJigsawStructureLoader().load(getDimension().getStronghold());
|
||||
place(writer, pos.toIris(), structure, new RNG(seed));
|
||||
place(writer, pos.toIris(), structure, new RNG(seed), true);
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -92,7 +92,7 @@ public class MantleJigsawComponent extends IrisMantleComponent {
|
||||
RNG rng = new RNG(seed);
|
||||
IrisPosition position = new IrisPosition((x << 4) + rng.nextInt(15), 0, (z << 4) + rng.nextInt(15));
|
||||
IrisJigsawStructure structure = getData().getJigsawStructureLoader().load(i.getStructure());
|
||||
return place(writer, position, structure, rng);
|
||||
return place(writer, position, structure, rng, false);
|
||||
}
|
||||
|
||||
@ChunkCoordinates
|
||||
@@ -161,8 +161,8 @@ public class MantleJigsawComponent extends IrisMantleComponent {
|
||||
}
|
||||
|
||||
@BlockCoordinates
|
||||
private boolean place(MantleWriter writer, IrisPosition position, IrisJigsawStructure structure, RNG rng) {
|
||||
return new PlannedStructure(structure, position, rng).place(writer, getMantle(), writer.getEngine());
|
||||
private boolean place(MantleWriter writer, IrisPosition position, IrisJigsawStructure structure, RNG rng, boolean forcePlace) {
|
||||
return new PlannedStructure(structure, position, rng, forcePlace).place(writer, getMantle(), writer.getEngine());
|
||||
}
|
||||
|
||||
private long jigsaw() {
|
||||
|
||||
@@ -67,6 +67,10 @@ public class IrisCarving {
|
||||
|
||||
@BlockCoordinates
|
||||
public void doCarving(MantleWriter writer, RNG rng, Engine engine, int x, int y, int z, int waterHint) {
|
||||
if (!engine.getDimension().isDoCaves()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (caves.isNotEmpty()) {
|
||||
for (IrisCavePlacer i : caves) {
|
||||
i.generateCave(writer, rng, engine, x, y, z, waterHint);
|
||||
|
||||
@@ -144,6 +144,10 @@ public class IrisDimension extends IrisRegistrant {
|
||||
@RegistryListResource(IrisJigsawStructure.class)
|
||||
@Desc("If defined, Iris will place the given jigsaw structure where minecraft should place the overworld stronghold.")
|
||||
private String stronghold;
|
||||
@Desc("Iris merger [Experimental] ( Deprecated for v3 )")
|
||||
private IrisMerger merger = new IrisMerger();
|
||||
@Desc("Cheap temp solution till v4 arrives [ Enables the experimental merger ] Requires studio restart to take effect!")
|
||||
private boolean EnableExperimentalMerger = false;
|
||||
@Desc("If set to true, Iris will remove chunks to allow visualizing cross sections of chunks easily")
|
||||
private boolean debugChunkCrossSections = false;
|
||||
@Desc("Vertically split up the biome palettes with 3 air blocks in between to visualize them")
|
||||
@@ -178,6 +182,10 @@ public class IrisDimension extends IrisRegistrant {
|
||||
private KList<IrisBlockDrops> blockDrops = new KList<>();
|
||||
@Desc("Should bedrock be generated or not.")
|
||||
private boolean bedrock = true;
|
||||
@Desc("If under 0 deepslate will be placed instead of the rockPalette")
|
||||
private boolean deepslateLayer = true;
|
||||
@Desc("If true caves get made")
|
||||
private boolean doCaves = true;
|
||||
@MinNumber(0)
|
||||
@MaxNumber(1)
|
||||
@Desc("The land chance. Up to 1.0 for total land or 0.0 for total sea")
|
||||
|
||||
@@ -271,7 +271,7 @@ public class IrisEntity extends IrisRegistrant {
|
||||
|
||||
for (String fi : getLoot().getTables()) {
|
||||
IrisLootTable i = gen.getData().getLootLoader().load(fi);
|
||||
items.addAll(i.getLoot(gen.isStudio(), rng.nextParallelRNG(345911), InventorySlotType.STORAGE, finalAt.getBlockX(), finalAt.getBlockY(), finalAt.getBlockZ()));
|
||||
items.addAll(i.getLoot(gen.isStudio(), rng.nextParallelRNG(345911), InventorySlotType.STORAGE, finalAt.getWorld(), finalAt.getBlockX(), finalAt.getBlockY(), finalAt.getBlockZ()));
|
||||
}
|
||||
|
||||
return items;
|
||||
|
||||
@@ -22,6 +22,7 @@ import com.volmit.iris.Iris;
|
||||
import com.volmit.iris.core.loader.IrisRegistrant;
|
||||
import com.volmit.iris.engine.data.cache.AtomicCache;
|
||||
import com.volmit.iris.engine.object.annotations.*;
|
||||
import com.volmit.iris.engine.object.annotations.functions.StructureKeyFunction;
|
||||
import com.volmit.iris.util.collection.KList;
|
||||
import com.volmit.iris.util.json.JSONObject;
|
||||
import com.volmit.iris.util.plugin.VolmitSender;
|
||||
|
||||
@@ -33,6 +33,7 @@ import lombok.EqualsAndHashCode;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.experimental.Accessors;
|
||||
import org.bukkit.Material;
|
||||
import org.bukkit.World;
|
||||
import org.bukkit.inventory.ItemStack;
|
||||
|
||||
@Accessors(chain = true)
|
||||
@@ -67,7 +68,7 @@ public class IrisLootTable extends IrisRegistrant {
|
||||
@ArrayType(min = 1, type = IrisLoot.class)
|
||||
private KList<IrisLoot> loot = new KList<>();
|
||||
|
||||
public KList<ItemStack> getLoot(boolean debug, RNG rng, InventorySlotType slot, int x, int y, int z) {
|
||||
public KList<ItemStack> getLoot(boolean debug, RNG rng, InventorySlotType slot, World world, int x, int y, int z) {
|
||||
KList<ItemStack> lootf = new KList<>();
|
||||
|
||||
int m = 0;
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
package com.volmit.iris.engine.object;
|
||||
|
||||
|
||||
import com.volmit.iris.engine.object.annotations.Desc;
|
||||
|
||||
@Desc("Modes for generator merging")
|
||||
public enum IrisMergeStrategies {
|
||||
@Desc("Splits the world in height. Use the split settings to customize this option")
|
||||
SPLIT,
|
||||
|
||||
@Desc("Merge from of the engine height")
|
||||
SPLIT_ENGINE_HEIGHT,
|
||||
}
|
||||
391
core/src/main/java/com/volmit/iris/engine/object/IrisMerger.java
Normal file
391
core/src/main/java/com/volmit/iris/engine/object/IrisMerger.java
Normal file
@@ -0,0 +1,391 @@
|
||||
package com.volmit.iris.engine.object;
|
||||
|
||||
import com.volmit.iris.Iris;
|
||||
import com.volmit.iris.core.nms.IMemoryWorld;
|
||||
import com.volmit.iris.core.nms.INMS;
|
||||
import com.volmit.iris.engine.framework.Engine;
|
||||
import com.volmit.iris.engine.object.annotations.Desc;
|
||||
import com.volmit.iris.util.context.ChunkedDataCache;
|
||||
import com.volmit.iris.util.format.Form;
|
||||
import com.volmit.iris.util.hunk.Hunk;
|
||||
import com.volmit.iris.util.math.RollingSequence;
|
||||
import com.volmit.iris.util.parallel.BurstExecutor;
|
||||
import com.volmit.iris.util.parallel.MultiBurst;
|
||||
import com.volmit.iris.util.scheduling.J;
|
||||
import com.volmit.iris.util.scheduling.PrecisionStopwatch;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
import org.bukkit.*;
|
||||
import org.bukkit.block.Biome;
|
||||
import org.bukkit.block.data.BlockData;
|
||||
import org.bukkit.event.EventHandler;
|
||||
import org.bukkit.event.Listener;
|
||||
import org.bukkit.event.world.WorldLoadEvent;
|
||||
import org.bukkit.generator.ChunkGenerator;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.locks.ReentrantLock;
|
||||
import java.util.function.Function;
|
||||
|
||||
@AllArgsConstructor
|
||||
@NoArgsConstructor
|
||||
@Desc("Dimension Merging only supports 1 for now.")
|
||||
@Data
|
||||
public class IrisMerger {
|
||||
private transient RollingSequence mergeDuration = new RollingSequence(20);
|
||||
private transient World worldsave;
|
||||
private transient ReentrantLock lock = new ReentrantLock();
|
||||
private transient ChunkGenerator chunkGenerator;
|
||||
private static final BlockData FILLER = Material.STONE.createBlockData();
|
||||
|
||||
@Desc("Selected Generator")
|
||||
private String generator;
|
||||
|
||||
@Desc("Use Generator")
|
||||
private boolean useMemoryWorld = false;
|
||||
|
||||
@Desc("Allows to gen chunks on the mergers world")
|
||||
private boolean allowGenChunks = true;
|
||||
|
||||
@Desc("Uses a world instead of a generator")
|
||||
private String world;
|
||||
|
||||
@Desc("Uses the generator as a datapack key")
|
||||
private boolean datapackMode;
|
||||
|
||||
@Desc("How deep till it should use vanilla terrain")
|
||||
private int depth = 30;
|
||||
|
||||
@Desc("Gets the terrain x,z height as the limit")
|
||||
private IrisMergeStrategies mode = null;
|
||||
|
||||
@Desc("If it should put the selected generator above or under the split")
|
||||
private boolean splitUnder = true;
|
||||
|
||||
@Desc("Splits in the engine height")
|
||||
private int split = 0;
|
||||
|
||||
@Desc("If it should translate iris deposits/ores to their deepslate variant")
|
||||
private boolean deepslateTranslator = true;
|
||||
|
||||
private final Map<Chunk, Long> lastUse = new ConcurrentHashMap<>();
|
||||
|
||||
/**
|
||||
* Merges underground from a selected chunk into the corresponding chunk in the outcome world.
|
||||
*/
|
||||
@Deprecated
|
||||
public void generateVanillaUnderground(int cx, int cz, Chunk ichunk, Engine engine) {
|
||||
if (engine.getMemoryWorld() == null && useMemoryWorld)
|
||||
throw new IllegalStateException("MemoryWorld is null. Ensure that it has been initialized.");
|
||||
if (engine.getWorld().realWorld() == null)
|
||||
return;
|
||||
|
||||
if (world == null) {
|
||||
Iris.error("World merger is null! cant generate chunks FALLBACK!");
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
PrecisionStopwatch p = PrecisionStopwatch.start();
|
||||
|
||||
IMemoryWorld memoryWorld;
|
||||
World bukkit;
|
||||
|
||||
if (world.isBlank()) {
|
||||
memoryWorld = engine.getMemoryWorld();
|
||||
bukkit = memoryWorld.getBukkit();
|
||||
} else {
|
||||
bukkit = Bukkit.getWorld(world);
|
||||
if (bukkit == null) {
|
||||
Iris.info("World " + world + " not loaded yet, cannot generate chunk at (" + cx + ", " + cz + ")");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
Chunk chunk;
|
||||
if (allowGenChunks) {
|
||||
chunk = bukkit.getChunkAt(cx, cz, true);
|
||||
} else {
|
||||
chunk = bukkit.getChunkAt(cx, cz, false);
|
||||
if (!chunk.isGenerated()) {
|
||||
throw new IllegalStateException("Chunk " + cx + ", " + cz + " not found. OUT OF BOUNDS");
|
||||
}
|
||||
}
|
||||
|
||||
// Chunk ichunk = engine.getWorld().realWorld().getChunkAt(cx, cz);
|
||||
|
||||
if (!chunk.isLoaded())
|
||||
J.s(chunk::load);
|
||||
|
||||
int totalHeight = bukkit.getMaxHeight() - bukkit.getMinHeight();
|
||||
int minHeight = Math.abs(bukkit.getMinHeight());
|
||||
|
||||
var world = engine.getWorld().realWorld();
|
||||
int wX = cx << 4;
|
||||
int wZ = cz << 4;
|
||||
|
||||
BurstExecutor b = MultiBurst.burst.burst();
|
||||
var cache = new ChunkedDataCache<>(b, engine.getComplex().getHeightStream(), wX, wZ);
|
||||
b.complete();
|
||||
|
||||
Set<Biome> caveBiomes = new HashSet<>(Arrays.asList(
|
||||
Biome.DRIPSTONE_CAVES,
|
||||
Biome.LUSH_CAVES,
|
||||
Biome.DEEP_DARK
|
||||
));
|
||||
|
||||
var nms = INMS.get();
|
||||
var flag = new Flags(false, false, true, false, false).value();
|
||||
|
||||
for (int xx = 0; xx < 16; xx += 4) {
|
||||
for (int zz = 0; zz < 16; zz += 4) {
|
||||
int maxHeightInSection = 0;
|
||||
|
||||
for (int x = 0; x < 4; x++) {
|
||||
for (int z = 0; z < 4; z++) {
|
||||
int globalX = xx + x;
|
||||
int globalZ = zz + z;
|
||||
int height = (int) Math.ceil(cache.get(globalX, globalZ) - depth);
|
||||
if (height > maxHeightInSection) {
|
||||
maxHeightInSection = height;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Hunk<BlockData> vh = getHunkSlice(chunk, xx, zz, maxHeightInSection);
|
||||
Hunk<BlockData> ih = getHunkSlice(ichunk, xx, zz, maxHeightInSection);
|
||||
|
||||
for (int x = 0; x < 4; x++) {
|
||||
for (int z = 0; z < 4; z++) {
|
||||
int globalX = xx + x;
|
||||
int globalZ = zz + z;
|
||||
int height = (int) Math.ceil(cache.get(globalX, globalZ) - depth);
|
||||
|
||||
for (int y = 0; y < totalHeight; y++) {
|
||||
if (shouldSkip(y, height))
|
||||
continue;
|
||||
|
||||
BlockData blockData = vh.get(x, y, z);
|
||||
if (!blockData.getMaterial().isAir() && deepslateTranslator) {
|
||||
if (ih.get(x, y, z).getMaterial() != FILLER.getMaterial() && blockData.getMaterial().isOccluding()) {
|
||||
try {
|
||||
BlockData newBlockData = ih.get(x, y, z);
|
||||
if (hasAround(vh, x, y, z, Material.DEEPSLATE)) {
|
||||
String id = newBlockData.getMaterial().getItemTranslationKey().replaceFirst("^block\\.[^.]+\\.", "").toUpperCase();
|
||||
id = "DEEPSLATE_" + id;
|
||||
Material dps = Material.getMaterial(id);
|
||||
if (dps != null)
|
||||
blockData = dps.createBlockData();
|
||||
}
|
||||
} catch (Exception e) {
|
||||
// no Handle exception
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
nms.setBlock(
|
||||
world,
|
||||
wX + globalX,
|
||||
y - minHeight,
|
||||
wZ + globalZ,
|
||||
blockData,
|
||||
flag,
|
||||
0
|
||||
);
|
||||
|
||||
if (nms.hasTile(blockData.getMaterial())) {
|
||||
var tile = nms.serializeTile(new Location(bukkit, wX + globalX, y - minHeight, wZ + globalZ));
|
||||
if (tile != null) {
|
||||
nms.deserializeTile(tile, new Location(world, wX + globalX, y - minHeight, wZ + globalZ));
|
||||
}
|
||||
}
|
||||
|
||||
if (globalX % 4 == 0 && globalZ % 4 == 0 && y % 4 == 0) {
|
||||
Biome biome;
|
||||
biome = bukkit.getBiome(wX + globalX, y, wZ + globalZ);
|
||||
if (caveBiomes.contains(biome)) {
|
||||
world.setBiome(wX + globalX, y - minHeight, wZ + globalZ, biome);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
lastUse.put(chunk, System.currentTimeMillis());
|
||||
mergeDuration.put(p.getMilliseconds());
|
||||
Iris.info("Vanilla merge average in: " + Form.duration(mergeDuration.getAverage(), 8));
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
private boolean shouldSkip(int y, int ht) {
|
||||
int threshold;
|
||||
switch (mode) {
|
||||
case SPLIT_ENGINE_HEIGHT:
|
||||
threshold = ht;
|
||||
break;
|
||||
case SPLIT:
|
||||
threshold = split;
|
||||
break;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
return splitUnder ? y > threshold : y < threshold;
|
||||
}
|
||||
|
||||
public record Flags(boolean listener, boolean flag, boolean client, boolean update, boolean physics) {
|
||||
public static Flags fromValue(int value) {
|
||||
return new Flags(
|
||||
(value & 1024) != 0,
|
||||
(value & 64) != 0,
|
||||
(value & 2) != 0,
|
||||
(value & 1) != 0,
|
||||
(value & 16) == 0
|
||||
);
|
||||
}
|
||||
|
||||
public int value() {
|
||||
int value = 0;
|
||||
if (!listener) value |= 1024;
|
||||
if (flag) value |= 64;
|
||||
if (client) value |= 2;
|
||||
if (update) value |= 1;
|
||||
if (!physics) value |= 16;
|
||||
return value;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves a 4x4 hunk slice starting at (sx, sz) up to the specified height.
|
||||
*
|
||||
* @param chunk The Bukkit chunk
|
||||
* @param sx Chunk Slice X (must be multiple of 4)
|
||||
* @param sz Chunk Slice Z (must be multiple of 4)
|
||||
* @param height The maximum height to process
|
||||
* @return A hunk of size 4x(totalHeight)x4
|
||||
*/
|
||||
private Hunk<BlockData> getHunkSlice(Chunk chunk, int sx, int sz, int height) {
|
||||
if (!chunk.isGenerated())
|
||||
throw new IllegalStateException("Chunk is not generated!");
|
||||
|
||||
int minHeight = chunk.getWorld().getMinHeight();
|
||||
int maxHeight = chunk.getWorld().getMaxHeight();
|
||||
int totalHeight = Math.abs(minHeight) + maxHeight;
|
||||
|
||||
Hunk<BlockData> h = Hunk.newHunk(4, totalHeight, 4);
|
||||
|
||||
for (int x = 0; x < 4; x++) {
|
||||
for (int z = 0; z < 4; z++) {
|
||||
for (int y = 0; y < totalHeight; y++) {
|
||||
if (shouldSkip(y, height))
|
||||
continue;
|
||||
BlockData data = chunk.getBlock(sx + x, y + minHeight, sz + z).getBlockData();
|
||||
h.set(x, y, z, data);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return h;
|
||||
}
|
||||
|
||||
private boolean hasAround(Hunk<BlockData> hunk, int x, int y, int z, Material material) {
|
||||
int[] d = {-1, 0, 1};
|
||||
|
||||
for (int dx : d) {
|
||||
for (int dy : d) {
|
||||
for (int dz : d) {
|
||||
|
||||
if (dx == 0 && dy == 0 && dz == 0) continue;
|
||||
|
||||
int nx = x + dx;
|
||||
int ny = y + dy;
|
||||
int nz = z + dz;
|
||||
|
||||
if (nx >= 0 && nx < hunk.getWidth() && nz >= 0 && nz < hunk.getDepth() && ny >= 0 && ny < hunk.getHeight()) {
|
||||
BlockData neighborBlock = hunk.get(nx, ny, nz);
|
||||
|
||||
if (neighborBlock.getMaterial() == material) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public static <T> Hunk<T> copyHunkParallel(Hunk<T> original, Function<T, T> elementCopier) {
|
||||
Hunk<T> copy = Hunk.newHunk(original.getWidth(), original.getHeight(), original.getDepth());
|
||||
original.compute3D((ox, oy, oz, section) -> {
|
||||
Hunk<T> copySection = copy.croppedView(ox, oy, oz, ox + section.getWidth(), oy + section.getHeight(), oz + section.getDepth());
|
||||
section.iterate((x, y, z, value) -> {
|
||||
T copiedValue = value != null ? elementCopier.apply(value) : null;
|
||||
copySection.set(x, y, z, copiedValue);
|
||||
});
|
||||
});
|
||||
|
||||
return copy;
|
||||
}
|
||||
|
||||
public void loadWorld(Engine engine) {
|
||||
if (!engine.getDimension().isEnableExperimentalMerger())
|
||||
return;
|
||||
|
||||
World bukkitWorld = Bukkit.getWorld(world);
|
||||
if (!new File(Bukkit.getWorldContainer(), world).exists())
|
||||
Iris.warn("World does not exist disabled merger generation for: " + engine.getWorld().name());
|
||||
//throw new IllegalStateException("World does not exist!");
|
||||
if (bukkitWorld == null) {
|
||||
Iris.info("World " + world + " is not loaded yet, creating it.");
|
||||
|
||||
Bukkit.getPluginManager().registerEvents(new Listener() {
|
||||
@EventHandler
|
||||
public void onWorldLoad(WorldLoadEvent event) {
|
||||
if (event.getWorld().getName().equals(world)) {
|
||||
worldsave = event.getWorld();
|
||||
Iris.info("World " + world + " has been loaded.");
|
||||
}
|
||||
}
|
||||
}, Iris.instance);
|
||||
|
||||
WorldCreator worldCreator = new WorldCreator(world);
|
||||
Bukkit.createWorld(worldCreator);
|
||||
} else {
|
||||
worldsave = bukkitWorld;
|
||||
}
|
||||
|
||||
init();
|
||||
}
|
||||
|
||||
public void init() {
|
||||
Bukkit.getScheduler().runTaskTimer(Iris.instance, this::unloadAndSaveAllChunks, 200L, 20L); // Runs every 10 seconds
|
||||
}
|
||||
|
||||
private void unloadAndSaveAllChunks() {
|
||||
try {
|
||||
if (worldsave == null) {
|
||||
Iris.warn("World was null somehow...");
|
||||
return;
|
||||
}
|
||||
|
||||
for (Chunk chunk : new ArrayList<>(lastUse.keySet())) {
|
||||
Long lastUsed = lastUse.get(chunk);
|
||||
if (lastUsed != null && System.currentTimeMillis() - lastUsed >= 10000) { // 10 seconds
|
||||
if (chunk.isLoaded()) {
|
||||
chunk.unload();
|
||||
}
|
||||
lastUse.remove(chunk);
|
||||
}
|
||||
}
|
||||
worldsave.save();
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -34,9 +34,15 @@ import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.experimental.Accessors;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.Material;
|
||||
import org.bukkit.NamespacedKey;
|
||||
import org.bukkit.TreeType;
|
||||
import org.bukkit.block.data.BlockData;
|
||||
import org.bukkit.loot.LootTable;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.function.Function;
|
||||
|
||||
@Snippet("object-placer")
|
||||
@EqualsAndHashCode()
|
||||
@@ -123,6 +129,9 @@ public class IrisObjectPlacement {
|
||||
@ArrayType(min = 1, type = IrisObjectLoot.class)
|
||||
@Desc("The loot tables to apply to these objects")
|
||||
private KList<IrisObjectLoot> loot = new KList<>();
|
||||
@ArrayType(min = 1, type = IrisObjectVanillaLoot.class)
|
||||
@Desc("The vanilla loot tables to apply to these objects")
|
||||
private KList<IrisObjectVanillaLoot> vanillaLoot = new KList<>();
|
||||
@Desc("Whether the given loot tables override any and all other loot tables available in the dimension, region or biome.")
|
||||
private boolean overrideGlobalLoot = false;
|
||||
@Desc("This object / these objects override the following trees when they grow...")
|
||||
@@ -138,7 +147,8 @@ public class IrisObjectPlacement {
|
||||
private KList<String> forbiddenCollisions = new KList<>();
|
||||
@Desc("Ignore any placement restrictions for this object")
|
||||
private boolean forcePlace = false;
|
||||
private transient AtomicCache<TableCache> cache = new AtomicCache<>();
|
||||
private transient AtomicCache<TableCache<IrisLootTable>> cache = new AtomicCache<>();
|
||||
private transient AtomicCache<TableCache<IrisVanillaLootTable>> vanillaCache = new AtomicCache<>();
|
||||
|
||||
public IrisObjectPlacement toPlacement(String... place) {
|
||||
IrisObjectPlacement p = new IrisObjectPlacement();
|
||||
@@ -209,48 +219,62 @@ public class IrisObjectPlacement {
|
||||
return (int) Math.round(densityStyle.get(rng, x, z, data));
|
||||
}
|
||||
|
||||
private TableCache getCache(IrisData manager) {
|
||||
return cache.aquire(() -> {
|
||||
TableCache tc = new TableCache();
|
||||
private TableCache<IrisLootTable> getCache(IrisData manager) {
|
||||
return cache.aquire(() -> getCache(manager, manager.getLootLoader()::load));
|
||||
}
|
||||
|
||||
for (IrisObjectLoot loot : getLoot()) {
|
||||
if (loot == null)
|
||||
continue;
|
||||
IrisLootTable table = manager.getLootLoader().load(loot.getName());
|
||||
if (table == null) {
|
||||
Iris.warn("Couldn't find loot table " + loot.getName());
|
||||
continue;
|
||||
private TableCache<IrisVanillaLootTable> getVanillaCache(IrisData manager) {
|
||||
return vanillaCache.aquire(() -> getCache(manager, IrisObjectPlacement::getVanillaTable));
|
||||
}
|
||||
|
||||
private <T> TableCache<T> getCache(IrisData manager, Function<String, T> loader) {
|
||||
TableCache<T> tc = new TableCache<>();
|
||||
|
||||
for (IrisObjectLoot loot : getLoot()) {
|
||||
if (loot == null)
|
||||
continue;
|
||||
T table = loader.apply(loot.getName());
|
||||
if (table == null) {
|
||||
Iris.warn("Couldn't find loot table " + loot.getName());
|
||||
continue;
|
||||
}
|
||||
|
||||
if (loot.getFilter().isEmpty()) //Table applies to all containers
|
||||
{
|
||||
tc.global.put(table, loot.getWeight());
|
||||
} else if (!loot.isExact()) //Table is meant to be by type
|
||||
{
|
||||
for (BlockData filterData : loot.getFilter(manager)) {
|
||||
if (!tc.basic.containsKey(filterData.getMaterial())) {
|
||||
tc.basic.put(filterData.getMaterial(), new WeightedRandom<>());
|
||||
}
|
||||
|
||||
tc.basic.get(filterData.getMaterial()).put(table, loot.getWeight());
|
||||
}
|
||||
|
||||
if (loot.getFilter().isEmpty()) //Table applies to all containers
|
||||
{
|
||||
tc.global.put(table, loot.getWeight());
|
||||
} else if (!loot.isExact()) //Table is meant to be by type
|
||||
{
|
||||
for (BlockData filterData : loot.getFilter(manager)) {
|
||||
if (!tc.basic.containsKey(filterData.getMaterial())) {
|
||||
tc.basic.put(filterData.getMaterial(), new WeightedRandom<>());
|
||||
}
|
||||
|
||||
tc.basic.get(filterData.getMaterial()).put(table, loot.getWeight());
|
||||
} else //Filter is exact
|
||||
{
|
||||
for (BlockData filterData : loot.getFilter(manager)) {
|
||||
if (!tc.exact.containsKey(filterData.getMaterial())) {
|
||||
tc.exact.put(filterData.getMaterial(), new KMap<>());
|
||||
}
|
||||
} else //Filter is exact
|
||||
{
|
||||
for (BlockData filterData : loot.getFilter(manager)) {
|
||||
if (!tc.exact.containsKey(filterData.getMaterial())) {
|
||||
tc.exact.put(filterData.getMaterial(), new KMap<>());
|
||||
}
|
||||
|
||||
if (!tc.exact.get(filterData.getMaterial()).containsKey(filterData)) {
|
||||
tc.exact.get(filterData.getMaterial()).put(filterData, new WeightedRandom<>());
|
||||
}
|
||||
|
||||
tc.exact.get(filterData.getMaterial()).get(filterData).put(table, loot.getWeight());
|
||||
if (!tc.exact.get(filterData.getMaterial()).containsKey(filterData)) {
|
||||
tc.exact.get(filterData.getMaterial()).put(filterData, new WeightedRandom<>());
|
||||
}
|
||||
|
||||
tc.exact.get(filterData.getMaterial()).get(filterData).put(table, loot.getWeight());
|
||||
}
|
||||
}
|
||||
return tc;
|
||||
});
|
||||
}
|
||||
return tc;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private static IrisVanillaLootTable getVanillaTable(String name) {
|
||||
NamespacedKey key = NamespacedKey.fromString(name);
|
||||
if (key == null)
|
||||
return null;
|
||||
return new IrisVanillaLootTable(Bukkit.getLootTable(key));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -261,10 +285,16 @@ public class IrisObjectPlacement {
|
||||
* @return The loot table it should use.
|
||||
*/
|
||||
public IrisLootTable getTable(BlockData data, IrisData dataManager) {
|
||||
TableCache cache = getCache(dataManager);
|
||||
IrisLootTable table = pickTable(data, getVanillaCache(dataManager));
|
||||
if (table == null) {
|
||||
table = pickTable(data, getCache(dataManager));
|
||||
}
|
||||
return table;
|
||||
}
|
||||
|
||||
private <T> T pickTable(BlockData data, TableCache<T> cache) {
|
||||
if (B.isStorageChest(data)) {
|
||||
IrisLootTable picked = null;
|
||||
T picked = null;
|
||||
if (cache.exact.containsKey(data.getMaterial()) && cache.exact.get(data.getMaterial()).containsKey(data)) {
|
||||
picked = cache.exact.get(data.getMaterial()).get(data).pullRandom();
|
||||
} else if (cache.basic.containsKey(data.getMaterial())) {
|
||||
@@ -279,9 +309,9 @@ public class IrisObjectPlacement {
|
||||
return null;
|
||||
}
|
||||
|
||||
private static class TableCache {
|
||||
final transient WeightedRandom<IrisLootTable> global = new WeightedRandom<>();
|
||||
final transient KMap<Material, WeightedRandom<IrisLootTable>> basic = new KMap<>();
|
||||
final transient KMap<Material, KMap<BlockData, WeightedRandom<IrisLootTable>>> exact = new KMap<>();
|
||||
private static class TableCache<T> {
|
||||
final transient WeightedRandom<T> global = new WeightedRandom<>();
|
||||
final transient KMap<Material, WeightedRandom<T>> basic = new KMap<>();
|
||||
final transient KMap<Material, KMap<BlockData, WeightedRandom<T>>> exact = new KMap<>();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,50 @@
|
||||
package com.volmit.iris.engine.object;
|
||||
|
||||
import com.volmit.iris.core.loader.IrisData;
|
||||
import com.volmit.iris.engine.data.cache.AtomicCache;
|
||||
import com.volmit.iris.engine.object.annotations.*;
|
||||
import com.volmit.iris.engine.object.annotations.functions.LootTableKeyFunction;
|
||||
import com.volmit.iris.util.collection.KList;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.experimental.Accessors;
|
||||
import org.bukkit.block.data.BlockData;
|
||||
|
||||
@Snippet("object-vanilla-loot")
|
||||
@Accessors(chain = true)
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
@Desc("Represents vanilla loot within this object or jigsaw piece")
|
||||
@Data
|
||||
public class IrisObjectVanillaLoot {
|
||||
private final transient AtomicCache<KList<BlockData>> filterCache = new AtomicCache<>();
|
||||
@ArrayType(min = 1, type = IrisBlockData.class)
|
||||
@Desc("The list of blocks this loot table should apply to")
|
||||
private KList<IrisBlockData> filter = new KList<>();
|
||||
@Desc("Exactly match the block data or not")
|
||||
private boolean exact = false;
|
||||
@RegistryListFunction(LootTableKeyFunction.class)
|
||||
@Desc("The vanilla loot table key")
|
||||
@Required
|
||||
private String name;
|
||||
@Desc("The weight of this loot table being chosen")
|
||||
private int weight = 1;
|
||||
|
||||
public KList<BlockData> getFilter(IrisData rdata) {
|
||||
return filterCache.aquire(() ->
|
||||
{
|
||||
KList<BlockData> b = new KList<>();
|
||||
|
||||
for (IrisBlockData i : filter) {
|
||||
BlockData bx = i.getBlockData(rdata);
|
||||
|
||||
if (bx != null) {
|
||||
b.add(bx);
|
||||
}
|
||||
}
|
||||
|
||||
return b;
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,84 @@
|
||||
package com.volmit.iris.engine.object;
|
||||
|
||||
import com.volmit.iris.core.loader.IrisData;
|
||||
import com.volmit.iris.util.collection.KList;
|
||||
import com.volmit.iris.util.math.RNG;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import org.bukkit.Location;
|
||||
import org.bukkit.World;
|
||||
import org.bukkit.inventory.ItemStack;
|
||||
import org.bukkit.loot.LootContext;
|
||||
import org.bukkit.loot.LootTable;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = false)
|
||||
public class IrisVanillaLootTable extends IrisLootTable {
|
||||
private final LootTable lootTable;
|
||||
|
||||
public KList<ItemStack> getLoot(RNG rng, World world, int x, int y, int z) {
|
||||
return new KList<>(lootTable.populateLoot(rng, new LootContext.Builder(new Location(world, x, y, z)).build()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
throw new IllegalStateException("Vanilla loot tables should not be used in Iris");
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getRarity() {
|
||||
throw new IllegalStateException("Vanilla loot tables should not be used in Iris");
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getMaxPicked() {
|
||||
throw new IllegalStateException("Vanilla loot tables should not be used in Iris");
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getMinPicked() {
|
||||
throw new IllegalStateException("Vanilla loot tables should not be used in Iris");
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getMaxTries() {
|
||||
throw new IllegalStateException("Vanilla loot tables should not be used in Iris");
|
||||
}
|
||||
|
||||
@Override
|
||||
public KList<IrisLoot> getLoot() {
|
||||
throw new IllegalStateException("Vanilla loot tables should not be used in Iris");
|
||||
}
|
||||
|
||||
@Override
|
||||
public KList<ItemStack> getLoot(boolean debug, RNG rng, InventorySlotType slot, World world, int x, int y, int z) {
|
||||
return new KList<>(lootTable.populateLoot(rng, new LootContext.Builder(new Location(world, x, y, z)).build()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getFolderName() {
|
||||
throw new IllegalStateException("Vanilla loot tables should not be used in Iris");
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getTypeName() {
|
||||
throw new IllegalStateException("Vanilla loot tables should not be used in Iris");
|
||||
}
|
||||
|
||||
@Override
|
||||
public File getLoadFile() {
|
||||
throw new IllegalStateException("Vanilla loot tables should not be used in Iris");
|
||||
}
|
||||
|
||||
@Override
|
||||
public IrisData getLoader() {
|
||||
throw new IllegalStateException("Vanilla loot tables should not be used in Iris");
|
||||
}
|
||||
|
||||
@Override
|
||||
public KList<String> getPreprocessors() {
|
||||
throw new IllegalStateException("Vanilla loot tables should not be used in Iris");
|
||||
}
|
||||
}
|
||||
@@ -1,14 +1,19 @@
|
||||
package com.volmit.iris.engine.object;
|
||||
|
||||
import com.volmit.iris.util.collection.KList;
|
||||
import com.volmit.iris.util.collection.KMap;
|
||||
import com.volmit.iris.util.scheduling.J;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.NonNull;
|
||||
import lombok.ToString;
|
||||
import org.apache.commons.io.function.IOFunction;
|
||||
import org.bukkit.DyeColor;
|
||||
import org.bukkit.Material;
|
||||
import org.bukkit.Tag;
|
||||
import org.bukkit.block.*;
|
||||
import org.bukkit.block.banner.Pattern;
|
||||
import org.bukkit.block.banner.PatternType;
|
||||
import org.bukkit.block.data.BlockData;
|
||||
import org.bukkit.entity.EntityType;
|
||||
|
||||
import java.io.DataInputStream;
|
||||
@@ -34,6 +39,21 @@ public class LegacyTileData extends TileData {
|
||||
handler = factory.apply(in);
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NonNull KMap<String, Object> getProperties() {
|
||||
return new KMap<>();
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NonNull Material getMaterial() {
|
||||
return handler.getMaterial();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isApplicable(BlockData data) {
|
||||
return handler.isApplicable(data);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void toBukkit(Block block) {
|
||||
J.s(() -> handler.toBukkit(block));
|
||||
@@ -51,6 +71,8 @@ public class LegacyTileData extends TileData {
|
||||
}
|
||||
|
||||
private interface Handler {
|
||||
Material getMaterial();
|
||||
boolean isApplicable(BlockData data);
|
||||
void toBinary(DataOutputStream out) throws IOException;
|
||||
void toBukkit(Block block);
|
||||
}
|
||||
@@ -72,6 +94,16 @@ public class LegacyTileData extends TileData {
|
||||
dyeColor = DyeColor.values()[in.readByte()];
|
||||
}
|
||||
|
||||
@Override
|
||||
public Material getMaterial() {
|
||||
return Material.OAK_SIGN;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isApplicable(BlockData data) {
|
||||
return Tag.ALL_SIGNS.isTagged(data.getMaterial());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void toBinary(DataOutputStream out) throws IOException {
|
||||
out.writeUTF(line1);
|
||||
@@ -101,6 +133,16 @@ public class LegacyTileData extends TileData {
|
||||
type = EntityType.values()[in.readShort()];
|
||||
}
|
||||
|
||||
@Override
|
||||
public Material getMaterial() {
|
||||
return Material.SPAWNER;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isApplicable(BlockData data) {
|
||||
return data.getMaterial() == Material.SPAWNER;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void toBinary(DataOutputStream out) throws IOException {
|
||||
out.writeShort(type.ordinal());
|
||||
@@ -130,6 +172,16 @@ public class LegacyTileData extends TileData {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Material getMaterial() {
|
||||
return Material.WHITE_BANNER;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isApplicable(BlockData data) {
|
||||
return Tag.BANNERS.isTagged(data.getMaterial());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void toBinary(DataOutputStream out) throws IOException {
|
||||
out.writeByte(baseColor.ordinal());
|
||||
|
||||
@@ -30,4 +30,6 @@ public @interface ArrayType {
|
||||
Class<?> type();
|
||||
|
||||
int min() default 0;
|
||||
|
||||
int max() default Integer.MAX_VALUE;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
package com.volmit.iris.engine.object.annotations.functions;
|
||||
|
||||
import com.volmit.iris.core.loader.IrisData;
|
||||
import com.volmit.iris.engine.framework.ListFunction;
|
||||
import com.volmit.iris.util.collection.KList;
|
||||
import org.bukkit.NamespacedKey;
|
||||
import org.bukkit.loot.LootTables;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
public class LootTableKeyFunction implements ListFunction<IrisData, KList<String>> {
|
||||
@Override
|
||||
public String key() {
|
||||
return "loot-table-key";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String fancyName() {
|
||||
return "LootTable Key";
|
||||
}
|
||||
|
||||
@Override
|
||||
public KList<String> apply(IrisData data) {
|
||||
return Arrays.stream(LootTables.values())
|
||||
.map(LootTables::getKey)
|
||||
.map(NamespacedKey::toString)
|
||||
.collect(Collectors.toCollection(KList::new));
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
package com.volmit.iris.engine.object.annotations;
|
||||
package com.volmit.iris.engine.object.annotations.functions;
|
||||
|
||||
import com.volmit.iris.core.loader.IrisData;
|
||||
import com.volmit.iris.core.nms.INMS;
|
||||
@@ -36,6 +36,7 @@ import com.volmit.iris.util.hunk.Hunk;
|
||||
import com.volmit.iris.util.hunk.view.BiomeGridHunkHolder;
|
||||
import com.volmit.iris.util.hunk.view.ChunkDataHunkHolder;
|
||||
import com.volmit.iris.util.io.ReactiveFolder;
|
||||
import com.volmit.iris.util.math.RollingSequence;
|
||||
import com.volmit.iris.util.scheduling.ChronoLatch;
|
||||
import com.volmit.iris.util.scheduling.J;
|
||||
import com.volmit.iris.util.scheduling.Looper;
|
||||
@@ -49,6 +50,7 @@ import org.bukkit.entity.Entity;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.event.EventHandler;
|
||||
import org.bukkit.event.Listener;
|
||||
import org.bukkit.event.world.ChunkLoadEvent;
|
||||
import org.bukkit.event.world.WorldInitEvent;
|
||||
import org.bukkit.generator.BiomeProvider;
|
||||
import org.bukkit.generator.BlockPopulator;
|
||||
@@ -87,6 +89,7 @@ public class BukkitChunkGenerator extends ChunkGenerator implements PlatformChun
|
||||
private final AtomicInteger a = new AtomicInteger(0);
|
||||
private final CompletableFuture<Integer> spawnChunks = new CompletableFuture<>();
|
||||
private final boolean smartVanillaHeight;
|
||||
private RollingSequence mergeDuration;
|
||||
private Engine engine;
|
||||
private Looper hotloader;
|
||||
private StudioMode lastMode;
|
||||
@@ -103,6 +106,7 @@ public class BukkitChunkGenerator extends ChunkGenerator implements PlatformChun
|
||||
populators = new KList<>();
|
||||
loadLock = new Semaphore(LOAD_LOCKS);
|
||||
this.world = world;
|
||||
this.mergeDuration = new RollingSequence(20);
|
||||
this.hotloadChecker = new ChronoLatch(1000, false);
|
||||
this.studio = studio;
|
||||
this.dataLocation = dataLocation;
|
||||
@@ -379,6 +383,13 @@ public class BukkitChunkGenerator extends ChunkGenerator implements PlatformChun
|
||||
}
|
||||
}
|
||||
|
||||
@EventHandler
|
||||
private void onChunkGeneration(ChunkLoadEvent event) {
|
||||
if (engine == null ||!event.isNewChunk() || !engine.getWorld().realWorld().equals(event.getWorld()) || !engine.getDimension().isEnableExperimentalMerger())
|
||||
return;
|
||||
engine.getMerger().generateVanillaUnderground(event.getChunk().getX(), event.getChunk().getZ(), event.getChunk(), engine);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getBaseHeight(@NotNull WorldInfo worldInfo, @NotNull Random random, int x, int z, @NotNull HeightMap heightMap) {
|
||||
return 4;
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
package com.volmit.iris.util.matter.slices;
|
||||
|
||||
import com.volmit.iris.util.data.palette.Palette;
|
||||
import com.volmit.iris.util.matter.Sliced;
|
||||
import com.volmit.iris.util.matter.slices.container.JigsawStructureContainer;
|
||||
|
||||
import java.io.DataInputStream;
|
||||
import java.io.DataOutputStream;
|
||||
import java.io.IOException;
|
||||
|
||||
@Sliced
|
||||
public class JigsawStructureMatter extends RawMatter<JigsawStructureContainer> {
|
||||
|
||||
public JigsawStructureMatter() {
|
||||
this(1, 1, 1);
|
||||
}
|
||||
|
||||
public JigsawStructureMatter(int width, int height, int depth) {
|
||||
super(width, height, depth, JigsawStructureContainer.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Palette<JigsawStructureContainer> getGlobalPalette() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeNode(JigsawStructureContainer b, DataOutputStream dos) throws IOException {
|
||||
dos.writeUTF(b.getLoadKey());
|
||||
}
|
||||
|
||||
@Override
|
||||
public JigsawStructureContainer readNode(DataInputStream din) throws IOException {
|
||||
return new JigsawStructureContainer(din.readUTF());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
package com.volmit.iris.util.matter.slices.container;
|
||||
|
||||
import com.volmit.iris.engine.object.IrisJigsawStructure;
|
||||
|
||||
public class JigsawStructureContainer extends RegistrantContainer<IrisJigsawStructure> {
|
||||
|
||||
public JigsawStructureContainer(String loadKey) {
|
||||
super(IrisJigsawStructure.class, loadKey);
|
||||
}
|
||||
|
||||
public static JigsawStructureContainer toContainer(IrisJigsawStructure structure) {
|
||||
return new JigsawStructureContainer(structure.getLoadKey());
|
||||
}
|
||||
}
|
||||
@@ -41,7 +41,6 @@ public class Chunk {
|
||||
private int lastMCAUpdate;
|
||||
private CompoundTag data;
|
||||
private int dataVersion;
|
||||
private int nativeIrisVersion;
|
||||
private long lastUpdate;
|
||||
private long inhabitedTime;
|
||||
private MCABiomeContainer biomes;
|
||||
@@ -150,8 +149,8 @@ public class Chunk {
|
||||
if ((loadFlags & STRUCTURES) != 0) {
|
||||
structures = level.getCompoundTag("Structures");
|
||||
}
|
||||
if ((loadFlags & (BLOCK_LIGHTS | BLOCK_STATES | SKY_LIGHT)) != 0 && level.containsKey("Sections")) {
|
||||
for (CompoundTag section : level.getListTag("Sections").asCompoundTagList()) {
|
||||
if ((loadFlags & (BLOCK_LIGHTS | BLOCK_STATES | SKY_LIGHT)) != 0 && level.containsKey("sections")) {
|
||||
for (CompoundTag section : level.getListTag("sections").asCompoundTagList()) {
|
||||
int sectionIndex = section.getByte("Y");
|
||||
if (sectionIndex > 15 || sectionIndex < 0) {
|
||||
continue;
|
||||
|
||||
@@ -0,0 +1,70 @@
|
||||
package com.volmit.iris.util.profile;
|
||||
|
||||
|
||||
import com.volmit.iris.Iris;
|
||||
import com.volmit.iris.core.IrisSettings;
|
||||
import com.volmit.iris.util.math.M;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
|
||||
import java.util.concurrent.Semaphore;
|
||||
|
||||
@Getter
|
||||
public class LoadBalancer extends MsptTimings {
|
||||
private final Semaphore semaphore;
|
||||
private final int maxPermits;
|
||||
private final double range;
|
||||
@Setter
|
||||
private int minMspt, maxMspt;
|
||||
private int permits, lastMspt;
|
||||
private long lastTime = M.ms();
|
||||
|
||||
public LoadBalancer(Semaphore semaphore, int maxPermits, IrisSettings.MsRange range) {
|
||||
this(semaphore, maxPermits, range.getMin(), range.getMax());
|
||||
}
|
||||
|
||||
public LoadBalancer(Semaphore semaphore, int maxPermits, int minMspt, int maxMspt) {
|
||||
this.semaphore = semaphore;
|
||||
this.maxPermits = maxPermits;
|
||||
this.minMspt = minMspt;
|
||||
this.maxMspt = maxMspt;
|
||||
this.range = maxMspt - minMspt;
|
||||
setName("LoadBalancer");
|
||||
start();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void update(int raw) {
|
||||
lastTime = M.ms();
|
||||
int mspt = raw;
|
||||
if (mspt < lastMspt) {
|
||||
int min = (int) Math.max(lastMspt * IrisSettings.get().getUpdater().getChunkLoadSensitivity(), 1);
|
||||
mspt = Math.max(mspt, min);
|
||||
}
|
||||
lastMspt = mspt;
|
||||
mspt = Math.max(mspt - minMspt, 0);
|
||||
double percent = mspt / range;
|
||||
|
||||
int target = (int) (maxPermits * percent);
|
||||
target = Math.min(target, maxPermits - 20);
|
||||
|
||||
int diff = target - permits;
|
||||
permits = target;
|
||||
|
||||
if (diff == 0) return;
|
||||
Iris.debug("Adjusting load to %s (%s) permits (%s mspt, %.2f)".formatted(target, diff, raw, percent));
|
||||
|
||||
if (diff > 0) semaphore.acquireUninterruptibly(diff);
|
||||
else semaphore.release(Math.abs(diff));
|
||||
}
|
||||
|
||||
public void close() {
|
||||
interrupt();
|
||||
semaphore.release(permits);
|
||||
}
|
||||
|
||||
public void setRange(IrisSettings.MsRange range) {
|
||||
minMspt = range.getMin();
|
||||
maxMspt = range.getMax();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,84 @@
|
||||
package com.volmit.iris.util.profile;
|
||||
|
||||
import com.volmit.iris.util.math.M;
|
||||
import com.volmit.iris.util.scheduling.J;
|
||||
import com.volmit.iris.util.scheduling.Looper;
|
||||
import org.bukkit.Bukkit;
|
||||
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
public abstract class MsptTimings extends Looper {
|
||||
private final AtomicInteger currentTick = new AtomicInteger(0);
|
||||
private int lastTick, lastMspt;
|
||||
private long lastTime;
|
||||
private int taskId = -1;
|
||||
|
||||
public MsptTimings() {
|
||||
setName("MsptTimings");
|
||||
setPriority(9);
|
||||
setDaemon(true);
|
||||
}
|
||||
|
||||
public static MsptTimings of(Consumer<Integer> update) {
|
||||
return new Simple(update);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected final long loop() {
|
||||
if (startTickTask())
|
||||
return 200;
|
||||
|
||||
long now = M.ms();
|
||||
int tick = currentTick.get();
|
||||
int deltaTick = tick - lastTick;
|
||||
if (deltaTick == 0)
|
||||
return 200;
|
||||
lastTick = tick;
|
||||
int deltaTime = (int) (now - lastTime);
|
||||
lastTime = now;
|
||||
int mspt = deltaTime / deltaTick;
|
||||
mspt -= 50;
|
||||
mspt = Math.max(mspt, 0);
|
||||
lastMspt = mspt;
|
||||
update(mspt);
|
||||
return 200;
|
||||
}
|
||||
|
||||
public final int getMspt() {
|
||||
return lastMspt;
|
||||
}
|
||||
|
||||
protected abstract void update(int mspt);
|
||||
|
||||
private boolean startTickTask() {
|
||||
if (taskId != -1 && (Bukkit.getScheduler().isQueued(taskId) || Bukkit.getScheduler().isCurrentlyRunning(taskId)))
|
||||
return false;
|
||||
|
||||
taskId = J.sr(() -> {
|
||||
if (isInterrupted()) {
|
||||
J.csr(taskId);
|
||||
return;
|
||||
}
|
||||
|
||||
currentTick.incrementAndGet();
|
||||
}, 1);
|
||||
return taskId != -1;
|
||||
}
|
||||
|
||||
private static class Simple extends MsptTimings {
|
||||
private final Consumer<Integer> update;
|
||||
|
||||
private Simple(Consumer<Integer> update) {
|
||||
this.update = update;
|
||||
start();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void update(int mspt) {
|
||||
if (update == null)
|
||||
return;
|
||||
update.accept(mspt);
|
||||
}
|
||||
}
|
||||
}
|
||||
42
core/src/main/java/com/volmit/iris/util/reflect/Reflect.java
Normal file
42
core/src/main/java/com/volmit/iris/util/reflect/Reflect.java
Normal file
@@ -0,0 +1,42 @@
|
||||
package com.volmit.iris.util.reflect;
|
||||
|
||||
import com.volmit.iris.core.nms.container.Pair;
|
||||
|
||||
import java.lang.reflect.Constructor;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
public class Reflect {
|
||||
|
||||
public static <T> T newInstance(Class<T> type, Object... initArgs) throws NoSuchMethodException, InvocationTargetException {
|
||||
var list = Arrays.stream(initArgs)
|
||||
.map(arg -> new Pair<Class<?>, Object>(arg.getClass(), arg))
|
||||
.toList();
|
||||
return newInstance(type, list);
|
||||
}
|
||||
|
||||
public static <T> T newInstance(Class<T> type, List<Pair<Class<?>, Object>> initArgs) throws NoSuchMethodException, InvocationTargetException{
|
||||
constructors:
|
||||
for (var c : type.getDeclaredConstructors()) {
|
||||
var types = c.getParameterTypes();
|
||||
for (int i = 0; i < types.length; i++) {
|
||||
if (!types[i].isAssignableFrom(initArgs.get(i).getA()))
|
||||
continue constructors;
|
||||
}
|
||||
|
||||
c.setAccessible(true);
|
||||
try {
|
||||
return (T) c.newInstance(initArgs.stream().map(Pair::getB).toArray());
|
||||
} catch (InstantiationException | IllegalAccessException e) {
|
||||
throw new InvocationTargetException(e);
|
||||
}
|
||||
}
|
||||
|
||||
var constructors = Arrays.stream(type.getDeclaredConstructors())
|
||||
.map(Constructor::toGenericString)
|
||||
.collect(Collectors.joining("\n"));
|
||||
throw new NoSuchMethodException("No matching constructor found in:\n" + constructors);
|
||||
}
|
||||
}
|
||||
8
core/src/main/java/com/volmit/iris/util/reflect/W.java
Normal file
8
core/src/main/java/com/volmit/iris/util/reflect/W.java
Normal file
@@ -0,0 +1,8 @@
|
||||
package com.volmit.iris.util.reflect;
|
||||
|
||||
import lombok.Getter;
|
||||
|
||||
public class W {
|
||||
@Getter
|
||||
private static final StackWalker stack = StackWalker.getInstance(StackWalker.Option.RETAIN_CLASS_REFERENCE);
|
||||
}
|
||||
@@ -0,0 +1,386 @@
|
||||
package com.volmit.iris.core.nms.v1_19_R1;
|
||||
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.mojang.serialization.Lifecycle;
|
||||
import com.volmit.iris.Iris;
|
||||
import com.volmit.iris.core.nms.IMemoryWorld;
|
||||
import com.volmit.iris.util.reflect.Reflect;
|
||||
import com.volmit.iris.util.scheduling.J;
|
||||
import net.minecraft.core.BlockPos;
|
||||
import net.minecraft.core.Registry;
|
||||
import net.minecraft.core.RegistryAccess;
|
||||
import net.minecraft.resources.ResourceKey;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
import net.minecraft.server.MinecraftServer;
|
||||
import net.minecraft.server.dedicated.DedicatedServerProperties;
|
||||
import net.minecraft.server.level.ServerLevel;
|
||||
import net.minecraft.util.GsonHelper;
|
||||
import net.minecraft.world.Difficulty;
|
||||
import net.minecraft.world.entity.ai.village.VillageSiege;
|
||||
import net.minecraft.world.entity.npc.CatSpawner;
|
||||
import net.minecraft.world.entity.npc.WanderingTraderSpawner;
|
||||
import net.minecraft.world.level.*;
|
||||
import net.minecraft.world.level.biome.BiomeManager;
|
||||
import net.minecraft.world.level.block.Blocks;
|
||||
import net.minecraft.world.level.block.EntityBlock;
|
||||
import net.minecraft.world.level.block.entity.BlockEntity;
|
||||
import net.minecraft.world.level.block.state.BlockState;
|
||||
import net.minecraft.world.level.chunk.ChunkAccess;
|
||||
import net.minecraft.world.level.chunk.LevelChunk;
|
||||
import net.minecraft.world.level.dimension.DimensionType;
|
||||
import net.minecraft.world.level.dimension.LevelStem;
|
||||
import net.minecraft.world.level.levelgen.PatrolSpawner;
|
||||
import net.minecraft.world.level.levelgen.PhantomSpawner;
|
||||
import net.minecraft.world.level.storage.LevelStorageSource;
|
||||
import net.minecraft.world.level.storage.PrimaryLevelData;
|
||||
import net.minecraft.world.level.storage.ServerLevelData;
|
||||
import org.bukkit.*;
|
||||
import org.bukkit.block.Biome;
|
||||
import org.bukkit.block.data.BlockData;
|
||||
import org.bukkit.craftbukkit.v1_19_R1.CraftChunk;
|
||||
import org.bukkit.craftbukkit.v1_19_R1.CraftServer;
|
||||
import org.bukkit.craftbukkit.v1_19_R1.block.CraftBlock;
|
||||
import org.bukkit.craftbukkit.v1_19_R1.block.data.CraftBlockData;
|
||||
import org.bukkit.craftbukkit.v1_19_R1.generator.CraftWorldInfo;
|
||||
import org.bukkit.craftbukkit.v1_19_R1.util.CraftMagicNumbers;
|
||||
import org.bukkit.event.EventHandler;
|
||||
import org.bukkit.event.EventPriority;
|
||||
import org.bukkit.event.HandlerList;
|
||||
import org.bukkit.event.world.WorldUnloadEvent;
|
||||
import org.bukkit.generator.ChunkGenerator;
|
||||
import org.bukkit.material.MaterialData;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.lang.ref.WeakReference;
|
||||
import java.lang.reflect.Constructor;
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.nio.file.Files;
|
||||
import java.util.Arrays;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.atomic.AtomicLong;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
public class MemoryWorld implements IMemoryWorld {
|
||||
private static final AtomicLong C = new AtomicLong();
|
||||
private static final Field WORLDS_FIELD;
|
||||
|
||||
private final AtomicReference<ServerLevel> level = new AtomicReference<>();
|
||||
|
||||
public MemoryWorld(NamespacedKey levelType, WorldCreator creator) throws IOException {
|
||||
var name = "memory_world"+C.getAndIncrement();
|
||||
while (Bukkit.getWorld(name) != null) {
|
||||
name = "memory_world"+C.getAndIncrement();
|
||||
}
|
||||
|
||||
var generator = creator.generator();
|
||||
var biomeProvider = creator.biomeProvider();
|
||||
var hardcore = creator.hardcore();
|
||||
var server = getServer();
|
||||
|
||||
var tempDir = Files.createTempDirectory("MemoryGenerator");
|
||||
LevelStorageSource source = LevelStorageSource.createDefault(tempDir);
|
||||
ResourceKey<LevelStem> stemKey = ResourceKey.create(Registry.LEVEL_STEM_REGISTRY, new ResourceLocation(levelType.getNamespace(), levelType.getKey()));
|
||||
var access = source.createAccess(name, stemKey);
|
||||
|
||||
var registry = server.registryAccess().registryOrThrow(Registry.LEVEL_STEM_REGISTRY);
|
||||
var properties = new DedicatedServerProperties.WorldGenProperties(Objects.toString(creator.seed()), GsonHelper.parse(creator.generatorSettings().isEmpty() ? "{}" : creator.generatorSettings()), creator.generateStructures(), creator.type().name().toLowerCase(Locale.ROOT));
|
||||
var settings = properties.create(server.registryAccess());
|
||||
var worldSettings = new LevelSettings(name, GameType.byId(Bukkit.getDefaultGameMode().getValue()), hardcore, Difficulty.EASY, false, new GameRules(), server.datapackconfiguration);
|
||||
var worldData = new PrimaryLevelData(worldSettings, settings, Lifecycle.stable());
|
||||
|
||||
long obfSeed = BiomeManager.obfuscateSeed(creator.seed());
|
||||
var list = ImmutableList.of(new PhantomSpawner(), new PatrolSpawner(), new CatSpawner(), new VillageSiege(), new WanderingTraderSpawner(worldData));
|
||||
var levelStem = registry.get(stemKey);
|
||||
if (levelStem == null)
|
||||
throw new IllegalStateException("Unknown dimension type: " + stemKey);
|
||||
|
||||
CraftWorldInfo worldInfo;
|
||||
try {
|
||||
worldInfo = new CraftWorldInfo(worldData, access, creator.environment(), levelStem.typeHolder().value());
|
||||
} catch (Throwable e) {
|
||||
try {
|
||||
worldInfo = Reflect.newInstance(CraftWorldInfo.class, worldData, access, creator.environment(), levelStem.typeHolder().value(), levelStem.generator(), server.registryAccess());
|
||||
} catch (NoSuchMethodException | InvocationTargetException ex) {
|
||||
throw new IOException("Failed to create CraftWorldInfo", ex);
|
||||
}
|
||||
}
|
||||
if (biomeProvider == null && generator != null) {
|
||||
biomeProvider = generator.getDefaultBiomeProvider(worldInfo);
|
||||
}
|
||||
|
||||
var levelKey = ResourceKey.create(Registry.DIMENSION_REGISTRY, new ResourceLocation(name));
|
||||
var level = new ServerLevel(
|
||||
server,
|
||||
server.executor,
|
||||
access,
|
||||
worldData,
|
||||
levelKey,
|
||||
levelStem,
|
||||
server.progressListenerFactory.create(0),
|
||||
worldData.worldGenSettings().isDebug(),
|
||||
obfSeed,
|
||||
creator.environment() == World.Environment.NORMAL ? list : ImmutableList.of(),
|
||||
true,
|
||||
creator.environment(),
|
||||
generator,
|
||||
biomeProvider
|
||||
);
|
||||
level.keepSpawnInMemory = false;
|
||||
Iris.instance.registerListener(this);
|
||||
this.level.set(level);
|
||||
}
|
||||
|
||||
public World getBukkit() {
|
||||
var level = this.level.get();
|
||||
if (level == null)
|
||||
throw new IllegalStateException("World is not loaded");
|
||||
|
||||
return level.getWorld();
|
||||
}
|
||||
|
||||
public Chunk getChunk(int x, int z) {
|
||||
var level = this.level.get();
|
||||
if (level == null)
|
||||
throw new IllegalStateException("World is not loaded");
|
||||
return new CraftChunk(level, x, z);
|
||||
}
|
||||
|
||||
public ChunkGenerator.ChunkData getChunkData(int x, int z) {
|
||||
var level = this.level.get();
|
||||
if (level == null)
|
||||
throw new IllegalStateException("World is not loaded");
|
||||
return new MemoryChunkData(x, z);
|
||||
}
|
||||
|
||||
@EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
|
||||
public void onWorldUnload(WorldUnloadEvent event) {
|
||||
var level = this.level.get();
|
||||
if (level == null || event.getWorld() != level.getWorld())
|
||||
return;
|
||||
|
||||
this.level.set(null);
|
||||
}
|
||||
|
||||
public boolean isClosed() {
|
||||
return this.level.get() == null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() throws Exception {
|
||||
if (!Bukkit.isPrimaryThread()) {
|
||||
var future = new CompletableFuture<Void>();
|
||||
J.s(() -> {
|
||||
try {
|
||||
close();
|
||||
future.complete(null);
|
||||
} catch (Exception e) {
|
||||
future.completeExceptionally(e);
|
||||
}
|
||||
});
|
||||
future.join();
|
||||
return;
|
||||
}
|
||||
|
||||
var level = this.level.get();
|
||||
if (level == null || !this.level.compareAndSet(level, null))
|
||||
return;
|
||||
|
||||
level.getChunkSource().close(false);
|
||||
level.entityManager.close(false);
|
||||
level.convertable.deleteLevel();
|
||||
level.convertable.close();
|
||||
|
||||
var map = (Map<String, World>) WORLDS_FIELD.get(Bukkit.getServer());
|
||||
map.remove(level.dimension().location().getPath());
|
||||
getServer().removeLevel(level);
|
||||
HandlerList.unregisterAll(this);
|
||||
}
|
||||
|
||||
private static MinecraftServer getServer() {
|
||||
return ((CraftServer) Bukkit.getServer()).getServer();
|
||||
}
|
||||
|
||||
static {
|
||||
try {
|
||||
WORLDS_FIELD = CraftServer.class.getDeclaredField("worlds");
|
||||
WORLDS_FIELD.setAccessible(true);
|
||||
} catch (NoSuchFieldException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
private class MemoryChunkData implements ChunkGenerator.ChunkData {
|
||||
private final int maxHeight;
|
||||
private final int minHeight;
|
||||
private final ChunkPos pos;
|
||||
private WeakReference<LevelChunk> chunk;
|
||||
|
||||
private MemoryChunkData(int x, int z) {
|
||||
var level = MemoryWorld.this.level.get();
|
||||
if (level == null)
|
||||
throw new IllegalStateException("World is not loaded");
|
||||
|
||||
var chunk = level.getChunk(x, z);
|
||||
this.minHeight = chunk.getMinBuildHeight();
|
||||
this.maxHeight = chunk.getMaxBuildHeight();
|
||||
this.pos = new ChunkPos(x, z);
|
||||
this.chunk = new WeakReference<>(chunk);
|
||||
}
|
||||
|
||||
public LevelChunk getHandle() {
|
||||
LevelChunk chunk = this.chunk.get();
|
||||
if (chunk == null) {
|
||||
var level = MemoryWorld.this.level.get();
|
||||
if (level == null)
|
||||
throw new IllegalStateException("World is not loaded");
|
||||
|
||||
chunk = level.getChunk(this.pos.x, this.pos.z);
|
||||
this.chunk = new WeakReference<>(chunk);
|
||||
}
|
||||
return chunk;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getMinHeight() {
|
||||
return minHeight;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getMaxHeight() {
|
||||
return maxHeight;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public Biome getBiome(int x, int y, int z) {
|
||||
return CraftBlock.biomeBaseToBiome(this.getHandle().biomeRegistry, this.getHandle().getNoiseBiome(x >> 2, y >> 2, z >> 2));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setBlock(int x, int y, int z, @NotNull Material material) {
|
||||
setBlock(x, y, z, material.createBlockData());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setBlock(int x, int y, int z, @NotNull MaterialData material) {
|
||||
setBlock(x, y, z, CraftMagicNumbers.getBlock(material));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setBlock(int x, int y, int z, @NotNull BlockData blockData) {
|
||||
setBlock(x, y, z, ((CraftBlockData) blockData).getState());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setRegion(int xMin, int yMin, int zMin, int xMax, int yMax, int zMax, Material material) {
|
||||
this.setRegion(xMin, yMin, zMin, xMax, yMax, zMax, material.createBlockData());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setRegion(int xMin, int yMin, int zMin, int xMax, int yMax, int zMax, MaterialData material) {
|
||||
this.setRegion(xMin, yMin, zMin, xMax, yMax, zMax, CraftMagicNumbers.getBlock(material));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setRegion(int xMin, int yMin, int zMin, int xMax, int yMax, int zMax, BlockData blockData) {
|
||||
this.setRegion(xMin, yMin, zMin, xMax, yMax, zMax, ((CraftBlockData)blockData).getState());
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public Material getType(int x, int y, int z) {
|
||||
return CraftMagicNumbers.getMaterial(this.getTypeId(x, y, z).getBlock());
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public MaterialData getTypeAndData(int x, int y, int z) {
|
||||
return CraftMagicNumbers.getMaterial(this.getTypeId(x, y, z));
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public BlockData getBlockData(int x, int y, int z) {
|
||||
return CraftBlockData.fromData(this.getTypeId(x, y, z));
|
||||
}
|
||||
|
||||
@Deprecated
|
||||
@Override
|
||||
public byte getData(int x, int y, int z) {
|
||||
return CraftMagicNumbers.toLegacyData(this.getTypeId(x, y, z));
|
||||
}
|
||||
|
||||
private void setRegion(int xMin, int yMin, int zMin, int xMax, int yMax, int zMax, BlockState type) {
|
||||
if (xMin <= 15 && yMin < this.maxHeight && zMin <= 15) {
|
||||
if (xMin < 0) {
|
||||
xMin = 0;
|
||||
}
|
||||
|
||||
if (yMin < this.minHeight) {
|
||||
yMin = this.minHeight;
|
||||
}
|
||||
|
||||
if (zMin < 0) {
|
||||
zMin = 0;
|
||||
}
|
||||
|
||||
if (xMax > 16) {
|
||||
xMax = 16;
|
||||
}
|
||||
|
||||
if (yMax > this.maxHeight) {
|
||||
yMax = this.maxHeight;
|
||||
}
|
||||
|
||||
if (zMax > 16) {
|
||||
zMax = 16;
|
||||
}
|
||||
|
||||
if (xMin < xMax && yMin < yMax && zMin < zMax) {
|
||||
for(int y = yMin; y < yMax; ++y) {
|
||||
for(int x = xMin; x < xMax; ++x) {
|
||||
for(int z = zMin; z < zMax; ++z) {
|
||||
this.setBlock(x, y, z, type);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private BlockState getTypeId(int x, int y, int z) {
|
||||
if (x == (x & 15) && y >= this.minHeight && y < this.maxHeight && z == (z & 15)) {
|
||||
ChunkAccess access = this.getHandle();
|
||||
return access.getBlockState(new BlockPos(access.getPos().getMinBlockX() + x, y, access.getPos().getMinBlockZ() + z));
|
||||
} else {
|
||||
return Blocks.AIR.defaultBlockState();
|
||||
}
|
||||
}
|
||||
|
||||
private void setBlock(int x, int y, int z, BlockState type) {
|
||||
if (x == (x & 15) && y >= this.minHeight && y < this.maxHeight && z == (z & 15)) {
|
||||
ChunkAccess access = this.getHandle();
|
||||
BlockPos blockPosition = new BlockPos(access.getPos().getMinBlockX() + x, y, access.getPos().getMinBlockZ() + z);
|
||||
BlockState oldBlockData = access.setBlockState(blockPosition, type, false);
|
||||
if (type.hasBlockEntity()) {
|
||||
BlockEntity tileEntity = ((EntityBlock)type.getBlock()).newBlockEntity(blockPosition, type);
|
||||
if (tileEntity == null) {
|
||||
access.removeBlockEntity(blockPosition);
|
||||
} else {
|
||||
access.setBlockEntity(tileEntity);
|
||||
}
|
||||
} else if (oldBlockData != null && oldBlockData.hasBlockEntity()) {
|
||||
access.removeBlockEntity(blockPosition);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,10 +1,7 @@
|
||||
package com.volmit.iris.core.nms.v1_19_R1;
|
||||
|
||||
import java.awt.Color;
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.DataInputStream;
|
||||
import java.io.DataOutputStream;
|
||||
import java.io.*;
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.Method;
|
||||
import java.lang.reflect.Modifier;
|
||||
@@ -15,6 +12,7 @@ import java.util.Vector;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
import com.mojang.datafixers.util.Pair;
|
||||
import com.volmit.iris.core.nms.IMemoryWorld;
|
||||
import com.volmit.iris.core.nms.container.BiomeColor;
|
||||
import com.volmit.iris.util.scheduling.J;
|
||||
import net.minecraft.nbt.*;
|
||||
@@ -607,6 +605,11 @@ public class NMSBinding implements INMSBinding {
|
||||
return keys;
|
||||
}
|
||||
|
||||
@Override
|
||||
public IMemoryWorld createMemoryWorld(NamespacedKey levelType, WorldCreator creator) throws IOException {
|
||||
return new MemoryWorld(levelType, creator);
|
||||
}
|
||||
|
||||
private static Field getField(Class<?> clazz, Class<?> fieldType) throws NoSuchFieldException {
|
||||
try {
|
||||
for (Field f : clazz.getDeclaredFields()) {
|
||||
|
||||
@@ -0,0 +1,379 @@
|
||||
package com.volmit.iris.core.nms.v1_19_R2;
|
||||
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.mojang.serialization.Lifecycle;
|
||||
import com.volmit.iris.Iris;
|
||||
import com.volmit.iris.core.nms.IMemoryWorld;
|
||||
import com.volmit.iris.util.reflect.Reflect;
|
||||
import net.minecraft.core.BlockPos;
|
||||
import net.minecraft.core.RegistryAccess;
|
||||
import net.minecraft.core.registries.Registries;
|
||||
import net.minecraft.resources.ResourceKey;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
import net.minecraft.server.MinecraftServer;
|
||||
import net.minecraft.server.dedicated.DedicatedServerProperties;
|
||||
import net.minecraft.server.level.ServerLevel;
|
||||
import net.minecraft.util.GsonHelper;
|
||||
import net.minecraft.world.Difficulty;
|
||||
import net.minecraft.world.entity.ai.village.VillageSiege;
|
||||
import net.minecraft.world.entity.npc.CatSpawner;
|
||||
import net.minecraft.world.entity.npc.WanderingTraderSpawner;
|
||||
import net.minecraft.world.level.ChunkPos;
|
||||
import net.minecraft.world.level.GameRules;
|
||||
import net.minecraft.world.level.GameType;
|
||||
import net.minecraft.world.level.LevelSettings;
|
||||
import net.minecraft.world.level.biome.BiomeManager;
|
||||
import net.minecraft.world.level.block.Blocks;
|
||||
import net.minecraft.world.level.block.EntityBlock;
|
||||
import net.minecraft.world.level.block.entity.BlockEntity;
|
||||
import net.minecraft.world.level.block.state.BlockState;
|
||||
import net.minecraft.world.level.chunk.ChunkAccess;
|
||||
import net.minecraft.world.level.chunk.LevelChunk;
|
||||
import net.minecraft.world.level.dimension.DimensionType;
|
||||
import net.minecraft.world.level.dimension.LevelStem;
|
||||
import net.minecraft.world.level.levelgen.PatrolSpawner;
|
||||
import net.minecraft.world.level.levelgen.PhantomSpawner;
|
||||
import net.minecraft.world.level.levelgen.WorldOptions;
|
||||
import net.minecraft.world.level.storage.LevelStorageSource;
|
||||
import net.minecraft.world.level.storage.PrimaryLevelData;
|
||||
import net.minecraft.world.level.storage.ServerLevelData;
|
||||
import org.bukkit.*;
|
||||
import org.bukkit.block.Biome;
|
||||
import org.bukkit.block.data.BlockData;
|
||||
import org.bukkit.craftbukkit.v1_19_R2.CraftChunk;
|
||||
import org.bukkit.craftbukkit.v1_19_R2.CraftServer;
|
||||
import org.bukkit.craftbukkit.v1_19_R2.block.CraftBlock;
|
||||
import org.bukkit.craftbukkit.v1_19_R2.block.data.CraftBlockData;
|
||||
import org.bukkit.craftbukkit.v1_19_R2.generator.CraftWorldInfo;
|
||||
import org.bukkit.craftbukkit.v1_19_R2.util.CraftMagicNumbers;
|
||||
import org.bukkit.event.EventHandler;
|
||||
import org.bukkit.event.EventPriority;
|
||||
import org.bukkit.event.HandlerList;
|
||||
import org.bukkit.event.world.WorldUnloadEvent;
|
||||
import org.bukkit.generator.ChunkGenerator;
|
||||
import org.bukkit.material.MaterialData;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.lang.ref.WeakReference;
|
||||
import java.lang.reflect.Constructor;
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.nio.file.Files;
|
||||
import java.util.Arrays;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.atomic.AtomicLong;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
public class MemoryWorld implements IMemoryWorld {
|
||||
private static final AtomicLong C = new AtomicLong();
|
||||
private static final Field WORLDS_FIELD;
|
||||
|
||||
private final AtomicReference<ServerLevel> level = new AtomicReference<>();
|
||||
|
||||
public MemoryWorld(NamespacedKey levelType, WorldCreator creator) throws IOException {
|
||||
var name = "memory_world"+C.getAndIncrement();
|
||||
while (Bukkit.getWorld(name) != null) {
|
||||
name = "memory_world"+C.getAndIncrement();
|
||||
}
|
||||
|
||||
var generator = creator.generator();
|
||||
var biomeProvider = creator.biomeProvider();
|
||||
var hardcore = creator.hardcore();
|
||||
var server = getServer();
|
||||
|
||||
var tempDir = Files.createTempDirectory("MemoryGenerator");
|
||||
LevelStorageSource source = LevelStorageSource.createDefault(tempDir);
|
||||
ResourceKey<LevelStem> stemKey = ResourceKey.create(Registries.LEVEL_STEM, new ResourceLocation(levelType.getNamespace(), levelType.getKey()));
|
||||
var access = source.createAccess(name, stemKey);
|
||||
|
||||
var worldLoader = server.worldLoader;
|
||||
var registry = server.registryAccess().registryOrThrow(Registries.LEVEL_STEM);
|
||||
var options = new WorldOptions(creator.seed(), creator.generateStructures(), false);
|
||||
var properties = new DedicatedServerProperties.WorldDimensionData(GsonHelper.parse(creator.generatorSettings().isEmpty() ? "{}" : creator.generatorSettings()), creator.type().name().toLowerCase(Locale.ROOT));
|
||||
var settings = new LevelSettings(name, GameType.byId(Bukkit.getDefaultGameMode().getValue()), hardcore, Difficulty.EASY, false, new GameRules(), worldLoader.dataConfiguration());
|
||||
var dimension = properties.create(worldLoader.datapackWorldgen()).bake(registry);
|
||||
Lifecycle lifecycle = dimension.lifecycle().add(worldLoader.datapackWorldgen().allRegistriesLifecycle());
|
||||
|
||||
var worldData = new PrimaryLevelData(settings, options, dimension.specialWorldProperty(), lifecycle);
|
||||
worldData.customDimensions = registry;
|
||||
|
||||
long obfSeed = BiomeManager.obfuscateSeed(creator.seed());
|
||||
var list = ImmutableList.of(new PhantomSpawner(), new PatrolSpawner(), new CatSpawner(), new VillageSiege(), new WanderingTraderSpawner(worldData));
|
||||
var levelStem = registry.get(stemKey);
|
||||
if (levelStem == null)
|
||||
throw new IllegalStateException("Unknown dimension type: " + stemKey);
|
||||
|
||||
CraftWorldInfo worldInfo;
|
||||
try {
|
||||
worldInfo = new CraftWorldInfo(worldData, access, creator.environment(), levelStem.type().value());
|
||||
} catch (Throwable e) {
|
||||
try {
|
||||
worldInfo = Reflect.newInstance(CraftWorldInfo.class, worldData, access, creator.environment(), levelStem.type().value(), levelStem.generator(), server.registryAccess());
|
||||
} catch (NoSuchMethodException | InvocationTargetException ex) {
|
||||
throw new IOException("Failed to create CraftWorldInfo", ex);
|
||||
}
|
||||
}
|
||||
if (biomeProvider == null && generator != null) {
|
||||
biomeProvider = generator.getDefaultBiomeProvider(worldInfo);
|
||||
}
|
||||
|
||||
var levelKey = ResourceKey.create(Registries.DIMENSION, new ResourceLocation(name));
|
||||
var level = new ServerLevel(
|
||||
server,
|
||||
server.executor,
|
||||
access,
|
||||
worldData,
|
||||
levelKey,
|
||||
levelStem,
|
||||
server.progressListenerFactory.create(0),
|
||||
worldData.isDebugWorld(),
|
||||
obfSeed,
|
||||
creator.environment() == World.Environment.NORMAL ? list : ImmutableList.of(),
|
||||
true,
|
||||
creator.environment(),
|
||||
generator,
|
||||
biomeProvider
|
||||
);
|
||||
level.keepSpawnInMemory = false;
|
||||
Iris.instance.registerListener(this);
|
||||
this.level.set(level);
|
||||
}
|
||||
|
||||
public World getBukkit() {
|
||||
var level = this.level.get();
|
||||
if (level == null)
|
||||
throw new IllegalStateException("World is not loaded");
|
||||
|
||||
return level.getWorld();
|
||||
}
|
||||
|
||||
public Chunk getChunk(int x, int z) {
|
||||
var level = this.level.get();
|
||||
if (level == null)
|
||||
throw new IllegalStateException("World is not loaded");
|
||||
return new CraftChunk(level, x, z);
|
||||
}
|
||||
|
||||
public ChunkGenerator.ChunkData getChunkData(int x, int z) {
|
||||
var level = this.level.get();
|
||||
if (level == null)
|
||||
throw new IllegalStateException("World is not loaded");
|
||||
return new MemoryChunkData(x, z);
|
||||
}
|
||||
|
||||
@EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
|
||||
public void onWorldUnload(WorldUnloadEvent event) {
|
||||
var level = this.level.get();
|
||||
if (level == null || event.getWorld() != level.getWorld())
|
||||
return;
|
||||
|
||||
this.level.set(null);
|
||||
}
|
||||
|
||||
public boolean isClosed() {
|
||||
return this.level.get() == null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() throws Exception {
|
||||
var level = this.level.get();
|
||||
if (level == null || !this.level.compareAndSet(level, null))
|
||||
return;
|
||||
|
||||
level.getChunkSource().close(false);
|
||||
level.entityManager.close(false);
|
||||
level.convertable.deleteLevel();
|
||||
level.convertable.close();
|
||||
|
||||
var map = (Map<String, World>) WORLDS_FIELD.get(Bukkit.getServer());
|
||||
map.remove(level.dimension().location().getPath());
|
||||
getServer().removeLevel(level);
|
||||
HandlerList.unregisterAll(this);
|
||||
}
|
||||
|
||||
private static MinecraftServer getServer() {
|
||||
return ((CraftServer) Bukkit.getServer()).getServer();
|
||||
}
|
||||
|
||||
static {
|
||||
try {
|
||||
WORLDS_FIELD = CraftServer.class.getDeclaredField("worlds");
|
||||
WORLDS_FIELD.setAccessible(true);
|
||||
} catch (NoSuchFieldException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
private class MemoryChunkData implements ChunkGenerator.ChunkData {
|
||||
private final int maxHeight;
|
||||
private final int minHeight;
|
||||
private final ChunkPos pos;
|
||||
private WeakReference<LevelChunk> chunk;
|
||||
|
||||
private MemoryChunkData(int x, int z) {
|
||||
var level = MemoryWorld.this.level.get();
|
||||
if (level == null)
|
||||
throw new IllegalStateException("World is not loaded");
|
||||
|
||||
var chunk = level.getChunk(x, z);
|
||||
this.minHeight = chunk.getMinBuildHeight();
|
||||
this.maxHeight = chunk.getMaxBuildHeight();
|
||||
this.pos = new ChunkPos(x, z);
|
||||
this.chunk = new WeakReference<>(chunk);
|
||||
}
|
||||
|
||||
public LevelChunk getHandle() {
|
||||
LevelChunk chunk = this.chunk.get();
|
||||
if (chunk == null) {
|
||||
var level = MemoryWorld.this.level.get();
|
||||
if (level == null)
|
||||
throw new IllegalStateException("World is not loaded");
|
||||
|
||||
chunk = level.getChunk(this.pos.x, this.pos.z);
|
||||
this.chunk = new WeakReference<>(chunk);
|
||||
}
|
||||
return chunk;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getMinHeight() {
|
||||
return minHeight;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getMaxHeight() {
|
||||
return maxHeight;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public Biome getBiome(int x, int y, int z) {
|
||||
return CraftBlock.biomeBaseToBiome(this.getHandle().biomeRegistry, this.getHandle().getNoiseBiome(x >> 2, y >> 2, z >> 2));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setBlock(int x, int y, int z, @NotNull Material material) {
|
||||
setBlock(x, y, z, material.createBlockData());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setBlock(int x, int y, int z, @NotNull MaterialData material) {
|
||||
setBlock(x, y, z, CraftMagicNumbers.getBlock(material));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setBlock(int x, int y, int z, @NotNull BlockData blockData) {
|
||||
setBlock(x, y, z, ((CraftBlockData) blockData).getState());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setRegion(int xMin, int yMin, int zMin, int xMax, int yMax, int zMax, Material material) {
|
||||
this.setRegion(xMin, yMin, zMin, xMax, yMax, zMax, material.createBlockData());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setRegion(int xMin, int yMin, int zMin, int xMax, int yMax, int zMax, MaterialData material) {
|
||||
this.setRegion(xMin, yMin, zMin, xMax, yMax, zMax, CraftMagicNumbers.getBlock(material));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setRegion(int xMin, int yMin, int zMin, int xMax, int yMax, int zMax, BlockData blockData) {
|
||||
this.setRegion(xMin, yMin, zMin, xMax, yMax, zMax, ((CraftBlockData)blockData).getState());
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public Material getType(int x, int y, int z) {
|
||||
return CraftMagicNumbers.getMaterial(this.getTypeId(x, y, z).getBlock());
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public MaterialData getTypeAndData(int x, int y, int z) {
|
||||
return CraftMagicNumbers.getMaterial(this.getTypeId(x, y, z));
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public BlockData getBlockData(int x, int y, int z) {
|
||||
return CraftBlockData.fromData(this.getTypeId(x, y, z));
|
||||
}
|
||||
|
||||
@Deprecated
|
||||
@Override
|
||||
public byte getData(int x, int y, int z) {
|
||||
return CraftMagicNumbers.toLegacyData(this.getTypeId(x, y, z));
|
||||
}
|
||||
|
||||
private void setRegion(int xMin, int yMin, int zMin, int xMax, int yMax, int zMax, BlockState type) {
|
||||
if (xMin <= 15 && yMin < this.maxHeight && zMin <= 15) {
|
||||
if (xMin < 0) {
|
||||
xMin = 0;
|
||||
}
|
||||
|
||||
if (yMin < this.minHeight) {
|
||||
yMin = this.minHeight;
|
||||
}
|
||||
|
||||
if (zMin < 0) {
|
||||
zMin = 0;
|
||||
}
|
||||
|
||||
if (xMax > 16) {
|
||||
xMax = 16;
|
||||
}
|
||||
|
||||
if (yMax > this.maxHeight) {
|
||||
yMax = this.maxHeight;
|
||||
}
|
||||
|
||||
if (zMax > 16) {
|
||||
zMax = 16;
|
||||
}
|
||||
|
||||
if (xMin < xMax && yMin < yMax && zMin < zMax) {
|
||||
for(int y = yMin; y < yMax; ++y) {
|
||||
for(int x = xMin; x < xMax; ++x) {
|
||||
for(int z = zMin; z < zMax; ++z) {
|
||||
this.setBlock(x, y, z, type);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private BlockState getTypeId(int x, int y, int z) {
|
||||
if (x == (x & 15) && y >= this.minHeight && y < this.maxHeight && z == (z & 15)) {
|
||||
ChunkAccess access = this.getHandle();
|
||||
return access.getBlockState(new BlockPos(access.getPos().getMinBlockX() + x, y, access.getPos().getMinBlockZ() + z));
|
||||
} else {
|
||||
return Blocks.AIR.defaultBlockState();
|
||||
}
|
||||
}
|
||||
|
||||
private void setBlock(int x, int y, int z, BlockState type) {
|
||||
if (x == (x & 15) && y >= this.minHeight && y < this.maxHeight && z == (z & 15)) {
|
||||
ChunkAccess access = this.getHandle();
|
||||
BlockPos blockPosition = new BlockPos(access.getPos().getMinBlockX() + x, y, access.getPos().getMinBlockZ() + z);
|
||||
BlockState oldBlockData = access.setBlockState(blockPosition, type, false);
|
||||
if (type.hasBlockEntity()) {
|
||||
BlockEntity tileEntity = ((EntityBlock)type.getBlock()).newBlockEntity(blockPosition, type);
|
||||
if (tileEntity == null) {
|
||||
access.removeBlockEntity(blockPosition);
|
||||
} else {
|
||||
access.setBlockEntity(tileEntity);
|
||||
}
|
||||
} else if (oldBlockData != null && oldBlockData.hasBlockEntity()) {
|
||||
access.removeBlockEntity(blockPosition);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,10 +1,7 @@
|
||||
package com.volmit.iris.core.nms.v1_19_R2;
|
||||
|
||||
import java.awt.Color;
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.DataInputStream;
|
||||
import java.io.DataOutputStream;
|
||||
import java.io.*;
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.Method;
|
||||
import java.lang.reflect.Modifier;
|
||||
@@ -15,6 +12,7 @@ import java.util.Vector;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
import com.mojang.datafixers.util.Pair;
|
||||
import com.volmit.iris.core.nms.IMemoryWorld;
|
||||
import com.volmit.iris.core.nms.container.BiomeColor;
|
||||
import com.volmit.iris.util.scheduling.J;
|
||||
import net.minecraft.nbt.*;
|
||||
@@ -609,6 +607,11 @@ public class NMSBinding implements INMSBinding {
|
||||
return keys;
|
||||
}
|
||||
|
||||
@Override
|
||||
public IMemoryWorld createMemoryWorld(NamespacedKey levelType, WorldCreator creator) throws IOException {
|
||||
return new MemoryWorld(levelType, creator);
|
||||
}
|
||||
|
||||
private static Field getField(Class<?> clazz, Class<?> fieldType) throws NoSuchFieldException {
|
||||
try {
|
||||
for (Field f : clazz.getDeclaredFields()) {
|
||||
|
||||
@@ -0,0 +1,394 @@
|
||||
package com.volmit.iris.core.nms.v1_19_R3;
|
||||
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.mojang.serialization.Lifecycle;
|
||||
import com.volmit.iris.Iris;
|
||||
import com.volmit.iris.core.nms.IMemoryWorld;
|
||||
import com.volmit.iris.util.reflect.Reflect;
|
||||
import com.volmit.iris.util.scheduling.J;
|
||||
import net.minecraft.core.BlockPos;
|
||||
import net.minecraft.core.RegistryAccess;
|
||||
import net.minecraft.core.registries.Registries;
|
||||
import net.minecraft.resources.ResourceKey;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
import net.minecraft.server.MinecraftServer;
|
||||
import net.minecraft.server.dedicated.DedicatedServerProperties;
|
||||
import net.minecraft.server.level.ServerLevel;
|
||||
import net.minecraft.util.GsonHelper;
|
||||
import net.minecraft.world.Difficulty;
|
||||
import net.minecraft.world.entity.ai.village.VillageSiege;
|
||||
import net.minecraft.world.entity.npc.CatSpawner;
|
||||
import net.minecraft.world.entity.npc.WanderingTraderSpawner;
|
||||
import net.minecraft.world.level.ChunkPos;
|
||||
import net.minecraft.world.level.GameRules;
|
||||
import net.minecraft.world.level.GameType;
|
||||
import net.minecraft.world.level.LevelSettings;
|
||||
import net.minecraft.world.level.biome.BiomeManager;
|
||||
import net.minecraft.world.level.block.Blocks;
|
||||
import net.minecraft.world.level.block.EntityBlock;
|
||||
import net.minecraft.world.level.block.entity.BlockEntity;
|
||||
import net.minecraft.world.level.block.state.BlockState;
|
||||
import net.minecraft.world.level.chunk.ChunkAccess;
|
||||
import net.minecraft.world.level.chunk.LevelChunk;
|
||||
import net.minecraft.world.level.dimension.DimensionType;
|
||||
import net.minecraft.world.level.dimension.LevelStem;
|
||||
import net.minecraft.world.level.levelgen.PatrolSpawner;
|
||||
import net.minecraft.world.level.levelgen.PhantomSpawner;
|
||||
import net.minecraft.world.level.levelgen.WorldOptions;
|
||||
import net.minecraft.world.level.storage.LevelStorageSource;
|
||||
import net.minecraft.world.level.storage.PrimaryLevelData;
|
||||
import net.minecraft.world.level.storage.ServerLevelData;
|
||||
import org.bukkit.*;
|
||||
import org.bukkit.block.Biome;
|
||||
import org.bukkit.block.data.BlockData;
|
||||
import org.bukkit.craftbukkit.v1_19_R3.CraftChunk;
|
||||
import org.bukkit.craftbukkit.v1_19_R3.CraftServer;
|
||||
import org.bukkit.craftbukkit.v1_19_R3.block.CraftBlock;
|
||||
import org.bukkit.craftbukkit.v1_19_R3.block.data.CraftBlockData;
|
||||
import org.bukkit.craftbukkit.v1_19_R3.generator.CraftWorldInfo;
|
||||
import org.bukkit.craftbukkit.v1_19_R3.util.CraftMagicNumbers;
|
||||
import org.bukkit.event.EventHandler;
|
||||
import org.bukkit.event.EventPriority;
|
||||
import org.bukkit.event.HandlerList;
|
||||
import org.bukkit.event.world.WorldUnloadEvent;
|
||||
import org.bukkit.generator.ChunkGenerator;
|
||||
import org.bukkit.material.MaterialData;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.lang.ref.WeakReference;
|
||||
import java.lang.reflect.Constructor;
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.nio.file.Files;
|
||||
import java.util.Arrays;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.atomic.AtomicLong;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
public class MemoryWorld implements IMemoryWorld {
|
||||
private static final AtomicLong C = new AtomicLong();
|
||||
private static final Field WORLDS_FIELD;
|
||||
|
||||
private final AtomicReference<ServerLevel> level = new AtomicReference<>();
|
||||
|
||||
public MemoryWorld(NamespacedKey levelType, WorldCreator creator) throws IOException {
|
||||
var name = "memory_world"+C.getAndIncrement();
|
||||
while (Bukkit.getWorld(name) != null) {
|
||||
name = "memory_world"+C.getAndIncrement();
|
||||
}
|
||||
|
||||
var generator = creator.generator();
|
||||
var biomeProvider = creator.biomeProvider();
|
||||
var hardcore = creator.hardcore();
|
||||
var server = getServer();
|
||||
|
||||
var tempDir = Files.createTempDirectory("MemoryGenerator");
|
||||
LevelStorageSource source = LevelStorageSource.createDefault(tempDir);
|
||||
ResourceKey<LevelStem> stemKey = ResourceKey.create(Registries.LEVEL_STEM, new ResourceLocation(levelType.getNamespace(), levelType.getKey()));
|
||||
var access = source.createAccess(name, stemKey);
|
||||
|
||||
var worldLoader = server.worldLoader;
|
||||
var registry = server.registryAccess().registryOrThrow(Registries.LEVEL_STEM);
|
||||
var options = new WorldOptions(creator.seed(), creator.generateStructures(), false);
|
||||
var properties = new DedicatedServerProperties.WorldDimensionData(GsonHelper.parse(creator.generatorSettings().isEmpty() ? "{}" : creator.generatorSettings()), creator.type().name().toLowerCase(Locale.ROOT));
|
||||
var settings = new LevelSettings(name, GameType.byId(Bukkit.getDefaultGameMode().getValue()), hardcore, Difficulty.EASY, false, new GameRules(), worldLoader.dataConfiguration());
|
||||
var dimension = properties.create(worldLoader.datapackWorldgen()).bake(registry);
|
||||
Lifecycle lifecycle = dimension.lifecycle().add(worldLoader.datapackWorldgen().allRegistriesLifecycle());
|
||||
|
||||
var worldData = new PrimaryLevelData(settings, options, dimension.specialWorldProperty(), lifecycle);
|
||||
worldData.customDimensions = registry;
|
||||
|
||||
long obfSeed = BiomeManager.obfuscateSeed(creator.seed());
|
||||
var list = ImmutableList.of(new PhantomSpawner(), new PatrolSpawner(), new CatSpawner(), new VillageSiege(), new WanderingTraderSpawner(worldData));
|
||||
var levelStem = registry.get(stemKey);
|
||||
if (levelStem == null)
|
||||
throw new IllegalStateException("Unknown dimension type: " + stemKey);
|
||||
|
||||
CraftWorldInfo worldInfo;
|
||||
try {
|
||||
worldInfo = new CraftWorldInfo(worldData, access, creator.environment(), levelStem.type().value());
|
||||
} catch (Throwable e) {
|
||||
try {
|
||||
worldInfo = Reflect.newInstance(CraftWorldInfo.class, worldData, access, creator.environment(), levelStem.type().value(), levelStem.generator(), server.registryAccess());
|
||||
} catch (NoSuchMethodException | InvocationTargetException ex) {
|
||||
throw new IOException("Failed to create CraftWorldInfo", ex);
|
||||
}
|
||||
}
|
||||
if (biomeProvider == null && generator != null) {
|
||||
biomeProvider = generator.getDefaultBiomeProvider(worldInfo);
|
||||
}
|
||||
|
||||
var levelKey = ResourceKey.create(Registries.DIMENSION, new ResourceLocation(name));
|
||||
var level = new ServerLevel(
|
||||
server,
|
||||
server.executor,
|
||||
access,
|
||||
worldData,
|
||||
levelKey,
|
||||
levelStem,
|
||||
server.progressListenerFactory.create(0),
|
||||
worldData.isDebugWorld(),
|
||||
obfSeed,
|
||||
creator.environment() == World.Environment.NORMAL ? list : ImmutableList.of(),
|
||||
true,
|
||||
creator.environment(),
|
||||
generator,
|
||||
biomeProvider
|
||||
);
|
||||
level.keepSpawnInMemory = false;
|
||||
Iris.instance.registerListener(this);
|
||||
this.level.set(level);
|
||||
}
|
||||
|
||||
public World getBukkit() {
|
||||
var level = this.level.get();
|
||||
if (level == null)
|
||||
throw new IllegalStateException("World is not loaded");
|
||||
|
||||
return level.getWorld();
|
||||
}
|
||||
|
||||
public Chunk getChunk(int x, int z) {
|
||||
var level = this.level.get();
|
||||
if (level == null)
|
||||
throw new IllegalStateException("World is not loaded");
|
||||
return new CraftChunk(level, x, z);
|
||||
}
|
||||
|
||||
public ChunkGenerator.ChunkData getChunkData(int x, int z) {
|
||||
var level = this.level.get();
|
||||
if (level == null)
|
||||
throw new IllegalStateException("World is not loaded");
|
||||
return new MemoryChunkData(x, z);
|
||||
}
|
||||
|
||||
@EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
|
||||
public void onWorldUnload(WorldUnloadEvent event) {
|
||||
var level = this.level.get();
|
||||
if (level == null || event.getWorld() != level.getWorld())
|
||||
return;
|
||||
|
||||
this.level.set(null);
|
||||
}
|
||||
|
||||
public boolean isClosed() {
|
||||
return this.level.get() == null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() throws Exception {
|
||||
if (!Bukkit.isPrimaryThread()) {
|
||||
var future = new CompletableFuture<Void>();
|
||||
J.s(() -> {
|
||||
try {
|
||||
close();
|
||||
future.complete(null);
|
||||
} catch (Exception e) {
|
||||
future.completeExceptionally(e);
|
||||
}
|
||||
});
|
||||
future.join();
|
||||
return;
|
||||
}
|
||||
|
||||
var level = this.level.get();
|
||||
if (level == null || !this.level.compareAndSet(level, null))
|
||||
return;
|
||||
|
||||
level.close();
|
||||
level.convertable.deleteLevel();
|
||||
level.convertable.close();
|
||||
|
||||
var map = (Map<String, World>) WORLDS_FIELD.get(Bukkit.getServer());
|
||||
map.remove(level.dimension().location().getPath());
|
||||
getServer().removeLevel(level);
|
||||
HandlerList.unregisterAll(this);
|
||||
}
|
||||
|
||||
private static MinecraftServer getServer() {
|
||||
return ((CraftServer) Bukkit.getServer()).getServer();
|
||||
}
|
||||
|
||||
static {
|
||||
try {
|
||||
WORLDS_FIELD = CraftServer.class.getDeclaredField("worlds");
|
||||
WORLDS_FIELD.setAccessible(true);
|
||||
} catch (NoSuchFieldException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
private class MemoryChunkData implements ChunkGenerator.ChunkData {
|
||||
private final int maxHeight;
|
||||
private final int minHeight;
|
||||
private final ChunkPos pos;
|
||||
private WeakReference<LevelChunk> chunk;
|
||||
|
||||
private MemoryChunkData(int x, int z) {
|
||||
var level = MemoryWorld.this.level.get();
|
||||
if (level == null)
|
||||
throw new IllegalStateException("World is not loaded");
|
||||
|
||||
var chunk = level.getChunk(x, z);
|
||||
this.minHeight = chunk.getMinBuildHeight();
|
||||
this.maxHeight = chunk.getMaxBuildHeight();
|
||||
this.pos = new ChunkPos(x, z);
|
||||
this.chunk = new WeakReference<>(chunk);
|
||||
}
|
||||
|
||||
public LevelChunk getHandle() {
|
||||
LevelChunk chunk = this.chunk.get();
|
||||
if (chunk == null) {
|
||||
var level = MemoryWorld.this.level.get();
|
||||
if (level == null)
|
||||
throw new IllegalStateException("World is not loaded");
|
||||
|
||||
chunk = level.getChunk(this.pos.x, this.pos.z);
|
||||
this.chunk = new WeakReference<>(chunk);
|
||||
}
|
||||
return chunk;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getMinHeight() {
|
||||
return minHeight;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getMaxHeight() {
|
||||
return maxHeight;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public Biome getBiome(int x, int y, int z) {
|
||||
return CraftBlock.biomeBaseToBiome(this.getHandle().biomeRegistry, this.getHandle().getNoiseBiome(x >> 2, y >> 2, z >> 2));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setBlock(int x, int y, int z, @NotNull Material material) {
|
||||
setBlock(x, y, z, material.createBlockData());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setBlock(int x, int y, int z, @NotNull MaterialData material) {
|
||||
setBlock(x, y, z, CraftMagicNumbers.getBlock(material));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setBlock(int x, int y, int z, @NotNull BlockData blockData) {
|
||||
setBlock(x, y, z, ((CraftBlockData) blockData).getState());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setRegion(int xMin, int yMin, int zMin, int xMax, int yMax, int zMax, Material material) {
|
||||
this.setRegion(xMin, yMin, zMin, xMax, yMax, zMax, material.createBlockData());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setRegion(int xMin, int yMin, int zMin, int xMax, int yMax, int zMax, MaterialData material) {
|
||||
this.setRegion(xMin, yMin, zMin, xMax, yMax, zMax, CraftMagicNumbers.getBlock(material));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setRegion(int xMin, int yMin, int zMin, int xMax, int yMax, int zMax, BlockData blockData) {
|
||||
this.setRegion(xMin, yMin, zMin, xMax, yMax, zMax, ((CraftBlockData)blockData).getState());
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public Material getType(int x, int y, int z) {
|
||||
return CraftMagicNumbers.getMaterial(this.getTypeId(x, y, z).getBlock());
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public MaterialData getTypeAndData(int x, int y, int z) {
|
||||
return CraftMagicNumbers.getMaterial(this.getTypeId(x, y, z));
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public BlockData getBlockData(int x, int y, int z) {
|
||||
return CraftBlockData.fromData(this.getTypeId(x, y, z));
|
||||
}
|
||||
|
||||
@Deprecated
|
||||
@Override
|
||||
public byte getData(int x, int y, int z) {
|
||||
return CraftMagicNumbers.toLegacyData(this.getTypeId(x, y, z));
|
||||
}
|
||||
|
||||
private void setRegion(int xMin, int yMin, int zMin, int xMax, int yMax, int zMax, BlockState type) {
|
||||
if (xMin <= 15 && yMin < this.maxHeight && zMin <= 15) {
|
||||
if (xMin < 0) {
|
||||
xMin = 0;
|
||||
}
|
||||
|
||||
if (yMin < this.minHeight) {
|
||||
yMin = this.minHeight;
|
||||
}
|
||||
|
||||
if (zMin < 0) {
|
||||
zMin = 0;
|
||||
}
|
||||
|
||||
if (xMax > 16) {
|
||||
xMax = 16;
|
||||
}
|
||||
|
||||
if (yMax > this.maxHeight) {
|
||||
yMax = this.maxHeight;
|
||||
}
|
||||
|
||||
if (zMax > 16) {
|
||||
zMax = 16;
|
||||
}
|
||||
|
||||
if (xMin < xMax && yMin < yMax && zMin < zMax) {
|
||||
for(int y = yMin; y < yMax; ++y) {
|
||||
for(int x = xMin; x < xMax; ++x) {
|
||||
for(int z = zMin; z < zMax; ++z) {
|
||||
this.setBlock(x, y, z, type);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private BlockState getTypeId(int x, int y, int z) {
|
||||
if (x == (x & 15) && y >= this.minHeight && y < this.maxHeight && z == (z & 15)) {
|
||||
ChunkAccess access = this.getHandle();
|
||||
return access.getBlockState(new BlockPos(access.getPos().getMinBlockX() + x, y, access.getPos().getMinBlockZ() + z));
|
||||
} else {
|
||||
return Blocks.AIR.defaultBlockState();
|
||||
}
|
||||
}
|
||||
|
||||
private void setBlock(int x, int y, int z, BlockState type) {
|
||||
if (x == (x & 15) && y >= this.minHeight && y < this.maxHeight && z == (z & 15)) {
|
||||
ChunkAccess access = this.getHandle();
|
||||
BlockPos blockPosition = new BlockPos(access.getPos().getMinBlockX() + x, y, access.getPos().getMinBlockZ() + z);
|
||||
BlockState oldBlockData = access.setBlockState(blockPosition, type, false);
|
||||
if (type.hasBlockEntity()) {
|
||||
BlockEntity tileEntity = ((EntityBlock)type.getBlock()).newBlockEntity(blockPosition, type);
|
||||
if (tileEntity == null) {
|
||||
access.removeBlockEntity(blockPosition);
|
||||
} else {
|
||||
access.setBlockEntity(tileEntity);
|
||||
}
|
||||
} else if (oldBlockData != null && oldBlockData.hasBlockEntity()) {
|
||||
access.removeBlockEntity(blockPosition);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,10 +1,7 @@
|
||||
package com.volmit.iris.core.nms.v1_19_R3;
|
||||
|
||||
import java.awt.Color;
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.DataInputStream;
|
||||
import java.io.DataOutputStream;
|
||||
import java.io.*;
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.Method;
|
||||
import java.lang.reflect.Modifier;
|
||||
@@ -15,6 +12,7 @@ import java.util.Vector;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
import com.mojang.datafixers.util.Pair;
|
||||
import com.volmit.iris.core.nms.IMemoryWorld;
|
||||
import com.volmit.iris.core.nms.container.BiomeColor;
|
||||
import com.volmit.iris.util.scheduling.J;
|
||||
import net.minecraft.nbt.*;
|
||||
@@ -619,6 +617,11 @@ public class NMSBinding implements INMSBinding {
|
||||
return keys;
|
||||
}
|
||||
|
||||
@Override
|
||||
public IMemoryWorld createMemoryWorld(NamespacedKey levelType, WorldCreator creator) throws IOException {
|
||||
return new MemoryWorld(levelType, creator);
|
||||
}
|
||||
|
||||
private static Field getField(Class<?> clazz, Class<?> fieldType) throws NoSuchFieldException {
|
||||
try {
|
||||
for (Field f : clazz.getDeclaredFields()) {
|
||||
|
||||
@@ -0,0 +1,396 @@
|
||||
package com.volmit.iris.core.nms.v1_20_R1;
|
||||
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.mojang.serialization.Lifecycle;
|
||||
import com.volmit.iris.Iris;
|
||||
import com.volmit.iris.core.nms.IMemoryWorld;
|
||||
import com.volmit.iris.util.reflect.Reflect;
|
||||
import com.volmit.iris.util.scheduling.J;
|
||||
import net.minecraft.core.BlockPos;
|
||||
import net.minecraft.core.RegistryAccess;
|
||||
import net.minecraft.core.registries.Registries;
|
||||
import net.minecraft.resources.ResourceKey;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
import net.minecraft.server.MinecraftServer;
|
||||
import net.minecraft.server.dedicated.DedicatedServerProperties;
|
||||
import net.minecraft.server.level.ServerLevel;
|
||||
import net.minecraft.util.GsonHelper;
|
||||
import net.minecraft.world.Difficulty;
|
||||
import net.minecraft.world.entity.ai.village.VillageSiege;
|
||||
import net.minecraft.world.entity.npc.CatSpawner;
|
||||
import net.minecraft.world.entity.npc.WanderingTraderSpawner;
|
||||
import net.minecraft.world.level.ChunkPos;
|
||||
import net.minecraft.world.level.GameRules;
|
||||
import net.minecraft.world.level.GameType;
|
||||
import net.minecraft.world.level.LevelSettings;
|
||||
import net.minecraft.world.level.biome.BiomeManager;
|
||||
import net.minecraft.world.level.block.Blocks;
|
||||
import net.minecraft.world.level.block.EntityBlock;
|
||||
import net.minecraft.world.level.block.entity.BlockEntity;
|
||||
import net.minecraft.world.level.block.state.BlockState;
|
||||
import net.minecraft.world.level.chunk.ChunkAccess;
|
||||
import net.minecraft.world.level.chunk.LevelChunk;
|
||||
import net.minecraft.world.level.dimension.DimensionType;
|
||||
import net.minecraft.world.level.dimension.LevelStem;
|
||||
import net.minecraft.world.level.levelgen.PatrolSpawner;
|
||||
import net.minecraft.world.level.levelgen.PhantomSpawner;
|
||||
import net.minecraft.world.level.levelgen.WorldOptions;
|
||||
import net.minecraft.world.level.storage.LevelStorageSource;
|
||||
import net.minecraft.world.level.storage.PrimaryLevelData;
|
||||
import net.minecraft.world.level.storage.ServerLevelData;
|
||||
import org.bukkit.*;
|
||||
import org.bukkit.block.Biome;
|
||||
import org.bukkit.block.data.BlockData;
|
||||
import org.bukkit.craftbukkit.v1_20_R1.CraftChunk;
|
||||
import org.bukkit.craftbukkit.v1_20_R1.CraftServer;
|
||||
import org.bukkit.craftbukkit.v1_20_R1.attribute.CraftAttributeInstance;
|
||||
import org.bukkit.craftbukkit.v1_20_R1.block.CraftBlock;
|
||||
import org.bukkit.craftbukkit.v1_20_R1.block.data.CraftBlockData;
|
||||
import org.bukkit.craftbukkit.v1_20_R1.generator.CraftWorldInfo;
|
||||
import org.bukkit.craftbukkit.v1_20_R1.util.CraftMagicNumbers;
|
||||
import org.bukkit.event.EventHandler;
|
||||
import org.bukkit.event.EventPriority;
|
||||
import org.bukkit.event.HandlerList;
|
||||
import org.bukkit.event.world.WorldUnloadEvent;
|
||||
import org.bukkit.generator.ChunkGenerator;
|
||||
import org.bukkit.material.MaterialData;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.lang.ref.WeakReference;
|
||||
import java.lang.reflect.Constructor;
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.nio.file.Files;
|
||||
import java.util.Arrays;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.atomic.AtomicLong;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
public class MemoryWorld implements IMemoryWorld {
|
||||
private static final AtomicLong C = new AtomicLong();
|
||||
private static final Field WORLDS_FIELD;
|
||||
|
||||
private final AtomicReference<ServerLevel> level = new AtomicReference<>();
|
||||
|
||||
public MemoryWorld(NamespacedKey levelType, WorldCreator creator) throws IOException {
|
||||
var name = "memory_world"+C.getAndIncrement();
|
||||
while (Bukkit.getWorld(name) != null) {
|
||||
name = "memory_world"+C.getAndIncrement();
|
||||
}
|
||||
|
||||
var generator = creator.generator();
|
||||
var biomeProvider = creator.biomeProvider();
|
||||
var hardcore = creator.hardcore();
|
||||
var server = getServer();
|
||||
|
||||
var tempDir = Files.createTempDirectory("MemoryGenerator");
|
||||
LevelStorageSource source = LevelStorageSource.createDefault(tempDir);
|
||||
ResourceKey<LevelStem> stemKey = ResourceKey.create(Registries.LEVEL_STEM, new ResourceLocation(levelType.getNamespace(), levelType.getKey()));
|
||||
var access = source.createAccess(name, stemKey);
|
||||
|
||||
var worldLoader = server.worldLoader;
|
||||
var registry = server.registryAccess().registryOrThrow(Registries.LEVEL_STEM);
|
||||
var options = new WorldOptions(creator.seed(), creator.generateStructures(), false);
|
||||
var properties = new DedicatedServerProperties.WorldDimensionData(GsonHelper.parse(creator.generatorSettings().isEmpty() ? "{}" : creator.generatorSettings()), creator.type().name().toLowerCase(Locale.ROOT));
|
||||
var settings = new LevelSettings(name, GameType.byId(Bukkit.getDefaultGameMode().getValue()), hardcore, Difficulty.EASY, false, new GameRules(), worldLoader.dataConfiguration());
|
||||
var dimension = properties.create(worldLoader.datapackWorldgen()).bake(registry);
|
||||
Lifecycle lifecycle = dimension.lifecycle().add(worldLoader.datapackWorldgen().allRegistriesLifecycle());
|
||||
|
||||
var worldData = new PrimaryLevelData(settings, options, dimension.specialWorldProperty(), lifecycle);
|
||||
worldData.customDimensions = registry;
|
||||
|
||||
long obfSeed = BiomeManager.obfuscateSeed(creator.seed());
|
||||
var list = ImmutableList.of(new PhantomSpawner(), new PatrolSpawner(), new CatSpawner(), new VillageSiege(), new WanderingTraderSpawner(worldData));
|
||||
var levelStem = registry.get(stemKey);
|
||||
if (levelStem == null)
|
||||
throw new IllegalStateException("Unknown dimension type: " + stemKey);
|
||||
|
||||
CraftWorldInfo worldInfo;
|
||||
try {
|
||||
worldInfo = new CraftWorldInfo(worldData, access, creator.environment(), levelStem.type().value());
|
||||
} catch (Throwable e) {
|
||||
try {
|
||||
worldInfo = Reflect.newInstance(CraftWorldInfo.class, worldData, access, creator.environment(), levelStem.type().value(), levelStem.generator(), server.registryAccess());
|
||||
} catch (NoSuchMethodException | InvocationTargetException ex) {
|
||||
throw new IOException("Failed to create CraftWorldInfo", ex);
|
||||
}
|
||||
}
|
||||
if (biomeProvider == null && generator != null) {
|
||||
biomeProvider = generator.getDefaultBiomeProvider(worldInfo);
|
||||
}
|
||||
|
||||
var levelKey = ResourceKey.create(Registries.DIMENSION, new ResourceLocation(name));
|
||||
var level = new ServerLevel(
|
||||
server,
|
||||
server.executor,
|
||||
access,
|
||||
worldData,
|
||||
levelKey,
|
||||
levelStem,
|
||||
server.progressListenerFactory.create(0),
|
||||
worldData.isDebugWorld(),
|
||||
obfSeed,
|
||||
creator.environment() == World.Environment.NORMAL ? list : ImmutableList.of(),
|
||||
true,
|
||||
server.overworld().getRandomSequences(),
|
||||
creator.environment(),
|
||||
generator,
|
||||
biomeProvider
|
||||
);
|
||||
level.keepSpawnInMemory = false;
|
||||
Iris.instance.registerListener(this);
|
||||
this.level.set(level);
|
||||
}
|
||||
|
||||
public World getBukkit() {
|
||||
var level = this.level.get();
|
||||
if (level == null)
|
||||
throw new IllegalStateException("World is not loaded");
|
||||
|
||||
return level.getWorld();
|
||||
}
|
||||
|
||||
public Chunk getChunk(int x, int z) {
|
||||
var level = this.level.get();
|
||||
if (level == null)
|
||||
throw new IllegalStateException("World is not loaded");
|
||||
return new CraftChunk(level, x, z);
|
||||
}
|
||||
|
||||
public ChunkGenerator.ChunkData getChunkData(int x, int z) {
|
||||
var level = this.level.get();
|
||||
if (level == null)
|
||||
throw new IllegalStateException("World is not loaded");
|
||||
return new MemoryChunkData(x, z);
|
||||
}
|
||||
|
||||
@EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
|
||||
public void onWorldUnload(WorldUnloadEvent event) {
|
||||
var level = this.level.get();
|
||||
if (level == null || event.getWorld() != level.getWorld())
|
||||
return;
|
||||
|
||||
this.level.set(null);
|
||||
}
|
||||
|
||||
public boolean isClosed() {
|
||||
return this.level.get() == null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() throws Exception {
|
||||
if (!Bukkit.isPrimaryThread()) {
|
||||
var future = new CompletableFuture<Void>();
|
||||
J.s(() -> {
|
||||
try {
|
||||
close();
|
||||
future.complete(null);
|
||||
} catch (Exception e) {
|
||||
future.completeExceptionally(e);
|
||||
}
|
||||
});
|
||||
future.join();
|
||||
return;
|
||||
}
|
||||
|
||||
var level = this.level.get();
|
||||
if (level == null || !this.level.compareAndSet(level, null))
|
||||
return;
|
||||
|
||||
level.close();
|
||||
level.convertable.deleteLevel();
|
||||
level.convertable.close();
|
||||
|
||||
var map = (Map<String, World>) WORLDS_FIELD.get(Bukkit.getServer());
|
||||
map.remove(level.dimension().location().getPath());
|
||||
getServer().removeLevel(level);
|
||||
HandlerList.unregisterAll(this);
|
||||
}
|
||||
|
||||
private static MinecraftServer getServer() {
|
||||
return ((CraftServer) Bukkit.getServer()).getServer();
|
||||
}
|
||||
|
||||
static {
|
||||
try {
|
||||
WORLDS_FIELD = CraftServer.class.getDeclaredField("worlds");
|
||||
WORLDS_FIELD.setAccessible(true);
|
||||
} catch (NoSuchFieldException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
private class MemoryChunkData implements ChunkGenerator.ChunkData {
|
||||
private final int maxHeight;
|
||||
private final int minHeight;
|
||||
private final ChunkPos pos;
|
||||
private WeakReference<LevelChunk> chunk;
|
||||
|
||||
private MemoryChunkData(int x, int z) {
|
||||
var level = MemoryWorld.this.level.get();
|
||||
if (level == null)
|
||||
throw new IllegalStateException("World is not loaded");
|
||||
|
||||
var chunk = level.getChunk(x, z);
|
||||
this.minHeight = chunk.getMinBuildHeight();
|
||||
this.maxHeight = chunk.getMaxBuildHeight();
|
||||
this.pos = new ChunkPos(x, z);
|
||||
this.chunk = new WeakReference<>(chunk);
|
||||
}
|
||||
|
||||
public LevelChunk getHandle() {
|
||||
LevelChunk chunk = this.chunk.get();
|
||||
if (chunk == null) {
|
||||
var level = MemoryWorld.this.level.get();
|
||||
if (level == null)
|
||||
throw new IllegalStateException("World is not loaded");
|
||||
|
||||
chunk = level.getChunk(this.pos.x, this.pos.z);
|
||||
this.chunk = new WeakReference<>(chunk);
|
||||
}
|
||||
return chunk;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getMinHeight() {
|
||||
return minHeight;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getMaxHeight() {
|
||||
return maxHeight;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public Biome getBiome(int x, int y, int z) {
|
||||
return CraftBlock.biomeBaseToBiome(this.getHandle().biomeRegistry, this.getHandle().getNoiseBiome(x >> 2, y >> 2, z >> 2));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setBlock(int x, int y, int z, @NotNull Material material) {
|
||||
setBlock(x, y, z, material.createBlockData());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setBlock(int x, int y, int z, @NotNull MaterialData material) {
|
||||
setBlock(x, y, z, CraftMagicNumbers.getBlock(material));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setBlock(int x, int y, int z, @NotNull BlockData blockData) {
|
||||
setBlock(x, y, z, ((CraftBlockData) blockData).getState());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setRegion(int xMin, int yMin, int zMin, int xMax, int yMax, int zMax, Material material) {
|
||||
this.setRegion(xMin, yMin, zMin, xMax, yMax, zMax, material.createBlockData());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setRegion(int xMin, int yMin, int zMin, int xMax, int yMax, int zMax, MaterialData material) {
|
||||
this.setRegion(xMin, yMin, zMin, xMax, yMax, zMax, CraftMagicNumbers.getBlock(material));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setRegion(int xMin, int yMin, int zMin, int xMax, int yMax, int zMax, BlockData blockData) {
|
||||
this.setRegion(xMin, yMin, zMin, xMax, yMax, zMax, ((CraftBlockData)blockData).getState());
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public Material getType(int x, int y, int z) {
|
||||
return CraftMagicNumbers.getMaterial(this.getTypeId(x, y, z).getBlock());
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public MaterialData getTypeAndData(int x, int y, int z) {
|
||||
return CraftMagicNumbers.getMaterial(this.getTypeId(x, y, z));
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public BlockData getBlockData(int x, int y, int z) {
|
||||
return CraftBlockData.fromData(this.getTypeId(x, y, z));
|
||||
}
|
||||
|
||||
@Deprecated
|
||||
@Override
|
||||
public byte getData(int x, int y, int z) {
|
||||
return CraftMagicNumbers.toLegacyData(this.getTypeId(x, y, z));
|
||||
}
|
||||
|
||||
private void setRegion(int xMin, int yMin, int zMin, int xMax, int yMax, int zMax, BlockState type) {
|
||||
if (xMin <= 15 && yMin < this.maxHeight && zMin <= 15) {
|
||||
if (xMin < 0) {
|
||||
xMin = 0;
|
||||
}
|
||||
|
||||
if (yMin < this.minHeight) {
|
||||
yMin = this.minHeight;
|
||||
}
|
||||
|
||||
if (zMin < 0) {
|
||||
zMin = 0;
|
||||
}
|
||||
|
||||
if (xMax > 16) {
|
||||
xMax = 16;
|
||||
}
|
||||
|
||||
if (yMax > this.maxHeight) {
|
||||
yMax = this.maxHeight;
|
||||
}
|
||||
|
||||
if (zMax > 16) {
|
||||
zMax = 16;
|
||||
}
|
||||
|
||||
if (xMin < xMax && yMin < yMax && zMin < zMax) {
|
||||
for(int y = yMin; y < yMax; ++y) {
|
||||
for(int x = xMin; x < xMax; ++x) {
|
||||
for(int z = zMin; z < zMax; ++z) {
|
||||
this.setBlock(x, y, z, type);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private BlockState getTypeId(int x, int y, int z) {
|
||||
if (x == (x & 15) && y >= this.minHeight && y < this.maxHeight && z == (z & 15)) {
|
||||
ChunkAccess access = this.getHandle();
|
||||
return access.getBlockState(new BlockPos(access.getPos().getMinBlockX() + x, y, access.getPos().getMinBlockZ() + z));
|
||||
} else {
|
||||
return Blocks.AIR.defaultBlockState();
|
||||
}
|
||||
}
|
||||
|
||||
private void setBlock(int x, int y, int z, BlockState type) {
|
||||
if (x == (x & 15) && y >= this.minHeight && y < this.maxHeight && z == (z & 15)) {
|
||||
ChunkAccess access = this.getHandle();
|
||||
BlockPos blockPosition = new BlockPos(access.getPos().getMinBlockX() + x, y, access.getPos().getMinBlockZ() + z);
|
||||
BlockState oldBlockData = access.setBlockState(blockPosition, type, false);
|
||||
if (type.hasBlockEntity()) {
|
||||
BlockEntity tileEntity = ((EntityBlock)type.getBlock()).newBlockEntity(blockPosition, type);
|
||||
if (tileEntity == null) {
|
||||
access.removeBlockEntity(blockPosition);
|
||||
} else {
|
||||
access.setBlockEntity(tileEntity);
|
||||
}
|
||||
} else if (oldBlockData != null && oldBlockData.hasBlockEntity()) {
|
||||
access.removeBlockEntity(blockPosition);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3,6 +3,7 @@ package com.volmit.iris.core.nms.v1_20_R1;
|
||||
import com.mojang.brigadier.exceptions.CommandSyntaxException;
|
||||
import com.mojang.datafixers.util.Pair;
|
||||
import com.volmit.iris.Iris;
|
||||
import com.volmit.iris.core.nms.IMemoryWorld;
|
||||
import com.volmit.iris.core.nms.INMSBinding;
|
||||
import com.volmit.iris.core.nms.container.BiomeColor;
|
||||
import com.volmit.iris.engine.data.cache.AtomicCache;
|
||||
@@ -66,10 +67,7 @@ import org.jetbrains.annotations.NotNull;
|
||||
import sun.misc.Unsafe;
|
||||
|
||||
import java.awt.Color;
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.DataInputStream;
|
||||
import java.io.DataOutputStream;
|
||||
import java.io.*;
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.Method;
|
||||
import java.lang.reflect.Modifier;
|
||||
@@ -612,6 +610,11 @@ public class NMSBinding implements INMSBinding {
|
||||
return keys;
|
||||
}
|
||||
|
||||
@Override
|
||||
public IMemoryWorld createMemoryWorld(NamespacedKey levelType, WorldCreator creator) throws IOException {
|
||||
return new MemoryWorld(levelType, creator);
|
||||
}
|
||||
|
||||
private static Field getField(Class<?> clazz, Class<?> fieldType) throws NoSuchFieldException {
|
||||
try {
|
||||
for (Field f : clazz.getDeclaredFields()) {
|
||||
|
||||
@@ -0,0 +1,395 @@
|
||||
package com.volmit.iris.core.nms.v1_20_R2;
|
||||
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.mojang.serialization.Lifecycle;
|
||||
import com.volmit.iris.Iris;
|
||||
import com.volmit.iris.core.nms.IMemoryWorld;
|
||||
import com.volmit.iris.util.reflect.Reflect;
|
||||
import com.volmit.iris.util.scheduling.J;
|
||||
import net.minecraft.core.BlockPos;
|
||||
import net.minecraft.core.RegistryAccess;
|
||||
import net.minecraft.core.registries.Registries;
|
||||
import net.minecraft.resources.ResourceKey;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
import net.minecraft.server.MinecraftServer;
|
||||
import net.minecraft.server.dedicated.DedicatedServerProperties;
|
||||
import net.minecraft.server.level.ServerLevel;
|
||||
import net.minecraft.util.GsonHelper;
|
||||
import net.minecraft.world.Difficulty;
|
||||
import net.minecraft.world.entity.ai.village.VillageSiege;
|
||||
import net.minecraft.world.entity.npc.CatSpawner;
|
||||
import net.minecraft.world.entity.npc.WanderingTraderSpawner;
|
||||
import net.minecraft.world.level.ChunkPos;
|
||||
import net.minecraft.world.level.GameRules;
|
||||
import net.minecraft.world.level.GameType;
|
||||
import net.minecraft.world.level.LevelSettings;
|
||||
import net.minecraft.world.level.biome.BiomeManager;
|
||||
import net.minecraft.world.level.block.Blocks;
|
||||
import net.minecraft.world.level.block.EntityBlock;
|
||||
import net.minecraft.world.level.block.entity.BlockEntity;
|
||||
import net.minecraft.world.level.block.state.BlockState;
|
||||
import net.minecraft.world.level.chunk.ChunkAccess;
|
||||
import net.minecraft.world.level.chunk.LevelChunk;
|
||||
import net.minecraft.world.level.dimension.DimensionType;
|
||||
import net.minecraft.world.level.dimension.LevelStem;
|
||||
import net.minecraft.world.level.levelgen.PatrolSpawner;
|
||||
import net.minecraft.world.level.levelgen.PhantomSpawner;
|
||||
import net.minecraft.world.level.levelgen.WorldOptions;
|
||||
import net.minecraft.world.level.storage.LevelStorageSource;
|
||||
import net.minecraft.world.level.storage.PrimaryLevelData;
|
||||
import net.minecraft.world.level.storage.ServerLevelData;
|
||||
import org.bukkit.*;
|
||||
import org.bukkit.block.Biome;
|
||||
import org.bukkit.block.data.BlockData;
|
||||
import org.bukkit.craftbukkit.v1_20_R2.CraftChunk;
|
||||
import org.bukkit.craftbukkit.v1_20_R2.CraftServer;
|
||||
import org.bukkit.craftbukkit.v1_20_R2.block.CraftBiome;
|
||||
import org.bukkit.craftbukkit.v1_20_R2.block.data.CraftBlockData;
|
||||
import org.bukkit.craftbukkit.v1_20_R2.generator.CraftWorldInfo;
|
||||
import org.bukkit.craftbukkit.v1_20_R2.util.CraftMagicNumbers;
|
||||
import org.bukkit.event.EventHandler;
|
||||
import org.bukkit.event.EventPriority;
|
||||
import org.bukkit.event.HandlerList;
|
||||
import org.bukkit.event.world.WorldUnloadEvent;
|
||||
import org.bukkit.generator.ChunkGenerator;
|
||||
import org.bukkit.material.MaterialData;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.lang.ref.WeakReference;
|
||||
import java.lang.reflect.Constructor;
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.nio.file.Files;
|
||||
import java.util.Arrays;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.atomic.AtomicLong;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
public class MemoryWorld implements IMemoryWorld {
|
||||
private static final AtomicLong C = new AtomicLong();
|
||||
private static final Field WORLDS_FIELD;
|
||||
|
||||
private final AtomicReference<ServerLevel> level = new AtomicReference<>();
|
||||
|
||||
public MemoryWorld(NamespacedKey levelType, WorldCreator creator) throws IOException {
|
||||
var name = "memory_world"+C.getAndIncrement();
|
||||
while (Bukkit.getWorld(name) != null) {
|
||||
name = "memory_world"+C.getAndIncrement();
|
||||
}
|
||||
|
||||
var generator = creator.generator();
|
||||
var biomeProvider = creator.biomeProvider();
|
||||
var hardcore = creator.hardcore();
|
||||
var server = getServer();
|
||||
|
||||
var tempDir = Files.createTempDirectory("MemoryGenerator");
|
||||
LevelStorageSource source = LevelStorageSource.createDefault(tempDir);
|
||||
ResourceKey<LevelStem> stemKey = ResourceKey.create(Registries.LEVEL_STEM, new ResourceLocation(levelType.getNamespace(), levelType.getKey()));
|
||||
var access = source.createAccess(name, stemKey);
|
||||
|
||||
var worldLoader = server.worldLoader;
|
||||
var registry = server.registryAccess().registryOrThrow(Registries.LEVEL_STEM);
|
||||
var options = new WorldOptions(creator.seed(), creator.generateStructures(), false);
|
||||
var properties = new DedicatedServerProperties.WorldDimensionData(GsonHelper.parse(creator.generatorSettings().isEmpty() ? "{}" : creator.generatorSettings()), creator.type().name().toLowerCase(Locale.ROOT));
|
||||
var settings = new LevelSettings(name, GameType.byId(Bukkit.getDefaultGameMode().getValue()), hardcore, Difficulty.EASY, false, new GameRules(), worldLoader.dataConfiguration());
|
||||
var dimension = properties.create(worldLoader.datapackWorldgen()).bake(registry);
|
||||
Lifecycle lifecycle = dimension.lifecycle().add(worldLoader.datapackWorldgen().allRegistriesLifecycle());
|
||||
|
||||
var worldData = new PrimaryLevelData(settings, options, dimension.specialWorldProperty(), lifecycle);
|
||||
worldData.customDimensions = registry;
|
||||
|
||||
long obfSeed = BiomeManager.obfuscateSeed(creator.seed());
|
||||
var list = ImmutableList.of(new PhantomSpawner(), new PatrolSpawner(), new CatSpawner(), new VillageSiege(), new WanderingTraderSpawner(worldData));
|
||||
var levelStem = registry.get(stemKey);
|
||||
if (levelStem == null)
|
||||
throw new IllegalStateException("Unknown dimension type: " + stemKey);
|
||||
|
||||
CraftWorldInfo worldInfo;
|
||||
try {
|
||||
worldInfo = new CraftWorldInfo(worldData, access, creator.environment(), levelStem.type().value());
|
||||
} catch (Throwable e) {
|
||||
try {
|
||||
worldInfo = Reflect.newInstance(CraftWorldInfo.class, worldData, access, creator.environment(), levelStem.type().value(), levelStem.generator(), server.registryAccess());
|
||||
} catch (NoSuchMethodException | InvocationTargetException ex) {
|
||||
throw new IOException("Failed to create CraftWorldInfo", ex);
|
||||
}
|
||||
}
|
||||
if (biomeProvider == null && generator != null) {
|
||||
biomeProvider = generator.getDefaultBiomeProvider(worldInfo);
|
||||
}
|
||||
|
||||
var levelKey = ResourceKey.create(Registries.DIMENSION, new ResourceLocation(name));
|
||||
var level = new ServerLevel(
|
||||
server,
|
||||
server.executor,
|
||||
access,
|
||||
worldData,
|
||||
levelKey,
|
||||
levelStem,
|
||||
server.progressListenerFactory.create(0),
|
||||
worldData.isDebugWorld(),
|
||||
obfSeed,
|
||||
creator.environment() == World.Environment.NORMAL ? list : ImmutableList.of(),
|
||||
true,
|
||||
server.overworld().getRandomSequences(),
|
||||
creator.environment(),
|
||||
generator,
|
||||
biomeProvider
|
||||
);
|
||||
level.keepSpawnInMemory = false;
|
||||
Iris.instance.registerListener(this);
|
||||
this.level.set(level);
|
||||
}
|
||||
|
||||
public World getBukkit() {
|
||||
var level = this.level.get();
|
||||
if (level == null)
|
||||
throw new IllegalStateException("World is not loaded");
|
||||
|
||||
return level.getWorld();
|
||||
}
|
||||
|
||||
public Chunk getChunk(int x, int z) {
|
||||
var level = this.level.get();
|
||||
if (level == null)
|
||||
throw new IllegalStateException("World is not loaded");
|
||||
return new CraftChunk(level, x, z);
|
||||
}
|
||||
|
||||
public ChunkGenerator.ChunkData getChunkData(int x, int z) {
|
||||
var level = this.level.get();
|
||||
if (level == null)
|
||||
throw new IllegalStateException("World is not loaded");
|
||||
return new MemoryChunkData(x, z);
|
||||
}
|
||||
|
||||
@EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
|
||||
public void onWorldUnload(WorldUnloadEvent event) {
|
||||
var level = this.level.get();
|
||||
if (level == null || event.getWorld() != level.getWorld())
|
||||
return;
|
||||
|
||||
this.level.set(null);
|
||||
}
|
||||
|
||||
public boolean isClosed() {
|
||||
return this.level.get() == null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() throws Exception {
|
||||
if (!Bukkit.isPrimaryThread()) {
|
||||
var future = new CompletableFuture<Void>();
|
||||
J.s(() -> {
|
||||
try {
|
||||
close();
|
||||
future.complete(null);
|
||||
} catch (Exception e) {
|
||||
future.completeExceptionally(e);
|
||||
}
|
||||
});
|
||||
future.join();
|
||||
return;
|
||||
}
|
||||
|
||||
var level = this.level.get();
|
||||
if (level == null || !this.level.compareAndSet(level, null))
|
||||
return;
|
||||
|
||||
level.close();
|
||||
level.convertable.deleteLevel();
|
||||
level.convertable.close();
|
||||
|
||||
var map = (Map<String, World>) WORLDS_FIELD.get(Bukkit.getServer());
|
||||
map.remove(level.dimension().location().getPath());
|
||||
getServer().removeLevel(level);
|
||||
HandlerList.unregisterAll(this);
|
||||
}
|
||||
|
||||
private static MinecraftServer getServer() {
|
||||
return ((CraftServer) Bukkit.getServer()).getServer();
|
||||
}
|
||||
|
||||
static {
|
||||
try {
|
||||
WORLDS_FIELD = CraftServer.class.getDeclaredField("worlds");
|
||||
WORLDS_FIELD.setAccessible(true);
|
||||
} catch (NoSuchFieldException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
private class MemoryChunkData implements ChunkGenerator.ChunkData {
|
||||
private final int maxHeight;
|
||||
private final int minHeight;
|
||||
private final ChunkPos pos;
|
||||
private WeakReference<LevelChunk> chunk;
|
||||
|
||||
private MemoryChunkData(int x, int z) {
|
||||
var level = MemoryWorld.this.level.get();
|
||||
if (level == null)
|
||||
throw new IllegalStateException("World is not loaded");
|
||||
|
||||
var chunk = level.getChunk(x, z);
|
||||
this.minHeight = chunk.getMinBuildHeight();
|
||||
this.maxHeight = chunk.getMaxBuildHeight();
|
||||
this.pos = new ChunkPos(x, z);
|
||||
this.chunk = new WeakReference<>(chunk);
|
||||
}
|
||||
|
||||
public LevelChunk getHandle() {
|
||||
LevelChunk chunk = this.chunk.get();
|
||||
if (chunk == null) {
|
||||
var level = MemoryWorld.this.level.get();
|
||||
if (level == null)
|
||||
throw new IllegalStateException("World is not loaded");
|
||||
|
||||
chunk = level.getChunk(this.pos.x, this.pos.z);
|
||||
this.chunk = new WeakReference<>(chunk);
|
||||
}
|
||||
return chunk;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getMinHeight() {
|
||||
return minHeight;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getMaxHeight() {
|
||||
return maxHeight;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public Biome getBiome(int x, int y, int z) {
|
||||
return CraftBiome.minecraftHolderToBukkit(getHandle().getNoiseBiome(x >> 2, y >> 2, z >> 2));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setBlock(int x, int y, int z, @NotNull Material material) {
|
||||
setBlock(x, y, z, material.createBlockData());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setBlock(int x, int y, int z, @NotNull MaterialData material) {
|
||||
setBlock(x, y, z, CraftMagicNumbers.getBlock(material));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setBlock(int x, int y, int z, @NotNull BlockData blockData) {
|
||||
setBlock(x, y, z, ((CraftBlockData) blockData).getState());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setRegion(int xMin, int yMin, int zMin, int xMax, int yMax, int zMax, Material material) {
|
||||
this.setRegion(xMin, yMin, zMin, xMax, yMax, zMax, material.createBlockData());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setRegion(int xMin, int yMin, int zMin, int xMax, int yMax, int zMax, MaterialData material) {
|
||||
this.setRegion(xMin, yMin, zMin, xMax, yMax, zMax, CraftMagicNumbers.getBlock(material));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setRegion(int xMin, int yMin, int zMin, int xMax, int yMax, int zMax, BlockData blockData) {
|
||||
this.setRegion(xMin, yMin, zMin, xMax, yMax, zMax, ((CraftBlockData)blockData).getState());
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public Material getType(int x, int y, int z) {
|
||||
return CraftMagicNumbers.getMaterial(this.getTypeId(x, y, z).getBlock());
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public MaterialData getTypeAndData(int x, int y, int z) {
|
||||
return CraftMagicNumbers.getMaterial(this.getTypeId(x, y, z));
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public BlockData getBlockData(int x, int y, int z) {
|
||||
return CraftBlockData.fromData(this.getTypeId(x, y, z));
|
||||
}
|
||||
|
||||
@Deprecated
|
||||
@Override
|
||||
public byte getData(int x, int y, int z) {
|
||||
return CraftMagicNumbers.toLegacyData(this.getTypeId(x, y, z));
|
||||
}
|
||||
|
||||
private void setRegion(int xMin, int yMin, int zMin, int xMax, int yMax, int zMax, BlockState type) {
|
||||
if (xMin <= 15 && yMin < this.maxHeight && zMin <= 15) {
|
||||
if (xMin < 0) {
|
||||
xMin = 0;
|
||||
}
|
||||
|
||||
if (yMin < this.minHeight) {
|
||||
yMin = this.minHeight;
|
||||
}
|
||||
|
||||
if (zMin < 0) {
|
||||
zMin = 0;
|
||||
}
|
||||
|
||||
if (xMax > 16) {
|
||||
xMax = 16;
|
||||
}
|
||||
|
||||
if (yMax > this.maxHeight) {
|
||||
yMax = this.maxHeight;
|
||||
}
|
||||
|
||||
if (zMax > 16) {
|
||||
zMax = 16;
|
||||
}
|
||||
|
||||
if (xMin < xMax && yMin < yMax && zMin < zMax) {
|
||||
for(int y = yMin; y < yMax; ++y) {
|
||||
for(int x = xMin; x < xMax; ++x) {
|
||||
for(int z = zMin; z < zMax; ++z) {
|
||||
this.setBlock(x, y, z, type);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private BlockState getTypeId(int x, int y, int z) {
|
||||
if (x == (x & 15) && y >= this.minHeight && y < this.maxHeight && z == (z & 15)) {
|
||||
ChunkAccess access = this.getHandle();
|
||||
return access.getBlockState(new BlockPos(access.getPos().getMinBlockX() + x, y, access.getPos().getMinBlockZ() + z));
|
||||
} else {
|
||||
return Blocks.AIR.defaultBlockState();
|
||||
}
|
||||
}
|
||||
|
||||
private void setBlock(int x, int y, int z, BlockState type) {
|
||||
if (x == (x & 15) && y >= this.minHeight && y < this.maxHeight && z == (z & 15)) {
|
||||
ChunkAccess access = this.getHandle();
|
||||
BlockPos blockPosition = new BlockPos(access.getPos().getMinBlockX() + x, y, access.getPos().getMinBlockZ() + z);
|
||||
BlockState oldBlockData = access.setBlockState(blockPosition, type, false);
|
||||
if (type.hasBlockEntity()) {
|
||||
BlockEntity tileEntity = ((EntityBlock)type.getBlock()).newBlockEntity(blockPosition, type);
|
||||
if (tileEntity == null) {
|
||||
access.removeBlockEntity(blockPosition);
|
||||
} else {
|
||||
access.setBlockEntity(tileEntity);
|
||||
}
|
||||
} else if (oldBlockData != null && oldBlockData.hasBlockEntity()) {
|
||||
access.removeBlockEntity(blockPosition);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,10 +1,7 @@
|
||||
package com.volmit.iris.core.nms.v1_20_R2;
|
||||
|
||||
import java.awt.Color;
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.DataInputStream;
|
||||
import java.io.DataOutputStream;
|
||||
import java.io.*;
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.Method;
|
||||
import java.lang.reflect.Modifier;
|
||||
@@ -15,6 +12,7 @@ import java.util.Vector;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
import com.mojang.datafixers.util.Pair;
|
||||
import com.volmit.iris.core.nms.IMemoryWorld;
|
||||
import com.volmit.iris.core.nms.container.BiomeColor;
|
||||
import com.volmit.iris.util.scheduling.J;
|
||||
import net.minecraft.nbt.*;
|
||||
@@ -610,6 +608,11 @@ public class NMSBinding implements INMSBinding {
|
||||
return keys;
|
||||
}
|
||||
|
||||
@Override
|
||||
public IMemoryWorld createMemoryWorld(NamespacedKey levelType, WorldCreator creator) throws IOException {
|
||||
return new MemoryWorld(levelType, creator);
|
||||
}
|
||||
|
||||
private static Field getField(Class<?> clazz, Class<?> fieldType) throws NoSuchFieldException {
|
||||
try {
|
||||
for (Field f : clazz.getDeclaredFields()) {
|
||||
|
||||
@@ -0,0 +1,400 @@
|
||||
package com.volmit.iris.core.nms.v1_20_R3;
|
||||
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.mojang.serialization.Lifecycle;
|
||||
import com.volmit.iris.Iris;
|
||||
import com.volmit.iris.core.nms.IMemoryWorld;
|
||||
import com.volmit.iris.util.reflect.Reflect;
|
||||
import com.volmit.iris.util.scheduling.J;
|
||||
import net.minecraft.core.BlockPos;
|
||||
import net.minecraft.core.RegistryAccess;
|
||||
import net.minecraft.core.registries.Registries;
|
||||
import net.minecraft.resources.ResourceKey;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
import net.minecraft.server.MinecraftServer;
|
||||
import net.minecraft.server.dedicated.DedicatedServerProperties;
|
||||
import net.minecraft.server.level.ServerLevel;
|
||||
import net.minecraft.util.GsonHelper;
|
||||
import net.minecraft.world.Difficulty;
|
||||
import net.minecraft.world.entity.ai.village.VillageSiege;
|
||||
import net.minecraft.world.entity.npc.CatSpawner;
|
||||
import net.minecraft.world.entity.npc.WanderingTraderSpawner;
|
||||
import net.minecraft.world.level.ChunkPos;
|
||||
import net.minecraft.world.level.GameRules;
|
||||
import net.minecraft.world.level.GameType;
|
||||
import net.minecraft.world.level.LevelSettings;
|
||||
import net.minecraft.world.level.biome.BiomeManager;
|
||||
import net.minecraft.world.level.block.Blocks;
|
||||
import net.minecraft.world.level.block.EntityBlock;
|
||||
import net.minecraft.world.level.block.entity.BlockEntity;
|
||||
import net.minecraft.world.level.block.state.BlockState;
|
||||
import net.minecraft.world.level.chunk.ChunkAccess;
|
||||
import net.minecraft.world.level.chunk.LevelChunk;
|
||||
import net.minecraft.world.level.dimension.DimensionType;
|
||||
import net.minecraft.world.level.dimension.LevelStem;
|
||||
import net.minecraft.world.level.levelgen.PatrolSpawner;
|
||||
import net.minecraft.world.level.levelgen.PhantomSpawner;
|
||||
import net.minecraft.world.level.levelgen.WorldOptions;
|
||||
import net.minecraft.world.level.storage.LevelStorageSource;
|
||||
import net.minecraft.world.level.storage.PrimaryLevelData;
|
||||
import net.minecraft.world.level.storage.ServerLevelData;
|
||||
import org.bukkit.*;
|
||||
import org.bukkit.block.Biome;
|
||||
import org.bukkit.block.data.BlockData;
|
||||
import org.bukkit.craftbukkit.v1_20_R3.CraftChunk;
|
||||
import org.bukkit.craftbukkit.v1_20_R3.CraftServer;
|
||||
import org.bukkit.craftbukkit.v1_20_R3.block.CraftBiome;
|
||||
import org.bukkit.craftbukkit.v1_20_R3.block.CraftBlockType;
|
||||
import org.bukkit.craftbukkit.v1_20_R3.block.data.CraftBlockData;
|
||||
import org.bukkit.craftbukkit.v1_20_R3.generator.CraftChunkData;
|
||||
import org.bukkit.craftbukkit.v1_20_R3.generator.CraftWorldInfo;
|
||||
import org.bukkit.craftbukkit.v1_20_R3.util.CraftMagicNumbers;
|
||||
import org.bukkit.event.EventHandler;
|
||||
import org.bukkit.event.EventPriority;
|
||||
import org.bukkit.event.HandlerList;
|
||||
import org.bukkit.event.Listener;
|
||||
import org.bukkit.event.world.WorldUnloadEvent;
|
||||
import org.bukkit.generator.ChunkGenerator;
|
||||
import org.bukkit.material.MaterialData;
|
||||
import org.bukkit.plugin.Plugin;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.lang.ref.WeakReference;
|
||||
import java.lang.reflect.Constructor;
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.nio.file.Files;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.atomic.AtomicLong;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
public class MemoryWorld implements IMemoryWorld {
|
||||
private static final AtomicLong C = new AtomicLong();
|
||||
private static final Field WORLDS_FIELD;
|
||||
|
||||
private final AtomicReference<ServerLevel> level = new AtomicReference<>();
|
||||
|
||||
public MemoryWorld(NamespacedKey levelType, WorldCreator creator) throws IOException {
|
||||
var name = "memory_world"+C.getAndIncrement();
|
||||
while (Bukkit.getWorld(name) != null) {
|
||||
name = "memory_world"+C.getAndIncrement();
|
||||
}
|
||||
|
||||
var generator = creator.generator();
|
||||
var biomeProvider = creator.biomeProvider();
|
||||
var hardcore = creator.hardcore();
|
||||
var server = getServer();
|
||||
|
||||
var tempDir = Files.createTempDirectory("MemoryGenerator");
|
||||
LevelStorageSource source = LevelStorageSource.createDefault(tempDir);
|
||||
ResourceKey<LevelStem> stemKey = ResourceKey.create(Registries.LEVEL_STEM, new ResourceLocation(levelType.getNamespace(), levelType.getKey()));
|
||||
var access = source.createAccess(name, stemKey);
|
||||
|
||||
var worldLoader = server.worldLoader;
|
||||
var registry = server.registryAccess().registryOrThrow(Registries.LEVEL_STEM);
|
||||
var options = new WorldOptions(creator.seed(), creator.generateStructures(), false);
|
||||
var properties = new DedicatedServerProperties.WorldDimensionData(GsonHelper.parse(creator.generatorSettings().isEmpty() ? "{}" : creator.generatorSettings()), creator.type().name().toLowerCase(Locale.ROOT));
|
||||
var settings = new LevelSettings(name, GameType.byId(Bukkit.getDefaultGameMode().getValue()), hardcore, Difficulty.EASY, false, new GameRules(), worldLoader.dataConfiguration());
|
||||
var dimension = properties.create(worldLoader.datapackWorldgen()).bake(registry);
|
||||
Lifecycle lifecycle = dimension.lifecycle().add(worldLoader.datapackWorldgen().allRegistriesLifecycle());
|
||||
|
||||
var worldData = new PrimaryLevelData(settings, options, dimension.specialWorldProperty(), lifecycle);
|
||||
worldData.customDimensions = registry;
|
||||
|
||||
long obfSeed = BiomeManager.obfuscateSeed(creator.seed());
|
||||
var list = ImmutableList.of(new PhantomSpawner(), new PatrolSpawner(), new CatSpawner(), new VillageSiege(), new WanderingTraderSpawner(worldData));
|
||||
var levelStem = registry.get(stemKey);
|
||||
if (levelStem == null)
|
||||
throw new IllegalStateException("Unknown dimension type: " + stemKey);
|
||||
|
||||
CraftWorldInfo worldInfo;
|
||||
try {
|
||||
worldInfo = new CraftWorldInfo(worldData, access, creator.environment(), levelStem.type().value());
|
||||
} catch (Throwable e) {
|
||||
try {
|
||||
worldInfo = Reflect.newInstance(CraftWorldInfo.class, worldData, access, creator.environment(), levelStem.type().value(), levelStem.generator(), server.registryAccess());
|
||||
} catch (NoSuchMethodException | InvocationTargetException ex) {
|
||||
throw new IOException("Failed to create CraftWorldInfo", ex);
|
||||
}
|
||||
}
|
||||
if (biomeProvider == null && generator != null) {
|
||||
biomeProvider = generator.getDefaultBiomeProvider(worldInfo);
|
||||
}
|
||||
|
||||
var levelKey = ResourceKey.create(Registries.DIMENSION, new ResourceLocation(name));
|
||||
var level = new ServerLevel(
|
||||
server,
|
||||
server.executor,
|
||||
access,
|
||||
worldData,
|
||||
levelKey,
|
||||
levelStem,
|
||||
server.progressListenerFactory.create(0),
|
||||
worldData.isDebugWorld(),
|
||||
obfSeed,
|
||||
creator.environment() == World.Environment.NORMAL ? list : ImmutableList.of(),
|
||||
true,
|
||||
server.overworld().getRandomSequences(),
|
||||
creator.environment(),
|
||||
generator,
|
||||
biomeProvider
|
||||
);
|
||||
level.keepSpawnInMemory = false;
|
||||
Iris.instance.registerListener(this);
|
||||
this.level.set(level);
|
||||
}
|
||||
|
||||
public World getBukkit() {
|
||||
var level = this.level.get();
|
||||
if (level == null)
|
||||
throw new IllegalStateException("World is not loaded");
|
||||
|
||||
return level.getWorld();
|
||||
}
|
||||
|
||||
public Chunk getChunk(int x, int z) {
|
||||
var level = this.level.get();
|
||||
if (level == null)
|
||||
throw new IllegalStateException("World is not loaded");
|
||||
return new CraftChunk(level, x, z);
|
||||
}
|
||||
|
||||
public ChunkGenerator.ChunkData getChunkData(int x, int z) {
|
||||
var level = this.level.get();
|
||||
if (level == null)
|
||||
throw new IllegalStateException("World is not loaded");
|
||||
return new MemoryChunkData(x, z);
|
||||
}
|
||||
|
||||
@EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
|
||||
public void onWorldUnload(WorldUnloadEvent event) {
|
||||
var level = this.level.get();
|
||||
if (level == null || event.getWorld() != level.getWorld())
|
||||
return;
|
||||
|
||||
this.level.set(null);
|
||||
}
|
||||
|
||||
public boolean isClosed() {
|
||||
return this.level.get() == null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() throws Exception {
|
||||
if (!Bukkit.isPrimaryThread()) {
|
||||
var future = new CompletableFuture<Void>();
|
||||
J.s(() -> {
|
||||
try {
|
||||
close();
|
||||
future.complete(null);
|
||||
} catch (Exception e) {
|
||||
future.completeExceptionally(e);
|
||||
}
|
||||
});
|
||||
future.join();
|
||||
return;
|
||||
}
|
||||
|
||||
var level = this.level.get();
|
||||
if (level == null || !this.level.compareAndSet(level, null))
|
||||
return;
|
||||
|
||||
level.close();
|
||||
level.convertable.deleteLevel();
|
||||
level.convertable.close();
|
||||
|
||||
var map = (Map<String, World>) WORLDS_FIELD.get(Bukkit.getServer());
|
||||
map.remove(level.dimension().location().getPath());
|
||||
getServer().removeLevel(level);
|
||||
HandlerList.unregisterAll(this);
|
||||
}
|
||||
|
||||
private static MinecraftServer getServer() {
|
||||
return ((CraftServer) Bukkit.getServer()).getServer();
|
||||
}
|
||||
|
||||
static {
|
||||
try {
|
||||
WORLDS_FIELD = CraftServer.class.getDeclaredField("worlds");
|
||||
WORLDS_FIELD.setAccessible(true);
|
||||
} catch (NoSuchFieldException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
private class MemoryChunkData implements ChunkGenerator.ChunkData {
|
||||
private final int maxHeight;
|
||||
private final int minHeight;
|
||||
private final ChunkPos pos;
|
||||
private WeakReference<LevelChunk> chunk;
|
||||
|
||||
private MemoryChunkData(int x, int z) {
|
||||
var level = MemoryWorld.this.level.get();
|
||||
if (level == null)
|
||||
throw new IllegalStateException("World is not loaded");
|
||||
|
||||
var chunk = level.getChunk(x, z);
|
||||
this.minHeight = chunk.getMinBuildHeight();
|
||||
this.maxHeight = chunk.getMaxBuildHeight();
|
||||
this.pos = new ChunkPos(x, z);
|
||||
this.chunk = new WeakReference<>(chunk);
|
||||
}
|
||||
|
||||
public LevelChunk getHandle() {
|
||||
LevelChunk chunk = this.chunk.get();
|
||||
if (chunk == null) {
|
||||
var level = MemoryWorld.this.level.get();
|
||||
if (level == null)
|
||||
throw new IllegalStateException("World is not loaded");
|
||||
|
||||
chunk = level.getChunk(this.pos.x, this.pos.z);
|
||||
this.chunk = new WeakReference<>(chunk);
|
||||
}
|
||||
return chunk;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getMinHeight() {
|
||||
return minHeight;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getMaxHeight() {
|
||||
return maxHeight;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public Biome getBiome(int x, int y, int z) {
|
||||
return CraftBiome.minecraftHolderToBukkit(getHandle().getNoiseBiome(x >> 2, y >> 2, z >> 2));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setBlock(int x, int y, int z, @NotNull Material material) {
|
||||
setBlock(x, y, z, material.createBlockData());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setBlock(int x, int y, int z, @NotNull MaterialData material) {
|
||||
setBlock(x, y, z, CraftMagicNumbers.getBlock(material));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setBlock(int x, int y, int z, @NotNull BlockData blockData) {
|
||||
setBlock(x, y, z, ((CraftBlockData) blockData).getState());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setRegion(int xMin, int yMin, int zMin, int xMax, int yMax, int zMax, Material material) {
|
||||
this.setRegion(xMin, yMin, zMin, xMax, yMax, zMax, material.createBlockData());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setRegion(int xMin, int yMin, int zMin, int xMax, int yMax, int zMax, MaterialData material) {
|
||||
this.setRegion(xMin, yMin, zMin, xMax, yMax, zMax, CraftMagicNumbers.getBlock(material));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setRegion(int xMin, int yMin, int zMin, int xMax, int yMax, int zMax, BlockData blockData) {
|
||||
this.setRegion(xMin, yMin, zMin, xMax, yMax, zMax, ((CraftBlockData)blockData).getState());
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public Material getType(int x, int y, int z) {
|
||||
return CraftBlockType.minecraftToBukkit(this.getTypeId(x, y, z).getBlock());
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public MaterialData getTypeAndData(int x, int y, int z) {
|
||||
return CraftMagicNumbers.getMaterial(this.getTypeId(x, y, z));
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public BlockData getBlockData(int x, int y, int z) {
|
||||
return CraftBlockData.fromData(this.getTypeId(x, y, z));
|
||||
}
|
||||
|
||||
@Deprecated
|
||||
@Override
|
||||
public byte getData(int x, int y, int z) {
|
||||
return CraftMagicNumbers.toLegacyData(this.getTypeId(x, y, z));
|
||||
}
|
||||
|
||||
private void setRegion(int xMin, int yMin, int zMin, int xMax, int yMax, int zMax, BlockState type) {
|
||||
if (xMin <= 15 && yMin < this.maxHeight && zMin <= 15) {
|
||||
if (xMin < 0) {
|
||||
xMin = 0;
|
||||
}
|
||||
|
||||
if (yMin < this.minHeight) {
|
||||
yMin = this.minHeight;
|
||||
}
|
||||
|
||||
if (zMin < 0) {
|
||||
zMin = 0;
|
||||
}
|
||||
|
||||
if (xMax > 16) {
|
||||
xMax = 16;
|
||||
}
|
||||
|
||||
if (yMax > this.maxHeight) {
|
||||
yMax = this.maxHeight;
|
||||
}
|
||||
|
||||
if (zMax > 16) {
|
||||
zMax = 16;
|
||||
}
|
||||
|
||||
if (xMin < xMax && yMin < yMax && zMin < zMax) {
|
||||
for(int y = yMin; y < yMax; ++y) {
|
||||
for(int x = xMin; x < xMax; ++x) {
|
||||
for(int z = zMin; z < zMax; ++z) {
|
||||
this.setBlock(x, y, z, type);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private BlockState getTypeId(int x, int y, int z) {
|
||||
if (x == (x & 15) && y >= this.minHeight && y < this.maxHeight && z == (z & 15)) {
|
||||
ChunkAccess access = this.getHandle();
|
||||
return access.getBlockState(new BlockPos(access.getPos().getMinBlockX() + x, y, access.getPos().getMinBlockZ() + z));
|
||||
} else {
|
||||
return Blocks.AIR.defaultBlockState();
|
||||
}
|
||||
}
|
||||
|
||||
private void setBlock(int x, int y, int z, BlockState type) {
|
||||
if (x == (x & 15) && y >= this.minHeight && y < this.maxHeight && z == (z & 15)) {
|
||||
ChunkAccess access = this.getHandle();
|
||||
BlockPos blockPosition = new BlockPos(access.getPos().getMinBlockX() + x, y, access.getPos().getMinBlockZ() + z);
|
||||
BlockState oldBlockData = access.setBlockState(blockPosition, type, false);
|
||||
if (type.hasBlockEntity()) {
|
||||
BlockEntity tileEntity = ((EntityBlock)type.getBlock()).newBlockEntity(blockPosition, type);
|
||||
if (tileEntity == null) {
|
||||
access.removeBlockEntity(blockPosition);
|
||||
} else {
|
||||
access.setBlockEntity(tileEntity);
|
||||
}
|
||||
} else if (oldBlockData != null && oldBlockData.hasBlockEntity()) {
|
||||
access.removeBlockEntity(blockPosition);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,10 +1,7 @@
|
||||
package com.volmit.iris.core.nms.v1_20_R3;
|
||||
|
||||
import java.awt.Color;
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.DataInputStream;
|
||||
import java.io.DataOutputStream;
|
||||
import java.io.*;
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.Method;
|
||||
import java.lang.reflect.Modifier;
|
||||
@@ -15,14 +12,19 @@ import java.util.Vector;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
import com.mojang.datafixers.util.Pair;
|
||||
import com.volmit.iris.core.nms.IMemoryWorld;
|
||||
import com.volmit.iris.core.nms.container.BiomeColor;
|
||||
import com.volmit.iris.util.data.B;
|
||||
import com.volmit.iris.util.scheduling.J;
|
||||
import net.minecraft.nbt.*;
|
||||
import net.minecraft.nbt.Tag;
|
||||
import net.minecraft.server.commands.data.BlockDataAccessor;
|
||||
import net.minecraft.tags.TagKey;
|
||||
import net.minecraft.util.datafix.fixes.BlockStateData;
|
||||
import net.minecraft.world.level.DataPackConfig;
|
||||
import net.minecraft.world.level.LevelReader;
|
||||
import net.minecraft.world.level.block.EntityBlock;
|
||||
import net.minecraft.world.level.chunk.LevelChunkSection;
|
||||
import org.bukkit.*;
|
||||
import org.bukkit.block.Biome;
|
||||
import org.bukkit.block.data.BlockData;
|
||||
@@ -40,6 +42,8 @@ import org.bukkit.entity.Entity;
|
||||
import org.bukkit.event.entity.CreatureSpawnEvent;
|
||||
import org.bukkit.generator.ChunkGenerator;
|
||||
import org.bukkit.inventory.ItemStack;
|
||||
import org.bukkit.packs.DataPack;
|
||||
import org.bukkit.packs.DataPackManager;
|
||||
import org.jetbrains.annotations.Contract;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
@@ -631,4 +635,44 @@ public class NMSBinding implements INMSBinding {
|
||||
public static Holder<net.minecraft.world.level.biome.Biome> biomeToBiomeBase(Registry<net.minecraft.world.level.biome.Biome> registry, Biome biome) {
|
||||
return registry.getHolderOrThrow(ResourceKey.create(Registries.BIOME, CraftNamespacedKey.toMinecraft(biome.getKey())));
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean setBlock(World world, int x, int y, int z, BlockData data, int flag, int updateDepth) {
|
||||
var level = ((CraftWorld) world).getHandle();
|
||||
var blockData = ((CraftBlockData) data).getState();
|
||||
return level.setBlock(new BlockPos(x, y, z), blockData, flag, updateDepth);
|
||||
}
|
||||
|
||||
@Override
|
||||
public BlockData getBlockData(CompoundTag tag) {
|
||||
if (tag == null) {
|
||||
return B.getAir();
|
||||
}
|
||||
|
||||
StringBuilder p = new StringBuilder(tag.getString("Name"));
|
||||
|
||||
if (tag.containsKey("Properties")) {
|
||||
CompoundTag props = tag.getCompoundTag("Properties");
|
||||
p.append('[');
|
||||
|
||||
for (String i : props.keySet()) {
|
||||
p.append(i).append('=').append(props.getString(i)).append(',');
|
||||
}
|
||||
|
||||
p.deleteCharAt(p.length() - 1).append(']');
|
||||
}
|
||||
|
||||
BlockData b = B.get(String.valueOf(p));
|
||||
|
||||
if (b == null) {
|
||||
return B.getAir();
|
||||
}
|
||||
|
||||
return b;
|
||||
}
|
||||
|
||||
@Override
|
||||
public IMemoryWorld createMemoryWorld(NamespacedKey levelType, WorldCreator creator) throws IOException {
|
||||
return new MemoryWorld(levelType, creator);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,396 @@
|
||||
package com.volmit.iris.core.nms.v1_20_R4;
|
||||
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.mojang.serialization.Lifecycle;
|
||||
import com.volmit.iris.Iris;
|
||||
import com.volmit.iris.core.nms.IMemoryWorld;
|
||||
import com.volmit.iris.util.reflect.Reflect;
|
||||
import com.volmit.iris.util.scheduling.J;
|
||||
import net.minecraft.core.BlockPos;
|
||||
import net.minecraft.core.RegistryAccess;
|
||||
import net.minecraft.core.registries.Registries;
|
||||
import net.minecraft.resources.ResourceKey;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
import net.minecraft.server.MinecraftServer;
|
||||
import net.minecraft.server.dedicated.DedicatedServerProperties;
|
||||
import net.minecraft.server.level.ServerLevel;
|
||||
import net.minecraft.util.GsonHelper;
|
||||
import net.minecraft.world.Difficulty;
|
||||
import net.minecraft.world.entity.ai.village.VillageSiege;
|
||||
import net.minecraft.world.entity.npc.CatSpawner;
|
||||
import net.minecraft.world.entity.npc.WanderingTraderSpawner;
|
||||
import net.minecraft.world.level.ChunkPos;
|
||||
import net.minecraft.world.level.GameRules;
|
||||
import net.minecraft.world.level.GameType;
|
||||
import net.minecraft.world.level.LevelSettings;
|
||||
import net.minecraft.world.level.biome.BiomeManager;
|
||||
import net.minecraft.world.level.block.Blocks;
|
||||
import net.minecraft.world.level.block.EntityBlock;
|
||||
import net.minecraft.world.level.block.entity.BlockEntity;
|
||||
import net.minecraft.world.level.block.state.BlockState;
|
||||
import net.minecraft.world.level.chunk.ChunkAccess;
|
||||
import net.minecraft.world.level.chunk.LevelChunk;
|
||||
import net.minecraft.world.level.dimension.DimensionType;
|
||||
import net.minecraft.world.level.dimension.LevelStem;
|
||||
import net.minecraft.world.level.levelgen.PatrolSpawner;
|
||||
import net.minecraft.world.level.levelgen.PhantomSpawner;
|
||||
import net.minecraft.world.level.levelgen.WorldOptions;
|
||||
import net.minecraft.world.level.storage.LevelStorageSource;
|
||||
import net.minecraft.world.level.storage.PrimaryLevelData;
|
||||
import net.minecraft.world.level.storage.ServerLevelData;
|
||||
import org.bukkit.*;
|
||||
import org.bukkit.block.Biome;
|
||||
import org.bukkit.block.data.BlockData;
|
||||
import org.bukkit.craftbukkit.v1_20_R4.CraftChunk;
|
||||
import org.bukkit.craftbukkit.v1_20_R4.CraftServer;
|
||||
import org.bukkit.craftbukkit.v1_20_R4.block.CraftBiome;
|
||||
import org.bukkit.craftbukkit.v1_20_R4.block.CraftBlockType;
|
||||
import org.bukkit.craftbukkit.v1_20_R4.block.data.CraftBlockData;
|
||||
import org.bukkit.craftbukkit.v1_20_R4.generator.CraftWorldInfo;
|
||||
import org.bukkit.craftbukkit.v1_20_R4.util.CraftMagicNumbers;
|
||||
import org.bukkit.event.EventHandler;
|
||||
import org.bukkit.event.EventPriority;
|
||||
import org.bukkit.event.HandlerList;
|
||||
import org.bukkit.event.world.WorldUnloadEvent;
|
||||
import org.bukkit.generator.ChunkGenerator;
|
||||
import org.bukkit.material.MaterialData;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.lang.ref.WeakReference;
|
||||
import java.lang.reflect.Constructor;
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.nio.file.Files;
|
||||
import java.util.Arrays;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.atomic.AtomicLong;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
public class MemoryWorld implements IMemoryWorld {
|
||||
private static final AtomicLong C = new AtomicLong();
|
||||
private static final Field WORLDS_FIELD;
|
||||
|
||||
private final AtomicReference<ServerLevel> level = new AtomicReference<>();
|
||||
|
||||
public MemoryWorld(NamespacedKey levelType, WorldCreator creator) throws IOException {
|
||||
var name = "memory_world"+C.getAndIncrement();
|
||||
while (Bukkit.getWorld(name) != null) {
|
||||
name = "memory_world"+C.getAndIncrement();
|
||||
}
|
||||
|
||||
var generator = creator.generator();
|
||||
var biomeProvider = creator.biomeProvider();
|
||||
var hardcore = creator.hardcore();
|
||||
var server = getServer();
|
||||
|
||||
var tempDir = Files.createTempDirectory("MemoryGenerator");
|
||||
LevelStorageSource source = LevelStorageSource.createDefault(tempDir);
|
||||
ResourceKey<LevelStem> stemKey = ResourceKey.create(Registries.LEVEL_STEM, new ResourceLocation(levelType.getNamespace(), levelType.getKey()));
|
||||
var access = source.createAccess(name, stemKey);
|
||||
|
||||
var worldLoader = server.worldLoader;
|
||||
var registry = server.registryAccess().registryOrThrow(Registries.LEVEL_STEM);
|
||||
var options = new WorldOptions(creator.seed(), creator.generateStructures(), false);
|
||||
var properties = new DedicatedServerProperties.WorldDimensionData(GsonHelper.parse(creator.generatorSettings().isEmpty() ? "{}" : creator.generatorSettings()), creator.type().name().toLowerCase(Locale.ROOT));
|
||||
var settings = new LevelSettings(name, GameType.byId(Bukkit.getDefaultGameMode().getValue()), hardcore, Difficulty.EASY, false, new GameRules(), worldLoader.dataConfiguration());
|
||||
var dimension = properties.create(worldLoader.datapackWorldgen()).bake(registry);
|
||||
Lifecycle lifecycle = dimension.lifecycle().add(worldLoader.datapackWorldgen().allRegistriesLifecycle());
|
||||
|
||||
var worldData = new PrimaryLevelData(settings, options, dimension.specialWorldProperty(), lifecycle);
|
||||
worldData.customDimensions = registry;
|
||||
worldData.getGameRules().getRule(GameRules.RULE_SPAWN_CHUNK_RADIUS).set(0, null);
|
||||
|
||||
long obfSeed = BiomeManager.obfuscateSeed(creator.seed());
|
||||
var list = ImmutableList.of(new PhantomSpawner(), new PatrolSpawner(), new CatSpawner(), new VillageSiege(), new WanderingTraderSpawner(worldData));
|
||||
var levelStem = registry.get(stemKey);
|
||||
if (levelStem == null)
|
||||
throw new IllegalStateException("Unknown dimension type: " + stemKey);
|
||||
|
||||
CraftWorldInfo worldInfo;
|
||||
try {
|
||||
worldInfo = new CraftWorldInfo(worldData, access, creator.environment(), levelStem.type().value());
|
||||
} catch (Throwable e) {
|
||||
try {
|
||||
worldInfo = Reflect.newInstance(CraftWorldInfo.class, worldData, access, creator.environment(), levelStem.type().value(), levelStem.generator(), server.registryAccess());
|
||||
} catch (NoSuchMethodException | InvocationTargetException ex) {
|
||||
throw new IOException("Failed to create CraftWorldInfo", ex);
|
||||
}
|
||||
}
|
||||
if (biomeProvider == null && generator != null) {
|
||||
biomeProvider = generator.getDefaultBiomeProvider(worldInfo);
|
||||
}
|
||||
|
||||
var levelKey = ResourceKey.create(Registries.DIMENSION, new ResourceLocation(name));
|
||||
var level = new ServerLevel(
|
||||
server,
|
||||
server.executor,
|
||||
access,
|
||||
worldData,
|
||||
levelKey,
|
||||
levelStem,
|
||||
server.progressListenerFactory.create(0),
|
||||
worldData.isDebugWorld(),
|
||||
obfSeed,
|
||||
creator.environment() == World.Environment.NORMAL ? list : ImmutableList.of(),
|
||||
true,
|
||||
server.overworld().getRandomSequences(),
|
||||
creator.environment(),
|
||||
generator,
|
||||
biomeProvider
|
||||
);
|
||||
Iris.instance.registerListener(this);
|
||||
this.level.set(level);
|
||||
}
|
||||
|
||||
public World getBukkit() {
|
||||
var level = this.level.get();
|
||||
if (level == null)
|
||||
throw new IllegalStateException("World is not loaded");
|
||||
|
||||
return level.getWorld();
|
||||
}
|
||||
|
||||
public Chunk getChunk(int x, int z) {
|
||||
var level = this.level.get();
|
||||
if (level == null)
|
||||
throw new IllegalStateException("World is not loaded");
|
||||
return new CraftChunk(level, x, z);
|
||||
}
|
||||
|
||||
public ChunkGenerator.ChunkData getChunkData(int x, int z) {
|
||||
var level = this.level.get();
|
||||
if (level == null)
|
||||
throw new IllegalStateException("World is not loaded");
|
||||
return new MemoryChunkData(x, z);
|
||||
}
|
||||
|
||||
@EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
|
||||
public void onWorldUnload(WorldUnloadEvent event) {
|
||||
var level = this.level.get();
|
||||
if (level == null || event.getWorld() != level.getWorld())
|
||||
return;
|
||||
|
||||
this.level.set(null);
|
||||
}
|
||||
|
||||
public boolean isClosed() {
|
||||
return this.level.get() == null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() throws Exception {
|
||||
if (!Bukkit.isPrimaryThread()) {
|
||||
var future = new CompletableFuture<Void>();
|
||||
J.s(() -> {
|
||||
try {
|
||||
close();
|
||||
future.complete(null);
|
||||
} catch (Exception e) {
|
||||
future.completeExceptionally(e);
|
||||
}
|
||||
});
|
||||
future.join();
|
||||
return;
|
||||
}
|
||||
|
||||
var level = this.level.get();
|
||||
if (level == null || !this.level.compareAndSet(level, null))
|
||||
return;
|
||||
|
||||
level.close();
|
||||
level.convertable.deleteLevel();
|
||||
level.convertable.close();
|
||||
|
||||
var map = (Map<String, World>) WORLDS_FIELD.get(Bukkit.getServer());
|
||||
map.remove(level.dimension().location().getPath());
|
||||
getServer().removeLevel(level);
|
||||
HandlerList.unregisterAll(this);
|
||||
}
|
||||
|
||||
private static MinecraftServer getServer() {
|
||||
return ((CraftServer) Bukkit.getServer()).getServer();
|
||||
}
|
||||
|
||||
static {
|
||||
try {
|
||||
WORLDS_FIELD = CraftServer.class.getDeclaredField("worlds");
|
||||
WORLDS_FIELD.setAccessible(true);
|
||||
} catch (NoSuchFieldException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
private class MemoryChunkData implements ChunkGenerator.ChunkData {
|
||||
private final int maxHeight;
|
||||
private final int minHeight;
|
||||
private final ChunkPos pos;
|
||||
private WeakReference<LevelChunk> chunk;
|
||||
|
||||
private MemoryChunkData(int x, int z) {
|
||||
var level = MemoryWorld.this.level.get();
|
||||
if (level == null)
|
||||
throw new IllegalStateException("World is not loaded");
|
||||
|
||||
var chunk = level.getChunk(x, z);
|
||||
this.minHeight = chunk.getMinBuildHeight();
|
||||
this.maxHeight = chunk.getMaxBuildHeight();
|
||||
this.pos = new ChunkPos(x, z);
|
||||
this.chunk = new WeakReference<>(chunk);
|
||||
}
|
||||
|
||||
public LevelChunk getHandle() {
|
||||
LevelChunk chunk = this.chunk.get();
|
||||
if (chunk == null) {
|
||||
var level = MemoryWorld.this.level.get();
|
||||
if (level == null)
|
||||
throw new IllegalStateException("World is not loaded");
|
||||
|
||||
chunk = level.getChunk(this.pos.x, this.pos.z);
|
||||
this.chunk = new WeakReference<>(chunk);
|
||||
}
|
||||
return chunk;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getMinHeight() {
|
||||
return minHeight;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getMaxHeight() {
|
||||
return maxHeight;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public Biome getBiome(int x, int y, int z) {
|
||||
return CraftBiome.minecraftHolderToBukkit(getHandle().getNoiseBiome(x >> 2, y >> 2, z >> 2));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setBlock(int x, int y, int z, @NotNull Material material) {
|
||||
setBlock(x, y, z, material.createBlockData());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setBlock(int x, int y, int z, @NotNull MaterialData material) {
|
||||
setBlock(x, y, z, CraftMagicNumbers.getBlock(material));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setBlock(int x, int y, int z, @NotNull BlockData blockData) {
|
||||
setBlock(x, y, z, ((CraftBlockData) blockData).getState());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setRegion(int xMin, int yMin, int zMin, int xMax, int yMax, int zMax, Material material) {
|
||||
this.setRegion(xMin, yMin, zMin, xMax, yMax, zMax, material.createBlockData());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setRegion(int xMin, int yMin, int zMin, int xMax, int yMax, int zMax, MaterialData material) {
|
||||
this.setRegion(xMin, yMin, zMin, xMax, yMax, zMax, CraftMagicNumbers.getBlock(material));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setRegion(int xMin, int yMin, int zMin, int xMax, int yMax, int zMax, BlockData blockData) {
|
||||
this.setRegion(xMin, yMin, zMin, xMax, yMax, zMax, ((CraftBlockData)blockData).getState());
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public Material getType(int x, int y, int z) {
|
||||
return CraftBlockType.minecraftToBukkit(this.getTypeId(x, y, z).getBlock());
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public MaterialData getTypeAndData(int x, int y, int z) {
|
||||
return CraftMagicNumbers.getMaterial(this.getTypeId(x, y, z));
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public BlockData getBlockData(int x, int y, int z) {
|
||||
return CraftBlockData.fromData(this.getTypeId(x, y, z));
|
||||
}
|
||||
|
||||
@Deprecated
|
||||
@Override
|
||||
public byte getData(int x, int y, int z) {
|
||||
return CraftMagicNumbers.toLegacyData(this.getTypeId(x, y, z));
|
||||
}
|
||||
|
||||
private void setRegion(int xMin, int yMin, int zMin, int xMax, int yMax, int zMax, BlockState type) {
|
||||
if (xMin <= 15 && yMin < this.maxHeight && zMin <= 15) {
|
||||
if (xMin < 0) {
|
||||
xMin = 0;
|
||||
}
|
||||
|
||||
if (yMin < this.minHeight) {
|
||||
yMin = this.minHeight;
|
||||
}
|
||||
|
||||
if (zMin < 0) {
|
||||
zMin = 0;
|
||||
}
|
||||
|
||||
if (xMax > 16) {
|
||||
xMax = 16;
|
||||
}
|
||||
|
||||
if (yMax > this.maxHeight) {
|
||||
yMax = this.maxHeight;
|
||||
}
|
||||
|
||||
if (zMax > 16) {
|
||||
zMax = 16;
|
||||
}
|
||||
|
||||
if (xMin < xMax && yMin < yMax && zMin < zMax) {
|
||||
for(int y = yMin; y < yMax; ++y) {
|
||||
for(int x = xMin; x < xMax; ++x) {
|
||||
for(int z = zMin; z < zMax; ++z) {
|
||||
this.setBlock(x, y, z, type);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private BlockState getTypeId(int x, int y, int z) {
|
||||
if (x == (x & 15) && y >= this.minHeight && y < this.maxHeight && z == (z & 15)) {
|
||||
ChunkAccess access = this.getHandle();
|
||||
return access.getBlockState(new BlockPos(access.getPos().getMinBlockX() + x, y, access.getPos().getMinBlockZ() + z));
|
||||
} else {
|
||||
return Blocks.AIR.defaultBlockState();
|
||||
}
|
||||
}
|
||||
|
||||
private void setBlock(int x, int y, int z, BlockState type) {
|
||||
if (x == (x & 15) && y >= this.minHeight && y < this.maxHeight && z == (z & 15)) {
|
||||
ChunkAccess access = this.getHandle();
|
||||
BlockPos blockPosition = new BlockPos(access.getPos().getMinBlockX() + x, y, access.getPos().getMinBlockZ() + z);
|
||||
BlockState oldBlockData = access.setBlockState(blockPosition, type, false);
|
||||
if (type.hasBlockEntity()) {
|
||||
BlockEntity tileEntity = ((EntityBlock)type.getBlock()).newBlockEntity(blockPosition, type);
|
||||
if (tileEntity == null) {
|
||||
access.removeBlockEntity(blockPosition);
|
||||
} else {
|
||||
access.setBlockEntity(tileEntity);
|
||||
}
|
||||
} else if (oldBlockData != null && oldBlockData.hasBlockEntity()) {
|
||||
access.removeBlockEntity(blockPosition);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
package com.volmit.iris.core.nms.v1_20_R4;
|
||||
|
||||
import java.awt.Color;
|
||||
import java.io.IOException;
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.Method;
|
||||
import java.lang.reflect.Modifier;
|
||||
@@ -8,6 +9,7 @@ import java.util.*;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
import com.mojang.datafixers.util.Pair;
|
||||
import com.volmit.iris.core.nms.IMemoryWorld;
|
||||
import com.volmit.iris.core.nms.container.BiomeColor;
|
||||
import com.volmit.iris.core.nms.datapack.DataVersion;
|
||||
import com.volmit.iris.util.nbt.tag.CompoundTag;
|
||||
@@ -570,12 +572,18 @@ public class NMSBinding implements INMSBinding {
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public Entity spawnEntity(Location location, org.bukkit.entity.EntityType type, CreatureSpawnEvent.SpawnReason reason) {
|
||||
return ((CraftWorld) location.getWorld()).spawn(location, type.getEntityClass(), null, reason);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean setBlock(World world, int x, int y, int z, BlockData data, int flag, int updateDepth) {
|
||||
var level = ((CraftWorld) world).getHandle();
|
||||
var blockData = ((CraftBlockData) data).getState();
|
||||
return level.setBlock(new BlockPos(x, y, z), blockData, flag, updateDepth);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Color getBiomeColor(Location location, BiomeColor type) {
|
||||
LevelReader reader = ((CraftWorld) location.getWorld()).getHandle();
|
||||
@@ -650,4 +658,9 @@ public class NMSBinding implements INMSBinding {
|
||||
|
||||
return keys;
|
||||
}
|
||||
|
||||
@Override
|
||||
public IMemoryWorld createMemoryWorld(NamespacedKey levelType, WorldCreator creator) throws IOException {
|
||||
return new MemoryWorld(levelType, creator);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,397 @@
|
||||
package com.volmit.iris.core.nms.v1_21_R1;
|
||||
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.mojang.serialization.Lifecycle;
|
||||
import com.volmit.iris.Iris;
|
||||
import com.volmit.iris.core.nms.IMemoryWorld;
|
||||
import com.volmit.iris.util.reflect.Reflect;
|
||||
import com.volmit.iris.util.scheduling.J;
|
||||
import net.minecraft.core.BlockPos;
|
||||
import net.minecraft.core.RegistryAccess;
|
||||
import net.minecraft.core.registries.Registries;
|
||||
import net.minecraft.resources.ResourceKey;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
import net.minecraft.server.MinecraftServer;
|
||||
import net.minecraft.server.dedicated.DedicatedServerProperties;
|
||||
import net.minecraft.server.level.ServerLevel;
|
||||
import net.minecraft.util.GsonHelper;
|
||||
import net.minecraft.world.Difficulty;
|
||||
import net.minecraft.world.entity.ai.village.VillageSiege;
|
||||
import net.minecraft.world.entity.npc.CatSpawner;
|
||||
import net.minecraft.world.entity.npc.WanderingTraderSpawner;
|
||||
import net.minecraft.world.level.ChunkPos;
|
||||
import net.minecraft.world.level.GameRules;
|
||||
import net.minecraft.world.level.GameType;
|
||||
import net.minecraft.world.level.LevelSettings;
|
||||
import net.minecraft.world.level.biome.BiomeManager;
|
||||
import net.minecraft.world.level.block.Blocks;
|
||||
import net.minecraft.world.level.block.EntityBlock;
|
||||
import net.minecraft.world.level.block.entity.BlockEntity;
|
||||
import net.minecraft.world.level.block.state.BlockState;
|
||||
import net.minecraft.world.level.chunk.ChunkAccess;
|
||||
import net.minecraft.world.level.chunk.LevelChunk;
|
||||
import net.minecraft.world.level.dimension.DimensionType;
|
||||
import net.minecraft.world.level.dimension.LevelStem;
|
||||
import net.minecraft.world.level.levelgen.PatrolSpawner;
|
||||
import net.minecraft.world.level.levelgen.PhantomSpawner;
|
||||
import net.minecraft.world.level.levelgen.WorldOptions;
|
||||
import net.minecraft.world.level.storage.LevelStorageSource;
|
||||
import net.minecraft.world.level.storage.PrimaryLevelData;
|
||||
import net.minecraft.world.level.storage.ServerLevelData;
|
||||
import org.bukkit.*;
|
||||
import org.bukkit.block.Biome;
|
||||
import org.bukkit.block.data.BlockData;
|
||||
import org.bukkit.craftbukkit.v1_21_R1.CraftChunk;
|
||||
import org.bukkit.craftbukkit.v1_21_R1.CraftServer;
|
||||
import org.bukkit.craftbukkit.v1_21_R1.block.CraftBiome;
|
||||
import org.bukkit.craftbukkit.v1_21_R1.block.CraftBlockType;
|
||||
import org.bukkit.craftbukkit.v1_21_R1.block.data.CraftBlockData;
|
||||
import org.bukkit.craftbukkit.v1_21_R1.generator.CraftWorldInfo;
|
||||
import org.bukkit.craftbukkit.v1_21_R1.util.CraftMagicNumbers;
|
||||
import org.bukkit.event.EventHandler;
|
||||
import org.bukkit.event.EventPriority;
|
||||
import org.bukkit.event.HandlerList;
|
||||
import org.bukkit.event.world.WorldUnloadEvent;
|
||||
import org.bukkit.generator.ChunkGenerator;
|
||||
import org.bukkit.material.MaterialData;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.lang.ref.WeakReference;
|
||||
import java.lang.reflect.Constructor;
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.lang.reflect.Modifier;
|
||||
import java.nio.file.Files;
|
||||
import java.util.Arrays;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.atomic.AtomicLong;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
public class MemoryWorld implements IMemoryWorld {
|
||||
private static final AtomicLong C = new AtomicLong();
|
||||
private static final Field WORLDS_FIELD;
|
||||
|
||||
private final AtomicReference<ServerLevel> level = new AtomicReference<>();
|
||||
|
||||
public MemoryWorld(NamespacedKey levelType, WorldCreator creator) throws IOException {
|
||||
var name = "memory_world"+C.getAndIncrement();
|
||||
while (Bukkit.getWorld(name) != null) {
|
||||
name = "memory_world"+C.getAndIncrement();
|
||||
}
|
||||
|
||||
var generator = creator.generator();
|
||||
var biomeProvider = creator.biomeProvider();
|
||||
var hardcore = creator.hardcore();
|
||||
var server = getServer();
|
||||
|
||||
var tempDir = Files.createTempDirectory("MemoryGenerator");
|
||||
LevelStorageSource source = LevelStorageSource.createDefault(tempDir);
|
||||
ResourceKey<LevelStem> stemKey = ResourceKey.create(Registries.LEVEL_STEM, ResourceLocation.fromNamespaceAndPath(levelType.getNamespace(), levelType.getKey()));
|
||||
var access = source.createAccess(name, stemKey);
|
||||
|
||||
var worldLoader = server.worldLoader;
|
||||
var registry = server.registryAccess().registryOrThrow(Registries.LEVEL_STEM);
|
||||
var options = new WorldOptions(creator.seed(), creator.generateStructures(), false);
|
||||
var properties = new DedicatedServerProperties.WorldDimensionData(GsonHelper.parse(creator.generatorSettings().isEmpty() ? "{}" : creator.generatorSettings()), creator.type().name().toLowerCase(Locale.ROOT));
|
||||
var settings = new LevelSettings(name, GameType.byId(Bukkit.getDefaultGameMode().getValue()), hardcore, Difficulty.EASY, false, new GameRules(), worldLoader.dataConfiguration());
|
||||
var dimension = properties.create(worldLoader.datapackWorldgen()).bake(registry);
|
||||
Lifecycle lifecycle = dimension.lifecycle().add(worldLoader.datapackWorldgen().allRegistriesLifecycle());
|
||||
|
||||
var worldData = new PrimaryLevelData(settings, options, dimension.specialWorldProperty(), lifecycle);
|
||||
worldData.customDimensions = registry;
|
||||
worldData.getGameRules().getRule(GameRules.RULE_SPAWN_CHUNK_RADIUS).set(0, null);
|
||||
|
||||
long obfSeed = BiomeManager.obfuscateSeed(creator.seed());
|
||||
var list = ImmutableList.of(new PhantomSpawner(), new PatrolSpawner(), new CatSpawner(), new VillageSiege(), new WanderingTraderSpawner(worldData));
|
||||
var levelStem = registry.get(stemKey);
|
||||
if (levelStem == null)
|
||||
throw new IllegalStateException("Unknown dimension type: " + stemKey);
|
||||
|
||||
CraftWorldInfo worldInfo;
|
||||
try {
|
||||
worldInfo = new CraftWorldInfo(worldData, access, creator.environment(), levelStem.type().value());
|
||||
} catch (Throwable e) {
|
||||
try {
|
||||
worldInfo = Reflect.newInstance(CraftWorldInfo.class, worldData, access, creator.environment(), levelStem.type().value(), levelStem.generator(), server.registryAccess());
|
||||
} catch (NoSuchMethodException | InvocationTargetException ex) {
|
||||
throw new IOException("Failed to create CraftWorldInfo", ex);
|
||||
}
|
||||
}
|
||||
if (biomeProvider == null && generator != null) {
|
||||
biomeProvider = generator.getDefaultBiomeProvider(worldInfo);
|
||||
}
|
||||
|
||||
var levelKey = ResourceKey.create(Registries.DIMENSION, ResourceLocation.withDefaultNamespace(name));
|
||||
var level = new ServerLevel(
|
||||
server,
|
||||
server.executor,
|
||||
access,
|
||||
worldData,
|
||||
levelKey,
|
||||
levelStem,
|
||||
server.progressListenerFactory.create(0),
|
||||
worldData.isDebugWorld(),
|
||||
obfSeed,
|
||||
creator.environment() == World.Environment.NORMAL ? list : ImmutableList.of(),
|
||||
true,
|
||||
server.overworld().getRandomSequences(),
|
||||
creator.environment(),
|
||||
generator,
|
||||
biomeProvider
|
||||
);
|
||||
Iris.instance.registerListener(this);
|
||||
this.level.set(level);
|
||||
}
|
||||
|
||||
public World getBukkit() {
|
||||
var level = this.level.get();
|
||||
if (level == null)
|
||||
throw new IllegalStateException("World is not loaded");
|
||||
|
||||
return level.getWorld();
|
||||
}
|
||||
|
||||
public Chunk getChunk(int x, int z) {
|
||||
var level = this.level.get();
|
||||
if (level == null)
|
||||
throw new IllegalStateException("World is not loaded");
|
||||
return new CraftChunk(level, x, z);
|
||||
}
|
||||
|
||||
public ChunkGenerator.ChunkData getChunkData(int x, int z) {
|
||||
var level = this.level.get();
|
||||
if (level == null)
|
||||
throw new IllegalStateException("World is not loaded");
|
||||
return new MemoryChunkData(x, z);
|
||||
}
|
||||
|
||||
@EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
|
||||
public void onWorldUnload(WorldUnloadEvent event) {
|
||||
var level = this.level.get();
|
||||
if (level == null || event.getWorld() != level.getWorld())
|
||||
return;
|
||||
|
||||
this.level.set(null);
|
||||
}
|
||||
|
||||
public boolean isClosed() {
|
||||
return this.level.get() == null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() throws Exception {
|
||||
if (!Bukkit.isPrimaryThread()) {
|
||||
var future = new CompletableFuture<Void>();
|
||||
J.s(() -> {
|
||||
try {
|
||||
close();
|
||||
future.complete(null);
|
||||
} catch (Exception e) {
|
||||
future.completeExceptionally(e);
|
||||
}
|
||||
});
|
||||
future.join();
|
||||
return;
|
||||
}
|
||||
|
||||
var level = this.level.get();
|
||||
if (level == null || !this.level.compareAndSet(level, null))
|
||||
return;
|
||||
|
||||
level.close();
|
||||
level.convertable.deleteLevel();
|
||||
level.convertable.close();
|
||||
|
||||
var map = (Map<String, World>) WORLDS_FIELD.get(Bukkit.getServer());
|
||||
map.remove(level.dimension().location().getPath());
|
||||
getServer().removeLevel(level);
|
||||
HandlerList.unregisterAll(this);
|
||||
}
|
||||
|
||||
private static MinecraftServer getServer() {
|
||||
return ((CraftServer) Bukkit.getServer()).getServer();
|
||||
}
|
||||
|
||||
static {
|
||||
try {
|
||||
WORLDS_FIELD = CraftServer.class.getDeclaredField("worlds");
|
||||
WORLDS_FIELD.setAccessible(true);
|
||||
} catch (NoSuchFieldException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
private class MemoryChunkData implements ChunkGenerator.ChunkData {
|
||||
private final int maxHeight;
|
||||
private final int minHeight;
|
||||
private final ChunkPos pos;
|
||||
private WeakReference<LevelChunk> chunk;
|
||||
|
||||
private MemoryChunkData(int x, int z) {
|
||||
var level = MemoryWorld.this.level.get();
|
||||
if (level == null)
|
||||
throw new IllegalStateException("World is not loaded");
|
||||
|
||||
var chunk = level.getChunk(x, z);
|
||||
this.minHeight = chunk.getMinBuildHeight();
|
||||
this.maxHeight = chunk.getMaxBuildHeight();
|
||||
this.pos = new ChunkPos(x, z);
|
||||
this.chunk = new WeakReference<>(chunk);
|
||||
}
|
||||
|
||||
public LevelChunk getHandle() {
|
||||
LevelChunk chunk = this.chunk.get();
|
||||
if (chunk == null) {
|
||||
var level = MemoryWorld.this.level.get();
|
||||
if (level == null)
|
||||
throw new IllegalStateException("World is not loaded");
|
||||
|
||||
chunk = level.getChunk(this.pos.x, this.pos.z);
|
||||
this.chunk = new WeakReference<>(chunk);
|
||||
}
|
||||
return chunk;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getMinHeight() {
|
||||
return minHeight;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getMaxHeight() {
|
||||
return maxHeight;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public Biome getBiome(int x, int y, int z) {
|
||||
return CraftBiome.minecraftHolderToBukkit(getHandle().getNoiseBiome(x >> 2, y >> 2, z >> 2));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setBlock(int x, int y, int z, @NotNull Material material) {
|
||||
setBlock(x, y, z, material.createBlockData());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setBlock(int x, int y, int z, @NotNull MaterialData material) {
|
||||
setBlock(x, y, z, CraftMagicNumbers.getBlock(material));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setBlock(int x, int y, int z, @NotNull BlockData blockData) {
|
||||
setBlock(x, y, z, ((CraftBlockData) blockData).getState());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setRegion(int xMin, int yMin, int zMin, int xMax, int yMax, int zMax, Material material) {
|
||||
this.setRegion(xMin, yMin, zMin, xMax, yMax, zMax, material.createBlockData());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setRegion(int xMin, int yMin, int zMin, int xMax, int yMax, int zMax, MaterialData material) {
|
||||
this.setRegion(xMin, yMin, zMin, xMax, yMax, zMax, CraftMagicNumbers.getBlock(material));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setRegion(int xMin, int yMin, int zMin, int xMax, int yMax, int zMax, BlockData blockData) {
|
||||
this.setRegion(xMin, yMin, zMin, xMax, yMax, zMax, ((CraftBlockData)blockData).getState());
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public Material getType(int x, int y, int z) {
|
||||
return CraftBlockType.minecraftToBukkit(this.getTypeId(x, y, z).getBlock());
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public MaterialData getTypeAndData(int x, int y, int z) {
|
||||
return CraftMagicNumbers.getMaterial(this.getTypeId(x, y, z));
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public BlockData getBlockData(int x, int y, int z) {
|
||||
return CraftBlockData.fromData(this.getTypeId(x, y, z));
|
||||
}
|
||||
|
||||
@Deprecated
|
||||
@Override
|
||||
public byte getData(int x, int y, int z) {
|
||||
return CraftMagicNumbers.toLegacyData(this.getTypeId(x, y, z));
|
||||
}
|
||||
|
||||
private void setRegion(int xMin, int yMin, int zMin, int xMax, int yMax, int zMax, BlockState type) {
|
||||
if (xMin <= 15 && yMin < this.maxHeight && zMin <= 15) {
|
||||
if (xMin < 0) {
|
||||
xMin = 0;
|
||||
}
|
||||
|
||||
if (yMin < this.minHeight) {
|
||||
yMin = this.minHeight;
|
||||
}
|
||||
|
||||
if (zMin < 0) {
|
||||
zMin = 0;
|
||||
}
|
||||
|
||||
if (xMax > 16) {
|
||||
xMax = 16;
|
||||
}
|
||||
|
||||
if (yMax > this.maxHeight) {
|
||||
yMax = this.maxHeight;
|
||||
}
|
||||
|
||||
if (zMax > 16) {
|
||||
zMax = 16;
|
||||
}
|
||||
|
||||
if (xMin < xMax && yMin < yMax && zMin < zMax) {
|
||||
for(int y = yMin; y < yMax; ++y) {
|
||||
for(int x = xMin; x < xMax; ++x) {
|
||||
for(int z = zMin; z < zMax; ++z) {
|
||||
this.setBlock(x, y, z, type);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private BlockState getTypeId(int x, int y, int z) {
|
||||
if (x == (x & 15) && y >= this.minHeight && y < this.maxHeight && z == (z & 15)) {
|
||||
ChunkAccess access = this.getHandle();
|
||||
return access.getBlockState(new BlockPos(access.getPos().getMinBlockX() + x, y, access.getPos().getMinBlockZ() + z));
|
||||
} else {
|
||||
return Blocks.AIR.defaultBlockState();
|
||||
}
|
||||
}
|
||||
|
||||
private void setBlock(int x, int y, int z, BlockState type) {
|
||||
if (x == (x & 15) && y >= this.minHeight && y < this.maxHeight && z == (z & 15)) {
|
||||
ChunkAccess access = this.getHandle();
|
||||
BlockPos blockPosition = new BlockPos(access.getPos().getMinBlockX() + x, y, access.getPos().getMinBlockZ() + z);
|
||||
BlockState oldBlockData = access.setBlockState(blockPosition, type, false);
|
||||
if (type.hasBlockEntity()) {
|
||||
BlockEntity tileEntity = ((EntityBlock)type.getBlock()).newBlockEntity(blockPosition, type);
|
||||
if (tileEntity == null) {
|
||||
access.removeBlockEntity(blockPosition);
|
||||
} else {
|
||||
access.setBlockEntity(tileEntity);
|
||||
}
|
||||
} else if (oldBlockData != null && oldBlockData.hasBlockEntity()) {
|
||||
access.removeBlockEntity(blockPosition);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,10 +1,7 @@
|
||||
package com.volmit.iris.core.nms.v1_21_R1;
|
||||
|
||||
import java.awt.Color;
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.DataInputStream;
|
||||
import java.io.DataOutputStream;
|
||||
import java.io.*;
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.Method;
|
||||
import java.lang.reflect.Modifier;
|
||||
@@ -12,6 +9,7 @@ import java.util.*;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
import com.mojang.datafixers.util.Pair;
|
||||
import com.volmit.iris.core.nms.IMemoryWorld;
|
||||
import com.volmit.iris.core.nms.container.BiomeColor;
|
||||
import com.volmit.iris.core.nms.datapack.DataVersion;
|
||||
import com.volmit.iris.util.scheduling.J;
|
||||
@@ -577,6 +575,12 @@ public class NMSBinding implements INMSBinding {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean setBlock(World world, int x, int y, int z, BlockData data, int flag, int updateDepth) {
|
||||
var level = ((CraftWorld) world).getHandle();
|
||||
var blockData = ((CraftBlockData) data).getState();
|
||||
return level.setBlock(new BlockPos(x, y, z), blockData, flag, updateDepth);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Entity spawnEntity(Location location, org.bukkit.entity.EntityType type, CreatureSpawnEvent.SpawnReason reason) {
|
||||
@@ -657,4 +661,9 @@ public class NMSBinding implements INMSBinding {
|
||||
|
||||
return keys;
|
||||
}
|
||||
|
||||
@Override
|
||||
public IMemoryWorld createMemoryWorld(NamespacedKey levelType, WorldCreator creator) throws IOException {
|
||||
return new MemoryWorld(levelType, creator);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user