9
0
mirror of https://github.com/VolmitSoftware/Iris.git synced 2025-12-20 15:39:31 +00:00

Compare commits

...

85 Commits

Author SHA1 Message Date
RePixelatedMC
b279bb7716 wee 2024-11-18 20:22:57 +01:00
RePixelatedMC
2976390e78 Merge branch 'v3.4.3' of https://github.com/VolmitSoftware/Iris into v3.4.3-dev 2024-11-15 11:19:15 +01:00
RePixelatedMC
578070dffe changes 2024-11-13 16:58:36 +01:00
Julian Krings
1f9c72d093 fix compile 2024-11-09 13:58:10 +01:00
Julian Krings
a1495a10e5 updater fixes 2024-11-08 21:01:58 +01:00
RePixelatedMC
a0d91dbc74 sync and fixes 2024-11-05 10:14:32 +01:00
RePixelatedMC
eb80ae62b1 Fix mem leak and further improvements 2024-11-01 20:47:19 +01:00
Julian Krings
11ae48bea1 revert previous 2024-10-31 21:33:24 +01:00
Julian Krings
b0f4b29a2d ehh 2024-10-31 21:05:07 +01:00
Julian Krings
c38bb1cd01 stability improvements for the chunk updater 2024-10-30 15:59:31 +01:00
Julian Krings
7faa727bd2 midsave ChunkUpdater improvements 2024-10-29 22:12:22 +01:00
RePixelatedMC
a8751c80ab Should fix? 2024-10-29 20:53:16 +01:00
RePixelatedMC
fd38f0d127 better 2024-10-29 13:36:32 +01:00
RePixelatedMC
537b0f8dd6 Should work 2024-10-28 15:44:44 +01:00
RePixelatedMC
560f2f4fdc lol 2024-10-28 15:37:14 +01:00
RePixelatedMC
7c725a9436 Slices + Huge performance increase 2024-10-28 15:30:51 +01:00
RePixelatedMC
6532715490 r 2024-10-28 13:30:31 +01:00
RePixelatedMC
b1bb01665a Stability 2024-10-28 11:12:24 +01:00
Julian Krings
6c69c69868 Merge branch 'v3.4.3' into v3.4.3-dev 2024-10-27 14:06:17 +01:00
RePixelatedMC
9cd7ad6770 Merge branch 'v3.4.3-dev' of https://github.com/VolmitSoftware/Iris into v3.4.3 2024-10-23 20:27:41 +02:00
RePixelatedMC
b70c9fd4d8 deepslate variant support 2024-10-23 13:50:24 +02:00
RePixelatedMC
412f592e1c removed comment 2024-10-22 21:22:01 +02:00
RePixelatedMC
6c0fa9361f Merge remote-tracking branch 'origin/v3.4.3' into v3.4.3
# Conflicts:
#	core/src/main/java/com/volmit/iris/engine/actuator/IrisTerrainNormalActuator.java
2024-10-22 21:20:45 +02:00
RePixelatedMC
b7dcdd2921 yey 2024-10-22 21:20:29 +02:00
RePixelatedMC
1d8acd2d62 Merge remote-tracking branch 'origin/v3.4.3' into v3.4.3 2024-10-22 15:29:05 +02:00
RePixelatedMC
05bf92ca50 huh 2024-10-22 15:28:39 +02:00
RePixelatedMC
dd598d18bf Merge remote-tracking branch 'origin/v3.4.3' into v3.4.3
# Conflicts:
#	core/src/main/java/com/volmit/iris/engine/actuator/IrisTerrainNormalActuator.java
2024-10-22 15:24:10 +02:00
RePixelatedMC
cc6de4a46d oh well back to scratch 2024-10-22 15:23:50 +02:00
RePixelatedMC
9950551ecb Help 2024-10-22 14:13:30 +02:00
RePixelatedMC
dfd27ecbff sync 2024-10-22 10:01:48 +02:00
RePixelatedMC
ac5d5793ab cleanup 2 2024-10-19 17:42:35 +02:00
RePixelatedMC
2133a03c21 cleanup 2024-10-19 17:16:29 +02:00
RePixelatedMC
8755449c13 Untested but should work though 2024-10-18 15:36:09 +02:00
Julian Krings
9e40259ca2 fix LegacyTileData not placing 2024-10-17 19:18:11 +02:00
Julian Krings
94a7692735 add force place for stronghold 2024-10-17 18:48:17 +02:00
Julian Krings
86ce02789e woops 2024-10-17 17:49:23 +02:00
Julian Krings
773c8129c9 cleanup and improve merger 2024-10-17 17:44:44 +02:00
RePixelatedMC
dbe9f81091 fixes seed inconsistency and a huge performance bump 2024-10-17 16:03:49 +02:00
RePixelatedMC
288e556f2a weee 2024-10-17 13:53:23 +02:00
RePixelatedMC
70822e37de merger! 2024-10-17 13:30:35 +02:00
Julian Krings
8b803a87f0 rename CrucibleDataProvider to MythicCrucibleDataProvider to fit naming conventions 2024-10-17 11:28:50 +02:00
Julian Krings
3ae6e92eaf woops 2024-10-16 22:05:47 +02:00
Julian Krings
0f3c52a5aa cleanup CrucibleDataProvider and fix furniture support 2024-10-16 21:59:38 +02:00
Schroddinger
747be7aa09 MythicCrucible support. Currently under testing.
(cherry picked from commit 06d0f0748bfb3a69b663c2c3604b65f52cb4c5c2)
2024-10-16 21:37:54 +02:00
RePixelatedMC
3f994c18ff sync 2024-10-14 12:24:10 +02:00
RePixelatedMC
36b99ae4b0 sync 2024-10-13 21:54:37 +02:00
RePixelatedMC
25dbc4dfd6 sync 2024-10-13 21:35:58 +02:00
RePixelatedMC
c35366caa4 mid save 2024-10-11 20:21:30 +02:00
Julian Krings
76365e7875 woops 2024-10-09 13:01:31 +02:00
RePixelatedMC
57649a9ec5 sync 2024-10-09 12:57:49 +02:00
Julian Krings
03582751c5 improve CraftWorldInfo creation 2024-10-09 12:24:52 +02:00
RePixelatedMC
13369bbf32 Merge remote-tracking branch 'origin/v3.4.3' into v3.4.3 2024-10-08 13:42:39 +02:00
Julian Krings
f3b1109804 improve error handling 2024-10-07 20:46:00 +02:00
RePixelatedMC
7f530a4b32 sync 2024-10-07 20:21:14 +02:00
Julian Krings
eda44a8ace fix memory worlds 2024-10-07 16:58:59 +02:00
Julian Krings
f6a354b890 fix missing ArrayType and add tabcompletion to vanilla loot tables 2024-10-06 18:27:48 +02:00
RePixelatedMC
db14861b40 mid save 2024-10-05 20:41:29 +02:00
Julian Krings
457b691add whops 2024-10-04 15:03:09 +02:00
Brian Fopiano
d651f204f8 Merge pull request #1117 from hUwUtao/master
make javadoc buildable...
2024-10-02 00:43:56 -04:00
Julian Krings
a61787ecfe woops 2024-10-01 19:31:31 +02:00
Julian Krings
093d77bf8a implement MemoryWorlds 2024-10-01 19:29:29 +02:00
RePixelatedMC
0101130d7a Weee 2024-09-30 20:21:01 +02:00
RePixelatedMC
a3b2a17e2d Merge remote-tracking branch 'mcwg/v3.4.3' into v3.4.3 2024-09-30 08:19:46 +02:00
RePixelatedMC
ff0a68c4f6 Mid changes - SYNC 2024-09-30 08:19:00 +02:00
Julian Krings
e0034dd718 implement vanilla loottables 2024-09-29 20:44:49 +02:00
RePixelatedMC
7c6df58c15 Saving chunks still is a problem though 2024-09-29 19:00:52 +02:00
RePixelatedMC
5d5c5ba9f4 Oh well 2024-09-28 18:43:05 +02:00
RePixelatedMC
85aefcd206 mid save 2024-09-28 18:03:07 +02:00
RePixelatedMC
34a67dc781 Merge remote-tracking branch 'mcwg/v3.4.3' into v3.4.3 2024-09-28 14:35:37 +02:00
RePixelatedMC
1a96128321 backup 2024-09-28 14:24:14 +02:00
RePixelatedMC
1b0f4e6af6 Merge remote-tracking branch 'mcwg/v3.4.3' into v3.4.3 2024-09-28 14:22:44 +02:00
RePixelatedMC
9eb01dd6ca Merge remote-tracking branch 'mcwg/v3.4.3' into v3.4.3 2024-09-28 14:22:05 +02:00
RePixelatedMC
bb6778dc63 Merge remote-tracking branch 'mcwg/v3.4.3' into v3.4.3 2024-09-28 14:20:38 +02:00
RePixelatedMC
9a24627ceb backup 2024-09-27 17:07:01 +02:00
RePixelatedMC
7583b91d46 Merge remote-tracking branch 'newIris/v3.4.3' into v3.4.3 2024-09-27 14:53:07 +02:00
Julian Krings
c418962411 Merge remote-tracking branch 'origin/v3.4.3' into v3.4.3 2024-09-26 21:23:07 +02:00
Julian Krings
8e8366b318 add jigsaw structure marker 2024-09-26 20:56:56 +02:00
RePixelatedMC
a4ad83d462 stuff 2024-09-25 19:21:43 +02:00
RePixelatedMC
ddf0e79a7e few new options!
- Disable caves
- Basic deepslate layer
2024-09-25 19:19:52 +02:00
stdpi
2bd3ac7a9b Merge branch 'VolmitSoftware:master' into master 2024-09-25 02:00:31 +07:00
Brian Neumann-Fopiano
5e437b34e3 Revert "Update LICENSE.md"
This reverts commit 6c6c9654c1.
2024-09-24 14:58:10 -04:00
stdpi
a5ef89a128 add irisDev task to copy javadocs and sources jar 2024-09-22 17:35:00 +07:00
stdpi
558b8e4eca add more jar package (javadocJar sourcesJar) 2024-09-22 16:09:48 +07:00
stdpi
0a001b8a63 fix: javadoc tolerate bad html
and match encoding, ugh
2024-09-22 04:48:18 +07:00
Brian Fopiano
6c6c9654c1 Update LICENSE.md 2024-09-09 16:54:57 -04:00
74 changed files with 5274 additions and 1437 deletions

View File

@@ -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;

View File

@@ -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'

View File

@@ -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..");

View File

@@ -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;

View File

@@ -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.");
}
}
}
}

View File

@@ -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<>();

View File

@@ -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;

View File

@@ -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!"));

View File

@@ -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.");
}
}
}

View File

@@ -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!");

View File

@@ -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();
}
}
}

View File

@@ -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.");
}
}
}
}

View File

@@ -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();
}
}

View File

@@ -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);

View File

@@ -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");
}
}

View File

@@ -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);
}

View File

@@ -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;
}

View File

@@ -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;

View File

@@ -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) { }
}

View File

@@ -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;
}
}

View File

@@ -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;
}
}

View File

@@ -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());

View File

@@ -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 ");
}
}
}

View File

@@ -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();

View File

@@ -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...");

View File

@@ -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);
}
}
}
}

View File

@@ -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();
}
}
}

View File

@@ -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
}
}
}

View File

@@ -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) {

View File

@@ -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));
}
}
}
}

View File

@@ -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,10 +537,10 @@ 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()) {
if (PaperLib.isPaper() && getWorld().hasRealWorld()) {
PaperLib.getChunkAtAsync(getWorld().realWorld(), x >> 4, z >> 4).thenAccept((c) -> {
Runnable r = () -> {
for (ItemStack i : items) {

View File

@@ -3,30 +3,107 @@ 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;
}
}
}

View File

@@ -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);
}

View File

@@ -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);
}

View File

@@ -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());
}

View File

@@ -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() {

View File

@@ -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);

View File

@@ -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")

View File

@@ -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;

View File

@@ -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;

View File

@@ -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;

View File

@@ -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,
}

View 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();
}
}
}

View File

@@ -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<>();
}
}

View File

@@ -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;
});
}
}

View File

@@ -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");
}
}

View File

@@ -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());

View File

@@ -30,4 +30,6 @@ public @interface ArrayType {
Class<?> type();
int min() default 0;
int max() default Integer.MAX_VALUE;
}

View File

@@ -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));
}
}

View File

@@ -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;

View File

@@ -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;

View File

@@ -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());
}
}

View File

@@ -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());
}
}

View File

@@ -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;

View File

@@ -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();
}
}

View File

@@ -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);
}
}
}

View 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);
}
}

View 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);
}

View File

@@ -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);
}
}
}
}
}

View File

@@ -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()) {

View File

@@ -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);
}
}
}
}
}

View File

@@ -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()) {

View File

@@ -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);
}
}
}
}
}

View File

@@ -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()) {

View File

@@ -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);
}
}
}
}
}

View File

@@ -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()) {

View File

@@ -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);
}
}
}
}
}

View File

@@ -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()) {

View File

@@ -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);
}
}
}
}
}

View File

@@ -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);
}
}

View File

@@ -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);
}
}
}
}
}

View File

@@ -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);
}
}

View File

@@ -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);
}
}
}
}
}

View File

@@ -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);
}
}