mirror of
https://github.com/VolmitSoftware/Iris.git
synced 2026-05-19 16:10:42 +00:00
Bulk changes and fixes
namely the chunk issue, and objects not wantingto place on cave tops.
This commit is contained in:
@@ -32,6 +32,10 @@ import art.arcane.iris.core.link.IrisPapiExpansion;
|
|||||||
import art.arcane.iris.core.link.MultiverseCoreLink;
|
import art.arcane.iris.core.link.MultiverseCoreLink;
|
||||||
import art.arcane.iris.core.loader.IrisData;
|
import art.arcane.iris.core.loader.IrisData;
|
||||||
import art.arcane.iris.core.nms.INMS;
|
import art.arcane.iris.core.nms.INMS;
|
||||||
|
import art.arcane.iris.core.pack.BrokenPackException;
|
||||||
|
import art.arcane.iris.core.pack.PackValidationRegistry;
|
||||||
|
import art.arcane.iris.core.pack.PackValidationResult;
|
||||||
|
import art.arcane.iris.core.pack.PackValidator;
|
||||||
import art.arcane.iris.core.service.StudioSVC;
|
import art.arcane.iris.core.service.StudioSVC;
|
||||||
import art.arcane.iris.core.tools.IrisToolbelt;
|
import art.arcane.iris.core.tools.IrisToolbelt;
|
||||||
import art.arcane.iris.engine.EnginePanic;
|
import art.arcane.iris.engine.EnginePanic;
|
||||||
@@ -537,6 +541,7 @@ public class Iris extends VolmitPlugin implements Listener {
|
|||||||
IO.delete(new File("iris"));
|
IO.delete(new File("iris"));
|
||||||
compat = IrisCompat.configured(getDataFile("compat.json"));
|
compat = IrisCompat.configured(getDataFile("compat.json"));
|
||||||
ServerConfigurator.configure();
|
ServerConfigurator.configure();
|
||||||
|
validateAllPacks();
|
||||||
IrisSafeguard.execute();
|
IrisSafeguard.execute();
|
||||||
getSender().setTag(getTag());
|
getSender().setTag(getTag());
|
||||||
IrisSafeguard.splash();
|
IrisSafeguard.splash();
|
||||||
@@ -1008,6 +1013,16 @@ public class Iris extends VolmitPlugin implements Listener {
|
|||||||
Iris.debug("Default World Generator Called for " + worldName + " using ID: " + id);
|
Iris.debug("Default World Generator Called for " + worldName + " using ID: " + id);
|
||||||
if (id == null || id.isEmpty()) id = IrisSettings.get().getGenerator().getDefaultWorldType();
|
if (id == null || id.isEmpty()) id = IrisSettings.get().getGenerator().getDefaultWorldType();
|
||||||
Iris.debug("Generator ID: " + id + " requested by bukkit/plugin");
|
Iris.debug("Generator ID: " + id + " requested by bukkit/plugin");
|
||||||
|
|
||||||
|
PackValidationResult validation = PackValidationRegistry.get(id);
|
||||||
|
if (validation != null && !validation.isLoadable()) {
|
||||||
|
Iris.error("Refusing to create world '" + worldName + "' using broken pack '" + id + "':");
|
||||||
|
for (String reason : validation.getBlockingErrors()) {
|
||||||
|
Iris.error(" - " + reason);
|
||||||
|
}
|
||||||
|
throw new BrokenPackException(id, validation.getBlockingErrors());
|
||||||
|
}
|
||||||
|
|
||||||
IrisDimension dim = loadDimension(worldName, id);
|
IrisDimension dim = loadDimension(worldName, id);
|
||||||
if (dim == null) {
|
if (dim == null) {
|
||||||
throw new RuntimeException("Can't find dimension " + id + "!");
|
throw new RuntimeException("Can't find dimension " + id + "!");
|
||||||
@@ -1039,6 +1054,38 @@ public class Iris extends VolmitPlugin implements Listener {
|
|||||||
return new BukkitChunkGenerator(w, false, ff, dim.getLoadKey());
|
return new BukkitChunkGenerator(w, false, ff, dim.getLoadKey());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static void validateAllPacks() {
|
||||||
|
File packsRoot = Iris.instance.getDataFolder("packs");
|
||||||
|
File[] packDirs = packsRoot.listFiles(File::isDirectory);
|
||||||
|
if (packDirs == null || packDirs.length == 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
PackValidationRegistry.clear();
|
||||||
|
for (File packDir : packDirs) {
|
||||||
|
try {
|
||||||
|
PackValidationResult result = PackValidator.validate(packDir);
|
||||||
|
PackValidationRegistry.publish(result);
|
||||||
|
if (!result.isLoadable()) {
|
||||||
|
Iris.error("Pack '" + result.getPackName() + "' FAILED validation - world/studio creation will be refused. Reasons:");
|
||||||
|
for (String reason : result.getBlockingErrors()) {
|
||||||
|
Iris.error(" - " + reason);
|
||||||
|
}
|
||||||
|
} else if (!result.getWarnings().isEmpty() || !result.getRemovedUnusedFiles().isEmpty()) {
|
||||||
|
Iris.info("Pack '" + result.getPackName() + "' validated ("
|
||||||
|
+ result.getRemovedUnusedFiles().size() + " unused file(s) quarantined to .iris-trash/, "
|
||||||
|
+ result.getWarnings().size() + " warning(s)).");
|
||||||
|
for (String warning : result.getWarnings()) {
|
||||||
|
Iris.warn(" [" + result.getPackName() + "] " + warning);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Iris.success("Pack '" + result.getPackName() + "' validated.");
|
||||||
|
}
|
||||||
|
} catch (Throwable e) {
|
||||||
|
Iris.reportError("Pack validation failed for '" + packDir.getName() + "'", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
public static IrisDimension loadDimension(@NonNull String worldName, @NonNull String id) {
|
public static IrisDimension loadDimension(@NonNull String worldName, @NonNull String id) {
|
||||||
File pack = new File(Bukkit.getWorldContainer(), String.join(File.separator, worldName, "iris", "pack"));
|
File pack = new File(Bukkit.getWorldContainer(), String.join(File.separator, worldName, "iris", "pack"));
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -185,6 +185,13 @@ public class ServerConfigurator {
|
|||||||
DimensionHeight height = new DimensionHeight(fixer);
|
DimensionHeight height = new DimensionHeight(fixer);
|
||||||
KList<File> baseFolders = getDatapacksFolder();
|
KList<File> baseFolders = getDatapacksFolder();
|
||||||
KList<File> folders = collectInstallDatapackFolders(baseFolders, extraWorldDatapackFoldersByPack);
|
KList<File> folders = collectInstallDatapackFolders(baseFolders, extraWorldDatapackFoldersByPack);
|
||||||
|
if (fullInstall) {
|
||||||
|
if (anyDimensionHasVanillaStructures()) {
|
||||||
|
VanillaDatapackDumper.dumpIfNeeded(baseFolders);
|
||||||
|
} else {
|
||||||
|
VanillaDatapackDumper.removeIfPresent(baseFolders);
|
||||||
|
}
|
||||||
|
}
|
||||||
if (includeExternal) {
|
if (includeExternal) {
|
||||||
installExternalDataPacks(baseFolders, extraWorldDatapackFoldersByPack);
|
installExternalDataPacks(baseFolders, extraWorldDatapackFoldersByPack);
|
||||||
}
|
}
|
||||||
@@ -254,6 +261,9 @@ public class ServerConfigurator {
|
|||||||
if (summary.getLegacyWorldCopyRemovals() > 0) {
|
if (summary.getLegacyWorldCopyRemovals() > 0) {
|
||||||
Iris.verbose("Removed " + summary.getLegacyWorldCopyRemovals() + " legacy managed world datapack copies.");
|
Iris.verbose("Removed " + summary.getLegacyWorldCopyRemovals() + " legacy managed world datapack copies.");
|
||||||
}
|
}
|
||||||
|
if (summary.getSkippedExistingRequests() > 0) {
|
||||||
|
Iris.verbose("Reused " + summary.getSkippedExistingRequests() + " already-installed external datapack(s) (no download/projection).");
|
||||||
|
}
|
||||||
int loadedDatapackCount = Math.max(0, summary.getRequests() - summary.getOptionalFailures() - summary.getRequiredFailures());
|
int loadedDatapackCount = Math.max(0, summary.getRequests() - summary.getOptionalFailures() - summary.getRequiredFailures());
|
||||||
Iris.info("Loaded Datapacks into Iris: " + loadedDatapackCount + "!");
|
Iris.info("Loaded Datapacks into Iris: " + loadedDatapackCount + "!");
|
||||||
if (summary.getRequiredFailures() > 0) {
|
if (summary.getRequiredFailures() > 0) {
|
||||||
@@ -261,6 +271,28 @@ public class ServerConfigurator {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static boolean anyDimensionHasVanillaStructures() {
|
||||||
|
try (Stream<IrisData> stream = allPacks()) {
|
||||||
|
return stream.anyMatch(data -> {
|
||||||
|
ResourceLoader<IrisDimension> loader = data.getDimensionLoader();
|
||||||
|
if (loader == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
String[] keys = loader.getPossibleKeys();
|
||||||
|
if (keys == null || keys.length == 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
for (String key : keys) {
|
||||||
|
IrisDimension dim = loader.load(key);
|
||||||
|
if (dim != null && dim.isVanillaStructures()) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private static boolean shouldDeferInstallUntilWorldsReady() {
|
private static boolean shouldDeferInstallUntilWorldsReady() {
|
||||||
String forcedMainWorld = IrisSettings.get().getGeneral().forceMainWorld;
|
String forcedMainWorld = IrisSettings.get().getGeneral().forceMainWorld;
|
||||||
if (forcedMainWorld != null && !forcedMainWorld.isBlank()) {
|
if (forcedMainWorld != null && !forcedMainWorld.isBlank()) {
|
||||||
@@ -370,17 +402,9 @@ public class ServerConfigurator {
|
|||||||
targetPack,
|
targetPack,
|
||||||
environment,
|
environment,
|
||||||
definition.isRequired(),
|
definition.isRequired(),
|
||||||
definition.isReplaceVanilla(),
|
definition.isReplace(),
|
||||||
definition.isSupportSmartBore(),
|
|
||||||
definition.getReplaceTargets(),
|
|
||||||
definition.getStructureAliases(),
|
|
||||||
definition.getStructureSetAliases(),
|
|
||||||
definition.getTemplateAliases(),
|
|
||||||
definition.getStructurePatches(),
|
|
||||||
Set.of(),
|
Set.of(),
|
||||||
scopeKey,
|
scopeKey
|
||||||
!definition.isReplaceVanilla(),
|
|
||||||
Set.of()
|
|
||||||
);
|
);
|
||||||
dedupeMerges += mergeDeduplicatedRequest(deduplicated, request);
|
dedupeMerges += mergeDeduplicatedRequest(deduplicated, request);
|
||||||
unscopedRequests++;
|
unscopedRequests++;
|
||||||
@@ -389,8 +413,7 @@ public class ServerConfigurator {
|
|||||||
+ ", dimension=" + dimension.getLoadKey()
|
+ ", dimension=" + dimension.getLoadKey()
|
||||||
+ ", scope=dimension-root"
|
+ ", scope=dimension-root"
|
||||||
+ ", forcedBiomes=0"
|
+ ", forcedBiomes=0"
|
||||||
+ ", replaceVanilla=" + definition.isReplaceVanilla()
|
+ ", replace=" + definition.isReplace()
|
||||||
+ ", alongsideMode=" + (!definition.isReplaceVanilla())
|
|
||||||
+ ", required=" + definition.isRequired());
|
+ ", required=" + definition.isRequired());
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@@ -403,16 +426,8 @@ public class ServerConfigurator {
|
|||||||
environment,
|
environment,
|
||||||
group.required(),
|
group.required(),
|
||||||
group.replaceVanilla(),
|
group.replaceVanilla(),
|
||||||
definition.isSupportSmartBore(),
|
|
||||||
definition.getReplaceTargets(),
|
|
||||||
definition.getStructureAliases(),
|
|
||||||
definition.getStructureSetAliases(),
|
|
||||||
definition.getTemplateAliases(),
|
|
||||||
definition.getStructurePatches(),
|
|
||||||
group.forcedBiomeKeys(),
|
group.forcedBiomeKeys(),
|
||||||
group.scopeKey(),
|
group.scopeKey()
|
||||||
!group.replaceVanilla(),
|
|
||||||
Set.of()
|
|
||||||
);
|
);
|
||||||
dedupeMerges += mergeDeduplicatedRequest(deduplicated, request);
|
dedupeMerges += mergeDeduplicatedRequest(deduplicated, request);
|
||||||
scopedRequests++;
|
scopedRequests++;
|
||||||
@@ -421,8 +436,7 @@ public class ServerConfigurator {
|
|||||||
+ ", dimension=" + dimension.getLoadKey()
|
+ ", dimension=" + dimension.getLoadKey()
|
||||||
+ ", scope=" + group.source()
|
+ ", scope=" + group.source()
|
||||||
+ ", forcedBiomes=" + group.forcedBiomeKeys().size()
|
+ ", forcedBiomes=" + group.forcedBiomeKeys().size()
|
||||||
+ ", replaceVanilla=" + group.replaceVanilla()
|
+ ", replace=" + group.replaceVanilla()
|
||||||
+ ", alongsideMode=" + (!group.replaceVanilla())
|
|
||||||
+ ", required=" + group.required());
|
+ ", required=" + group.required());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -507,9 +521,9 @@ public class ServerConfigurator {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
boolean replaceVanilla = binding.getReplaceVanillaOverride() == null
|
boolean replaceVanilla = binding.getReplaceOverride() == null
|
||||||
? definition.isReplaceVanilla()
|
? definition.isReplace()
|
||||||
: binding.getReplaceVanillaOverride();
|
: binding.getReplaceOverride();
|
||||||
boolean required = binding.getRequiredOverride() == null
|
boolean required = binding.getRequiredOverride() == null
|
||||||
? definition.isRequired()
|
? definition.isRequired()
|
||||||
: binding.getRequiredOverride();
|
: binding.getRequiredOverride();
|
||||||
@@ -551,9 +565,9 @@ public class ServerConfigurator {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
boolean replaceVanilla = binding.getReplaceVanillaOverride() == null
|
boolean replaceVanilla = binding.getReplaceOverride() == null
|
||||||
? definition.isReplaceVanilla()
|
? definition.isReplace()
|
||||||
: binding.getReplaceVanillaOverride();
|
: binding.getReplaceOverride();
|
||||||
boolean required = binding.getRequiredOverride() == null
|
boolean required = binding.getRequiredOverride() == null
|
||||||
? definition.isRequired()
|
? definition.isRequired()
|
||||||
: binding.getRequiredOverride();
|
: binding.getRequiredOverride();
|
||||||
|
|||||||
@@ -0,0 +1,153 @@
|
|||||||
|
package art.arcane.iris.core;
|
||||||
|
|
||||||
|
import art.arcane.iris.Iris;
|
||||||
|
import art.arcane.iris.core.nms.INMS;
|
||||||
|
import art.arcane.iris.core.nms.datapack.DataVersion;
|
||||||
|
import art.arcane.volmlib.util.collection.KList;
|
||||||
|
import art.arcane.volmlib.util.json.JSONObject;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.FileOutputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.nio.file.Files;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.zip.ZipEntry;
|
||||||
|
import java.util.zip.ZipOutputStream;
|
||||||
|
|
||||||
|
public final class VanillaDatapackDumper {
|
||||||
|
private static final String DUMP_ZIP_NAME = "00-iris-vanilla-worldgen.zip";
|
||||||
|
private static final String MARKER_FILE = "vanilla-datapack-version.txt";
|
||||||
|
private static final String PACK_DESCRIPTION = "Iris extracted vanilla worldgen datapack.";
|
||||||
|
|
||||||
|
private VanillaDatapackDumper() {
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void dumpIfNeeded(KList<File> datapackFolders) {
|
||||||
|
if (datapackFolders == null || datapackFolders.isEmpty()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
String currentVersion = resolveVersionKey();
|
||||||
|
if (currentVersion == null) {
|
||||||
|
Iris.warn("Unable to determine server version for vanilla datapack dump.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
boolean needsDump = false;
|
||||||
|
for (File folder : datapackFolders) {
|
||||||
|
File zip = new File(folder, DUMP_ZIP_NAME);
|
||||||
|
File marker = new File(folder, MARKER_FILE);
|
||||||
|
if (!zip.exists() || !marker.exists() || !currentVersion.equals(readMarker(marker))) {
|
||||||
|
needsDump = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!needsDump) {
|
||||||
|
Iris.verbose("Vanilla datapack is up to date, skipping dump.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Iris.info("Dumping vanilla worldgen datapack...");
|
||||||
|
Map<String, byte[]> entries = INMS.get().extractVanillaDatapack();
|
||||||
|
if (entries.isEmpty()) {
|
||||||
|
Iris.warn("Vanilla datapack extraction returned no entries. Skipping dump.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
byte[] zipBytes = buildZip(entries);
|
||||||
|
if (zipBytes == null) {
|
||||||
|
Iris.error("Failed to build vanilla datapack ZIP.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
int written = 0;
|
||||||
|
for (File folder : datapackFolders) {
|
||||||
|
folder.mkdirs();
|
||||||
|
File zip = new File(folder, DUMP_ZIP_NAME);
|
||||||
|
File marker = new File(folder, MARKER_FILE);
|
||||||
|
try {
|
||||||
|
Files.write(zip.toPath(), zipBytes);
|
||||||
|
Files.writeString(marker.toPath(), currentVersion, StandardCharsets.UTF_8);
|
||||||
|
written++;
|
||||||
|
} catch (IOException e) {
|
||||||
|
Iris.error("Failed to write vanilla datapack to " + folder.getAbsolutePath());
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Iris.info("Vanilla datapack written to " + written + " world(s) with " + entries.size() + " entries.");
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void removeIfPresent(KList<File> datapackFolders) {
|
||||||
|
if (datapackFolders == null || datapackFolders.isEmpty()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
int removed = 0;
|
||||||
|
for (File folder : datapackFolders) {
|
||||||
|
File zip = new File(folder, DUMP_ZIP_NAME);
|
||||||
|
File marker = new File(folder, MARKER_FILE);
|
||||||
|
if (zip.exists() && zip.delete()) {
|
||||||
|
removed++;
|
||||||
|
}
|
||||||
|
if (marker.exists()) {
|
||||||
|
marker.delete();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (removed > 0) {
|
||||||
|
Iris.info("Removed vanilla datapack from " + removed + " world(s) (vanillaStructures disabled).");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static byte[] buildZip(Map<String, byte[]> entries) {
|
||||||
|
try {
|
||||||
|
java.io.ByteArrayOutputStream baos = new java.io.ByteArrayOutputStream();
|
||||||
|
try (ZipOutputStream zos = new ZipOutputStream(baos)) {
|
||||||
|
zos.putNextEntry(new ZipEntry("pack.mcmeta"));
|
||||||
|
zos.write(buildPackMeta());
|
||||||
|
zos.closeEntry();
|
||||||
|
|
||||||
|
for (Map.Entry<String, byte[]> entry : entries.entrySet()) {
|
||||||
|
zos.putNextEntry(new ZipEntry(entry.getKey()));
|
||||||
|
zos.write(entry.getValue());
|
||||||
|
zos.closeEntry();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return baos.toByteArray();
|
||||||
|
} catch (IOException e) {
|
||||||
|
Iris.error("Failed to build vanilla datapack ZIP");
|
||||||
|
e.printStackTrace();
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static byte[] buildPackMeta() {
|
||||||
|
int packFormat = INMS.get().getDataVersion().getPackFormat();
|
||||||
|
JSONObject root = new JSONObject();
|
||||||
|
JSONObject pack = new JSONObject();
|
||||||
|
pack.put("description", PACK_DESCRIPTION);
|
||||||
|
pack.put("pack_format", packFormat);
|
||||||
|
root.put("pack", pack);
|
||||||
|
return root.toString(4).getBytes(StandardCharsets.UTF_8);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String resolveVersionKey() {
|
||||||
|
try {
|
||||||
|
DataVersion dv = INMS.get().getDataVersion();
|
||||||
|
return dv.getVersion() + ":" + dv.getPackFormat();
|
||||||
|
} catch (Exception e) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String readMarker(File marker) {
|
||||||
|
try {
|
||||||
|
return Files.readString(marker.toPath(), StandardCharsets.UTF_8).trim();
|
||||||
|
} catch (IOException e) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -30,7 +30,6 @@ import art.arcane.iris.core.tools.IrisToolbelt;
|
|||||||
import art.arcane.iris.engine.framework.Engine;
|
import art.arcane.iris.engine.framework.Engine;
|
||||||
import art.arcane.iris.engine.object.IrisDimension;
|
import art.arcane.iris.engine.object.IrisDimension;
|
||||||
import art.arcane.iris.engine.object.IrisExternalDatapack;
|
import art.arcane.iris.engine.object.IrisExternalDatapack;
|
||||||
import art.arcane.iris.engine.object.IrisExternalDatapackReplaceTargets;
|
|
||||||
import art.arcane.iris.engine.platform.ChunkReplacementListener;
|
import art.arcane.iris.engine.platform.ChunkReplacementListener;
|
||||||
import art.arcane.iris.engine.platform.ChunkReplacementOptions;
|
import art.arcane.iris.engine.platform.ChunkReplacementOptions;
|
||||||
import art.arcane.iris.engine.platform.PlatformChunkGenerator;
|
import art.arcane.iris.engine.platform.PlatformChunkGenerator;
|
||||||
@@ -99,6 +98,7 @@ public class CommandIris implements DirectorExecutor {
|
|||||||
private CommandEdit edit;
|
private CommandEdit edit;
|
||||||
private CommandFind find;
|
private CommandFind find;
|
||||||
private CommandDeveloper developer;
|
private CommandDeveloper developer;
|
||||||
|
private CommandPack pack;
|
||||||
public static boolean worldCreation = false;
|
public static boolean worldCreation = false;
|
||||||
private static final AtomicReference<Thread> mainWorld = new AtomicReference<>();
|
private static final AtomicReference<Thread> mainWorld = new AtomicReference<>();
|
||||||
String WorldEngine;
|
String WorldEngine;
|
||||||
@@ -591,42 +591,7 @@ public class CommandIris implements DirectorExecutor {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private static Map<String, Set<String>> buildExternalLocateFallbackById(KList<IrisExternalDatapack> externalDatapacks) {
|
private static Map<String, Set<String>> buildExternalLocateFallbackById(KList<IrisExternalDatapack> externalDatapacks) {
|
||||||
Map<String, Set<String>> mapped = new ConcurrentHashMap<>();
|
return new ConcurrentHashMap<>();
|
||||||
if (externalDatapacks == null || externalDatapacks.isEmpty()) {
|
|
||||||
return mapped;
|
|
||||||
}
|
|
||||||
|
|
||||||
for (IrisExternalDatapack externalDatapack : externalDatapacks) {
|
|
||||||
if (externalDatapack == null) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
String url = externalDatapack.getUrl() == null ? "" : externalDatapack.getUrl().trim();
|
|
||||||
String entryId = externalDatapack.getId() == null ? "" : externalDatapack.getId().trim();
|
|
||||||
String normalizedEntryId = normalizeLocateExternalToken(entryId.isBlank() ? url : entryId);
|
|
||||||
if (normalizedEntryId.isBlank()) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
IrisExternalDatapackReplaceTargets replaceTargets = externalDatapack.getReplaceTargets();
|
|
||||||
if (replaceTargets == null || replaceTargets.getStructures() == null || replaceTargets.getStructures().isEmpty()) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
LinkedHashSet<String> structures = new LinkedHashSet<>();
|
|
||||||
for (String structure : replaceTargets.getStructures()) {
|
|
||||||
String normalizedStructure = normalizeLocateStructureToken(structure);
|
|
||||||
if (!normalizedStructure.isBlank()) {
|
|
||||||
structures.add(normalizedStructure);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!structures.isEmpty()) {
|
|
||||||
mapped.put(normalizedEntryId, Set.copyOf(structures));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return mapped;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static String normalizeLocateStructureToken(String structure) {
|
private static String normalizeLocateStructureToken(String structure) {
|
||||||
@@ -803,7 +768,7 @@ public class CommandIris implements DirectorExecutor {
|
|||||||
public void download(
|
public void download(
|
||||||
@Param(name = "pack", description = "The pack to download", defaultValue = "overworld", aliases = "project")
|
@Param(name = "pack", description = "The pack to download", defaultValue = "overworld", aliases = "project")
|
||||||
String pack,
|
String pack,
|
||||||
@Param(name = "branch", description = "The branch to download from", defaultValue = "main")
|
@Param(name = "branch", description = "The branch to download from", defaultValue = "stable")
|
||||||
String branch,
|
String branch,
|
||||||
//@Param(name = "trim", description = "Whether or not to download a trimmed version (do not enable when editing)", defaultValue = "false")
|
//@Param(name = "trim", description = "Whether or not to download a trimmed version (do not enable when editing)", defaultValue = "false")
|
||||||
//boolean trim,
|
//boolean trim,
|
||||||
|
|||||||
@@ -0,0 +1,164 @@
|
|||||||
|
/*
|
||||||
|
* 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 art.arcane.iris.core.commands;
|
||||||
|
|
||||||
|
import art.arcane.iris.Iris;
|
||||||
|
import art.arcane.iris.core.pack.PackValidationRegistry;
|
||||||
|
import art.arcane.iris.core.pack.PackValidationResult;
|
||||||
|
import art.arcane.iris.core.pack.PackValidator;
|
||||||
|
import art.arcane.iris.util.common.director.DirectorExecutor;
|
||||||
|
import art.arcane.iris.util.common.format.C;
|
||||||
|
import art.arcane.iris.util.common.plugin.VolmitSender;
|
||||||
|
import art.arcane.volmlib.util.director.annotations.Director;
|
||||||
|
import art.arcane.volmlib.util.director.annotations.Param;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
|
||||||
|
@Director(name = "pack", aliases = {"pk"}, description = "Pack validation and maintenance")
|
||||||
|
public class CommandPack implements DirectorExecutor {
|
||||||
|
|
||||||
|
@Director(description = "Validate a pack (or all packs) and re-publish results", aliases = {"v", "check"})
|
||||||
|
public void validate(
|
||||||
|
@Param(description = "The pack folder name to validate (leave empty for all)", defaultValue = "")
|
||||||
|
String pack
|
||||||
|
) {
|
||||||
|
VolmitSender s = sender();
|
||||||
|
File packsRoot = Iris.instance.getDataFolder("packs");
|
||||||
|
if (!packsRoot.isDirectory()) {
|
||||||
|
s.sendMessage(C.RED + "packs/ folder not found.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (pack == null || pack.isBlank()) {
|
||||||
|
File[] dirs = packsRoot.listFiles(File::isDirectory);
|
||||||
|
if (dirs == null || dirs.length == 0) {
|
||||||
|
s.sendMessage(C.YELLOW + "No packs to validate.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
int broken = 0;
|
||||||
|
for (File dir : dirs) {
|
||||||
|
PackValidationResult result = runValidate(s, dir);
|
||||||
|
if (result != null && !result.isLoadable()) {
|
||||||
|
broken++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
s.sendMessage(C.GREEN + "Validation complete. Broken packs: " + broken + "/" + dirs.length);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
File target = new File(packsRoot, pack);
|
||||||
|
if (!target.isDirectory()) {
|
||||||
|
s.sendMessage(C.RED + "Pack '" + pack + "' not found under packs/.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
runValidate(s, target);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Director(description = "Restore most recent trashed files for a pack", aliases = {"r", "undelete"})
|
||||||
|
public void restore(
|
||||||
|
@Param(description = "The pack folder name to restore")
|
||||||
|
String pack
|
||||||
|
) {
|
||||||
|
VolmitSender s = sender();
|
||||||
|
if (pack == null || pack.isBlank()) {
|
||||||
|
s.sendMessage(C.RED + "You must specify a pack name.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
File packFolder = new File(Iris.instance.getDataFolder("packs"), pack);
|
||||||
|
if (!packFolder.isDirectory()) {
|
||||||
|
s.sendMessage(C.RED + "Pack '" + pack + "' not found under packs/.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
int restored = PackValidator.restoreTrash(packFolder);
|
||||||
|
if (restored == 0) {
|
||||||
|
s.sendMessage(C.YELLOW + "Nothing to restore for pack '" + pack + "'.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
s.sendMessage(C.GREEN + "Restored " + restored + " file(s) from the most recent trash dump for pack '" + pack + "'.");
|
||||||
|
s.sendMessage(C.GRAY + "Re-run /iris pack validate " + pack + " to re-check.");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Director(description = "Show cached validation status for a pack", aliases = {"s", "info"})
|
||||||
|
public void status(
|
||||||
|
@Param(description = "The pack folder name", defaultValue = "")
|
||||||
|
String pack
|
||||||
|
) {
|
||||||
|
VolmitSender s = sender();
|
||||||
|
if (pack == null || pack.isBlank()) {
|
||||||
|
if (PackValidationRegistry.snapshot().isEmpty()) {
|
||||||
|
s.sendMessage(C.YELLOW + "No validation results recorded. Run /iris pack validate first.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
PackValidationRegistry.snapshot().forEach((name, result) -> {
|
||||||
|
String tag = result.isLoadable() ? (C.GREEN + "OK") : (C.RED + "BROKEN");
|
||||||
|
s.sendMessage(tag + C.RESET + " " + name
|
||||||
|
+ C.GRAY + " (blocking=" + result.getBlockingErrors().size()
|
||||||
|
+ ", warnings=" + result.getWarnings().size()
|
||||||
|
+ ", trashed=" + result.getRemovedUnusedFiles().size() + ")");
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
PackValidationResult result = PackValidationRegistry.get(pack);
|
||||||
|
if (result == null) {
|
||||||
|
s.sendMessage(C.YELLOW + "No validation result for '" + pack + "'. Run /iris pack validate " + pack + ".");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
reportResult(s, result);
|
||||||
|
}
|
||||||
|
|
||||||
|
private PackValidationResult runValidate(VolmitSender s, File packFolder) {
|
||||||
|
try {
|
||||||
|
PackValidationResult result = PackValidator.validate(packFolder);
|
||||||
|
PackValidationRegistry.publish(result);
|
||||||
|
reportResult(s, result);
|
||||||
|
return result;
|
||||||
|
} catch (Throwable e) {
|
||||||
|
Iris.reportError("Pack validation failed for '" + packFolder.getName() + "'", e);
|
||||||
|
s.sendMessage(C.RED + "Validation of '" + packFolder.getName() + "' failed: " + e.getMessage());
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void reportResult(VolmitSender s, PackValidationResult result) {
|
||||||
|
if (result.isLoadable()) {
|
||||||
|
s.sendMessage(C.GREEN + "Pack '" + result.getPackName() + "' is loadable."
|
||||||
|
+ C.GRAY + " (warnings=" + result.getWarnings().size()
|
||||||
|
+ ", trashed=" + result.getRemovedUnusedFiles().size() + ")");
|
||||||
|
} else {
|
||||||
|
s.sendMessage(C.RED + "Pack '" + result.getPackName() + "' is BROKEN:");
|
||||||
|
for (String reason : result.getBlockingErrors()) {
|
||||||
|
s.sendMessage(C.RED + " - " + reason);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
int wMax = Math.min(10, result.getWarnings().size());
|
||||||
|
for (int i = 0; i < wMax; i++) {
|
||||||
|
s.sendMessage(C.YELLOW + " ! " + result.getWarnings().get(i));
|
||||||
|
}
|
||||||
|
if (result.getWarnings().size() > wMax) {
|
||||||
|
s.sendMessage(C.GRAY + " ... and " + (result.getWarnings().size() - wMax) + " more warning(s).");
|
||||||
|
}
|
||||||
|
int tMax = Math.min(10, result.getRemovedUnusedFiles().size());
|
||||||
|
for (int i = 0; i < tMax; i++) {
|
||||||
|
s.sendMessage(C.GRAY + " ~ trashed " + result.getRemovedUnusedFiles().get(i));
|
||||||
|
}
|
||||||
|
if (result.getRemovedUnusedFiles().size() > tMax) {
|
||||||
|
s.sendMessage(C.GRAY + " ... and " + (result.getRemovedUnusedFiles().size() - tMax) + " more trashed file(s).");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -103,7 +103,7 @@ public class CommandStudio implements DirectorExecutor {
|
|||||||
public void download(
|
public void download(
|
||||||
@Param(name = "pack", description = "The pack to download", defaultValue = "overworld", aliases = "project")
|
@Param(name = "pack", description = "The pack to download", defaultValue = "overworld", aliases = "project")
|
||||||
String pack,
|
String pack,
|
||||||
@Param(name = "branch", description = "The branch to download from", defaultValue = "master")
|
@Param(name = "branch", description = "The branch to download from", defaultValue = "stable")
|
||||||
String branch,
|
String branch,
|
||||||
//@Param(name = "trim", description = "Whether or not to download a trimmed version (do not enable when editing)", defaultValue = "false")
|
//@Param(name = "trim", description = "Whether or not to download a trimmed version (do not enable when editing)", defaultValue = "false")
|
||||||
//boolean trim,
|
//boolean trim,
|
||||||
|
|||||||
@@ -21,8 +21,12 @@ package art.arcane.iris.core.gui;
|
|||||||
import art.arcane.iris.Iris;
|
import art.arcane.iris.Iris;
|
||||||
import art.arcane.iris.core.IrisSettings;
|
import art.arcane.iris.core.IrisSettings;
|
||||||
import art.arcane.iris.core.events.IrisEngineHotloadEvent;
|
import art.arcane.iris.core.events.IrisEngineHotloadEvent;
|
||||||
|
import art.arcane.iris.core.loader.IrisData;
|
||||||
|
import art.arcane.iris.core.tools.IrisToolbelt;
|
||||||
|
import art.arcane.iris.engine.framework.Engine;
|
||||||
|
import art.arcane.iris.engine.object.IrisGenerator;
|
||||||
import art.arcane.iris.engine.object.NoiseStyle;
|
import art.arcane.iris.engine.object.NoiseStyle;
|
||||||
import art.arcane.volmlib.util.collection.KList;
|
import art.arcane.iris.engine.platform.PlatformChunkGenerator;
|
||||||
import art.arcane.volmlib.util.function.Function2;
|
import art.arcane.volmlib.util.function.Function2;
|
||||||
import art.arcane.volmlib.util.math.M;
|
import art.arcane.volmlib.util.math.M;
|
||||||
import art.arcane.volmlib.util.math.RNG;
|
import art.arcane.volmlib.util.math.RNG;
|
||||||
@@ -32,305 +36,519 @@ import art.arcane.iris.util.common.parallel.BurstExecutor;
|
|||||||
import art.arcane.iris.util.common.parallel.MultiBurst;
|
import art.arcane.iris.util.common.parallel.MultiBurst;
|
||||||
import art.arcane.iris.util.common.scheduling.J;
|
import art.arcane.iris.util.common.scheduling.J;
|
||||||
import art.arcane.volmlib.util.scheduling.PrecisionStopwatch;
|
import art.arcane.volmlib.util.scheduling.PrecisionStopwatch;
|
||||||
|
import org.bukkit.Bukkit;
|
||||||
|
import org.bukkit.World;
|
||||||
import org.bukkit.event.EventHandler;
|
import org.bukkit.event.EventHandler;
|
||||||
import org.bukkit.event.Listener;
|
import org.bukkit.event.Listener;
|
||||||
|
|
||||||
import javax.imageio.ImageIO;
|
|
||||||
import javax.swing.*;
|
import javax.swing.*;
|
||||||
|
import javax.swing.event.DocumentEvent;
|
||||||
|
import javax.swing.event.DocumentListener;
|
||||||
import java.awt.*;
|
import java.awt.*;
|
||||||
import java.awt.event.*;
|
import java.awt.event.*;
|
||||||
import java.awt.image.BufferedImage;
|
import java.awt.image.BufferedImage;
|
||||||
import java.io.File;
|
import java.awt.image.DataBufferInt;
|
||||||
import java.io.IOException;
|
import java.util.*;
|
||||||
import java.util.concurrent.locks.ReentrantLock;
|
import java.util.List;
|
||||||
import java.util.function.Supplier;
|
import java.util.function.Supplier;
|
||||||
|
|
||||||
public class NoiseExplorerGUI extends JPanel implements MouseWheelListener, Listener {
|
public class NoiseExplorerGUI extends JPanel implements MouseWheelListener, Listener {
|
||||||
|
|
||||||
private static final long serialVersionUID = 2094606939770332040L;
|
private static final long serialVersionUID = 2094606939770332040L;
|
||||||
|
private static final Color BG = new Color(24, 24, 28);
|
||||||
|
private static final Color SIDEBAR_BG = new Color(20, 20, 24);
|
||||||
|
private static final Color SIDEBAR_SELECTED = new Color(40, 50, 70);
|
||||||
|
private static final Color SIDEBAR_ITEM_COLOR = new Color(170, 170, 185);
|
||||||
|
private static final Color SEARCH_BG = new Color(30, 30, 38);
|
||||||
|
private static final Color SEARCH_FG = new Color(180, 180, 190);
|
||||||
|
private static final Color STATUS_BG = new Color(32, 32, 38, 230);
|
||||||
|
private static final Color STATUS_TEXT = new Color(180, 180, 190);
|
||||||
|
private static final Color ACCENT = new Color(90, 140, 255);
|
||||||
|
private static final Color SEPARATOR = new Color(40, 40, 50);
|
||||||
|
private static final Font STATUS_FONT = new Font(Font.MONOSPACED, Font.PLAIN, 12);
|
||||||
|
private static final Font SIDEBAR_HEADER_FONT = new Font(Font.SANS_SERIF, Font.BOLD, 11);
|
||||||
|
private static final Font SIDEBAR_ITEM_FONT = new Font(Font.SANS_SERIF, Font.PLAIN, 12);
|
||||||
|
private static final Font SEARCH_FONT = new Font(Font.SANS_SERIF, Font.PLAIN, 13);
|
||||||
|
private static final int SIDEBAR_WIDTH = 240;
|
||||||
|
private static final int[] HSB_LUT = new int[256];
|
||||||
|
|
||||||
static JComboBox<String> combo;
|
private static final String[] CATEGORY_ORDER = {
|
||||||
@SuppressWarnings("CanBeFinal")
|
"Pack Generators", "Simplex", "Perlin", "Cellular", "Iris", "Clover",
|
||||||
static boolean hd = false;
|
"Hexagon", "Vascular", "Globe", "Cubic", "Fractal", "Static",
|
||||||
static double ascale = 10;
|
"Nowhere", "Sierpinski", "Utility", "Other"
|
||||||
static double oxp = 0;
|
};
|
||||||
static double ozp = 0;
|
|
||||||
static double mxx = 0;
|
static {
|
||||||
static double mzz = 0;
|
for (int i = 0; i < 256; i++) {
|
||||||
@SuppressWarnings("CanBeFinal")
|
float n = i / 255f;
|
||||||
static boolean down = false;
|
HSB_LUT[i] = Color.HSBtoRGB(0.666f - n * 0.666f, 1f, 1f - n * 0.8f);
|
||||||
@SuppressWarnings("CanBeFinal")
|
}
|
||||||
RollingSequence r = new RollingSequence(20);
|
}
|
||||||
@SuppressWarnings("CanBeFinal")
|
|
||||||
boolean colorMode = IrisSettings.get().getGui().colorMode;
|
private final RollingSequence fpsHistory = new RollingSequence(60);
|
||||||
double scale = 1;
|
private final boolean colorMode = IrisSettings.get().getGui().colorMode;
|
||||||
CNG cng = NoiseStyle.STATIC.create(new RNG(RNG.r.nextLong()));
|
private final MultiBurst gx = MultiBurst.burst;
|
||||||
@SuppressWarnings("CanBeFinal")
|
private double scale = 1;
|
||||||
MultiBurst gx = MultiBurst.burst;
|
private double animScale = 10;
|
||||||
ReentrantLock l = new ReentrantLock();
|
private double ox = 0;
|
||||||
BufferedImage img;
|
private double oz = 0;
|
||||||
int w = 0;
|
private double animOx = 0;
|
||||||
int h = 0;
|
private double animOz = 0;
|
||||||
Function2<Double, Double, Double> generator;
|
private double lastMouseX = Double.MAX_VALUE;
|
||||||
Supplier<Function2<Double, Double, Double>> loader;
|
private double lastMouseZ = Double.MAX_VALUE;
|
||||||
double ox = 0; //Offset X
|
private double time = 0;
|
||||||
double oz = 0; //Offset Y
|
private double animTime = 0;
|
||||||
double mx = 0;
|
private int imgWidth = 0;
|
||||||
double mz = 0;
|
private int imgHeight = 0;
|
||||||
double lx = Double.MAX_VALUE; //MouseX
|
private BufferedImage img;
|
||||||
double lz = Double.MAX_VALUE; //MouseY
|
private CNG cng = NoiseStyle.STATIC.create(new RNG(RNG.r.nextLong()));
|
||||||
double t;
|
private Function2<Double, Double, Double> generator;
|
||||||
double tz;
|
private Supplier<Function2<Double, Double, Double>> loader;
|
||||||
|
private String currentName = "STATIC";
|
||||||
|
|
||||||
public NoiseExplorerGUI() {
|
public NoiseExplorerGUI() {
|
||||||
Iris.instance.registerListener(this);
|
Iris.instance.registerListener(this);
|
||||||
|
setBackground(BG);
|
||||||
addMouseWheelListener(this);
|
addMouseWheelListener(this);
|
||||||
addMouseMotionListener(new MouseMotionListener() {
|
addMouseMotionListener(new MouseMotionListener() {
|
||||||
@Override
|
@Override
|
||||||
public void mouseMoved(MouseEvent e) {
|
public void mouseMoved(MouseEvent e) {
|
||||||
Point cp = e.getPoint();
|
Point cp = e.getPoint();
|
||||||
|
lastMouseX = cp.getX();
|
||||||
lx = (cp.getX());
|
lastMouseZ = cp.getY();
|
||||||
lz = (cp.getY());
|
|
||||||
mx = lx;
|
|
||||||
mz = lz;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void mouseDragged(MouseEvent e) {
|
public void mouseDragged(MouseEvent e) {
|
||||||
Point cp = e.getPoint();
|
Point cp = e.getPoint();
|
||||||
ox += (lx - cp.getX()) * scale;
|
ox += (lastMouseX - cp.getX()) * scale;
|
||||||
oz += (lz - cp.getY()) * scale;
|
oz += (lastMouseZ - cp.getY()) * scale;
|
||||||
lx = cp.getX();
|
lastMouseX = cp.getX();
|
||||||
lz = cp.getY();
|
lastMouseZ = cp.getY();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void createAndShowGUI(Supplier<Function2<Double, Double, Double>> loader, String genName) {
|
public static void launch() {
|
||||||
JFrame frame = new JFrame("Noise Explorer: " + genName);
|
Engine engine = findActiveEngine();
|
||||||
|
EventQueue.invokeLater(() -> {
|
||||||
NoiseExplorerGUI nv = new NoiseExplorerGUI();
|
NoiseExplorerGUI nv = new NoiseExplorerGUI();
|
||||||
frame.setDefaultCloseOperation(JFrame.HIDE_ON_CLOSE);
|
buildFrame("Noise Explorer", nv, engine, null, null);
|
||||||
JLayeredPane pane = new JLayeredPane();
|
|
||||||
nv.setSize(new Dimension(1440, 820));
|
|
||||||
pane.add(nv, 1, 0);
|
|
||||||
nv.loader = loader;
|
|
||||||
nv.generator = loader.get();
|
|
||||||
frame.add(pane);
|
|
||||||
File file = Iris.getCached("Iris Icon", "https://raw.githubusercontent.com/VolmitSoftware/Iris/master/icon.png");
|
|
||||||
|
|
||||||
if (file != null) {
|
|
||||||
try {
|
|
||||||
frame.setIconImage(ImageIO.read(file));
|
|
||||||
} catch (IOException e) {
|
|
||||||
Iris.reportError(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
frame.setSize(1440, 820);
|
|
||||||
frame.setVisible(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void createAndShowGUI() {
|
|
||||||
JFrame frame = new JFrame("Noise Explorer");
|
|
||||||
NoiseExplorerGUI nv = new NoiseExplorerGUI();
|
|
||||||
frame.setDefaultCloseOperation(JFrame.HIDE_ON_CLOSE);
|
|
||||||
KList<String> li = new KList<>(NoiseStyle.values()).toStringList().sort();
|
|
||||||
combo = new JComboBox<>(li.toArray(new String[0]));
|
|
||||||
combo.setSelectedItem("STATIC");
|
|
||||||
combo.setFocusable(false);
|
|
||||||
combo.addActionListener(e -> {
|
|
||||||
@SuppressWarnings("unchecked")
|
|
||||||
String b = (String) (((JComboBox<String>) e.getSource()).getSelectedItem());
|
|
||||||
NoiseStyle s = NoiseStyle.valueOf(b);
|
|
||||||
nv.cng = s.create(RNG.r.nextParallelRNG(RNG.r.imax()));
|
|
||||||
});
|
|
||||||
|
|
||||||
combo.setSize(500, 30);
|
|
||||||
JLayeredPane pane = new JLayeredPane();
|
|
||||||
nv.setSize(new Dimension(1440, 820));
|
|
||||||
pane.add(nv, 1, 0);
|
|
||||||
pane.add(combo, 2, 0);
|
|
||||||
frame.add(pane);
|
|
||||||
File file = Iris.getCached("Iris Icon", "https://raw.githubusercontent.com/VolmitSoftware/Iris/master/icon.png");
|
|
||||||
|
|
||||||
if (file != null) {
|
|
||||||
try {
|
|
||||||
frame.setIconImage(ImageIO.read(file));
|
|
||||||
} catch (IOException e) {
|
|
||||||
Iris.reportError(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
frame.setSize(1440, 820);
|
|
||||||
frame.setVisible(true);
|
|
||||||
frame.addWindowListener(new java.awt.event.WindowAdapter() {
|
|
||||||
@Override
|
|
||||||
public void windowClosing(java.awt.event.WindowEvent windowEvent) {
|
|
||||||
Iris.instance.unregisterListener(nv);
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void launch(Supplier<Function2<Double, Double, Double>> gen, String genName) {
|
public static void launch(Supplier<Function2<Double, Double, Double>> gen, String genName) {
|
||||||
EventQueue.invokeLater(() -> createAndShowGUI(gen, genName));
|
Engine engine = findActiveEngine();
|
||||||
|
EventQueue.invokeLater(() -> {
|
||||||
|
NoiseExplorerGUI nv = new NoiseExplorerGUI();
|
||||||
|
nv.loader = gen;
|
||||||
|
nv.generator = gen.get();
|
||||||
|
nv.currentName = genName;
|
||||||
|
buildFrame("Noise Explorer: " + genName, nv, engine, gen, genName);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void launch() {
|
private static Engine findActiveEngine() {
|
||||||
EventQueue.invokeLater(NoiseExplorerGUI::createAndShowGUI);
|
try {
|
||||||
|
for (World w : new ArrayList<>(Bukkit.getWorlds())) {
|
||||||
|
try {
|
||||||
|
PlatformChunkGenerator access = IrisToolbelt.access(w);
|
||||||
|
if (access != null && access.getEngine() != null && !access.getEngine().isClosed()) {
|
||||||
|
return access.getEngine();
|
||||||
|
}
|
||||||
|
} catch (Throwable ignored) {}
|
||||||
|
}
|
||||||
|
} catch (Throwable ignored) {}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static JFrame buildFrame(String title, NoiseExplorerGUI nv, Engine engine,
|
||||||
|
Supplier<Function2<Double, Double, Double>> customGen, String customName) {
|
||||||
|
JFrame frame = new JFrame(title);
|
||||||
|
frame.setDefaultCloseOperation(JFrame.HIDE_ON_CLOSE);
|
||||||
|
frame.getContentPane().setBackground(BG);
|
||||||
|
frame.setLayout(new BorderLayout());
|
||||||
|
|
||||||
|
JPanel sidebar = buildSidebar(nv, engine, customGen, customName);
|
||||||
|
frame.add(sidebar, BorderLayout.WEST);
|
||||||
|
frame.add(nv, BorderLayout.CENTER);
|
||||||
|
|
||||||
|
frame.setSize(1440, 820);
|
||||||
|
frame.setMinimumSize(new Dimension(640, 480));
|
||||||
|
frame.setLocationRelativeTo(null);
|
||||||
|
frame.setVisible(true);
|
||||||
|
frame.addWindowListener(new WindowAdapter() {
|
||||||
|
@Override
|
||||||
|
public void windowClosing(WindowEvent e) {
|
||||||
|
Iris.instance.unregisterListener(nv);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return frame;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static JPanel buildSidebar(NoiseExplorerGUI nv, Engine engine,
|
||||||
|
Supplier<Function2<Double, Double, Double>> customGen, String customName) {
|
||||||
|
JPanel sidebar = new JPanel(new BorderLayout());
|
||||||
|
sidebar.setPreferredSize(new Dimension(SIDEBAR_WIDTH, 0));
|
||||||
|
sidebar.setBackground(SIDEBAR_BG);
|
||||||
|
sidebar.setBorder(BorderFactory.createMatteBorder(0, 0, 0, 1, SEPARATOR));
|
||||||
|
|
||||||
|
JTextField search = new JTextField();
|
||||||
|
search.setBackground(SEARCH_BG);
|
||||||
|
search.setForeground(SEARCH_FG);
|
||||||
|
search.setCaretColor(SEARCH_FG);
|
||||||
|
search.setFont(SEARCH_FONT);
|
||||||
|
search.setBorder(BorderFactory.createCompoundBorder(
|
||||||
|
BorderFactory.createMatteBorder(0, 0, 1, 0, SEPARATOR),
|
||||||
|
BorderFactory.createEmptyBorder(8, 10, 8, 10)
|
||||||
|
));
|
||||||
|
search.putClientProperty("JTextField.placeholderText", "Search...");
|
||||||
|
|
||||||
|
LinkedHashMap<String, List<ListItem>> categories = buildCategoryMap(nv, engine, customGen, customName);
|
||||||
|
DefaultListModel<ListItem> model = new DefaultListModel<>();
|
||||||
|
populateModel(model, categories, "");
|
||||||
|
|
||||||
|
JList<ListItem> list = new JList<>(model);
|
||||||
|
list.setBackground(SIDEBAR_BG);
|
||||||
|
list.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
|
||||||
|
list.setCellRenderer(new SidebarCellRenderer());
|
||||||
|
list.setFixedCellHeight(-1);
|
||||||
|
|
||||||
|
list.addListSelectionListener(e -> {
|
||||||
|
if (e.getValueIsAdjusting()) return;
|
||||||
|
ListItem selected = list.getSelectedValue();
|
||||||
|
if (selected != null && !selected.header && selected.action != null) {
|
||||||
|
selected.action.run();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
search.getDocument().addDocumentListener(new DocumentListener() {
|
||||||
|
private void filter() {
|
||||||
|
String text = search.getText().trim();
|
||||||
|
populateModel(model, categories, text);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void insertUpdate(DocumentEvent e) { filter(); }
|
||||||
|
public void removeUpdate(DocumentEvent e) { filter(); }
|
||||||
|
public void changedUpdate(DocumentEvent e) { filter(); }
|
||||||
|
});
|
||||||
|
|
||||||
|
JScrollPane scrollPane = new JScrollPane(list);
|
||||||
|
scrollPane.setBorder(BorderFactory.createEmptyBorder());
|
||||||
|
scrollPane.getVerticalScrollBar().setUnitIncrement(16);
|
||||||
|
scrollPane.getVerticalScrollBar().setBackground(SIDEBAR_BG);
|
||||||
|
|
||||||
|
sidebar.add(search, BorderLayout.NORTH);
|
||||||
|
sidebar.add(scrollPane, BorderLayout.CENTER);
|
||||||
|
return sidebar;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void populateModel(DefaultListModel<ListItem> model, LinkedHashMap<String, List<ListItem>> categories, String filter) {
|
||||||
|
model.clear();
|
||||||
|
String lower = filter.toLowerCase();
|
||||||
|
for (Map.Entry<String, List<ListItem>> entry : categories.entrySet()) {
|
||||||
|
List<ListItem> matching = new ArrayList<>();
|
||||||
|
for (ListItem item : entry.getValue()) {
|
||||||
|
if (lower.isEmpty() || item.text.toLowerCase().contains(lower) || item.rawName.toLowerCase().contains(lower)) {
|
||||||
|
matching.add(item);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!matching.isEmpty()) {
|
||||||
|
model.addElement(new ListItem(entry.getKey(), entry.getKey(), true, null));
|
||||||
|
for (ListItem item : matching) {
|
||||||
|
model.addElement(item);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static LinkedHashMap<String, List<ListItem>> buildCategoryMap(NoiseExplorerGUI nv, Engine engine,
|
||||||
|
Supplier<Function2<Double, Double, Double>> customGen, String customName) {
|
||||||
|
LinkedHashMap<String, List<ListItem>> categories = new LinkedHashMap<>();
|
||||||
|
|
||||||
|
if (customGen != null && customName != null) {
|
||||||
|
List<ListItem> custom = new ArrayList<>();
|
||||||
|
custom.add(new ListItem(customName, customName, false, () -> {
|
||||||
|
nv.generator = customGen.get();
|
||||||
|
nv.loader = customGen;
|
||||||
|
nv.currentName = customName;
|
||||||
|
}));
|
||||||
|
categories.put("Custom", custom);
|
||||||
|
}
|
||||||
|
|
||||||
|
Map<String, List<NoiseStyle>> styleGroups = new LinkedHashMap<>();
|
||||||
|
for (NoiseStyle style : NoiseStyle.values()) {
|
||||||
|
String cat = categorize(style);
|
||||||
|
styleGroups.computeIfAbsent(cat, k -> new ArrayList<>()).add(style);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (engine != null && !engine.isClosed()) {
|
||||||
|
List<ListItem> genItems = new ArrayList<>();
|
||||||
|
try {
|
||||||
|
IrisData data = engine.getData();
|
||||||
|
String[] keys = data.getGeneratorLoader().getPossibleKeys();
|
||||||
|
Arrays.sort(keys);
|
||||||
|
for (String key : keys) {
|
||||||
|
IrisGenerator gen = data.getGeneratorLoader().load(key);
|
||||||
|
if (gen != null) {
|
||||||
|
long seed = new RNG(12345).nextParallelRNG(3245).lmax();
|
||||||
|
genItems.add(new ListItem(formatName(key), key, false, () -> {
|
||||||
|
nv.generator = (x, z) -> gen.getHeight(x, z, seed);
|
||||||
|
nv.loader = null;
|
||||||
|
nv.currentName = key;
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (Throwable ignored) {}
|
||||||
|
if (!genItems.isEmpty()) {
|
||||||
|
categories.put("Pack Generators", genItems);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (String cat : CATEGORY_ORDER) {
|
||||||
|
if ("Pack Generators".equals(cat)) continue;
|
||||||
|
List<NoiseStyle> styles = styleGroups.get(cat);
|
||||||
|
if (styles != null && !styles.isEmpty()) {
|
||||||
|
List<ListItem> items = new ArrayList<>();
|
||||||
|
for (NoiseStyle style : styles) {
|
||||||
|
items.add(new ListItem(formatName(style.name()), style.name(), false, () -> {
|
||||||
|
nv.cng = style.create(RNG.r.nextParallelRNG(RNG.r.imax()));
|
||||||
|
nv.generator = null;
|
||||||
|
nv.loader = null;
|
||||||
|
nv.currentName = style.name();
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
categories.put(cat, items);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (Map.Entry<String, List<NoiseStyle>> entry : styleGroups.entrySet()) {
|
||||||
|
if (!categories.containsKey(entry.getKey())) {
|
||||||
|
List<ListItem> items = new ArrayList<>();
|
||||||
|
for (NoiseStyle style : entry.getValue()) {
|
||||||
|
items.add(new ListItem(formatName(style.name()), style.name(), false, () -> {
|
||||||
|
nv.cng = style.create(RNG.r.nextParallelRNG(RNG.r.imax()));
|
||||||
|
nv.generator = null;
|
||||||
|
nv.loader = null;
|
||||||
|
nv.currentName = style.name();
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
categories.put(entry.getKey(), items);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return categories;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String categorize(NoiseStyle style) {
|
||||||
|
String n = style.name();
|
||||||
|
if (n.startsWith("STATIC")) return "Static";
|
||||||
|
if (n.startsWith("IRIS")) return "Iris";
|
||||||
|
if (n.startsWith("CLOVER")) return "Clover";
|
||||||
|
if (n.startsWith("VASCULAR")) return "Vascular";
|
||||||
|
if (n.equals("FLAT")) return "Utility";
|
||||||
|
if (n.startsWith("CELLULAR")) return "Cellular";
|
||||||
|
if (n.startsWith("HEX") || n.equals("HEXAGON")) return "Hexagon";
|
||||||
|
if (n.startsWith("SIERPINSKI")) return "Sierpinski";
|
||||||
|
if (n.startsWith("NOWHERE")) return "Nowhere";
|
||||||
|
if (n.startsWith("GLOB")) return "Globe";
|
||||||
|
if (n.startsWith("PERLIN")) return "Perlin";
|
||||||
|
if (n.startsWith("CUBIC") || (n.startsWith("FRACTAL") && n.contains("CUBIC"))) return "Cubic";
|
||||||
|
if (n.contains("SIMPLEX") && !n.startsWith("FRACTAL")) return "Simplex";
|
||||||
|
if (n.startsWith("FRACTAL")) return "Fractal";
|
||||||
|
return "Other";
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String formatName(String enumName) {
|
||||||
|
String lower = enumName.toLowerCase().replace('_', ' ');
|
||||||
|
return Character.toUpperCase(lower.charAt(0)) + lower.substring(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
@EventHandler
|
@EventHandler
|
||||||
public void on(IrisEngineHotloadEvent e) {
|
public void on(IrisEngineHotloadEvent e) {
|
||||||
if (generator != null)
|
if (generator != null && loader != null) {
|
||||||
generator = loader.get();
|
generator = loader.get();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void mouseWheelMoved(MouseWheelEvent e) {
|
|
||||||
|
|
||||||
int notches = e.getWheelRotation();
|
|
||||||
if (e.isControlDown()) {
|
|
||||||
t = t + ((0.0025 * t) * notches);
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void mouseWheelMoved(MouseWheelEvent e) {
|
||||||
|
int notches = e.getWheelRotation();
|
||||||
|
if (e.isControlDown()) {
|
||||||
|
time = time + ((0.0025 * time) * notches);
|
||||||
|
return;
|
||||||
|
}
|
||||||
scale = scale + ((0.044 * scale) * notches);
|
scale = scale + ((0.044 * scale) * notches);
|
||||||
scale = Math.max(scale, 0.00001);
|
scale = Math.max(scale, 0.00001);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private double lerp(double current, double target, double speed) {
|
||||||
|
double diff = target - current;
|
||||||
|
if (Math.abs(diff) < 0.001) return target;
|
||||||
|
return current + diff * speed;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void paint(Graphics g) {
|
public void paint(Graphics g) {
|
||||||
if (scale < ascale) {
|
animScale = lerp(animScale, scale, 0.16);
|
||||||
ascale -= Math.abs(scale - ascale) * 0.16;
|
animTime = lerp(animTime, time, 0.29);
|
||||||
}
|
animOx = lerp(animOx, ox, 0.16);
|
||||||
|
animOz = lerp(animOz, oz, 0.16);
|
||||||
if (scale > ascale) {
|
|
||||||
ascale += Math.abs(ascale - scale) * 0.16;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (t < tz) {
|
|
||||||
tz -= Math.abs(t - tz) * 0.29;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (t > tz) {
|
|
||||||
tz += Math.abs(tz - t) * 0.29;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (ox < oxp) {
|
|
||||||
oxp -= Math.abs(ox - oxp) * 0.16;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (ox > oxp) {
|
|
||||||
oxp += Math.abs(oxp - ox) * 0.16;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (oz < ozp) {
|
|
||||||
ozp -= Math.abs(oz - ozp) * 0.16;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (oz > ozp) {
|
|
||||||
ozp += Math.abs(ozp - oz) * 0.16;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (mx < mxx) {
|
|
||||||
mxx -= Math.abs(mx - mxx) * 0.16;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (mx > mxx) {
|
|
||||||
mxx += Math.abs(mxx - mx) * 0.16;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (mz < mzz) {
|
|
||||||
mzz -= Math.abs(mz - mzz) * 0.16;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (mz > mzz) {
|
|
||||||
mzz += Math.abs(mzz - mz) * 0.16;
|
|
||||||
}
|
|
||||||
|
|
||||||
PrecisionStopwatch p = PrecisionStopwatch.start();
|
PrecisionStopwatch p = PrecisionStopwatch.start();
|
||||||
int accuracy = hd ? 1 : M.clip((r.getAverage() / 12D), 2D, 128D).intValue();
|
|
||||||
accuracy = down ? accuracy * 4 : accuracy;
|
|
||||||
int v = 1000;
|
|
||||||
|
|
||||||
if (g instanceof Graphics2D gg) {
|
if (g instanceof Graphics2D gg) {
|
||||||
|
gg.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_LCD_HRGB);
|
||||||
|
gg.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR);
|
||||||
|
|
||||||
if (getParent().getWidth() != w || getParent().getHeight() != h) {
|
int pw = getWidth();
|
||||||
w = getParent().getWidth();
|
int ph = getHeight();
|
||||||
h = getParent().getHeight();
|
|
||||||
|
if (pw != imgWidth || ph != imgHeight || img == null) {
|
||||||
|
imgWidth = pw;
|
||||||
|
imgHeight = ph;
|
||||||
img = null;
|
img = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (img == null) {
|
int accuracy = M.clip((fpsHistory.getAverage() / 14D), 1D, 64D).intValue();
|
||||||
img = new BufferedImage(w / accuracy, h / accuracy, BufferedImage.TYPE_INT_RGB);
|
int rw = Math.max(1, pw / accuracy);
|
||||||
|
int rh = Math.max(1, ph / accuracy);
|
||||||
|
|
||||||
|
if (img == null || img.getWidth() != rw || img.getHeight() != rh) {
|
||||||
|
img = new BufferedImage(rw, rh, BufferedImage.TYPE_INT_RGB);
|
||||||
}
|
}
|
||||||
|
|
||||||
BurstExecutor e = gx.burst(w);
|
int[] pixels = ((DataBufferInt) img.getRaster().getDataBuffer()).getData();
|
||||||
|
|
||||||
for (int x = 0; x < w / accuracy; x++) {
|
BurstExecutor burst = gx.burst(rw);
|
||||||
|
for (int x = 0; x < rw; x++) {
|
||||||
int xx = x;
|
int xx = x;
|
||||||
|
burst.queue(() -> {
|
||||||
|
for (int z = 0; z < rh; z++) {
|
||||||
|
double worldX = (xx * accuracy * animScale) + animOx;
|
||||||
|
double worldZ = (z * accuracy * animScale) + animOz;
|
||||||
|
double n = generator != null
|
||||||
|
? generator.apply(worldX, worldZ)
|
||||||
|
: cng.noise(worldX, worldZ);
|
||||||
|
n = Math.max(0, Math.min(1, n));
|
||||||
|
|
||||||
int finalAccuracy = accuracy;
|
int rgb;
|
||||||
e.queue(() -> {
|
if (colorMode) {
|
||||||
for (int z = 0; z < h / finalAccuracy; z++) {
|
rgb = HSB_LUT[(int) (n * 255)];
|
||||||
double n = generator != null ? generator.apply(((xx * finalAccuracy) * ascale) + oxp, ((z * finalAccuracy) * ascale) + ozp) : cng.noise(((xx * finalAccuracy) * ascale) + oxp, ((z * finalAccuracy) * ascale) + ozp);
|
} else {
|
||||||
n = n > 1 ? 1 : n < 0 ? 0 : n;
|
int v = (int) (n * 255);
|
||||||
|
rgb = (v << 16) | (v << 8) | v;
|
||||||
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) (0.666f - n * 0.666f), 1f, (float) (1f - n * 0.8f)) : Color.getHSBColor(0f, 0f, (float) n);
|
|
||||||
int rgb = color.getRGB();
|
|
||||||
img.setRGB(xx, z, rgb);
|
|
||||||
} catch (Throwable ignored) {
|
|
||||||
}
|
}
|
||||||
|
pixels[z * rw + xx] = rgb;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
burst.complete();
|
||||||
|
|
||||||
e.complete();
|
gg.setColor(BG);
|
||||||
gg.drawImage(img, 0, 0, getParent().getWidth() * accuracy, getParent().getHeight() * accuracy, (img, infoflags, x, y, width, height) -> true);
|
gg.fillRect(0, 0, pw, ph);
|
||||||
|
gg.drawImage(img, 0, 0, pw, ph, null);
|
||||||
|
|
||||||
|
renderStatusBar(gg, pw, ph, p.getMilliseconds());
|
||||||
|
renderCrosshair(gg, pw, ph);
|
||||||
}
|
}
|
||||||
|
|
||||||
p.end();
|
p.end();
|
||||||
|
time += 1D;
|
||||||
|
fpsHistory.put(p.getMilliseconds());
|
||||||
|
|
||||||
t += 1D;
|
if (!isVisible() || !getParent().isVisible()) {
|
||||||
r.put(p.getMilliseconds());
|
|
||||||
|
|
||||||
if (!isVisible()) {
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!getParent().isVisible()) {
|
long sleepMs = Math.max(1, 16 - (long) p.getMilliseconds());
|
||||||
return;
|
EventQueue.invokeLater(() -> {
|
||||||
}
|
J.sleep(sleepMs);
|
||||||
|
|
||||||
if (!getParent().getParent().isVisible()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
EventQueue.invokeLater(() ->
|
|
||||||
{
|
|
||||||
J.sleep((long) Math.max(0, 32 - r.getAverage()));
|
|
||||||
repaint();
|
repaint();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
static class HandScrollListener extends MouseAdapter {
|
private void renderCrosshair(Graphics2D g, int w, int h) {
|
||||||
private static final Point pp = new Point();
|
int cx = w / 2;
|
||||||
|
int cy = h / 2;
|
||||||
|
g.setColor(new Color(255, 255, 255, 40));
|
||||||
|
g.drawLine(cx - 8, cy, cx + 8, cy);
|
||||||
|
g.drawLine(cx, cy - 8, cx, cy + 8);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
private void renderStatusBar(Graphics2D g, int w, int h, double frameMs) {
|
||||||
public void mouseDragged(MouseEvent e) {
|
int barHeight = 28;
|
||||||
JViewport vport = (JViewport) e.getSource();
|
int y = h - barHeight;
|
||||||
JComponent label = (JComponent) vport.getView();
|
|
||||||
Point cp = e.getPoint();
|
|
||||||
Point vp = vport.getViewPosition();
|
|
||||||
vp.translate(pp.x - cp.x, pp.y - cp.y);
|
|
||||||
label.scrollRectToVisible(new Rectangle(vp, vport.getSize()));
|
|
||||||
|
|
||||||
pp.setLocation(cp);
|
g.setColor(STATUS_BG);
|
||||||
|
g.fillRect(0, y, w, barHeight);
|
||||||
|
g.setColor(new Color(50, 50, 60));
|
||||||
|
g.drawLine(0, y, w, y);
|
||||||
|
|
||||||
|
g.setFont(STATUS_FONT);
|
||||||
|
g.setColor(STATUS_TEXT);
|
||||||
|
|
||||||
|
double worldX = (w / 2.0 * animScale) + animOx;
|
||||||
|
double worldZ = (h / 2.0 * animScale) + animOz;
|
||||||
|
double noiseVal = generator != null
|
||||||
|
? generator.apply(worldX, worldZ)
|
||||||
|
: cng.noise(worldX, worldZ);
|
||||||
|
noiseVal = Math.max(0, Math.min(1, noiseVal));
|
||||||
|
|
||||||
|
int fps = frameMs > 0 ? (int) (1000.0 / frameMs) : 0;
|
||||||
|
|
||||||
|
String status = String.format(" %s | X: %.1f Z: %.1f | Zoom: %.4f | Value: %.4f | %d FPS",
|
||||||
|
currentName, worldX, worldZ, animScale, noiseVal, fps);
|
||||||
|
g.drawString(status, 8, y + 18);
|
||||||
|
|
||||||
|
int barW = 60;
|
||||||
|
int barX = w - barW - 12;
|
||||||
|
int barY = y + 6;
|
||||||
|
int barH = barHeight - 12;
|
||||||
|
g.setColor(new Color(40, 40, 48));
|
||||||
|
g.fillRoundRect(barX, barY, barW, barH, 4, 4);
|
||||||
|
int fillW = (int) (noiseVal * (barW - 2));
|
||||||
|
g.setColor(ACCENT);
|
||||||
|
g.fillRoundRect(barX + 1, barY + 1, fillW, barH - 2, 3, 3);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static final class ListItem {
|
||||||
|
final String text;
|
||||||
|
final String rawName;
|
||||||
|
final boolean header;
|
||||||
|
final Runnable action;
|
||||||
|
|
||||||
|
ListItem(String text, String rawName, boolean header, Runnable action) {
|
||||||
|
this.text = text;
|
||||||
|
this.rawName = rawName;
|
||||||
|
this.header = header;
|
||||||
|
this.action = action;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void mousePressed(MouseEvent e) {
|
public String toString() {
|
||||||
pp.setLocation(e.getPoint());
|
return text;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static final class SidebarCellRenderer extends DefaultListCellRenderer {
|
||||||
|
@Override
|
||||||
|
public Component getListCellRendererComponent(JList<?> list, Object value, int index, boolean selected, boolean focus) {
|
||||||
|
ListItem item = (ListItem) value;
|
||||||
|
super.getListCellRendererComponent(list, item.text, index, !item.header && selected, false);
|
||||||
|
setOpaque(true);
|
||||||
|
if (item.header) {
|
||||||
|
setFont(SIDEBAR_HEADER_FONT);
|
||||||
|
setForeground(ACCENT);
|
||||||
|
setBackground(SIDEBAR_BG);
|
||||||
|
setBorder(BorderFactory.createEmptyBorder(10, 10, 4, 10));
|
||||||
|
} else {
|
||||||
|
setFont(SIDEBAR_ITEM_FONT);
|
||||||
|
setForeground(selected ? Color.WHITE : SIDEBAR_ITEM_COLOR);
|
||||||
|
setBackground(selected ? SIDEBAR_SELECTED : SIDEBAR_BG);
|
||||||
|
setBorder(BorderFactory.createEmptyBorder(3, 20, 3, 10));
|
||||||
|
}
|
||||||
|
return this;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -22,11 +22,17 @@ import art.arcane.iris.engine.framework.Engine;
|
|||||||
import art.arcane.iris.engine.object.IrisBiome;
|
import art.arcane.iris.engine.object.IrisBiome;
|
||||||
import art.arcane.iris.engine.object.IrisBiomeGeneratorLink;
|
import art.arcane.iris.engine.object.IrisBiomeGeneratorLink;
|
||||||
import art.arcane.iris.util.project.interpolation.IrisInterpolation;
|
import art.arcane.iris.util.project.interpolation.IrisInterpolation;
|
||||||
|
|
||||||
import java.awt.*;
|
import java.awt.*;
|
||||||
import java.awt.image.BufferedImage;
|
import java.awt.image.BufferedImage;
|
||||||
|
import java.awt.image.DataBufferInt;
|
||||||
import java.util.function.BiFunction;
|
import java.util.function.BiFunction;
|
||||||
|
|
||||||
public class IrisRenderer {
|
public class IrisRenderer {
|
||||||
|
private static final int BLUE = Color.BLUE.getRGB();
|
||||||
|
private static final int YELLOW = Color.YELLOW.getRGB();
|
||||||
|
private static final int GREEN = Color.GREEN.getRGB();
|
||||||
|
|
||||||
private final Engine renderer;
|
private final Engine renderer;
|
||||||
|
|
||||||
public IrisRenderer(Engine renderer) {
|
public IrisRenderer(Engine renderer) {
|
||||||
@@ -35,7 +41,8 @@ public class IrisRenderer {
|
|||||||
|
|
||||||
public BufferedImage render(double sx, double sz, double size, int resolution, RenderType currentType) {
|
public BufferedImage render(double sx, double sz, double size, int resolution, RenderType currentType) {
|
||||||
BufferedImage image = new BufferedImage(resolution, resolution, BufferedImage.TYPE_INT_RGB);
|
BufferedImage image = new BufferedImage(resolution, resolution, BufferedImage.TYPE_INT_RGB);
|
||||||
BiFunction<Double, Double, Integer> colorFunction = (d, dx) -> Color.black.getRGB();
|
int[] pixels = ((DataBufferInt) image.getRaster().getDataBuffer()).getData();
|
||||||
|
BiFunction<Double, Double, Integer> colorFunction = (d, dx) -> 0;
|
||||||
|
|
||||||
switch (currentType) {
|
switch (currentType) {
|
||||||
case BIOME, DECORATOR_LOAD, OBJECT_LOAD, LAYER_LOAD ->
|
case BIOME, DECORATOR_LOAD, OBJECT_LOAD, LAYER_LOAD ->
|
||||||
@@ -49,33 +56,22 @@ public class IrisRenderer {
|
|||||||
case CAVE_LAND ->
|
case CAVE_LAND ->
|
||||||
colorFunction = (x, z) -> renderer.getComplex().getCaveBiomeStream().get(x, z).getColor(renderer, currentType).getRGB();
|
colorFunction = (x, z) -> renderer.getComplex().getCaveBiomeStream().get(x, z).getColor(renderer, currentType).getRGB();
|
||||||
case HEIGHT ->
|
case HEIGHT ->
|
||||||
colorFunction = (x, z) -> Color.getHSBColor(renderer.getComplex().getHeightStream().get(x, z).floatValue(), 100, 100).getRGB();
|
colorFunction = (x, z) -> Color.getHSBColor(renderer.getComplex().getHeightStream().get(x, z).floatValue(), 1f, 1f).getRGB();
|
||||||
case CONTINENT -> colorFunction = (x, z) -> {
|
case CONTINENT -> colorFunction = (x, z) -> {
|
||||||
IrisBiome b = renderer.getBiome((int) Math.round(x), renderer.getMaxHeight() - 1, (int) Math.round(z));
|
IrisBiome b = renderer.getBiome((int) Math.round(x), renderer.getMaxHeight() - 1, (int) Math.round(z));
|
||||||
IrisBiomeGeneratorLink g = b.getGenerators().get(0);
|
IrisBiomeGeneratorLink g = b.getGenerators().get(0);
|
||||||
Color c;
|
if (g.getMax() <= 0) return BLUE;
|
||||||
if (g.getMax() <= 0) {
|
if (g.getMin() < 0) return YELLOW;
|
||||||
// Max is below water level, so it is most likely an ocean biome
|
return GREEN;
|
||||||
c = Color.BLUE;
|
|
||||||
} else if (g.getMin() < 0) {
|
|
||||||
// Min is below water level, but max is not, so it is most likely a shore biome
|
|
||||||
c = Color.YELLOW;
|
|
||||||
} else {
|
|
||||||
// Both min and max are above water level, so it is most likely a land biome
|
|
||||||
c = Color.GREEN;
|
|
||||||
}
|
|
||||||
return c.getRGB();
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
double x, z;
|
double x, z;
|
||||||
int i, j;
|
for (int i = 0; i < resolution; i++) {
|
||||||
for (i = 0; i < resolution; i++) {
|
x = IrisInterpolation.lerp(sx, sx + size, (double) i / (double) resolution);
|
||||||
x = IrisInterpolation.lerp(sx, sx + size, (double) i / (double) (resolution));
|
for (int j = 0; j < resolution; j++) {
|
||||||
|
z = IrisInterpolation.lerp(sz, sz + size, (double) j / (double) resolution);
|
||||||
for (j = 0; j < resolution; j++) {
|
pixels[j * resolution + i] = colorFunction.apply(x, z);
|
||||||
z = IrisInterpolation.lerp(sz, sz + size, (double) j / (double) (resolution));
|
|
||||||
image.setRGB(i, j, colorFunction.apply(x, z));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -47,6 +47,7 @@ import org.bukkit.inventory.ItemStack;
|
|||||||
|
|
||||||
import java.awt.Color;
|
import java.awt.Color;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
import java.util.concurrent.CompletableFuture;
|
import java.util.concurrent.CompletableFuture;
|
||||||
|
|
||||||
public interface INMSBinding {
|
public interface INMSBinding {
|
||||||
@@ -172,6 +173,10 @@ public interface INMSBinding {
|
|||||||
|
|
||||||
KMap<Identifier, StructurePlacement> collectStructures();
|
KMap<Identifier, StructurePlacement> collectStructures();
|
||||||
|
|
||||||
|
default Map<String, byte[]> extractVanillaDatapack() {
|
||||||
|
return Map.of();
|
||||||
|
}
|
||||||
|
|
||||||
private void validateDimensionTypes(WorldCreator c) {
|
private void validateDimensionTypes(WorldCreator c) {
|
||||||
if (c.generator() instanceof PlatformChunkGenerator gen
|
if (c.generator() instanceof PlatformChunkGenerator gen
|
||||||
&& missingDimensionTypes(gen.getTarget().getDimension().getDimensionTypeKey())) {
|
&& missingDimensionTypes(gen.getTarget().getDimension().getDimensionTypeKey())) {
|
||||||
|
|||||||
@@ -0,0 +1,56 @@
|
|||||||
|
/*
|
||||||
|
* 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 art.arcane.iris.core.pack;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public final class BrokenPackException extends RuntimeException {
|
||||||
|
private final String packName;
|
||||||
|
private final List<String> reasons;
|
||||||
|
|
||||||
|
public BrokenPackException(String packName, List<String> reasons) {
|
||||||
|
super(buildMessage(packName, reasons));
|
||||||
|
this.packName = packName;
|
||||||
|
this.reasons = reasons == null ? new ArrayList<>() : new ArrayList<>(reasons);
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getPackName() {
|
||||||
|
return packName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<String> getReasons() {
|
||||||
|
return Collections.unmodifiableList(reasons);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String buildMessage(String packName, List<String> reasons) {
|
||||||
|
StringBuilder sb = new StringBuilder();
|
||||||
|
sb.append("Iris pack '").append(packName).append("' is broken and cannot be used for world or studio creation.");
|
||||||
|
if (reasons != null) {
|
||||||
|
for (String reason : reasons) {
|
||||||
|
if (reason == null || reason.isBlank()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
sb.append(System.lineSeparator()).append(" - ").append(reason);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return sb.toString();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -45,7 +45,7 @@ public class IrisPackRepository {
|
|||||||
private String repo = "overworld";
|
private String repo = "overworld";
|
||||||
|
|
||||||
@Builder.Default
|
@Builder.Default
|
||||||
private String branch = "master";
|
private String branch = "stable";
|
||||||
|
|
||||||
@Builder.Default
|
@Builder.Default
|
||||||
private String tag = "";
|
private String tag = "";
|
||||||
@@ -94,7 +94,7 @@ public class IrisPackRepository {
|
|||||||
return IrisPackRepository.builder()
|
return IrisPackRepository.builder()
|
||||||
.user("IrisDimensions")
|
.user("IrisDimensions")
|
||||||
.repo(g)
|
.repo(g)
|
||||||
.branch(g.equals("overworld") ? "stable" : "master")
|
.branch("stable")
|
||||||
.build();
|
.build();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,64 @@
|
|||||||
|
/*
|
||||||
|
* 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 art.arcane.iris.core.pack;
|
||||||
|
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
|
|
||||||
|
public final class PackValidationRegistry {
|
||||||
|
private static final Map<String, PackValidationResult> RESULTS = new ConcurrentHashMap<>();
|
||||||
|
|
||||||
|
private PackValidationRegistry() {
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void publish(PackValidationResult result) {
|
||||||
|
if (result == null || result.getPackName() == null || result.getPackName().isBlank()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
RESULTS.put(result.getPackName(), result);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static PackValidationResult get(String packName) {
|
||||||
|
if (packName == null || packName.isBlank()) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return RESULTS.get(packName);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean isBroken(String packName) {
|
||||||
|
PackValidationResult result = get(packName);
|
||||||
|
return result != null && !result.isLoadable();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Map<String, PackValidationResult> snapshot() {
|
||||||
|
return Collections.unmodifiableMap(RESULTS);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void remove(String packName) {
|
||||||
|
if (packName == null || packName.isBlank()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
RESULTS.remove(packName);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void clear() {
|
||||||
|
RESULTS.clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,67 @@
|
|||||||
|
/*
|
||||||
|
* 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 art.arcane.iris.core.pack;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public final class PackValidationResult {
|
||||||
|
private final String packName;
|
||||||
|
private final List<String> blockingErrors;
|
||||||
|
private final List<String> warnings;
|
||||||
|
private final List<String> removedUnusedFiles;
|
||||||
|
private final long validatedAtMillis;
|
||||||
|
|
||||||
|
public PackValidationResult(String packName,
|
||||||
|
List<String> blockingErrors,
|
||||||
|
List<String> warnings,
|
||||||
|
List<String> removedUnusedFiles,
|
||||||
|
long validatedAtMillis) {
|
||||||
|
this.packName = packName;
|
||||||
|
this.blockingErrors = blockingErrors == null ? new ArrayList<>() : new ArrayList<>(blockingErrors);
|
||||||
|
this.warnings = warnings == null ? new ArrayList<>() : new ArrayList<>(warnings);
|
||||||
|
this.removedUnusedFiles = removedUnusedFiles == null ? new ArrayList<>() : new ArrayList<>(removedUnusedFiles);
|
||||||
|
this.validatedAtMillis = validatedAtMillis;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getPackName() {
|
||||||
|
return packName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isLoadable() {
|
||||||
|
return blockingErrors.isEmpty();
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<String> getBlockingErrors() {
|
||||||
|
return Collections.unmodifiableList(blockingErrors);
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<String> getWarnings() {
|
||||||
|
return Collections.unmodifiableList(warnings);
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<String> getRemovedUnusedFiles() {
|
||||||
|
return Collections.unmodifiableList(removedUnusedFiles);
|
||||||
|
}
|
||||||
|
|
||||||
|
public long getValidatedAtMillis() {
|
||||||
|
return validatedAtMillis;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,369 @@
|
|||||||
|
/*
|
||||||
|
* 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 art.arcane.iris.core.pack;
|
||||||
|
|
||||||
|
import art.arcane.iris.Iris;
|
||||||
|
import art.arcane.volmlib.util.json.JSONArray;
|
||||||
|
import art.arcane.volmlib.util.json.JSONObject;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.nio.file.Files;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
import java.nio.file.StandardCopyOption;
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
import java.time.format.DateTimeFormatter;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.Comparator;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.LinkedHashSet;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
|
public final class PackValidator {
|
||||||
|
private static final String TRASH_ROOT = ".iris-trash";
|
||||||
|
private static final String DATAPACK_IMPORTS = "datapack-imports";
|
||||||
|
private static final String EXTERNAL_DATAPACKS = "externaldatapacks";
|
||||||
|
private static final String OBJECTS_FOLDER = "objects";
|
||||||
|
private static final String DIMENSIONS_FOLDER = "dimensions";
|
||||||
|
private static final List<String> MANAGED_RESOURCE_FOLDERS = List.of(
|
||||||
|
"biomes",
|
||||||
|
"regions",
|
||||||
|
"entities",
|
||||||
|
"spawners",
|
||||||
|
"loot",
|
||||||
|
"generators",
|
||||||
|
"expressions",
|
||||||
|
"markers",
|
||||||
|
"blocks",
|
||||||
|
"mods"
|
||||||
|
);
|
||||||
|
private static final DateTimeFormatter TRASH_STAMP = DateTimeFormatter.ofPattern("yyyyMMdd-HHmmss");
|
||||||
|
|
||||||
|
private PackValidator() {
|
||||||
|
}
|
||||||
|
|
||||||
|
public static PackValidationResult validate(File packFolder) {
|
||||||
|
String packName = packFolder == null ? "<unknown>" : packFolder.getName();
|
||||||
|
List<String> blockingErrors = new ArrayList<>();
|
||||||
|
List<String> warnings = new ArrayList<>();
|
||||||
|
List<String> removedUnusedFiles = new ArrayList<>();
|
||||||
|
long validatedAt = System.currentTimeMillis();
|
||||||
|
|
||||||
|
if (packFolder == null || !packFolder.isDirectory()) {
|
||||||
|
blockingErrors.add("Pack folder does not exist or is not a directory.");
|
||||||
|
return new PackValidationResult(packName, blockingErrors, warnings, removedUnusedFiles, validatedAt);
|
||||||
|
}
|
||||||
|
|
||||||
|
File dimensionsFolder = new File(packFolder, DIMENSIONS_FOLDER);
|
||||||
|
if (!dimensionsFolder.isDirectory()) {
|
||||||
|
blockingErrors.add("Missing dimensions/ folder.");
|
||||||
|
return new PackValidationResult(packName, blockingErrors, warnings, removedUnusedFiles, validatedAt);
|
||||||
|
}
|
||||||
|
|
||||||
|
File[] dimensionFiles = dimensionsFolder.listFiles(f -> f.isFile() && f.getName().endsWith(".json"));
|
||||||
|
if (dimensionFiles == null || dimensionFiles.length == 0) {
|
||||||
|
blockingErrors.add("No dimension JSON files under dimensions/.");
|
||||||
|
return new PackValidationResult(packName, blockingErrors, warnings, removedUnusedFiles, validatedAt);
|
||||||
|
}
|
||||||
|
|
||||||
|
validateDimensions(packFolder, dimensionFiles, blockingErrors, warnings);
|
||||||
|
|
||||||
|
try {
|
||||||
|
String packTextCorpus = buildPackTextCorpus(packFolder);
|
||||||
|
runUnusedResourceGc(packFolder, packTextCorpus, removedUnusedFiles, warnings);
|
||||||
|
} catch (Throwable e) {
|
||||||
|
Iris.reportError("PackValidator GC pass failed for pack '" + packName + "'", e);
|
||||||
|
warnings.add("Unused-resource GC pass failed: " + e.getMessage());
|
||||||
|
}
|
||||||
|
|
||||||
|
return new PackValidationResult(packName, blockingErrors, warnings, removedUnusedFiles, validatedAt);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void validateDimensions(File packFolder, File[] dimensionFiles, List<String> blockingErrors, List<String> warnings) {
|
||||||
|
File regionsFolder = new File(packFolder, "regions");
|
||||||
|
File biomesFolder = new File(packFolder, "biomes");
|
||||||
|
|
||||||
|
for (File dimFile : dimensionFiles) {
|
||||||
|
String dimensionKey = stripExtension(dimFile.getName());
|
||||||
|
JSONObject dimJson;
|
||||||
|
try {
|
||||||
|
dimJson = new JSONObject(Files.readString(dimFile.toPath(), StandardCharsets.UTF_8));
|
||||||
|
} catch (Throwable e) {
|
||||||
|
blockingErrors.add("Dimension '" + dimensionKey + "' has invalid JSON: " + e.getMessage());
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
JSONArray regionsArray = dimJson.optJSONArray("regions");
|
||||||
|
if (regionsArray == null || regionsArray.length() == 0) {
|
||||||
|
blockingErrors.add("Dimension '" + dimensionKey + "' declares no regions.");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
int resolvedRegions = 0;
|
||||||
|
for (int i = 0; i < regionsArray.length(); i++) {
|
||||||
|
String regionKey = regionsArray.optString(i, null);
|
||||||
|
if (regionKey == null || regionKey.isBlank()) {
|
||||||
|
warnings.add("Dimension '" + dimensionKey + "' has a blank region entry at index " + i + ".");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
File regionFile = new File(regionsFolder, regionKey + ".json");
|
||||||
|
if (!regionFile.isFile()) {
|
||||||
|
blockingErrors.add("Dimension '" + dimensionKey + "' references missing region '" + regionKey + "'.");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
JSONObject regionJson;
|
||||||
|
try {
|
||||||
|
regionJson = new JSONObject(Files.readString(regionFile.toPath(), StandardCharsets.UTF_8));
|
||||||
|
} catch (Throwable e) {
|
||||||
|
blockingErrors.add("Region '" + regionKey + "' has invalid JSON: " + e.getMessage());
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
int anyBiome = countBiomeRefs(regionJson, "landBiomes", biomesFolder, regionKey, warnings)
|
||||||
|
+ countBiomeRefs(regionJson, "seaBiomes", biomesFolder, regionKey, warnings)
|
||||||
|
+ countBiomeRefs(regionJson, "shoreBiomes", biomesFolder, regionKey, warnings)
|
||||||
|
+ countBiomeRefs(regionJson, "caveBiomes", biomesFolder, regionKey, warnings);
|
||||||
|
if (anyBiome == 0) {
|
||||||
|
blockingErrors.add("Region '" + regionKey + "' has no resolvable biomes.");
|
||||||
|
}
|
||||||
|
resolvedRegions++;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (resolvedRegions == 0) {
|
||||||
|
blockingErrors.add("Dimension '" + dimensionKey + "' has no resolvable regions.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int countBiomeRefs(JSONObject regionJson, String field, File biomesFolder, String regionKey, List<String> warnings) {
|
||||||
|
JSONArray arr = regionJson.optJSONArray(field);
|
||||||
|
if (arr == null) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
int resolved = 0;
|
||||||
|
for (int i = 0; i < arr.length(); i++) {
|
||||||
|
String biomeKey = arr.optString(i, null);
|
||||||
|
if (biomeKey == null || biomeKey.isBlank()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
File biomeFile = new File(biomesFolder, biomeKey + ".json");
|
||||||
|
if (!biomeFile.isFile()) {
|
||||||
|
warnings.add("Region '" + regionKey + "' references missing biome '" + biomeKey + "' in " + field + ".");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
resolved++;
|
||||||
|
}
|
||||||
|
return resolved;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String buildPackTextCorpus(File packFolder) {
|
||||||
|
StringBuilder sb = new StringBuilder(1 << 16);
|
||||||
|
try (Stream<Path> stream = Files.walk(packFolder.toPath())) {
|
||||||
|
stream.filter(Files::isRegularFile)
|
||||||
|
.filter(PackValidator::isScannableJsonPath)
|
||||||
|
.forEach(p -> {
|
||||||
|
try {
|
||||||
|
sb.append(Files.readString(p, StandardCharsets.UTF_8));
|
||||||
|
sb.append('\n');
|
||||||
|
} catch (Throwable ignored) {
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} catch (Throwable e) {
|
||||||
|
Iris.reportError("PackValidator failed to walk pack folder for corpus scan", e);
|
||||||
|
}
|
||||||
|
return sb.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static boolean isScannableJsonPath(Path path) {
|
||||||
|
String name = path.getFileName().toString();
|
||||||
|
if (!name.endsWith(".json")) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
String str = path.toString().replace(File.separatorChar, '/');
|
||||||
|
if (str.contains("/" + TRASH_ROOT + "/")) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (str.contains("/" + DATAPACK_IMPORTS + "/")) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (str.contains("/" + EXTERNAL_DATAPACKS + "/")) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (str.contains("/" + OBJECTS_FOLDER + "/")) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (str.contains("/.iris/")) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void runUnusedResourceGc(File packFolder, String corpus, List<String> removedUnusedFiles, List<String> warnings) {
|
||||||
|
if (corpus == null || corpus.isEmpty()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
File trashRoot = new File(packFolder, TRASH_ROOT + File.separator + LocalDateTime.now().format(TRASH_STAMP));
|
||||||
|
Set<File> scheduledForTrash = new LinkedHashSet<>();
|
||||||
|
|
||||||
|
for (String folderName : MANAGED_RESOURCE_FOLDERS) {
|
||||||
|
File resourceFolder = new File(packFolder, folderName);
|
||||||
|
if (!resourceFolder.isDirectory()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
List<File> files = listJsonRecursive(resourceFolder);
|
||||||
|
for (File resourceFile : files) {
|
||||||
|
String key = deriveKey(resourceFolder, resourceFile);
|
||||||
|
if (key == null || key.isBlank()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (isReferenced(corpus, key)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
scheduledForTrash.add(resourceFile);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (scheduledForTrash.isEmpty()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (File file : scheduledForTrash) {
|
||||||
|
try {
|
||||||
|
Path src = file.toPath();
|
||||||
|
Path relative = packFolder.toPath().relativize(src);
|
||||||
|
Path dest = trashRoot.toPath().resolve(relative);
|
||||||
|
Files.createDirectories(dest.getParent());
|
||||||
|
Files.move(src, dest, StandardCopyOption.REPLACE_EXISTING);
|
||||||
|
removedUnusedFiles.add(relative.toString().replace(File.separatorChar, '/'));
|
||||||
|
} catch (Throwable e) {
|
||||||
|
Iris.reportError("PackValidator failed to move unused file " + file.getPath() + " to trash", e);
|
||||||
|
warnings.add("Failed to quarantine unused file " + file.getName() + ": " + e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static boolean isReferenced(String corpus, String key) {
|
||||||
|
String needleQuoted = "\"" + key + "\"";
|
||||||
|
if (corpus.contains(needleQuoted)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
int slash = key.indexOf('/');
|
||||||
|
if (slash > 0) {
|
||||||
|
String tail = key.substring(slash + 1);
|
||||||
|
if (!tail.isBlank() && corpus.contains("\"" + tail + "\"")) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static List<File> listJsonRecursive(File root) {
|
||||||
|
List<File> out = new ArrayList<>();
|
||||||
|
try (Stream<Path> stream = Files.walk(root.toPath())) {
|
||||||
|
stream.filter(Files::isRegularFile)
|
||||||
|
.filter(p -> p.getFileName().toString().endsWith(".json"))
|
||||||
|
.forEach(p -> out.add(p.toFile()));
|
||||||
|
} catch (Throwable ignored) {
|
||||||
|
}
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String deriveKey(File resourceFolder, File resourceFile) {
|
||||||
|
Path relative = resourceFolder.toPath().relativize(resourceFile.toPath());
|
||||||
|
String str = relative.toString().replace(File.separatorChar, '/');
|
||||||
|
if (!str.endsWith(".json")) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return str.substring(0, str.length() - ".json".length());
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String stripExtension(String name) {
|
||||||
|
int dot = name.lastIndexOf('.');
|
||||||
|
return dot <= 0 ? name : name.substring(0, dot);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static int restoreTrash(File packFolder) {
|
||||||
|
if (packFolder == null || !packFolder.isDirectory()) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
File trashRoot = new File(packFolder, TRASH_ROOT);
|
||||||
|
if (!trashRoot.isDirectory()) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
File[] dumps = trashRoot.listFiles(File::isDirectory);
|
||||||
|
if (dumps == null || dumps.length == 0) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
Arrays.sort(dumps, Comparator.comparing(File::getName));
|
||||||
|
File latestDump = dumps[dumps.length - 1];
|
||||||
|
int restored = 0;
|
||||||
|
try (Stream<Path> stream = Files.walk(latestDump.toPath())) {
|
||||||
|
List<Path> files = stream.filter(Files::isRegularFile).toList();
|
||||||
|
for (Path src : files) {
|
||||||
|
Path relative = latestDump.toPath().relativize(src);
|
||||||
|
Path dest = packFolder.toPath().resolve(relative);
|
||||||
|
Files.createDirectories(dest.getParent());
|
||||||
|
Files.move(src, dest, StandardCopyOption.REPLACE_EXISTING);
|
||||||
|
restored++;
|
||||||
|
}
|
||||||
|
} catch (Throwable e) {
|
||||||
|
Iris.reportError("PackValidator failed to restore trash for pack " + packFolder.getName(), e);
|
||||||
|
}
|
||||||
|
deleteFolderQuiet(latestDump);
|
||||||
|
return restored;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void deleteFolderQuiet(File folder) {
|
||||||
|
if (folder == null || !folder.exists()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
try (Stream<Path> stream = Files.walk(folder.toPath())) {
|
||||||
|
stream.sorted(Comparator.reverseOrder())
|
||||||
|
.map(Path::toFile)
|
||||||
|
.forEach(File::delete);
|
||||||
|
} catch (Throwable ignored) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Set<String> listReferencedKeysFromCorpus(String corpus) {
|
||||||
|
Set<String> keys = new HashSet<>();
|
||||||
|
if (corpus == null) {
|
||||||
|
return keys;
|
||||||
|
}
|
||||||
|
int i = 0;
|
||||||
|
while (i < corpus.length()) {
|
||||||
|
int start = corpus.indexOf('"', i);
|
||||||
|
if (start < 0) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
int end = corpus.indexOf('"', start + 1);
|
||||||
|
if (end < 0) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
keys.add(corpus.substring(start + 1, end));
|
||||||
|
i = end + 1;
|
||||||
|
}
|
||||||
|
return keys;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -157,14 +157,13 @@ public class DeepSearchPregenerator extends Thread implements Listener {
|
|||||||
for (int j = 0; j < 16; j++) {
|
for (int j = 0; j < 16; j++) {
|
||||||
int height = engine.getHeight(xx + i, zz + j);
|
int height = engine.getHeight(xx + i, zz + j);
|
||||||
if (height > 300) {
|
if (height > 300) {
|
||||||
File found = new File("plugins" + "iris" + "found.txt");
|
File found = new File("plugins", "iris" + File.separator + "found.txt");
|
||||||
FileWriter writer = new FileWriter(found);
|
found.getParentFile().mkdirs();
|
||||||
if (!found.exists()) {
|
|
||||||
found.createNewFile();
|
|
||||||
}
|
|
||||||
IrisBiome biome = engine.getBiome(xx, engine.getHeight(), zz);
|
IrisBiome biome = engine.getBiome(xx, engine.getHeight(), zz);
|
||||||
Iris.info("Found at! " + xx + ", " + zz + "Biome ID: " + biome.getName() + ", ");
|
Iris.info("Found at! " + xx + ", " + zz + " Biome ID: " + biome.getName());
|
||||||
writer.write("Biome at: X: " + xx + " Z: " + zz + "Biome ID: " + biome.getName() + ", ");
|
try (FileWriter writer = new FileWriter(found, true)) {
|
||||||
|
writer.write("Biome at: X: " + xx + " Z: " + zz + " Biome ID: " + biome.getName() + "\n");
|
||||||
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -156,11 +156,16 @@ public class IrisPregenerator {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private long computeETA() {
|
private long computeETA() {
|
||||||
double d = (long) (generated.get() > 1024 ? // Generated chunks exceed 1/8th of total?
|
long gen = generated.get();
|
||||||
// If yes, use smooth function (which gets more accurate over time since its less sensitive to outliers)
|
long total = totalChunks.get();
|
||||||
((totalChunks.get() - generated.get()) * ((double) (M.ms() - startTime.get()) / (double) generated.get())) :
|
long remaining = total - gen;
|
||||||
// If no, use quick function (which is less accurate over time but responds better to the initial delay)
|
double d;
|
||||||
((totalChunks.get() - generated.get()) / chunksPerSecond.getAverage()) * 1000);
|
if (gen > 1024) {
|
||||||
|
d = remaining * ((double) (M.ms() - startTime.get()) / (double) gen);
|
||||||
|
} else {
|
||||||
|
double cps = chunksPerSecond.getAverage();
|
||||||
|
d = cps > 0 ? (remaining / cps) * 1000 : 0;
|
||||||
|
}
|
||||||
return Double.isFinite(d) && d != INVALID ? (long) d : 0;
|
return Double.isFinite(d) && d != INVALID ? (long) d : 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -175,11 +180,7 @@ public class IrisPregenerator {
|
|||||||
checkRegions();
|
checkRegions();
|
||||||
PrecisionStopwatch p = PrecisionStopwatch.start();
|
PrecisionStopwatch p = PrecisionStopwatch.start();
|
||||||
task.iterateRegions((x, z) -> visitRegion(x, z, true));
|
task.iterateRegions((x, z) -> visitRegion(x, z, true));
|
||||||
if (generator.isAsyncChunkMode()) {
|
|
||||||
visitChunksInterleaved();
|
|
||||||
} else {
|
|
||||||
task.iterateRegions((x, z) -> visitRegion(x, z, false));
|
task.iterateRegions((x, z) -> visitRegion(x, z, false));
|
||||||
}
|
|
||||||
Iris.info("Pregen took " + Form.duration((long) p.getMilliseconds()));
|
Iris.info("Pregen took " + Form.duration((long) p.getMilliseconds()));
|
||||||
shutdown();
|
shutdown();
|
||||||
if (benchmarking == null) {
|
if (benchmarking == null) {
|
||||||
@@ -248,6 +249,10 @@ public class IrisPregenerator {
|
|||||||
if (saveLatch.flip()) {
|
if (saveLatch.flip()) {
|
||||||
listener.onSaving();
|
listener.onSaving();
|
||||||
generator.save();
|
generator.save();
|
||||||
|
Mantle mantle = getMantle();
|
||||||
|
if (mantle != null) {
|
||||||
|
mantle.trim(0, 0);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
generatedRegions.add(pos);
|
generatedRegions.add(pos);
|
||||||
@@ -263,46 +268,6 @@ public class IrisPregenerator {
|
|||||||
generator.supportsRegions(x, z, listener);
|
generator.supportsRegions(x, z, listener);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void visitChunksInterleaved() {
|
|
||||||
task.iterateAllChunksInterleaved((regionX, regionZ, chunkX, chunkZ, firstChunkInRegion, lastChunkInRegion) -> {
|
|
||||||
while (paused.get() && !shutdown.get()) {
|
|
||||||
J.sleep(50);
|
|
||||||
}
|
|
||||||
|
|
||||||
Position2 regionPos = new Position2(regionX, regionZ);
|
|
||||||
if (shutdown.get()) {
|
|
||||||
if (!generatedRegions.contains(regionPos)) {
|
|
||||||
listener.onRegionSkipped(regionX, regionZ);
|
|
||||||
generatedRegions.add(regionPos);
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (generatedRegions.contains(regionPos)) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (firstChunkInRegion) {
|
|
||||||
currentGeneratorMethod.set(generator.getMethod(regionX, regionZ));
|
|
||||||
listener.onRegionGenerating(regionX, regionZ);
|
|
||||||
}
|
|
||||||
|
|
||||||
generator.generateChunk(chunkX, chunkZ, listener);
|
|
||||||
|
|
||||||
if (lastChunkInRegion) {
|
|
||||||
listener.onRegionGenerated(regionX, regionZ);
|
|
||||||
if (saveLatch.flip()) {
|
|
||||||
listener.onSaving();
|
|
||||||
generator.save();
|
|
||||||
}
|
|
||||||
generatedRegions.add(regionPos);
|
|
||||||
checkRegions();
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
public void pause() {
|
public void pause() {
|
||||||
paused.set(true);
|
paused.set(true);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -121,56 +121,6 @@ public class PregenTask {
|
|||||||
iterateRegions(((rX, rZ) -> iterateChunks(rX, rZ, s)));
|
iterateRegions(((rX, rZ) -> iterateChunks(rX, rZ, s)));
|
||||||
}
|
}
|
||||||
|
|
||||||
public void iterateAllChunksInterleaved(InterleavedChunkSpiraled spiraled) {
|
|
||||||
if (spiraled == null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
KList<RegionChunkCursor> cursors = new KList<>();
|
|
||||||
Bound bound = bounds.chunk();
|
|
||||||
iterateRegions((regionX, regionZ) -> {
|
|
||||||
RegionChunkCursor cursor = new RegionChunkCursor(regionX, regionZ, bound);
|
|
||||||
if (cursor.hasNext()) {
|
|
||||||
cursors.add(cursor);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
boolean hasProgress = true;
|
|
||||||
while (hasProgress) {
|
|
||||||
hasProgress = false;
|
|
||||||
for (RegionChunkCursor cursor : cursors) {
|
|
||||||
if (!cursor.hasNext()) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
hasProgress = true;
|
|
||||||
long chunk = cursor.next();
|
|
||||||
if (chunk == Long.MIN_VALUE) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
int chunkX = (int) (chunk >> 32);
|
|
||||||
int chunkZ = (int) chunk;
|
|
||||||
|
|
||||||
boolean shouldContinue = spiraled.on(
|
|
||||||
cursor.getRegionX(),
|
|
||||||
cursor.getRegionZ(),
|
|
||||||
chunkX,
|
|
||||||
chunkZ,
|
|
||||||
cursor.getIndex() == 1,
|
|
||||||
!cursor.hasNext()
|
|
||||||
);
|
|
||||||
if (!shouldContinue) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@FunctionalInterface
|
|
||||||
public interface InterleavedChunkSpiraled {
|
|
||||||
boolean on(int regionX, int regionZ, int chunkX, int chunkZ, boolean firstChunkInRegion, boolean lastChunkInRegion);
|
|
||||||
}
|
|
||||||
|
|
||||||
private class Bounds {
|
private class Bounds {
|
||||||
private Bound chunk = null;
|
private Bound chunk = null;
|
||||||
private Bound region = null;
|
private Bound region = null;
|
||||||
@@ -206,9 +156,9 @@ public class PregenTask {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private record Bound(int minX, int maxX, int minZ, int maxZ, int sizeX, int sizeZ) {
|
private record Bound(int minX, int minZ, int maxX, int maxZ, int sizeX, int sizeZ) {
|
||||||
private Bound(int minX, int minZ, int maxX, int maxZ) {
|
private Bound(int minX, int minZ, int maxX, int maxZ) {
|
||||||
this(minX, maxX, minZ, maxZ, maxZ - minZ + 1, maxZ - minZ + 1);
|
this(minX, minZ, maxX, maxZ, maxX - minX + 1, maxZ - minZ + 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
boolean check(int x, int z) {
|
boolean check(int x, int z) {
|
||||||
@@ -231,76 +181,4 @@ public class PregenTask {
|
|||||||
throw new IllegalStateException("This Position2 may not be modified");
|
throw new IllegalStateException("This Position2 may not be modified");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static final class RegionChunkCursor {
|
|
||||||
private final int regionX;
|
|
||||||
private final int regionZ;
|
|
||||||
private final Bound bound;
|
|
||||||
private final int[] order;
|
|
||||||
private final int chunkOffsetX;
|
|
||||||
private final int chunkOffsetZ;
|
|
||||||
private int scanIndex;
|
|
||||||
private int emittedIndex;
|
|
||||||
private int currentChunkX;
|
|
||||||
private int currentChunkZ;
|
|
||||||
private boolean hasCurrent;
|
|
||||||
|
|
||||||
private RegionChunkCursor(int regionX, int regionZ, Bound bound) {
|
|
||||||
this.regionX = regionX;
|
|
||||||
this.regionZ = regionZ;
|
|
||||||
this.bound = bound;
|
|
||||||
this.chunkOffsetX = PowerOfTwoCoordinates.regionToChunk(regionX);
|
|
||||||
this.chunkOffsetZ = PowerOfTwoCoordinates.regionToChunk(regionZ);
|
|
||||||
this.order = orderForPull(-chunkOffsetX, -chunkOffsetZ);
|
|
||||||
this.scanIndex = 0;
|
|
||||||
this.emittedIndex = 0;
|
|
||||||
advance();
|
|
||||||
}
|
|
||||||
|
|
||||||
private boolean hasNext() {
|
|
||||||
return hasCurrent;
|
|
||||||
}
|
|
||||||
|
|
||||||
private long next() {
|
|
||||||
if (!hasNext()) {
|
|
||||||
return Long.MIN_VALUE;
|
|
||||||
}
|
|
||||||
|
|
||||||
long high = (long) currentChunkX << 32;
|
|
||||||
long low = currentChunkZ & 0xFFFFFFFFL;
|
|
||||||
emittedIndex++;
|
|
||||||
advance();
|
|
||||||
return high | low;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void advance() {
|
|
||||||
hasCurrent = false;
|
|
||||||
while (scanIndex < order.length) {
|
|
||||||
int local = order[scanIndex];
|
|
||||||
scanIndex++;
|
|
||||||
int chunkX = chunkOffsetX + PowerOfTwoCoordinates.unpackLocal32X(local);
|
|
||||||
int chunkZ = chunkOffsetZ + PowerOfTwoCoordinates.unpackLocal32Z(local);
|
|
||||||
if (!bound.check(chunkX, chunkZ)) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
currentChunkX = chunkX;
|
|
||||||
currentChunkZ = chunkZ;
|
|
||||||
hasCurrent = true;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private int getRegionX() {
|
|
||||||
return regionX;
|
|
||||||
}
|
|
||||||
|
|
||||||
private int getRegionZ() {
|
|
||||||
return regionZ;
|
|
||||||
}
|
|
||||||
|
|
||||||
private int getIndex() {
|
|
||||||
return emittedIndex;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -293,49 +293,138 @@ public class IrisProject {
|
|||||||
return future;
|
return future;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static final int STUDIO_PROGRESS_BAR_WIDTH = 44;
|
||||||
|
|
||||||
private void startStudioOpenReporter(VolmitSender sender, AtomicReference<String> stage, AtomicReference<Double> progress, AtomicBoolean complete, AtomicBoolean failed) {
|
private void startStudioOpenReporter(VolmitSender sender, AtomicReference<String> stage, AtomicReference<Double> progress, AtomicBoolean complete, AtomicBoolean failed) {
|
||||||
String[] spinner = {"|", "/", "-", "\\"};
|
|
||||||
AtomicInteger spinIndex = new AtomicInteger(0);
|
|
||||||
AtomicLong nextConsoleUpdate = new AtomicLong(0L);
|
AtomicLong nextConsoleUpdate = new AtomicLong(0L);
|
||||||
|
AtomicLong startMs = new AtomicLong(System.currentTimeMillis());
|
||||||
AtomicInteger taskId = new AtomicInteger(-1);
|
AtomicInteger taskId = new AtomicInteger(-1);
|
||||||
|
org.bukkit.boss.BossBar bossBar;
|
||||||
|
|
||||||
|
if (sender.isPlayer() && sender.player() != null) {
|
||||||
|
bossBar = Bukkit.createBossBar(
|
||||||
|
C.GOLD + "Studio " + C.AQUA + "OPENING",
|
||||||
|
org.bukkit.boss.BarColor.BLUE,
|
||||||
|
org.bukkit.boss.BarStyle.SEGMENTED_20
|
||||||
|
);
|
||||||
|
bossBar.setProgress(0.0D);
|
||||||
|
bossBar.addPlayer(sender.player());
|
||||||
|
bossBar.setVisible(true);
|
||||||
|
} else {
|
||||||
|
bossBar = null;
|
||||||
|
}
|
||||||
|
|
||||||
int scheduledTaskId = J.ar(() -> {
|
int scheduledTaskId = J.ar(() -> {
|
||||||
|
double currentProgress = Math.max(0D, Math.min(0.99D, progress.get()));
|
||||||
|
String currentStage = describeStage(stage.get());
|
||||||
|
int percent = (int) Math.round(currentProgress * 100.0D);
|
||||||
|
long elapsed = System.currentTimeMillis() - startMs.get();
|
||||||
|
|
||||||
if (complete.get()) {
|
if (complete.get()) {
|
||||||
J.car(taskId.get());
|
J.car(taskId.get());
|
||||||
|
|
||||||
if (failed.get()) {
|
if (failed.get()) {
|
||||||
|
if (bossBar != null) {
|
||||||
|
bossBar.setProgress(Math.max(0.0D, Math.min(1.0D, currentProgress)));
|
||||||
|
bossBar.setColor(org.bukkit.boss.BarColor.RED);
|
||||||
|
bossBar.setTitle(C.GOLD + "Studio " + C.RED + "FAILED" + C.GRAY + " " + C.YELLOW + percent + "%");
|
||||||
|
J.a(() -> { bossBar.removeAll(); bossBar.setVisible(false); }, 60);
|
||||||
|
}
|
||||||
if (sender.isPlayer()) {
|
if (sender.isPlayer()) {
|
||||||
sender.sendProgress(1D, "Studio open failed");
|
String action = buildStudioProgressBar(currentProgress)
|
||||||
|
+ C.GRAY + " " + C.RED + "FAILED"
|
||||||
|
+ C.GRAY + " | " + C.WHITE + currentStage;
|
||||||
|
sender.sendAction(action);
|
||||||
} else {
|
} else {
|
||||||
sender.sendMessage(C.RED + "Studio open failed.");
|
sender.sendMessage(C.RED + "Studio open failed.");
|
||||||
}
|
}
|
||||||
} else if (sender.isPlayer()) {
|
|
||||||
sender.sendProgress(1D, "Studio ready");
|
|
||||||
} else {
|
} else {
|
||||||
sender.sendMessage(C.GREEN + "Studio ready.");
|
if (bossBar != null) {
|
||||||
|
bossBar.setProgress(1.0D);
|
||||||
|
bossBar.setColor(org.bukkit.boss.BarColor.GREEN);
|
||||||
|
bossBar.setTitle(C.GOLD + "Studio " + C.GREEN + "READY" + C.GRAY + " " + C.YELLOW + "100%");
|
||||||
|
J.a(() -> { bossBar.removeAll(); bossBar.setVisible(false); }, 60);
|
||||||
}
|
}
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
double currentProgress = Math.max(0D, Math.min(0.97D, progress.get()));
|
|
||||||
String currentStage = stage.get();
|
|
||||||
String currentSpinner = spinner[Math.floorMod(spinIndex.getAndIncrement(), spinner.length)];
|
|
||||||
|
|
||||||
if (sender.isPlayer()) {
|
if (sender.isPlayer()) {
|
||||||
sender.sendProgress(currentProgress, "Studio " + currentSpinner + " " + currentStage);
|
String action = buildStudioProgressBar(1.0D)
|
||||||
|
+ C.GRAY + " " + C.GREEN + "100%"
|
||||||
|
+ C.GRAY + " | " + C.GREEN + "Studio ready"
|
||||||
|
+ C.DARK_GRAY + " " + Form.duration(elapsed, 1);
|
||||||
|
sender.sendAction(action);
|
||||||
|
} else {
|
||||||
|
sender.sendMessage(C.GREEN + "Studio ready " + C.GRAY + "(" + Form.duration(elapsed, 1) + ")");
|
||||||
|
}
|
||||||
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (sender.isPlayer() && sender.player() != null) {
|
||||||
|
if (bossBar != null) {
|
||||||
|
bossBar.setProgress(Math.max(0.0D, Math.min(1.0D, currentProgress)));
|
||||||
|
bossBar.setTitle(C.GOLD + "Studio " + C.AQUA + "OPENING" + C.GRAY + " " + C.YELLOW + percent + "%");
|
||||||
|
}
|
||||||
|
|
||||||
|
String action = buildStudioProgressBar(currentProgress)
|
||||||
|
+ C.GRAY + " " + C.YELLOW + percent + "%"
|
||||||
|
+ C.GRAY + " | " + C.WHITE + currentStage
|
||||||
|
+ C.DARK_GRAY + " " + Form.duration(elapsed, 0);
|
||||||
|
sender.sendAction(action);
|
||||||
|
} else {
|
||||||
long now = System.currentTimeMillis();
|
long now = System.currentTimeMillis();
|
||||||
long nextUpdate = nextConsoleUpdate.get();
|
long nextUpdate = nextConsoleUpdate.get();
|
||||||
if (now >= nextUpdate) {
|
if (now >= nextUpdate) {
|
||||||
sender.sendMessage(C.WHITE + "Studio " + Form.pc(currentProgress, 0) + C.GRAY + " - " + currentStage);
|
String bar = buildStudioConsoleBar(currentProgress);
|
||||||
|
sender.sendMessage(C.GOLD + "Studio " + C.AQUA + bar + " " + C.YELLOW + percent + "%" + C.GRAY + " " + currentStage + C.DARK_GRAY + " (" + Form.duration(elapsed, 0) + ")");
|
||||||
nextConsoleUpdate.set(now + 1500L);
|
nextConsoleUpdate.set(now + 1500L);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}, 3);
|
}, 3);
|
||||||
|
|
||||||
taskId.set(scheduledTaskId);
|
taskId.set(scheduledTaskId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static String buildStudioProgressBar(double progress) {
|
||||||
|
int filled = (int) Math.round(Math.max(0.0D, Math.min(1.0D, progress)) * STUDIO_PROGRESS_BAR_WIDTH);
|
||||||
|
StringBuilder bar = new StringBuilder(STUDIO_PROGRESS_BAR_WIDTH * 3 + 4);
|
||||||
|
bar.append(C.DARK_GRAY).append("[");
|
||||||
|
for (int i = 0; i < STUDIO_PROGRESS_BAR_WIDTH; i++) {
|
||||||
|
bar.append(i < filled ? C.GREEN : C.DARK_GRAY).append("|");
|
||||||
|
}
|
||||||
|
bar.append(C.DARK_GRAY).append("]");
|
||||||
|
return bar.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String buildStudioConsoleBar(double progress) {
|
||||||
|
int width = 20;
|
||||||
|
int filled = (int) Math.round(Math.max(0.0D, Math.min(1.0D, progress)) * width);
|
||||||
|
StringBuilder bar = new StringBuilder();
|
||||||
|
bar.append("[");
|
||||||
|
for (int i = 0; i < width; i++) {
|
||||||
|
bar.append(i < filled ? "#" : "-");
|
||||||
|
}
|
||||||
|
bar.append("]");
|
||||||
|
return bar.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String describeStage(String stage) {
|
||||||
|
if (stage == null || stage.isBlank()) return "Initializing";
|
||||||
|
return switch (stage) {
|
||||||
|
case "Queued" -> "Queued";
|
||||||
|
case "resolve_dimension" -> "Resolving dimension";
|
||||||
|
case "prepare_world_pack" -> "Preparing world pack";
|
||||||
|
case "install_datapacks" -> "Installing datapacks";
|
||||||
|
case "create_world" -> "Creating world";
|
||||||
|
case "apply_world_rules" -> "Applying world rules";
|
||||||
|
case "prepare_generator" -> "Preparing generator";
|
||||||
|
case "request_entry_chunk" -> "Loading entry chunk";
|
||||||
|
case "resolve_safe_entry" -> "Finding safe spawn";
|
||||||
|
case "teleport_player" -> "Teleporting";
|
||||||
|
case "finalize_open" -> "Finalizing";
|
||||||
|
case "cleanup" -> "Cleaning up";
|
||||||
|
default -> Form.capitalizeWords(stage.replace('_', ' '));
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
public CompletableFuture<StudioOpenCoordinator.StudioCloseResult> close() {
|
public CompletableFuture<StudioOpenCoordinator.StudioCloseResult> close() {
|
||||||
if (activeProvider == null) {
|
if (activeProvider == null) {
|
||||||
return CompletableFuture.completedFuture(new StudioOpenCoordinator.StudioCloseResult(null, true, true, false, null));
|
return CompletableFuture.completedFuture(new StudioOpenCoordinator.StudioCloseResult(null, true, true, false, null));
|
||||||
|
|||||||
@@ -294,19 +294,12 @@ public final class WorldRuntimeControlService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static int[] buildSafeLocationScanOrder(World world, Location source) {
|
static int[] buildSafeLocationScanOrder(World world, Location source) {
|
||||||
int x = source.getBlockX();
|
|
||||||
int z = source.getBlockZ();
|
|
||||||
int minY = world.getMinHeight() + 1;
|
int minY = world.getMinHeight() + 1;
|
||||||
int maxY = world.getMaxHeight() - 2;
|
int maxY = world.getMaxHeight() - 2;
|
||||||
int highestY = Math.max(minY, Math.min(maxY, world.getHighestBlockYAt(x, z) + 1));
|
|
||||||
int[] scanOrder = new int[maxY - minY + 1];
|
int[] scanOrder = new int[maxY - minY + 1];
|
||||||
int index = 0;
|
int index = 0;
|
||||||
|
|
||||||
for (int y = highestY; y >= minY; y--) {
|
for (int y = maxY; y >= minY; y--) {
|
||||||
scanOrder[index++] = y;
|
|
||||||
}
|
|
||||||
|
|
||||||
for (int y = highestY + 1; y <= maxY; y++) {
|
|
||||||
scanOrder[index++] = y;
|
scanOrder[index++] = y;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -26,6 +26,8 @@ import art.arcane.iris.core.lifecycle.WorldLifecycleService;
|
|||||||
import art.arcane.iris.core.loader.IrisData;
|
import art.arcane.iris.core.loader.IrisData;
|
||||||
import art.arcane.iris.core.nms.INMS;
|
import art.arcane.iris.core.nms.INMS;
|
||||||
import art.arcane.iris.core.pack.IrisPack;
|
import art.arcane.iris.core.pack.IrisPack;
|
||||||
|
import art.arcane.iris.core.pack.PackValidationRegistry;
|
||||||
|
import art.arcane.iris.core.pack.PackValidationResult;
|
||||||
import art.arcane.iris.core.project.IrisProject;
|
import art.arcane.iris.core.project.IrisProject;
|
||||||
import art.arcane.iris.core.runtime.TransientWorldCleanupSupport;
|
import art.arcane.iris.core.runtime.TransientWorldCleanupSupport;
|
||||||
import art.arcane.iris.core.tools.IrisToolbelt;
|
import art.arcane.iris.core.tools.IrisToolbelt;
|
||||||
@@ -69,12 +71,12 @@ public class StudioSVC implements IrisService {
|
|||||||
File f = IrisPack.packsPack(pack);
|
File f = IrisPack.packsPack(pack);
|
||||||
|
|
||||||
if (!f.exists()) {
|
if (!f.exists()) {
|
||||||
Iris.info("Downloading Default Pack " + pack);
|
|
||||||
if (pack.equals("overworld")) {
|
if (pack.equals("overworld")) {
|
||||||
|
Iris.info("Downloading Default Pack " + pack);
|
||||||
String url = "https://github.com/IrisDimensions/overworld/releases/download/" + INMS.OVERWORLD_TAG + "/overworld.zip";
|
String url = "https://github.com/IrisDimensions/overworld/releases/download/" + INMS.OVERWORLD_TAG + "/overworld.zip";
|
||||||
Iris.service(StudioSVC.class).downloadRelease(Iris.getSender(), url, false, false);
|
Iris.service(StudioSVC.class).downloadRelease(Iris.getSender(), url, false, false);
|
||||||
} else {
|
} else {
|
||||||
downloadSearch(Iris.getSender(), pack, false);
|
Iris.warn("Default pack '" + pack + "' is not installed. Please download it manually with /iris download");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -130,13 +132,16 @@ public class StudioSVC implements IrisService {
|
|||||||
IrisDimension dim = IrisData.loadAnyDimension(type, null);
|
IrisDimension dim = IrisData.loadAnyDimension(type, null);
|
||||||
|
|
||||||
if (dim == null) {
|
if (dim == null) {
|
||||||
for (File i : getWorkspaceFolder().listFiles()) {
|
File[] workspaceFiles = getWorkspaceFolder().listFiles();
|
||||||
|
if (workspaceFiles != null) {
|
||||||
|
for (File i : workspaceFiles) {
|
||||||
if (i.isFile() && i.getName().equals(type + ".iris")) {
|
if (i.isFile() && i.getName().equals(type + ".iris")) {
|
||||||
sender.sendMessage("Found " + type + ".iris in " + WORKSPACE_NAME + " folder");
|
sender.sendMessage("Found " + type + ".iris in " + WORKSPACE_NAME + " folder");
|
||||||
ZipUtil.unpack(i, folder);
|
ZipUtil.unpack(i, folder);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
sender.sendMessage("Found " + type + " dimension in " + WORKSPACE_NAME + " folder. Repackaging");
|
sender.sendMessage("Found " + type + " dimension in " + WORKSPACE_NAME + " folder. Repackaging");
|
||||||
File f = new IrisProject(new File(getWorkspaceFolder(), type)).getPath();
|
File f = new IrisProject(new File(getWorkspaceFolder(), type)).getPath();
|
||||||
@@ -153,8 +158,10 @@ public class StudioSVC implements IrisService {
|
|||||||
if (!dimensionFile.exists() || !dimensionFile.isFile()) {
|
if (!dimensionFile.exists() || !dimensionFile.isFile()) {
|
||||||
downloadSearch(sender, type, false);
|
downloadSearch(sender, type, false);
|
||||||
File downloaded = getWorkspaceFolder(type);
|
File downloaded = getWorkspaceFolder(type);
|
||||||
|
File[] files = downloaded.listFiles();
|
||||||
|
|
||||||
for (File i : downloaded.listFiles()) {
|
if (files != null) {
|
||||||
|
for (File i : files) {
|
||||||
if (i.isFile()) {
|
if (i.isFile()) {
|
||||||
try {
|
try {
|
||||||
FileUtils.copyFile(i, new File(folder, i.getName()));
|
FileUtils.copyFile(i, new File(folder, i.getName()));
|
||||||
@@ -174,6 +181,7 @@ public class StudioSVC implements IrisService {
|
|||||||
|
|
||||||
IO.delete(downloaded);
|
IO.delete(downloaded);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (!dimensionFile.exists() || !dimensionFile.isFile()) {
|
if (!dimensionFile.exists() || !dimensionFile.isFile()) {
|
||||||
sender.sendMessage("Can't find the " + dimensionFile.getName() + " in the dimensions folder of this pack! Failed!");
|
sender.sendMessage("Can't find the " + dimensionFile.getName() + " in the dimensions folder of this pack! Failed!");
|
||||||
@@ -198,26 +206,24 @@ public class StudioSVC implements IrisService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void downloadSearch(VolmitSender sender, String key, boolean trim, boolean forceOverwrite) {
|
public void downloadSearch(VolmitSender sender, String key, boolean trim, boolean forceOverwrite) {
|
||||||
String url = "?";
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
url = getListing(false).get(key);
|
String url = getListing(false).get(key);
|
||||||
|
|
||||||
if (url == null) {
|
if (url == null) {
|
||||||
Iris.warn("ITS ULL for " + key);
|
sender.sendMessage("Pack '" + key + "' was not found in the pack listing.");
|
||||||
|
sender.sendMessage("Use /iris download <user/repo> <branch> to download manually.");
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
url = url == null ? key : url;
|
Iris.info("Resolved pack '" + key + "' to " + url);
|
||||||
Iris.info("Assuming URL " + url);
|
|
||||||
String branch = "master";
|
|
||||||
String[] nodes = url.split("\\Q/\\E");
|
String[] nodes = url.split("\\Q/\\E");
|
||||||
String repo = nodes.length == 1 ? "IrisDimensions/" + nodes[0] : nodes[0] + "/" + nodes[1];
|
String repo = nodes.length == 1 ? "IrisDimensions/" + nodes[0] : nodes[0] + "/" + nodes[1];
|
||||||
branch = nodes.length > 2 ? nodes[2] : branch;
|
String branch = nodes.length > 2 ? nodes[2] : "stable";
|
||||||
download(sender, repo, branch, trim, forceOverwrite, false);
|
download(sender, repo, branch, trim, forceOverwrite, false);
|
||||||
} catch (Throwable e) {
|
} catch (Throwable e) {
|
||||||
Iris.reportError(e);
|
Iris.reportError(e);
|
||||||
e.printStackTrace();
|
e.printStackTrace();
|
||||||
sender.sendMessage("Failed to download '" + key + "' from " + url + ".");
|
sender.sendMessage("Failed to download '" + key + "'.");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -246,7 +252,7 @@ public class StudioSVC implements IrisService {
|
|||||||
if (zip == null || !zip.exists()) {
|
if (zip == null || !zip.exists()) {
|
||||||
sender.sendMessage("Failed to find pack at " + url);
|
sender.sendMessage("Failed to find pack at " + url);
|
||||||
sender.sendMessage("Make sure you specified the correct repo and branch!");
|
sender.sendMessage("Make sure you specified the correct repo and branch!");
|
||||||
sender.sendMessage("For example: /iris download IrisDimensions/overworld branch=master");
|
sender.sendMessage("For example: /iris download IrisDimensions/overworld branch=stable");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
sender.sendMessage("Unpacking " + repo);
|
sender.sendMessage("Unpacking " + repo);
|
||||||
@@ -355,9 +361,6 @@ public class StudioSVC implements IrisService {
|
|||||||
l.put(i, a.getString(i));
|
l.put(i, a.getString(i));
|
||||||
}
|
}
|
||||||
|
|
||||||
// TEMP FIX
|
|
||||||
l.put("IrisDimensions/overworld/master", "IrisDimensions/overworld/stable");
|
|
||||||
l.put("overworld", "IrisDimensions/overworld/stable");
|
|
||||||
return l;
|
return l;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -379,7 +382,23 @@ public class StudioSVC implements IrisService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static boolean blockIfPackBroken(VolmitSender sender, String dimm) {
|
||||||
|
PackValidationResult validation = PackValidationRegistry.get(dimm);
|
||||||
|
if (validation == null || validation.isLoadable()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
sender.sendMessage("Cannot open studio '" + dimm + "' - pack has blocking errors:");
|
||||||
|
for (String reason : validation.getBlockingErrors()) {
|
||||||
|
sender.sendMessage(" - " + reason);
|
||||||
|
}
|
||||||
|
sender.sendMessage("Fix the pack and run /iris pack validate " + dimm + " to revalidate.");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
public void open(VolmitSender sender, long seed, String dimm, Consumer<World> onDone) throws IrisException {
|
public void open(VolmitSender sender, long seed, String dimm, Consumer<World> onDone) throws IrisException {
|
||||||
|
if (blockIfPackBroken(sender, dimm)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
CompletableFuture<art.arcane.iris.core.runtime.StudioOpenCoordinator.StudioCloseResult> pendingClose = close();
|
CompletableFuture<art.arcane.iris.core.runtime.StudioOpenCoordinator.StudioCloseResult> pendingClose = close();
|
||||||
pendingClose.whenComplete((closeResult, closeThrowable) -> {
|
pendingClose.whenComplete((closeResult, closeThrowable) -> {
|
||||||
if (closeThrowable != null) {
|
if (closeThrowable != null) {
|
||||||
@@ -572,16 +591,18 @@ public class StudioSVC implements IrisService {
|
|||||||
public void create(VolmitSender sender, String s, String downloadable) {
|
public void create(VolmitSender sender, String s, String downloadable) {
|
||||||
boolean shouldDelete = false;
|
boolean shouldDelete = false;
|
||||||
File importPack = getWorkspaceFolder(downloadable);
|
File importPack = getWorkspaceFolder(downloadable);
|
||||||
|
File[] packFiles = importPack.listFiles();
|
||||||
|
|
||||||
if (importPack.listFiles().length == 0) {
|
if (packFiles == null || packFiles.length == 0) {
|
||||||
downloadSearch(sender, downloadable, false);
|
downloadSearch(sender, downloadable, false);
|
||||||
|
packFiles = importPack.listFiles();
|
||||||
|
|
||||||
if (importPack.listFiles().length > 0) {
|
if (packFiles != null && packFiles.length > 0) {
|
||||||
shouldDelete = true;
|
shouldDelete = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (importPack.listFiles().length == 0) {
|
if (packFiles == null || packFiles.length == 0) {
|
||||||
sender.sendMessage("Couldn't find the pack to create a new dimension from.");
|
sender.sendMessage("Couldn't find the pack to create a new dimension from.");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -336,12 +336,22 @@ public class IrisCreator {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int percent = (int) Math.round(progress * 100.0D);
|
||||||
|
int remaining = required - generated;
|
||||||
if (sender.isPlayer()) {
|
if (sender.isPlayer()) {
|
||||||
sender.sendProgress(progress, "Generating");
|
int barWidth = 44;
|
||||||
|
int filled = (int) Math.round(Math.max(0.0D, Math.min(1.0D, progress)) * barWidth);
|
||||||
|
StringBuilder bar = new StringBuilder(barWidth * 3 + 4);
|
||||||
|
bar.append(C.DARK_GRAY).append("[");
|
||||||
|
for (int bi = 0; bi < barWidth; bi++) {
|
||||||
|
bar.append(bi < filled ? C.GREEN : C.DARK_GRAY).append("|");
|
||||||
|
}
|
||||||
|
bar.append(C.DARK_GRAY).append("]");
|
||||||
|
sender.sendAction(bar.toString() + C.GRAY + " " + C.YELLOW + percent + "%" + C.DARK_GRAY + " " + Form.f(generated) + "/" + Form.f(required) + " chunks");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
sender.sendMessage(C.WHITE + "Generating " + Form.pc(progress) + ((C.GRAY + " (" + (required - generated) + " Left)")));
|
sender.sendMessage(C.GOLD + "Generating " + C.YELLOW + percent + "%" + C.GRAY + " " + Form.f(generated) + "/" + Form.f(required) + " chunks" + C.DARK_GRAY + " (" + remaining + " left)");
|
||||||
}, interval));
|
}, interval));
|
||||||
});
|
});
|
||||||
return taskId;
|
return taskId;
|
||||||
@@ -356,12 +366,22 @@ public class IrisCreator {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
double p = progress.get();
|
||||||
|
int percent = (int) Math.round(p * 100.0D);
|
||||||
if (sender.isPlayer()) {
|
if (sender.isPlayer()) {
|
||||||
sender.sendProgress(progress.get(), "Pregenerating");
|
int barWidth = 44;
|
||||||
|
int filled = (int) Math.round(Math.max(0.0D, Math.min(1.0D, p)) * barWidth);
|
||||||
|
StringBuilder bar = new StringBuilder(barWidth * 3 + 4);
|
||||||
|
bar.append(C.DARK_GRAY).append("[");
|
||||||
|
for (int bi = 0; bi < barWidth; bi++) {
|
||||||
|
bar.append(bi < filled ? C.GREEN : C.DARK_GRAY).append("|");
|
||||||
|
}
|
||||||
|
bar.append(C.DARK_GRAY).append("]");
|
||||||
|
sender.sendAction(bar.toString() + C.GRAY + " " + C.YELLOW + percent + "%" + C.GRAY + " | " + C.WHITE + "Pregenerating");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
sender.sendMessage(C.WHITE + "Pregenerating " + Form.pc(progress.get()));
|
sender.sendMessage(C.GOLD + "Pregenerating " + C.YELLOW + percent + "%");
|
||||||
}, interval));
|
}, interval));
|
||||||
return taskId;
|
return taskId;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,9 +19,11 @@
|
|||||||
package art.arcane.iris.engine.decorator;
|
package art.arcane.iris.engine.decorator;
|
||||||
|
|
||||||
import art.arcane.iris.engine.framework.Engine;
|
import art.arcane.iris.engine.framework.Engine;
|
||||||
|
import art.arcane.iris.engine.object.InferredType;
|
||||||
import art.arcane.iris.engine.object.IrisBiome;
|
import art.arcane.iris.engine.object.IrisBiome;
|
||||||
import art.arcane.iris.engine.object.IrisDecorationPart;
|
import art.arcane.iris.engine.object.IrisDecorationPart;
|
||||||
import art.arcane.iris.engine.object.IrisDecorator;
|
import art.arcane.iris.engine.object.IrisDecorator;
|
||||||
|
import art.arcane.iris.util.common.data.B;
|
||||||
import art.arcane.volmlib.util.documentation.BlockCoordinates;
|
import art.arcane.volmlib.util.documentation.BlockCoordinates;
|
||||||
import art.arcane.iris.util.project.hunk.Hunk;
|
import art.arcane.iris.util.project.hunk.Hunk;
|
||||||
import art.arcane.volmlib.util.math.RNG;
|
import art.arcane.volmlib.util.math.RNG;
|
||||||
@@ -40,9 +42,13 @@ public class IrisCeilingDecorator extends IrisEngineDecorator {
|
|||||||
public void decorate(int x, int z, int realX, int realX1, int realX_1, int realZ, int realZ1, int realZ_1, Hunk<BlockData> data, IrisBiome biome, int height, int max) {
|
public void decorate(int x, int z, int realX, int realX1, int realX_1, int realZ, int realZ1, int realZ_1, Hunk<BlockData> data, IrisBiome biome, int height, int max) {
|
||||||
RNG rng = getRNG(realX, realZ);
|
RNG rng = getRNG(realX, realZ);
|
||||||
IrisDecorator decorator = getDecorator(rng, biome, realX, realZ);
|
IrisDecorator decorator = getDecorator(rng, biome, realX, realZ);
|
||||||
|
boolean caveSkipFluid = biome.getInferredType() == InferredType.CAVE;
|
||||||
|
|
||||||
if (decorator != null) {
|
if (decorator != null) {
|
||||||
if (!decorator.isStacking()) {
|
if (!decorator.isStacking()) {
|
||||||
|
if (caveSkipFluid && B.isFluid(data.get(x, height, z))) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
data.set(x, height, z, fixFaces(decorator.getBlockData100(biome, rng, realX, height, realZ, getData()), data, x, z, realX, height, realZ));
|
data.set(x, height, z, fixFaces(decorator.getBlockData100(biome, rng, realX, height, realZ, getData()), data, x, z, realX, height, realZ));
|
||||||
} else {
|
} else {
|
||||||
int stack = decorator.getHeight(rng, realX, realZ, getData());
|
int stack = decorator.getHeight(rng, realX, realZ, getData());
|
||||||
@@ -53,6 +59,9 @@ public class IrisCeilingDecorator extends IrisEngineDecorator {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (stack == 1) {
|
if (stack == 1) {
|
||||||
|
if (caveSkipFluid && B.isFluid(data.get(x, height, z))) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
data.set(x, height, z, decorator.getBlockDataForTop(biome, rng, realX, height, realZ, getData()));
|
data.set(x, height, z, decorator.getBlockDataForTop(biome, rng, realX, height, realZ, getData()));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -92,6 +101,9 @@ public class IrisCeilingDecorator extends IrisEngineDecorator {
|
|||||||
((PointedDripstone) bd).setVerticalDirection(BlockFace.DOWN);
|
((PointedDripstone) bd).setVerticalDirection(BlockFace.DOWN);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (caveSkipFluid && B.isFluid(data.get(x, h, z))) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
data.set(x, h, z, bd);
|
data.set(x, h, z, bd);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -51,6 +51,7 @@ public class IrisSurfaceDecorator extends IrisEngineDecorator {
|
|||||||
IrisDecorator decorator = getDecorator(rng, biome, realX, realZ);
|
IrisDecorator decorator = getDecorator(rng, biome, realX, realZ);
|
||||||
bdx = data.get(x, height, z);
|
bdx = data.get(x, height, z);
|
||||||
boolean underwater = height < getDimension().getFluidHeight() && biome.getInferredType() != InferredType.CAVE;
|
boolean underwater = height < getDimension().getFluidHeight() && biome.getInferredType() != InferredType.CAVE;
|
||||||
|
boolean caveSkipFluid = biome.getInferredType() == InferredType.CAVE;
|
||||||
|
|
||||||
if (decorator != null) {
|
if (decorator != null) {
|
||||||
if (!decorator.isForcePlace() && !decorator.getSlopeCondition().isDefault()
|
if (!decorator.isForcePlace() && !decorator.getSlopeCondition().isDefault()
|
||||||
@@ -68,6 +69,9 @@ public class IrisSurfaceDecorator extends IrisEngineDecorator {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (decorator.getForceBlock() != null) {
|
if (decorator.getForceBlock() != null) {
|
||||||
|
if (caveSkipFluid && B.isFluid(data.get(x, height, z))) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
data.set(x, height, z, fixFaces(decorator.getForceBlock().getBlockData(getData()), data, x, z, realX, height, realZ));
|
data.set(x, height, z, fixFaces(decorator.getForceBlock().getBlockData(getData()), data, x, z, realX, height, realZ));
|
||||||
} else if (!decorator.isForcePlace()) {
|
} else if (!decorator.isForcePlace()) {
|
||||||
if (decorator.getWhitelist() != null && decorator.getWhitelist().stream().noneMatch(d -> d.getBlockData(getData()).equals(bdx))) {
|
if (decorator.getWhitelist() != null && decorator.getWhitelist().stream().noneMatch(d -> d.getBlockData(getData()).equals(bdx))) {
|
||||||
@@ -82,7 +86,9 @@ public class IrisSurfaceDecorator extends IrisEngineDecorator {
|
|||||||
bd = bd.clone();
|
bd = bd.clone();
|
||||||
((Bisected) bd).setHalf(Bisected.Half.TOP);
|
((Bisected) bd).setHalf(Bisected.Half.TOP);
|
||||||
try {
|
try {
|
||||||
|
if (!caveSkipFluid || !B.isFluid(data.get(x, height + 2, z))) {
|
||||||
data.set(x, height + 2, z, bd);
|
data.set(x, height + 2, z, bd);
|
||||||
|
}
|
||||||
} catch (Throwable e) {
|
} catch (Throwable e) {
|
||||||
Iris.reportError(e);
|
Iris.reportError(e);
|
||||||
}
|
}
|
||||||
@@ -107,6 +113,9 @@ public class IrisSurfaceDecorator extends IrisEngineDecorator {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (stack == 1) {
|
if (stack == 1) {
|
||||||
|
if (caveSkipFluid && B.isFluid(data.get(x, height, z))) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
data.set(x, height, z, decorator.getBlockDataForTop(biome, rng, realX, height, realZ, getData()));
|
data.set(x, height, z, decorator.getBlockDataForTop(biome, rng, realX, height, realZ, getData()));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -130,6 +139,10 @@ public class IrisSurfaceDecorator extends IrisEngineDecorator {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (caveSkipFluid && B.isFluid(data.get(x, height + 1 + i, z))) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
if (bd instanceof PointedDripstone) {
|
if (bd instanceof PointedDripstone) {
|
||||||
PointedDripstone.Thickness th = PointedDripstone.Thickness.BASE;
|
PointedDripstone.Thickness th = PointedDripstone.Thickness.BASE;
|
||||||
|
|
||||||
|
|||||||
@@ -960,50 +960,11 @@ public interface Engine extends DataProvider, Fallible, LootProvider, BlockUpdat
|
|||||||
}
|
}
|
||||||
|
|
||||||
default void gotoBiome(IrisBiome biome, Player player, boolean teleport) {
|
default void gotoBiome(IrisBiome biome, Player player, boolean teleport) {
|
||||||
Set<String> regionKeys = getDimension()
|
Locator.surfaceBiome(biome.getLoadKey()).find(player, teleport, "Biome " + biome.getName());
|
||||||
.getAllRegions(this).stream()
|
|
||||||
.filter((i) -> i.getAllBiomeIds().contains(biome.getLoadKey()))
|
|
||||||
.map(IrisRegistrant::getLoadKey)
|
|
||||||
.collect(Collectors.toSet());
|
|
||||||
Locator<IrisBiome> lb = Locator.surfaceBiome(biome.getLoadKey());
|
|
||||||
Locator<IrisBiome> locator = (engine, chunk)
|
|
||||||
-> regionKeys.contains(getRegion((chunk.getX() << 4) + 8, (chunk.getZ() << 4) + 8).getLoadKey())
|
|
||||||
&& lb.matches(engine, chunk);
|
|
||||||
|
|
||||||
if (!regionKeys.isEmpty()) {
|
|
||||||
locator.find(player, teleport, "Biome " + biome.getName());
|
|
||||||
} else {
|
|
||||||
player.sendMessage(C.RED + biome.getName() + " is not in any defined regions!");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
default void gotoObject(String s, Player player, boolean teleport) {
|
default void gotoObject(String s, Player player, boolean teleport) {
|
||||||
Set<String> biomeKeys = getDimension().getAllBiomes(this).stream()
|
Locator.object(s).find(player, teleport, "Object " + s);
|
||||||
.filter((i) -> i.getObjects().stream().anyMatch((f) -> f.getPlace().contains(s)))
|
|
||||||
.map(IrisRegistrant::getLoadKey)
|
|
||||||
.collect(Collectors.toSet());
|
|
||||||
Set<String> regionKeys = getDimension().getAllRegions(this).stream()
|
|
||||||
.filter((i) -> i.getAllBiomeIds().stream().anyMatch(biomeKeys::contains)
|
|
||||||
|| i.getObjects().stream().anyMatch((f) -> f.getPlace().contains(s)))
|
|
||||||
.map(IrisRegistrant::getLoadKey)
|
|
||||||
.collect(Collectors.toSet());
|
|
||||||
|
|
||||||
Locator<IrisObject> sl = Locator.object(s);
|
|
||||||
Locator<IrisBiome> locator = (engine, chunk) -> {
|
|
||||||
if (biomeKeys.contains(getSurfaceBiome((chunk.getX() << 4) + 8, (chunk.getZ() << 4) + 8).getLoadKey())) {
|
|
||||||
return sl.matches(engine, chunk);
|
|
||||||
} else if (regionKeys.contains(getRegion((chunk.getX() << 4) + 8, (chunk.getZ() << 4) + 8).getLoadKey())) {
|
|
||||||
return sl.matches(engine, chunk);
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
};
|
|
||||||
|
|
||||||
if (!regionKeys.isEmpty()) {
|
|
||||||
locator.find(player, teleport, "Object " + s);
|
|
||||||
} else {
|
|
||||||
player.sendMessage(C.RED + s + " is not in any defined regions or biomes!");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
default boolean hasObjectPlacement(String objectKey) {
|
default boolean hasObjectPlacement(String objectKey) {
|
||||||
|
|||||||
@@ -114,9 +114,14 @@ public interface Locator<T> {
|
|||||||
boolean matches(Engine engine, Position2 chunk);
|
boolean matches(Engine engine, Position2 chunk);
|
||||||
|
|
||||||
default void find(Player player, boolean teleport, String message) {
|
default void find(Player player, boolean teleport, String message) {
|
||||||
find(player, location -> {
|
find(player, 120_000, location -> {
|
||||||
|
if (location == null) {
|
||||||
|
player.sendMessage(C.RED + "Could not find " + message + " within search range.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
if (teleport) {
|
if (teleport) {
|
||||||
J.runEntity(player, () -> teleportAsyncSafely(player, location));
|
J.runEntity(player, () -> teleportAsyncSafely(player, location));
|
||||||
|
player.sendMessage(C.GREEN + "Teleporting to " + message + "...");
|
||||||
} else {
|
} else {
|
||||||
player.sendMessage(C.GREEN + message + " at: " + location.getBlockX() + " " + location.getBlockY() + " " + location.getBlockZ());
|
player.sendMessage(C.GREEN + message + " at: " + location.getBlockX() + " " + location.getBlockY() + " " + location.getBlockZ());
|
||||||
}
|
}
|
||||||
@@ -124,7 +129,7 @@ public interface Locator<T> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
default void find(Player player, Consumer<Location> consumer) {
|
default void find(Player player, Consumer<Location> consumer) {
|
||||||
find(player, 30_000, consumer);
|
find(player, 120_000, consumer);
|
||||||
}
|
}
|
||||||
|
|
||||||
default void find(Player player, long timeout, Consumer<Location> consumer) {
|
default void find(Player player, long timeout, Consumer<Location> consumer) {
|
||||||
@@ -137,11 +142,13 @@ public interface Locator<T> {
|
|||||||
Position2 at = find(engine, new Position2(player.getLocation().getBlockX() >> 4, player.getLocation().getBlockZ() >> 4), timeout, checks::set).get();
|
Position2 at = find(engine, new Position2(player.getLocation().getBlockX() >> 4, player.getLocation().getBlockZ() >> 4), timeout, checks::set).get();
|
||||||
|
|
||||||
if (at != null) {
|
if (at != null) {
|
||||||
consumer.accept(new Location(world, (at.getX() << 4) + 8,
|
int bx = (at.getX() << 4) + 8;
|
||||||
engine.getHeight(
|
int bz = (at.getZ() << 4) + 8;
|
||||||
(at.getX() << 4) + 8,
|
consumer.accept(new Location(world, bx,
|
||||||
(at.getZ() << 4) + 8, false),
|
world.getHighestBlockYAt(bx, bz) + 2,
|
||||||
(at.getZ() << 4) + 8));
|
bz));
|
||||||
|
} else {
|
||||||
|
consumer.accept(null);
|
||||||
}
|
}
|
||||||
} catch (WrongEngineBroException | InterruptedException | ExecutionException e) {
|
} catch (WrongEngineBroException | InterruptedException | ExecutionException e) {
|
||||||
e.printStackTrace();
|
e.printStackTrace();
|
||||||
@@ -172,18 +179,17 @@ public interface Locator<T> {
|
|||||||
cancelSearch();
|
cancelSearch();
|
||||||
|
|
||||||
return MultiBurst.burst.completeValue(() -> {
|
return MultiBurst.burst.completeValue(() -> {
|
||||||
int tc = IrisSettings.getThreadCount(IrisSettings.get().getConcurrency().getParallelism()) * 17;
|
int tc = IrisSettings.getThreadCount(IrisSettings.get().getConcurrency().getParallelism()) * 32;
|
||||||
MultiBurst burst = MultiBurst.burst;
|
MultiBurst burst = MultiBurst.burst;
|
||||||
AtomicBoolean found = new AtomicBoolean(false);
|
AtomicBoolean found = new AtomicBoolean(false);
|
||||||
Position2 cursor = pos;
|
|
||||||
AtomicInteger searched = new AtomicInteger();
|
AtomicInteger searched = new AtomicInteger();
|
||||||
AtomicBoolean stop = new AtomicBoolean(false);
|
AtomicBoolean stop = new AtomicBoolean(false);
|
||||||
AtomicReference<Position2> foundPos = new AtomicReference<>();
|
AtomicReference<Position2> foundPos = new AtomicReference<>();
|
||||||
PrecisionStopwatch px = PrecisionStopwatch.start();
|
PrecisionStopwatch px = PrecisionStopwatch.start();
|
||||||
LocatorCanceller.cancel = () -> stop.set(true);
|
LocatorCanceller.cancel = () -> stop.set(true);
|
||||||
AtomicReference<Position2> next = new AtomicReference<>(cursor);
|
AtomicReference<Position2> next = new AtomicReference<>(pos);
|
||||||
Spiraler s = new Spiraler(100000, 100000, (x, z) -> next.set(new Position2(x, z)));
|
Spiraler s = new Spiraler(100000, 100000, (x, z) -> next.set(new Position2(x, z)));
|
||||||
s.setOffset(cursor.getX(), cursor.getZ());
|
s.setOffset(pos.getX(), pos.getZ());
|
||||||
s.next();
|
s.next();
|
||||||
while (!found.get() && !stop.get() && px.getMilliseconds() < timeout) {
|
while (!found.get() && !stop.get() && px.getMilliseconds() < timeout) {
|
||||||
BurstExecutor e = burst.burst(tc);
|
BurstExecutor e = burst.burst(tc);
|
||||||
@@ -192,11 +198,11 @@ public interface Locator<T> {
|
|||||||
Position2 p = next.get();
|
Position2 p = next.get();
|
||||||
s.next();
|
s.next();
|
||||||
e.queue(() -> {
|
e.queue(() -> {
|
||||||
if (matches(engine, p)) {
|
if (found.get()) {
|
||||||
if (foundPos.get() == null) {
|
return;
|
||||||
foundPos.set(p);
|
|
||||||
}
|
}
|
||||||
|
if (matches(engine, p)) {
|
||||||
|
foundPos.compareAndSet(null, p);
|
||||||
found.set(true);
|
found.set(true);
|
||||||
}
|
}
|
||||||
searched.incrementAndGet();
|
searched.incrementAndGet();
|
||||||
|
|||||||
@@ -42,10 +42,8 @@ public class IrisCaveCarver3D {
|
|||||||
private static final byte LIQUID_LAVA = 2;
|
private static final byte LIQUID_LAVA = 2;
|
||||||
private static final byte LIQUID_FORCED_AIR = 3;
|
private static final byte LIQUID_FORCED_AIR = 3;
|
||||||
private static final int ADAPTIVE_MIN_PLANE_COLUMNS = 32;
|
private static final int ADAPTIVE_MIN_PLANE_COLUMNS = 32;
|
||||||
private static final int ADAPTIVE_DEEP_MIN_PLANE_COLUMNS = 64;
|
|
||||||
private static final int ADAPTIVE_DEEP_SAMPLE_STEP = 8;
|
private static final int ADAPTIVE_DEEP_SAMPLE_STEP = 8;
|
||||||
private static final int ADAPTIVE_DEEP_SURFACE_MARGIN = 12;
|
private static final int ADAPTIVE_DEEP_SURFACE_MARGIN = 12;
|
||||||
private static final int ADAPTIVE_DEEP_NEAR_SURFACE_DIVISOR = 4;
|
|
||||||
private static final double ADAPTIVE_LOCAL_RANGE_SCALE = 0.25D;
|
private static final double ADAPTIVE_LOCAL_RANGE_SCALE = 0.25D;
|
||||||
private static final double ADAPTIVE_DEEP_MARGIN_BOOST = 0.015D;
|
private static final double ADAPTIVE_DEEP_MARGIN_BOOST = 0.015D;
|
||||||
private static final ThreadLocal<Scratch> SCRATCH = ThreadLocal.withInitial(Scratch::new);
|
private static final ThreadLocal<Scratch> SCRATCH = ThreadLocal.withInitial(Scratch::new);
|
||||||
@@ -232,8 +230,6 @@ public class IrisCaveCarver3D {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
int minCarveCells = Math.max(0, profile.getMinCarveCells());
|
|
||||||
double recoveryThresholdBoost = Math.max(0, profile.getRecoveryThresholdBoost());
|
|
||||||
int carved;
|
int carved;
|
||||||
if (exactSampling) {
|
if (exactSampling) {
|
||||||
if (adaptiveSampling) {
|
if (adaptiveSampling) {
|
||||||
@@ -258,29 +254,6 @@ public class IrisCaveCarver3D {
|
|||||||
0D,
|
0D,
|
||||||
false
|
false
|
||||||
);
|
);
|
||||||
if (carved < minCarveCells && recoveryThresholdBoost > 0D) {
|
|
||||||
carved += carvePassAdaptive(
|
|
||||||
chunk,
|
|
||||||
x0,
|
|
||||||
z0,
|
|
||||||
minY,
|
|
||||||
maxY,
|
|
||||||
adaptiveSampleStep,
|
|
||||||
adaptiveThresholdMargin,
|
|
||||||
surfaceBreakThresholdBoost,
|
|
||||||
columnMaxY,
|
|
||||||
surfaceBreakFloorY,
|
|
||||||
surfaceBreakColumn,
|
|
||||||
columnThreshold,
|
|
||||||
clampedWeights,
|
|
||||||
verticalEdgeFade,
|
|
||||||
matterByY,
|
|
||||||
resolvedMinWeight,
|
|
||||||
resolvedThresholdPenalty,
|
|
||||||
recoveryThresholdBoost,
|
|
||||||
true
|
|
||||||
);
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
carved = carvePassExact(
|
carved = carvePassExact(
|
||||||
chunk,
|
chunk,
|
||||||
@@ -301,27 +274,6 @@ public class IrisCaveCarver3D {
|
|||||||
0D,
|
0D,
|
||||||
false
|
false
|
||||||
);
|
);
|
||||||
if (carved < minCarveCells && recoveryThresholdBoost > 0D) {
|
|
||||||
carved += carvePassExact(
|
|
||||||
chunk,
|
|
||||||
x0,
|
|
||||||
z0,
|
|
||||||
minY,
|
|
||||||
maxY,
|
|
||||||
surfaceBreakThresholdBoost,
|
|
||||||
columnMaxY,
|
|
||||||
surfaceBreakFloorY,
|
|
||||||
surfaceBreakColumn,
|
|
||||||
columnThreshold,
|
|
||||||
clampedWeights,
|
|
||||||
verticalEdgeFade,
|
|
||||||
matterByY,
|
|
||||||
resolvedMinWeight,
|
|
||||||
resolvedThresholdPenalty,
|
|
||||||
recoveryThresholdBoost,
|
|
||||||
true
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
int latticeStep = sampleStep;
|
int latticeStep = sampleStep;
|
||||||
@@ -345,28 +297,6 @@ public class IrisCaveCarver3D {
|
|||||||
0D,
|
0D,
|
||||||
false
|
false
|
||||||
);
|
);
|
||||||
if (carved < minCarveCells && recoveryThresholdBoost > 0D) {
|
|
||||||
carved += carvePassLattice(
|
|
||||||
chunk,
|
|
||||||
x0,
|
|
||||||
z0,
|
|
||||||
minY,
|
|
||||||
maxY,
|
|
||||||
latticeStep,
|
|
||||||
surfaceBreakThresholdBoost,
|
|
||||||
columnMaxY,
|
|
||||||
surfaceBreakFloorY,
|
|
||||||
surfaceBreakColumn,
|
|
||||||
columnThreshold,
|
|
||||||
clampedWeights,
|
|
||||||
verticalEdgeFade,
|
|
||||||
matterByY,
|
|
||||||
resolvedMinWeight,
|
|
||||||
resolvedThresholdPenalty,
|
|
||||||
recoveryThresholdBoost,
|
|
||||||
true
|
|
||||||
);
|
|
||||||
}
|
|
||||||
if (carved == 0 && hasFallbackCandidates(columnMaxY, clampedWeights, minY, resolvedMinWeight)) {
|
if (carved == 0 && hasFallbackCandidates(columnMaxY, clampedWeights, minY, resolvedMinWeight)) {
|
||||||
carved += carvePassFallback(
|
carved += carvePassFallback(
|
||||||
chunk,
|
chunk,
|
||||||
@@ -388,28 +318,6 @@ public class IrisCaveCarver3D {
|
|||||||
0D,
|
0D,
|
||||||
false
|
false
|
||||||
);
|
);
|
||||||
if (carved < minCarveCells && recoveryThresholdBoost > 0D) {
|
|
||||||
carved += carvePassFallback(
|
|
||||||
chunk,
|
|
||||||
x0,
|
|
||||||
z0,
|
|
||||||
minY,
|
|
||||||
maxY,
|
|
||||||
sampleStep,
|
|
||||||
surfaceBreakThresholdBoost,
|
|
||||||
columnMaxY,
|
|
||||||
surfaceBreakFloorY,
|
|
||||||
surfaceBreakColumn,
|
|
||||||
columnThreshold,
|
|
||||||
clampedWeights,
|
|
||||||
verticalEdgeFade,
|
|
||||||
matterByY,
|
|
||||||
resolvedMinWeight,
|
|
||||||
resolvedThresholdPenalty,
|
|
||||||
recoveryThresholdBoost,
|
|
||||||
true
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -614,14 +522,7 @@ public class IrisCaveCarver3D {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
int effectiveAdaptiveSampleStep = resolveAdaptivePlaneSampleStep(
|
int effectiveAdaptiveSampleStep = resolveAdaptivePlaneSampleStep(y, adaptiveSampleStep);
|
||||||
y,
|
|
||||||
planeColumnIndices,
|
|
||||||
planeCount,
|
|
||||||
adaptiveSampleStep,
|
|
||||||
surfaceBreakColumn,
|
|
||||||
surfaceBreakFloorY
|
|
||||||
);
|
|
||||||
double effectiveAdaptiveThresholdMargin = resolveAdaptivePlaneThresholdMargin(
|
double effectiveAdaptiveThresholdMargin = resolveAdaptivePlaneThresholdMargin(
|
||||||
adaptiveThresholdMargin,
|
adaptiveThresholdMargin,
|
||||||
adaptiveSampleStep,
|
adaptiveSampleStep,
|
||||||
@@ -678,31 +579,14 @@ public class IrisCaveCarver3D {
|
|||||||
return carved;
|
return carved;
|
||||||
}
|
}
|
||||||
|
|
||||||
private int resolveAdaptivePlaneSampleStep(
|
private int resolveAdaptivePlaneSampleStep(int y, int adaptiveSampleStep) {
|
||||||
int y,
|
if (adaptiveSampleStep >= ADAPTIVE_DEEP_SAMPLE_STEP) {
|
||||||
int[] planeColumnIndices,
|
|
||||||
int planeCount,
|
|
||||||
int adaptiveSampleStep,
|
|
||||||
boolean[] surfaceBreakColumn,
|
|
||||||
int[] surfaceBreakFloorY
|
|
||||||
) {
|
|
||||||
if (adaptiveSampleStep >= ADAPTIVE_DEEP_SAMPLE_STEP || planeCount < ADAPTIVE_DEEP_MIN_PLANE_COLUMNS) {
|
|
||||||
return adaptiveSampleStep;
|
return adaptiveSampleStep;
|
||||||
}
|
}
|
||||||
|
|
||||||
int nearSurfaceColumns = 0;
|
int profileMaxY = (int) Math.ceil(profile.getVerticalRange().getMax());
|
||||||
int allowedNearSurfaceColumns = Math.max(8, planeCount / ADAPTIVE_DEEP_NEAR_SURFACE_DIVISOR);
|
int fineBandFloorY = profileMaxY - profile.getSurfaceBreakDepth() - ADAPTIVE_DEEP_SURFACE_MARGIN;
|
||||||
for (int planeIndex = 0; planeIndex < planeCount; planeIndex++) {
|
return y >= fineBandFloorY ? adaptiveSampleStep : ADAPTIVE_DEEP_SAMPLE_STEP;
|
||||||
int columnIndex = planeColumnIndices[planeIndex];
|
|
||||||
if (surfaceBreakColumn[columnIndex] || y > (surfaceBreakFloorY[columnIndex] - ADAPTIVE_DEEP_SURFACE_MARGIN)) {
|
|
||||||
nearSurfaceColumns++;
|
|
||||||
if (nearSurfaceColumns > allowedNearSurfaceColumns) {
|
|
||||||
return adaptiveSampleStep;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return ADAPTIVE_DEEP_SAMPLE_STEP;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private double resolveAdaptivePlaneThresholdMargin(
|
private double resolveAdaptivePlaneThresholdMargin(
|
||||||
|
|||||||
+10
-19
@@ -140,37 +140,28 @@ public class MantleCarvingComponent extends IrisMantleComponent {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
IrisCaveProfile dominantProfile = null;
|
int columnIndex = PowerOfTwoCoordinates.packLocal16(localX, localZ);
|
||||||
double dominantKernelWeight = Double.NEGATIVE_INFINITY;
|
|
||||||
for (int profileIndex = 0; profileIndex < profileCount; profileIndex++) {
|
for (int profileIndex = 0; profileIndex < profileCount; profileIndex++) {
|
||||||
IrisCaveProfile profile = kernelProfiles[profileIndex];
|
IrisCaveProfile profile = kernelProfiles[profileIndex];
|
||||||
double kernelWeight = kernelProfileWeights[profileIndex];
|
double kernelWeight = kernelProfileWeights[profileIndex];
|
||||||
if (kernelWeight > dominantKernelWeight) {
|
|
||||||
dominantProfile = profile;
|
|
||||||
dominantKernelWeight = kernelWeight;
|
|
||||||
} else if (kernelWeight == dominantKernelWeight
|
|
||||||
&& profileSortKey(profile) < profileSortKey(dominantProfile)) {
|
|
||||||
dominantProfile = profile;
|
|
||||||
}
|
|
||||||
kernelProfiles[profileIndex] = null;
|
kernelProfiles[profileIndex] = null;
|
||||||
kernelProfileWeights[profileIndex] = 0D;
|
kernelProfileWeights[profileIndex] = 0D;
|
||||||
}
|
|
||||||
|
|
||||||
if (dominantProfile == null) {
|
double columnWeight = clampWeight(kernelWeight / totalKernelWeight);
|
||||||
|
if (columnWeight < MIN_WEIGHT) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
int columnIndex = PowerOfTwoCoordinates.packLocal16(localX, localZ);
|
double[] weights = columnProfileWeights.get(profile);
|
||||||
double dominantWeight = clampWeight(dominantKernelWeight / totalKernelWeight);
|
|
||||||
double[] weights = columnProfileWeights.get(dominantProfile);
|
|
||||||
if (weights == null) {
|
if (weights == null) {
|
||||||
weights = new double[CHUNK_AREA];
|
weights = new double[CHUNK_AREA];
|
||||||
columnProfileWeights.put(dominantProfile, weights);
|
columnProfileWeights.put(profile, weights);
|
||||||
} else if (!activeProfiles.containsKey(dominantProfile)) {
|
} else if (!activeProfiles.containsKey(profile)) {
|
||||||
Arrays.fill(weights, 0D);
|
Arrays.fill(weights, 0D);
|
||||||
}
|
}
|
||||||
activeProfiles.put(dominantProfile, Boolean.TRUE);
|
activeProfiles.put(profile, Boolean.TRUE);
|
||||||
weights[columnIndex] = dominantWeight;
|
weights[columnIndex] = columnWeight;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -440,7 +431,7 @@ public class MantleCarvingComponent extends IrisMantleComponent {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if (cachedChunkHeights != null) {
|
if (cachedChunkHeights != null) {
|
||||||
surfaceHeights[columnIndex] = (int) Math.round(cachedChunkHeights[columnIndex]);
|
surfaceHeights[columnIndex] = (int) Math.round(cachedChunkHeights[(localZ << 4) + localX]);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
surfaceHeights[columnIndex] = getEngineMantle().getEngine().getHeight(worldX, worldZ);
|
surfaceHeights[columnIndex] = getEngineMantle().getEngine().getHeight(worldX, worldZ);
|
||||||
|
|||||||
+75
-37
@@ -37,10 +37,8 @@ import art.arcane.iris.util.project.stream.ProceduralStream;
|
|||||||
import art.arcane.volmlib.util.documentation.BlockCoordinates;
|
import art.arcane.volmlib.util.documentation.BlockCoordinates;
|
||||||
import art.arcane.volmlib.util.documentation.ChunkCoordinates;
|
import art.arcane.volmlib.util.documentation.ChunkCoordinates;
|
||||||
import art.arcane.volmlib.util.format.Form;
|
import art.arcane.volmlib.util.format.Form;
|
||||||
import art.arcane.volmlib.util.mantle.runtime.MantleChunk;
|
|
||||||
import art.arcane.volmlib.util.mantle.flag.ReservedFlag;
|
import art.arcane.volmlib.util.mantle.flag.ReservedFlag;
|
||||||
import art.arcane.volmlib.util.math.RNG;
|
import art.arcane.volmlib.util.math.RNG;
|
||||||
import art.arcane.volmlib.util.matter.Matter;
|
|
||||||
import art.arcane.volmlib.util.matter.MatterStructurePOI;
|
import art.arcane.volmlib.util.matter.MatterStructurePOI;
|
||||||
import art.arcane.iris.util.project.noise.CNG;
|
import art.arcane.iris.util.project.noise.CNG;
|
||||||
import art.arcane.iris.util.project.noise.NoiseType;
|
import art.arcane.iris.util.project.noise.NoiseType;
|
||||||
@@ -373,19 +371,46 @@ public class MantleObjectComponent extends IrisMantleComponent {
|
|||||||
int zz = rng.i(z, z + 15);
|
int zz = rng.i(z, z + 15);
|
||||||
int surfaceObjectExclusionDepth = resolveSurfaceObjectExclusionDepth(surfaceObjectExclusionBaseDepth, v);
|
int surfaceObjectExclusionDepth = resolveSurfaceObjectExclusionDepth(surfaceObjectExclusionBaseDepth, v);
|
||||||
int surfaceObjectExclusionRadius = resolveSurfaceObjectExclusionRadius(v);
|
int surfaceObjectExclusionRadius = resolveSurfaceObjectExclusionRadius(v);
|
||||||
if (surfaceObjectExclusionDepth > 0 && hasSurfaceCarveExposure(writer, surfaceHeightLookup, xx, zz, surfaceObjectExclusionDepth, surfaceObjectExclusionRadius)) {
|
boolean overCave = surfaceObjectExclusionDepth > 0 && hasSurfaceCarveExposure(writer, surfaceHeightLookup, xx, zz, surfaceObjectExclusionDepth, surfaceObjectExclusionRadius);
|
||||||
rejected++;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
int id = rng.i(0, Integer.MAX_VALUE);
|
int id = rng.i(0, Integer.MAX_VALUE);
|
||||||
IrisObjectPlacement effectivePlacement = resolveEffectivePlacement(objectPlacement, v);
|
IrisObjectPlacement effectivePlacement = resolveEffectivePlacement(objectPlacement, v);
|
||||||
try {
|
try {
|
||||||
int result = v.place(xx, -1, zz, writer, effectivePlacement, rng, (b, data) -> {
|
int result = -1;
|
||||||
|
String fallbackPath = "surface";
|
||||||
|
|
||||||
|
if (overCave) {
|
||||||
|
int caveFloorY = findNearestCaveFloor(writer, xx, zz);
|
||||||
|
if (caveFloorY > 0) {
|
||||||
|
IrisObjectPlacement floorPlacement = effectivePlacement.toPlacement(v.getLoadKey());
|
||||||
|
floorPlacement.setMode(ObjectPlaceMode.FAST_MIN_HEIGHT);
|
||||||
|
result = v.place(xx, caveFloorY, zz, writer, floorPlacement, rng, (b, data) -> {
|
||||||
writer.setData(b.getX(), b.getY(), b.getZ(), v.getLoadKey() + "@" + id);
|
writer.setData(b.getX(), b.getY(), b.getZ(), v.getLoadKey() + "@" + id);
|
||||||
if (effectivePlacement.isDolphinTarget() && effectivePlacement.isUnderwater() && B.isStorageChest(data)) {
|
if (effectivePlacement.isDolphinTarget() && effectivePlacement.isUnderwater() && B.isStorageChest(data)) {
|
||||||
writer.setData(b.getX(), b.getY(), b.getZ(), MatterStructurePOI.BURIED_TREASURE);
|
writer.setData(b.getX(), b.getY(), b.getZ(), MatterStructurePOI.BURIED_TREASURE);
|
||||||
}
|
}
|
||||||
}, null, getData());
|
}, null, getData());
|
||||||
|
fallbackPath = "cave-floor";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (result < 0) {
|
||||||
|
IrisObjectPlacement stiltPlacement = effectivePlacement.toPlacement(v.getLoadKey());
|
||||||
|
stiltPlacement.setMode(ObjectPlaceMode.FAST_MIN_STILT);
|
||||||
|
result = v.place(xx, -1, zz, writer, stiltPlacement, rng, (b, data) -> {
|
||||||
|
writer.setData(b.getX(), b.getY(), b.getZ(), v.getLoadKey() + "@" + id);
|
||||||
|
if (effectivePlacement.isDolphinTarget() && effectivePlacement.isUnderwater() && B.isStorageChest(data)) {
|
||||||
|
writer.setData(b.getX(), b.getY(), b.getZ(), MatterStructurePOI.BURIED_TREASURE);
|
||||||
|
}
|
||||||
|
}, null, getData());
|
||||||
|
fallbackPath = "stilt";
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
result = v.place(xx, -1, zz, writer, effectivePlacement, rng, (b, data) -> {
|
||||||
|
writer.setData(b.getX(), b.getY(), b.getZ(), v.getLoadKey() + "@" + id);
|
||||||
|
if (effectivePlacement.isDolphinTarget() && effectivePlacement.isUnderwater() && B.isStorageChest(data)) {
|
||||||
|
writer.setData(b.getX(), b.getY(), b.getZ(), MatterStructurePOI.BURIED_TREASURE);
|
||||||
|
}
|
||||||
|
}, null, getData());
|
||||||
|
}
|
||||||
|
|
||||||
if (result >= 0) {
|
if (result >= 0) {
|
||||||
placed++;
|
placed++;
|
||||||
@@ -400,6 +425,8 @@ public class MantleObjectComponent extends IrisMantleComponent {
|
|||||||
+ " resultY=" + result
|
+ " resultY=" + result
|
||||||
+ " px=" + xx
|
+ " px=" + xx
|
||||||
+ " pz=" + zz
|
+ " pz=" + zz
|
||||||
|
+ " overCave=" + overCave
|
||||||
|
+ " fallback=" + fallbackPath
|
||||||
+ " densityIndex=" + i
|
+ " densityIndex=" + i
|
||||||
+ " density=" + density);
|
+ " density=" + density);
|
||||||
}
|
}
|
||||||
@@ -729,6 +756,14 @@ public class MantleObjectComponent extends IrisMantleComponent {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private int findNearestCaveFloor(MantleWriter writer, int x, int z) {
|
||||||
|
KList<Integer> anchors = scanCaveAnchorColumn(writer, IrisCaveAnchorMode.FLOOR, 1, 0, x, z);
|
||||||
|
if (anchors.isEmpty()) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
return anchors.get(anchors.size() - 1);
|
||||||
|
}
|
||||||
|
|
||||||
private int findCaveAnchorY(MantleWriter writer, RNG rng, int x, int z, IrisCaveAnchorMode anchorMode, int anchorScanStep, int objectMinDepthBelowSurface, KMap<Long, KList<Integer>> anchorCache) {
|
private int findCaveAnchorY(MantleWriter writer, RNG rng, int x, int z, IrisCaveAnchorMode anchorMode, int anchorScanStep, int objectMinDepthBelowSurface, KMap<Long, KList<Integer>> anchorCache) {
|
||||||
long key = Cache.key(x, z);
|
long key = Cache.key(x, z);
|
||||||
KList<Integer> anchors = anchorCache.computeIfAbsent(key, (k) -> scanCaveAnchorColumn(writer, anchorMode, anchorScanStep, objectMinDepthBelowSurface, x, z));
|
KList<Integer> anchors = anchorCache.computeIfAbsent(key, (k) -> scanCaveAnchorColumn(writer, anchorMode, anchorScanStep, objectMinDepthBelowSurface, x, z));
|
||||||
@@ -744,55 +779,58 @@ public class MantleObjectComponent extends IrisMantleComponent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private KList<Integer> scanCaveAnchorColumn(MantleWriter writer, IrisCaveAnchorMode anchorMode, int anchorScanStep, int objectMinDepthBelowSurface, int x, int z) {
|
private KList<Integer> scanCaveAnchorColumn(MantleWriter writer, IrisCaveAnchorMode anchorMode, int anchorScanStep, int objectMinDepthBelowSurface, int x, int z) {
|
||||||
KList<Integer> anchors = new KList<>();
|
|
||||||
int height = getEngineMantle().getEngine().getHeight();
|
int height = getEngineMantle().getEngine().getHeight();
|
||||||
int step = Math.max(1, anchorScanStep);
|
int step = Math.max(1, anchorScanStep);
|
||||||
int surfaceY = getEngineMantle().getEngine().getHeight(x, z);
|
int surfaceY = getEngineMantle().getEngine().getHeight(x, z);
|
||||||
int maxAnchorY = Math.min(height - 1, surfaceY - Math.max(0, objectMinDepthBelowSurface));
|
int baseMaxAnchorY = Math.min(height - 1, surfaceY - Math.max(0, objectMinDepthBelowSurface));
|
||||||
if (maxAnchorY <= 1) {
|
if (baseMaxAnchorY <= 1) {
|
||||||
logCaveAnchorDiag(writer, x, z, surfaceY, maxAnchorY, height, objectMinDepthBelowSurface, 0, 0);
|
return new KList<>();
|
||||||
|
}
|
||||||
|
|
||||||
|
KList<Integer> anchors = scanCaveAnchorRange(writer, anchorMode, step, x, z, height, baseMaxAnchorY);
|
||||||
|
if (!anchors.isEmpty()) {
|
||||||
return anchors;
|
return anchors;
|
||||||
}
|
}
|
||||||
|
|
||||||
int carvedCount = 0;
|
int widenedMaxAnchorY = Math.min(height - 1, surfaceY - 3);
|
||||||
|
widenedMaxAnchorY = Math.min(widenedMaxAnchorY, baseMaxAnchorY + Math.max(0, objectMinDepthBelowSurface) / 2);
|
||||||
|
if (widenedMaxAnchorY > baseMaxAnchorY) {
|
||||||
|
anchors = scanCaveAnchorRange(writer, anchorMode, step, x, z, height, widenedMaxAnchorY);
|
||||||
|
if (!anchors.isEmpty()) {
|
||||||
|
return anchors;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return anchors;
|
||||||
|
}
|
||||||
|
|
||||||
|
private KList<Integer> scanCaveAnchorRange(MantleWriter writer, IrisCaveAnchorMode anchorMode, int step, int x, int z, int height, int maxAnchorY) {
|
||||||
|
KList<Integer> anchors = new KList<>();
|
||||||
for (int y = 1; y < maxAnchorY; y += step) {
|
for (int y = 1; y < maxAnchorY; y += step) {
|
||||||
if (!writer.isCarved(x, y, z)) {
|
if (!writer.isCarved(x, y, z)) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
carvedCount++;
|
boolean solidBelow = hasSolidNeighbor(writer, x, y, z, height, -1);
|
||||||
boolean solidBelow = y <= 0 || !writer.isCarved(x, y - 1, z);
|
boolean solidAbove = hasSolidNeighbor(writer, x, y, z, height, 1);
|
||||||
boolean solidAbove = y >= (height - 1) || !writer.isCarved(x, y + 1, z);
|
|
||||||
if (matchesCaveAnchor(anchorMode, solidBelow, solidAbove)) {
|
if (matchesCaveAnchor(anchorMode, solidBelow, solidAbove)) {
|
||||||
anchors.add(y);
|
anchors.add(y);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (anchors.isEmpty()) {
|
|
||||||
logCaveAnchorDiag(writer, x, z, surfaceY, maxAnchorY, height, objectMinDepthBelowSurface, carvedCount, 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
return anchors;
|
return anchors;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void logCaveAnchorDiag(MantleWriter writer, int x, int z, int surfaceY, int maxAnchorY, int height, int minDepth, int carvedCount, int anchorCount) {
|
private boolean hasSolidNeighbor(MantleWriter writer, int x, int y, int z, int height, int direction) {
|
||||||
long now = System.currentTimeMillis();
|
for (int d = 1; d <= 3; d++) {
|
||||||
CaveRejectLogState state = CAVE_REJECT_LOG_STATE.computeIfAbsent("anchor-diag-" + (x >> 4) + "," + (z >> 4), k -> new CaveRejectLogState());
|
int ny = y + (direction * d);
|
||||||
if (now - state.lastLogMs.get() < CAVE_REJECT_LOG_THROTTLE_MS) {
|
if (ny < 0 || ny >= height) {
|
||||||
return;
|
return true;
|
||||||
}
|
}
|
||||||
state.lastLogMs.set(now);
|
if (!writer.isCarved(x, ny, z)) {
|
||||||
MantleChunk<Matter> chunk = writer.acquireChunk(x >> 4, z >> 4);
|
return true;
|
||||||
Iris.info("Cave anchor diag: block=" + x + "," + z
|
}
|
||||||
+ " chunk=" + (x >> 4) + "," + (z >> 4)
|
}
|
||||||
+ " surfaceY=" + surfaceY
|
return false;
|
||||||
+ " maxAnchorY=" + maxAnchorY
|
|
||||||
+ " worldHeight=" + height
|
|
||||||
+ " minDepth=" + minDepth
|
|
||||||
+ " carvedInColumn=" + carvedCount
|
|
||||||
+ " anchorsFound=" + anchorCount
|
|
||||||
+ " chunkRef=" + (chunk == null ? "null" : System.identityHashCode(chunk))
|
|
||||||
+ " writerRef=" + System.identityHashCode(writer));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean matchesCaveAnchor(IrisCaveAnchorMode anchorMode, boolean solidBelow, boolean solidAbove) {
|
private boolean matchesCaveAnchor(IrisCaveAnchorMode anchorMode, boolean solidBelow, boolean solidAbove) {
|
||||||
|
|||||||
@@ -52,6 +52,9 @@ import java.util.Map;
|
|||||||
|
|
||||||
public class IrisCarveModifier extends EngineAssignedModifier<BlockData> {
|
public class IrisCarveModifier extends EngineAssignedModifier<BlockData> {
|
||||||
private static final ThreadLocal<CarveScratch> SCRATCH = ThreadLocal.withInitial(CarveScratch::new);
|
private static final ThreadLocal<CarveScratch> SCRATCH = ThreadLocal.withInitial(CarveScratch::new);
|
||||||
|
private static final int CAVE_BIOME_BLEND_RADIUS = 3;
|
||||||
|
private static final int CAVE_BIOME_BLEND_CENTER_WEIGHT = 4;
|
||||||
|
private static final int CAVE_BIOME_BLEND_TOTAL_WEIGHT = 8;
|
||||||
private final RNG rng;
|
private final RNG rng;
|
||||||
private final BlockData AIR = Material.CAVE_AIR.createBlockData();
|
private final BlockData AIR = Material.CAVE_AIR.createBlockData();
|
||||||
private final BlockData LAVA = Material.LAVA.createBlockData();
|
private final BlockData LAVA = Material.LAVA.createBlockData();
|
||||||
@@ -75,6 +78,8 @@ public class IrisCarveModifier extends EngineAssignedModifier<BlockData> {
|
|||||||
scratch.reset();
|
scratch.reset();
|
||||||
PackedWallBuffer walls = scratch.walls;
|
PackedWallBuffer walls = scratch.walls;
|
||||||
ColumnMask[] columnMasks = scratch.columnMasks;
|
ColumnMask[] columnMasks = scratch.columnMasks;
|
||||||
|
ColumnMask[] boundaryMasks = scratch.boundaryMasks;
|
||||||
|
MatterCavern[] boundaryCaverns = scratch.boundaryCaverns;
|
||||||
int[] surfaceHeights = scratch.surfaceHeights;
|
int[] surfaceHeights = scratch.surfaceHeights;
|
||||||
Map<String, IrisBiome> customBiomeCache = scratch.customBiomeCache;
|
Map<String, IrisBiome> customBiomeCache = scratch.customBiomeCache;
|
||||||
for (int columnIndex = 0; columnIndex < 256; columnIndex++) {
|
for (int columnIndex = 0; columnIndex < 256; columnIndex++) {
|
||||||
@@ -137,6 +142,7 @@ public class IrisCarveModifier extends EngineAssignedModifier<BlockData> {
|
|||||||
output.setRaw(rx, yy, rz, AIR);
|
output.setRaw(rx, yy, rz, AIR);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
addCrossChunkBoundaryWalls(mantle, mc, walls, boundaryMasks, boundaryCaverns, x, z, surfaceHeights);
|
||||||
getEngine().getMetrics().getCarveResolve().put(resolveStopwatch.getMilliseconds());
|
getEngine().getMetrics().getCarveResolve().put(resolveStopwatch.getMilliseconds());
|
||||||
|
|
||||||
PrecisionStopwatch applyStopwatch = PrecisionStopwatch.start();
|
PrecisionStopwatch applyStopwatch = PrecisionStopwatch.start();
|
||||||
@@ -154,7 +160,7 @@ public class IrisCarveModifier extends EngineAssignedModifier<BlockData> {
|
|||||||
BlockData data = biome.getWall().get(rng, worldX, yy, worldZ, getData());
|
BlockData data = biome.getWall().get(rng, worldX, yy, worldZ, getData());
|
||||||
int columnIndex = PowerOfTwoCoordinates.packLocal16(rx, rz);
|
int columnIndex = PowerOfTwoCoordinates.packLocal16(rx, rz);
|
||||||
|
|
||||||
if (data != null && B.isSolid(output.getRaw(rx, yy, rz)) && yy <= surfaceHeights[columnIndex]) {
|
if (data != null && B.isSolid(output.getRaw(rx, yy, rz)) && yy < surfaceHeights[columnIndex]) {
|
||||||
output.setRaw(rx, yy, rz, data);
|
output.setRaw(rx, yy, rz, data);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -163,6 +169,17 @@ public class IrisCarveModifier extends EngineAssignedModifier<BlockData> {
|
|||||||
for (int columnIndex = 0; columnIndex < 256; columnIndex++) {
|
for (int columnIndex = 0; columnIndex < 256; columnIndex++) {
|
||||||
processColumnFromMask(output, mc, mantle, columnMasks[columnIndex], columnIndex, x, z, resolverState, caveBiomeCache);
|
processColumnFromMask(output, mc, mantle, columnMasks[columnIndex], columnIndex, x, z, resolverState, caveBiomeCache);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for (int columnIndex = 0; columnIndex < 256; columnIndex++) {
|
||||||
|
if (boundaryMasks[columnIndex].isEmpty() || !columnMasks[columnIndex].isEmpty()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
MatterCavern cavern = boundaryCaverns[columnIndex];
|
||||||
|
if (cavern == null) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
processBoundaryColumnFromMask(output, boundaryMasks[columnIndex], cavern, columnIndex, x, z, resolverState, caveBiomeCache, customBiomeCache);
|
||||||
|
}
|
||||||
} finally {
|
} finally {
|
||||||
getEngine().getMetrics().getCarveApply().put(applyStopwatch.getMilliseconds());
|
getEngine().getMetrics().getCarveApply().put(applyStopwatch.getMilliseconds());
|
||||||
}
|
}
|
||||||
@@ -172,6 +189,86 @@ public class IrisCarveModifier extends EngineAssignedModifier<BlockData> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void addCrossChunkBoundaryWalls(
|
||||||
|
Mantle<Matter> mantle,
|
||||||
|
MantleChunk<Matter> mc,
|
||||||
|
PackedWallBuffer walls,
|
||||||
|
ColumnMask[] boundaryMasks,
|
||||||
|
MatterCavern[] boundaryCaverns,
|
||||||
|
int chunkX,
|
||||||
|
int chunkZ,
|
||||||
|
int[] surfaceHeights
|
||||||
|
) {
|
||||||
|
int baseX = PowerOfTwoCoordinates.chunkToBlock(chunkX);
|
||||||
|
int baseZ = PowerOfTwoCoordinates.chunkToBlock(chunkZ);
|
||||||
|
int maxSurfaceY = 0;
|
||||||
|
for (int index = 0; index < surfaceHeights.length; index++) {
|
||||||
|
if (surfaceHeights[index] > maxSurfaceY) {
|
||||||
|
maxSurfaceY = surfaceHeights[index];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
int maxY = Math.min(getEngine().getWorld().maxHeight() - getEngine().getWorld().minHeight() - 1, maxSurfaceY + 1);
|
||||||
|
if (maxY < 1) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
boolean westLoaded = mantle.hasTectonicPlate(((chunkX - 1) >> 5), (chunkZ >> 5));
|
||||||
|
boolean eastLoaded = mantle.hasTectonicPlate(((chunkX + 1) >> 5), (chunkZ >> 5));
|
||||||
|
boolean northLoaded = mantle.hasTectonicPlate((chunkX >> 5), ((chunkZ - 1) >> 5));
|
||||||
|
boolean southLoaded = mantle.hasTectonicPlate((chunkX >> 5), ((chunkZ + 1) >> 5));
|
||||||
|
if (!westLoaded && !eastLoaded && !northLoaded && !southLoaded) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int yy = 1; yy <= maxY; yy++) {
|
||||||
|
for (int offset = 0; offset < 16; offset++) {
|
||||||
|
if (westLoaded) {
|
||||||
|
tryAddBoundaryWall(mantle, mc, walls, boundaryMasks, boundaryCaverns, 0, yy, offset, baseX, baseZ, -1, 0);
|
||||||
|
}
|
||||||
|
if (eastLoaded) {
|
||||||
|
tryAddBoundaryWall(mantle, mc, walls, boundaryMasks, boundaryCaverns, 15, yy, offset, baseX, baseZ, 1, 0);
|
||||||
|
}
|
||||||
|
if (northLoaded) {
|
||||||
|
tryAddBoundaryWall(mantle, mc, walls, boundaryMasks, boundaryCaverns, offset, yy, 0, baseX, baseZ, 0, -1);
|
||||||
|
}
|
||||||
|
if (southLoaded) {
|
||||||
|
tryAddBoundaryWall(mantle, mc, walls, boundaryMasks, boundaryCaverns, offset, yy, 15, baseX, baseZ, 0, 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void tryAddBoundaryWall(
|
||||||
|
Mantle<Matter> mantle,
|
||||||
|
MantleChunk<Matter> mc,
|
||||||
|
PackedWallBuffer walls,
|
||||||
|
ColumnMask[] boundaryMasks,
|
||||||
|
MatterCavern[] boundaryCaverns,
|
||||||
|
int localX,
|
||||||
|
int yy,
|
||||||
|
int localZ,
|
||||||
|
int baseX,
|
||||||
|
int baseZ,
|
||||||
|
int dx,
|
||||||
|
int dz
|
||||||
|
) {
|
||||||
|
int worldX = baseX + localX;
|
||||||
|
int worldZ = baseZ + localZ;
|
||||||
|
if (mc.get(worldX, yy, worldZ, MatterCavern.class) != null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
MatterCavern neighbor = mantle.get(worldX + dx, yy, worldZ + dz, MatterCavern.class);
|
||||||
|
if (neighbor == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
walls.put(localX, yy, localZ, neighbor);
|
||||||
|
int columnIndex = PowerOfTwoCoordinates.packLocal16(localX, localZ);
|
||||||
|
boundaryMasks[columnIndex].add(yy);
|
||||||
|
if (boundaryCaverns[columnIndex] == null) {
|
||||||
|
boundaryCaverns[columnIndex] = neighbor;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private void processColumnFromMask(
|
private void processColumnFromMask(
|
||||||
Hunk<BlockData> output,
|
Hunk<BlockData> output,
|
||||||
MantleChunk<Matter> mc,
|
MantleChunk<Matter> mc,
|
||||||
@@ -226,6 +323,117 @@ public class IrisCarveModifier extends EngineAssignedModifier<BlockData> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void processBoundaryColumnFromMask(
|
||||||
|
Hunk<BlockData> output,
|
||||||
|
ColumnMask boundaryMask,
|
||||||
|
MatterCavern cavern,
|
||||||
|
int columnIndex,
|
||||||
|
int chunkX,
|
||||||
|
int chunkZ,
|
||||||
|
IrisDimensionCarvingResolver.State resolverState,
|
||||||
|
Long2ObjectOpenHashMap<IrisBiome> caveBiomeCache,
|
||||||
|
Map<String, IrisBiome> customBiomeCache
|
||||||
|
) {
|
||||||
|
int firstHeight = boundaryMask.nextSetBit(0);
|
||||||
|
if (firstHeight < 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
int rx = PowerOfTwoCoordinates.unpackLocal16X(columnIndex);
|
||||||
|
int rz = columnIndex & 15;
|
||||||
|
int worldX = rx + PowerOfTwoCoordinates.chunkToBlock(chunkX);
|
||||||
|
int worldZ = rz + PowerOfTwoCoordinates.chunkToBlock(chunkZ);
|
||||||
|
int zoneFloor = firstHeight;
|
||||||
|
int zoneCeiling = firstHeight;
|
||||||
|
int y = boundaryMask.nextSetBit(firstHeight + 1);
|
||||||
|
|
||||||
|
while (y >= 0) {
|
||||||
|
if (y == zoneCeiling + 1) {
|
||||||
|
zoneCeiling = y;
|
||||||
|
} else {
|
||||||
|
paintBoundaryZone(output, cavern, rx, rz, worldX, worldZ, zoneFloor, zoneCeiling, resolverState, caveBiomeCache, customBiomeCache);
|
||||||
|
zoneFloor = y;
|
||||||
|
zoneCeiling = y;
|
||||||
|
}
|
||||||
|
y = boundaryMask.nextSetBit(y + 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
paintBoundaryZone(output, cavern, rx, rz, worldX, worldZ, zoneFloor, zoneCeiling, resolverState, caveBiomeCache, customBiomeCache);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void paintBoundaryZone(
|
||||||
|
Hunk<BlockData> output,
|
||||||
|
MatterCavern cavern,
|
||||||
|
int rx,
|
||||||
|
int rz,
|
||||||
|
int worldX,
|
||||||
|
int worldZ,
|
||||||
|
int zoneFloor,
|
||||||
|
int zoneCeiling,
|
||||||
|
IrisDimensionCarvingResolver.State resolverState,
|
||||||
|
Long2ObjectOpenHashMap<IrisBiome> caveBiomeCache,
|
||||||
|
Map<String, IrisBiome> customBiomeCache
|
||||||
|
) {
|
||||||
|
int center = (zoneFloor + zoneCeiling) / 2;
|
||||||
|
String customBiome = cavern.getCustomBiome();
|
||||||
|
IrisBiome biome = customBiome.isEmpty()
|
||||||
|
? resolveCaveBiome(caveBiomeCache, worldX, center, worldZ, resolverState)
|
||||||
|
: resolveCustomBiome(customBiomeCache, customBiome);
|
||||||
|
|
||||||
|
if (biome == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
biome.setInferredType(InferredType.CAVE);
|
||||||
|
|
||||||
|
KList<BlockData> floorLayers = biome.generateLayers(getDimension(), worldX, worldZ, rng, 3, zoneFloor, getData(), getComplex());
|
||||||
|
for (int i = 0; i < zoneFloor - 1; i++) {
|
||||||
|
if (!floorLayers.hasIndex(i)) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
int fy = zoneFloor - i - 1;
|
||||||
|
if (fy < 0) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
BlockData down = output.getRaw(rx, fy, rz);
|
||||||
|
if (!B.isSolid(down)) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
BlockData layer = floorLayers.get(i);
|
||||||
|
if (B.isOre(down)) {
|
||||||
|
output.setRaw(rx, fy, rz, B.toDeepSlateOre(down, layer));
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
output.setRaw(rx, fy, rz, layer);
|
||||||
|
}
|
||||||
|
|
||||||
|
int worldMaxY = getEngine().getWorld().maxHeight() - getEngine().getWorld().minHeight();
|
||||||
|
KList<BlockData> ceilingLayers = biome.generateCeilingLayers(getDimension(), worldX, worldZ, rng, 3, zoneCeiling, getData(), getComplex());
|
||||||
|
for (int i = 0; i < ceilingLayers.size(); i++) {
|
||||||
|
int cy = zoneCeiling + i + 1;
|
||||||
|
if (cy >= worldMaxY) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
BlockData up = output.getRaw(rx, cy, rz);
|
||||||
|
if (!B.isSolid(up)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
BlockData layer = ceilingLayers.get(i);
|
||||||
|
if (B.isOre(up)) {
|
||||||
|
output.setRaw(rx, cy, rz, B.toDeepSlateOre(up, layer));
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
output.setRaw(rx, cy, rz, layer);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private void processZone(Hunk<BlockData> output, MantleChunk<Matter> mc, Mantle<Matter> mantle, CaveZone zone, int rx, int rz, int xx, int zz, IrisDimensionCarvingResolver.State resolverState, Long2ObjectOpenHashMap<IrisBiome> caveBiomeCache) {
|
private void processZone(Hunk<BlockData> output, MantleChunk<Matter> mc, Mantle<Matter> mantle, CaveZone zone, int rx, int rz, int xx, int zz, IrisDimensionCarvingResolver.State resolverState, Long2ObjectOpenHashMap<IrisBiome> caveBiomeCache) {
|
||||||
int center = (zone.floor + zone.ceiling) / 2;
|
int center = (zone.floor + zone.ceiling) / 2;
|
||||||
String customBiome = "";
|
String customBiome = "";
|
||||||
@@ -323,6 +531,38 @@ public class IrisCarveModifier extends EngineAssignedModifier<BlockData> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private IrisBiome resolveCaveBiome(Long2ObjectOpenHashMap<IrisBiome> caveBiomeCache, int x, int y, int z, IrisDimensionCarvingResolver.State resolverState) {
|
private IrisBiome resolveCaveBiome(Long2ObjectOpenHashMap<IrisBiome> caveBiomeCache, int x, int y, int z, IrisDimensionCarvingResolver.State resolverState) {
|
||||||
|
IrisBiome center = sampleCaveBiome(caveBiomeCache, x, y, z, resolverState);
|
||||||
|
if (center == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
IrisBiome xPos = sampleCaveBiome(caveBiomeCache, x + CAVE_BIOME_BLEND_RADIUS, y, z, resolverState);
|
||||||
|
IrisBiome xNeg = sampleCaveBiome(caveBiomeCache, x - CAVE_BIOME_BLEND_RADIUS, y, z, resolverState);
|
||||||
|
IrisBiome zPos = sampleCaveBiome(caveBiomeCache, x, y, z + CAVE_BIOME_BLEND_RADIUS, resolverState);
|
||||||
|
IrisBiome zNeg = sampleCaveBiome(caveBiomeCache, x, y, z - CAVE_BIOME_BLEND_RADIUS, resolverState);
|
||||||
|
|
||||||
|
if (xPos == center && xNeg == center && zPos == center && zNeg == center) {
|
||||||
|
return center;
|
||||||
|
}
|
||||||
|
|
||||||
|
int roll = Math.floorMod(rng.nextParallelRNG(BlockPosition.toLong(x, y, z)).nextInt(), CAVE_BIOME_BLEND_TOTAL_WEIGHT);
|
||||||
|
if (roll < CAVE_BIOME_BLEND_CENTER_WEIGHT) {
|
||||||
|
return center;
|
||||||
|
}
|
||||||
|
roll -= CAVE_BIOME_BLEND_CENTER_WEIGHT;
|
||||||
|
if (roll == 0) {
|
||||||
|
return xPos != null ? xPos : center;
|
||||||
|
}
|
||||||
|
if (roll == 1) {
|
||||||
|
return xNeg != null ? xNeg : center;
|
||||||
|
}
|
||||||
|
if (roll == 2) {
|
||||||
|
return zPos != null ? zPos : center;
|
||||||
|
}
|
||||||
|
return zNeg != null ? zNeg : center;
|
||||||
|
}
|
||||||
|
|
||||||
|
private IrisBiome sampleCaveBiome(Long2ObjectOpenHashMap<IrisBiome> caveBiomeCache, int x, int y, int z, IrisDimensionCarvingResolver.State resolverState) {
|
||||||
long key = BlockPosition.toLong(x, y, z);
|
long key = BlockPosition.toLong(x, y, z);
|
||||||
IrisBiome cachedBiome = caveBiomeCache.get(key);
|
IrisBiome cachedBiome = caveBiomeCache.get(key);
|
||||||
if (cachedBiome != null) {
|
if (cachedBiome != null) {
|
||||||
@@ -478,6 +718,8 @@ public class IrisCarveModifier extends EngineAssignedModifier<BlockData> {
|
|||||||
|
|
||||||
private static final class CarveScratch {
|
private static final class CarveScratch {
|
||||||
private final ColumnMask[] columnMasks = new ColumnMask[256];
|
private final ColumnMask[] columnMasks = new ColumnMask[256];
|
||||||
|
private final ColumnMask[] boundaryMasks = new ColumnMask[256];
|
||||||
|
private final MatterCavern[] boundaryCaverns = new MatterCavern[256];
|
||||||
private final int[] surfaceHeights = new int[256];
|
private final int[] surfaceHeights = new int[256];
|
||||||
private final PackedWallBuffer walls = new PackedWallBuffer(512);
|
private final PackedWallBuffer walls = new PackedWallBuffer(512);
|
||||||
private final Map<String, IrisBiome> customBiomeCache = new HashMap<>();
|
private final Map<String, IrisBiome> customBiomeCache = new HashMap<>();
|
||||||
@@ -485,12 +727,15 @@ public class IrisCarveModifier extends EngineAssignedModifier<BlockData> {
|
|||||||
private CarveScratch() {
|
private CarveScratch() {
|
||||||
for (int index = 0; index < columnMasks.length; index++) {
|
for (int index = 0; index < columnMasks.length; index++) {
|
||||||
columnMasks[index] = new ColumnMask();
|
columnMasks[index] = new ColumnMask();
|
||||||
|
boundaryMasks[index] = new ColumnMask();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void reset() {
|
private void reset() {
|
||||||
for (int index = 0; index < columnMasks.length; index++) {
|
for (int index = 0; index < columnMasks.length; index++) {
|
||||||
columnMasks[index].clear();
|
columnMasks[index].clear();
|
||||||
|
boundaryMasks[index].clear();
|
||||||
|
boundaryCaverns[index] = null;
|
||||||
}
|
}
|
||||||
walls.clear();
|
walls.clear();
|
||||||
customBiomeCache.clear();
|
customBiomeCache.clear();
|
||||||
|
|||||||
@@ -154,6 +154,8 @@ public class IrisDimension extends IrisRegistrant {
|
|||||||
private IrisCaveProfile caveProfile = new IrisCaveProfile();
|
private IrisCaveProfile caveProfile = new IrisCaveProfile();
|
||||||
@Desc("Configuration of fluid bodies such as rivers & lakes")
|
@Desc("Configuration of fluid bodies such as rivers & lakes")
|
||||||
private IrisFluidBodies fluidBodies = new IrisFluidBodies();
|
private IrisFluidBodies fluidBodies = new IrisFluidBodies();
|
||||||
|
@Desc("Enable or disable vanilla structure generation from the extracted vanilla datapack. When disabled, no vanilla structures spawn. When enabled, structures come from the vanilla datapack and can be overridden by external datapacks.")
|
||||||
|
private boolean vanillaStructures = true;
|
||||||
@ArrayType(type = IrisExternalDatapack.class, min = 1)
|
@ArrayType(type = IrisExternalDatapack.class, min = 1)
|
||||||
@Desc("Pack-scoped external datapack sources for structure import and optional vanilla replacement")
|
@Desc("Pack-scoped external datapack sources for structure import and optional vanilla replacement")
|
||||||
private KList<IrisExternalDatapack> externalDatapacks = new KList<>();
|
private KList<IrisExternalDatapack> externalDatapacks = new KList<>();
|
||||||
|
|||||||
@@ -1,8 +1,6 @@
|
|||||||
package art.arcane.iris.engine.object;
|
package art.arcane.iris.engine.object;
|
||||||
|
|
||||||
import art.arcane.iris.engine.object.annotations.ArrayType;
|
|
||||||
import art.arcane.iris.engine.object.annotations.Desc;
|
import art.arcane.iris.engine.object.annotations.Desc;
|
||||||
import art.arcane.volmlib.util.collection.KList;
|
|
||||||
import lombok.AllArgsConstructor;
|
import lombok.AllArgsConstructor;
|
||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
import lombok.NoArgsConstructor;
|
import lombok.NoArgsConstructor;
|
||||||
@@ -12,7 +10,7 @@ import lombok.experimental.Accessors;
|
|||||||
@NoArgsConstructor
|
@NoArgsConstructor
|
||||||
@AllArgsConstructor
|
@AllArgsConstructor
|
||||||
@Accessors(chain = true)
|
@Accessors(chain = true)
|
||||||
@Desc("Defines a pack-scoped external datapack source for structure import and optional vanilla replacement")
|
@Desc("Defines an external datapack source. When replace is true, minecraft namespace entries override the vanilla datapack.")
|
||||||
public class IrisExternalDatapack {
|
public class IrisExternalDatapack {
|
||||||
@Desc("Stable id for this external datapack entry")
|
@Desc("Stable id for this external datapack entry")
|
||||||
private String id = "";
|
private String id = "";
|
||||||
@@ -26,28 +24,6 @@ public class IrisExternalDatapack {
|
|||||||
@Desc("If true, Iris hard-fails startup when this external datapack cannot be synced/imported/installed")
|
@Desc("If true, Iris hard-fails startup when this external datapack cannot be synced/imported/installed")
|
||||||
private boolean required = false;
|
private boolean required = false;
|
||||||
|
|
||||||
@Desc("If true, minecraft namespace worldgen assets may replace vanilla targets listed in replaceTargets")
|
@Desc("If true, this datapack replaces vanilla worldgen entries. The datapack itself determines what it overrides.")
|
||||||
private boolean replaceVanilla = false;
|
private boolean replace = false;
|
||||||
|
|
||||||
@Desc("If true, structures projected from this datapack id receive smartbore foundation extension during generation")
|
|
||||||
private boolean supportSmartBore = true;
|
|
||||||
|
|
||||||
@Desc("Explicit replacement targets for minecraft namespace assets")
|
|
||||||
private IrisExternalDatapackReplaceTargets replaceTargets = new IrisExternalDatapackReplaceTargets();
|
|
||||||
|
|
||||||
@ArrayType(type = IrisExternalDatapackStructureAlias.class, min = 1)
|
|
||||||
@Desc("Optional structure alias mappings used to synthesize vanilla structure replacements from non-minecraft source keys")
|
|
||||||
private KList<IrisExternalDatapackStructureAlias> structureAliases = new KList<>();
|
|
||||||
|
|
||||||
@ArrayType(type = IrisExternalDatapackStructureSetAlias.class, min = 1)
|
|
||||||
@Desc("Optional structure-set alias mappings used to synthesize vanilla structure_set replacements from non-minecraft source keys")
|
|
||||||
private KList<IrisExternalDatapackStructureSetAlias> structureSetAliases = new KList<>();
|
|
||||||
|
|
||||||
@ArrayType(type = IrisExternalDatapackTemplateAlias.class, min = 1)
|
|
||||||
@Desc("Optional template location alias mappings applied while projecting template pools")
|
|
||||||
private KList<IrisExternalDatapackTemplateAlias> templateAliases = new KList<>();
|
|
||||||
|
|
||||||
@ArrayType(type = IrisExternalDatapackStructurePatch.class, min = 1)
|
|
||||||
@Desc("Structure placement patches applied when this external datapack is projected")
|
|
||||||
private KList<IrisExternalDatapackStructurePatch> structurePatches = new KList<>();
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,8 +18,8 @@ public class IrisExternalDatapackBinding {
|
|||||||
@Desc("Enable or disable this scoped binding")
|
@Desc("Enable or disable this scoped binding")
|
||||||
private boolean enabled = true;
|
private boolean enabled = true;
|
||||||
|
|
||||||
@Desc("Override replaceVanilla behavior for this scoped binding (null keeps dimension default)")
|
@Desc("Override replace behavior for this scoped binding (null keeps dimension default)")
|
||||||
private Boolean replaceVanillaOverride = null;
|
private Boolean replaceOverride = null;
|
||||||
|
|
||||||
@Desc("Include child biomes recursively when collecting scoped biome boundaries")
|
@Desc("Include child biomes recursively when collecting scoped biome boundaries")
|
||||||
private boolean includeChildren = true;
|
private boolean includeChildren = true;
|
||||||
|
|||||||
-54
@@ -1,54 +0,0 @@
|
|||||||
package art.arcane.iris.engine.object;
|
|
||||||
|
|
||||||
import art.arcane.iris.engine.object.annotations.ArrayType;
|
|
||||||
import art.arcane.iris.engine.object.annotations.Desc;
|
|
||||||
import art.arcane.volmlib.util.collection.KList;
|
|
||||||
import lombok.AllArgsConstructor;
|
|
||||||
import lombok.Data;
|
|
||||||
import lombok.NoArgsConstructor;
|
|
||||||
import lombok.experimental.Accessors;
|
|
||||||
|
|
||||||
@Data
|
|
||||||
@NoArgsConstructor
|
|
||||||
@AllArgsConstructor
|
|
||||||
@Accessors(chain = true)
|
|
||||||
@Desc("Explicit minecraft namespace targets that may be replaced by an external datapack")
|
|
||||||
public class IrisExternalDatapackReplaceTargets {
|
|
||||||
@ArrayType(type = String.class, min = 1)
|
|
||||||
@Desc("Structure ids that may be replaced when replaceVanilla is enabled")
|
|
||||||
private KList<String> structures = new KList<>();
|
|
||||||
|
|
||||||
@ArrayType(type = String.class, min = 1)
|
|
||||||
@Desc("Structure set ids that may be replaced when replaceVanilla is enabled")
|
|
||||||
private KList<String> structureSets = new KList<>();
|
|
||||||
|
|
||||||
@ArrayType(type = String.class, min = 1)
|
|
||||||
@Desc("Template pool ids that may be replaced when replaceVanilla is enabled")
|
|
||||||
private KList<String> templatePools = new KList<>();
|
|
||||||
|
|
||||||
@ArrayType(type = String.class, min = 1)
|
|
||||||
@Desc("Processor list ids that may be replaced when replaceVanilla is enabled")
|
|
||||||
private KList<String> processorLists = new KList<>();
|
|
||||||
|
|
||||||
@ArrayType(type = String.class, min = 1)
|
|
||||||
@Desc("Biome has_structure tag ids that may be replaced when replaceVanilla is enabled")
|
|
||||||
private KList<String> biomeHasStructureTags = new KList<>();
|
|
||||||
|
|
||||||
@ArrayType(type = String.class, min = 1)
|
|
||||||
@Desc("Configured feature ids that may be replaced when replaceVanilla is enabled")
|
|
||||||
private KList<String> configuredFeatures = new KList<>();
|
|
||||||
|
|
||||||
@ArrayType(type = String.class, min = 1)
|
|
||||||
@Desc("Placed feature ids that may be replaced when replaceVanilla is enabled")
|
|
||||||
private KList<String> placedFeatures = new KList<>();
|
|
||||||
|
|
||||||
public boolean hasAnyTargets() {
|
|
||||||
return !structures.isEmpty()
|
|
||||||
|| !structureSets.isEmpty()
|
|
||||||
|| !templatePools.isEmpty()
|
|
||||||
|| !processorLists.isEmpty()
|
|
||||||
|| !biomeHasStructureTags.isEmpty()
|
|
||||||
|| !configuredFeatures.isEmpty()
|
|
||||||
|| !placedFeatures.isEmpty();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
-20
@@ -1,20 +0,0 @@
|
|||||||
package art.arcane.iris.engine.object;
|
|
||||||
|
|
||||||
import art.arcane.iris.engine.object.annotations.Desc;
|
|
||||||
import lombok.AllArgsConstructor;
|
|
||||||
import lombok.Data;
|
|
||||||
import lombok.NoArgsConstructor;
|
|
||||||
import lombok.experimental.Accessors;
|
|
||||||
|
|
||||||
@Data
|
|
||||||
@NoArgsConstructor
|
|
||||||
@AllArgsConstructor
|
|
||||||
@Accessors(chain = true)
|
|
||||||
@Desc("Maps a vanilla structure replacement target to a source structure key from an external datapack")
|
|
||||||
public class IrisExternalDatapackStructureAlias {
|
|
||||||
@Desc("Vanilla replacement target structure id")
|
|
||||||
private String target = "";
|
|
||||||
|
|
||||||
@Desc("Source structure id to clone when the target id is not provided directly")
|
|
||||||
private String source = "";
|
|
||||||
}
|
|
||||||
-23
@@ -1,23 +0,0 @@
|
|||||||
package art.arcane.iris.engine.object;
|
|
||||||
|
|
||||||
import art.arcane.iris.engine.object.annotations.Desc;
|
|
||||||
import lombok.AllArgsConstructor;
|
|
||||||
import lombok.Data;
|
|
||||||
import lombok.NoArgsConstructor;
|
|
||||||
import lombok.experimental.Accessors;
|
|
||||||
|
|
||||||
@Data
|
|
||||||
@NoArgsConstructor
|
|
||||||
@AllArgsConstructor
|
|
||||||
@Accessors(chain = true)
|
|
||||||
@Desc("Defines a structure-level patch override for external datapack projection")
|
|
||||||
public class IrisExternalDatapackStructurePatch {
|
|
||||||
@Desc("Structure id to patch")
|
|
||||||
private String structure = "";
|
|
||||||
|
|
||||||
@Desc("Enable or disable this patch entry")
|
|
||||||
private boolean enabled = true;
|
|
||||||
|
|
||||||
@Desc("Absolute start height override for this structure")
|
|
||||||
private int startHeightAbsolute = -27;
|
|
||||||
}
|
|
||||||
-20
@@ -1,20 +0,0 @@
|
|||||||
package art.arcane.iris.engine.object;
|
|
||||||
|
|
||||||
import art.arcane.iris.engine.object.annotations.Desc;
|
|
||||||
import lombok.AllArgsConstructor;
|
|
||||||
import lombok.Data;
|
|
||||||
import lombok.NoArgsConstructor;
|
|
||||||
import lombok.experimental.Accessors;
|
|
||||||
|
|
||||||
@Data
|
|
||||||
@NoArgsConstructor
|
|
||||||
@AllArgsConstructor
|
|
||||||
@Accessors(chain = true)
|
|
||||||
@Desc("Maps a vanilla structure_set replacement target to a source structure_set key from an external datapack")
|
|
||||||
public class IrisExternalDatapackStructureSetAlias {
|
|
||||||
@Desc("Vanilla replacement target structure_set id")
|
|
||||||
private String target = "";
|
|
||||||
|
|
||||||
@Desc("Source structure_set id to clone when the target id is not provided directly")
|
|
||||||
private String source = "";
|
|
||||||
}
|
|
||||||
-23
@@ -1,23 +0,0 @@
|
|||||||
package art.arcane.iris.engine.object;
|
|
||||||
|
|
||||||
import art.arcane.iris.engine.object.annotations.Desc;
|
|
||||||
import lombok.AllArgsConstructor;
|
|
||||||
import lombok.Data;
|
|
||||||
import lombok.NoArgsConstructor;
|
|
||||||
import lombok.experimental.Accessors;
|
|
||||||
|
|
||||||
@Data
|
|
||||||
@NoArgsConstructor
|
|
||||||
@AllArgsConstructor
|
|
||||||
@Accessors(chain = true)
|
|
||||||
@Desc("Maps missing template-pool element locations from an external datapack to replacement template locations")
|
|
||||||
public class IrisExternalDatapackTemplateAlias {
|
|
||||||
@Desc("Source template location to rewrite")
|
|
||||||
private String from = "";
|
|
||||||
|
|
||||||
@Desc("Target template location. Use minecraft:empty to convert the element to an empty pool element")
|
|
||||||
private String to = "";
|
|
||||||
|
|
||||||
@Desc("Enable or disable this alias entry")
|
|
||||||
private boolean enabled = true;
|
|
||||||
}
|
|
||||||
@@ -57,6 +57,8 @@ import org.bukkit.block.data.BlockData;
|
|||||||
import org.bukkit.block.data.MultipleFacing;
|
import org.bukkit.block.data.MultipleFacing;
|
||||||
import org.bukkit.block.data.Waterlogged;
|
import org.bukkit.block.data.Waterlogged;
|
||||||
import org.bukkit.block.data.type.Leaves;
|
import org.bukkit.block.data.type.Leaves;
|
||||||
|
import org.bukkit.block.data.type.Slab;
|
||||||
|
import org.bukkit.block.data.type.Stairs;
|
||||||
import org.bukkit.util.BlockVector;
|
import org.bukkit.util.BlockVector;
|
||||||
import org.bukkit.util.Vector;
|
import org.bukkit.util.Vector;
|
||||||
|
|
||||||
@@ -131,13 +133,10 @@ public class IrisObject extends IrisRegistrant {
|
|||||||
new IrisPosition(new BlockVector(size.getX() - 1, size.getY() - 1, size.getZ() - 1).subtract(center).toBlockVector()));
|
new IrisPosition(new BlockVector(size.getX() - 1, size.getY() - 1, size.getZ() - 1).subtract(center).toBlockVector()));
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressWarnings({"resource", "RedundantSuppression"})
|
|
||||||
public static BlockVector sampleSize(File file) throws IOException {
|
public static BlockVector sampleSize(File file) throws IOException {
|
||||||
FileInputStream in = new FileInputStream(file);
|
try (DataInputStream din = new DataInputStream(new FileInputStream(file))) {
|
||||||
DataInputStream din = new DataInputStream(in);
|
return new BlockVector(din.readInt(), din.readInt(), din.readInt());
|
||||||
BlockVector bv = new BlockVector(din.readInt(), din.readInt(), din.readInt());
|
}
|
||||||
Iris.later(din::close);
|
|
||||||
return bv;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static List<BlockVector> blocksBetweenTwoPoints(Vector loc1, Vector loc2) {
|
private static List<BlockVector> blocksBetweenTwoPoints(Vector loc1, Vector loc2) {
|
||||||
@@ -159,6 +158,16 @@ public class IrisObject extends IrisRegistrant {
|
|||||||
return locations;
|
return locations;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static boolean shouldStilt(BlockData data) {
|
||||||
|
if (!data.getMaterial().isOccluding()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (data instanceof Stairs || data instanceof Slab) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return data.getMaterial() != Material.DIRT_PATH;
|
||||||
|
}
|
||||||
|
|
||||||
public AxisAlignedBB getAABB() {
|
public AxisAlignedBB getAABB() {
|
||||||
return aabb.aquire(() -> getAABBFor(new BlockVector(w, h, d)));
|
return aabb.aquire(() -> getAABBFor(new BlockVector(w, h, d)));
|
||||||
}
|
}
|
||||||
@@ -494,9 +503,9 @@ public class IrisObject extends IrisRegistrant {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
FileOutputStream out = new FileOutputStream(file);
|
try (FileOutputStream out = new FileOutputStream(file)) {
|
||||||
write(out);
|
write(out);
|
||||||
out.close();
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void write(File file, VolmitSender sender) throws IOException {
|
public void write(File file, VolmitSender sender) throws IOException {
|
||||||
@@ -504,9 +513,9 @@ public class IrisObject extends IrisRegistrant {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
FileOutputStream out = new FileOutputStream(file);
|
try (FileOutputStream out = new FileOutputStream(file)) {
|
||||||
write(out, sender);
|
write(out, sender);
|
||||||
out.close();
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void shrinkwrap() {
|
public void shrinkwrap() {
|
||||||
@@ -672,7 +681,8 @@ public class IrisObject extends IrisRegistrant {
|
|||||||
boolean warped = !config.getWarp().isFlat();
|
boolean warped = !config.getWarp().isFlat();
|
||||||
boolean stilting = (config.getMode().equals(ObjectPlaceMode.STILT) || config.getMode().equals(ObjectPlaceMode.FAST_STILT) ||
|
boolean stilting = (config.getMode().equals(ObjectPlaceMode.STILT) || config.getMode().equals(ObjectPlaceMode.FAST_STILT) ||
|
||||||
config.getMode() == ObjectPlaceMode.MIN_STILT || config.getMode() == ObjectPlaceMode.FAST_MIN_STILT ||
|
config.getMode() == ObjectPlaceMode.MIN_STILT || config.getMode() == ObjectPlaceMode.FAST_MIN_STILT ||
|
||||||
config.getMode() == ObjectPlaceMode.CENTER_STILT);
|
config.getMode() == ObjectPlaceMode.CENTER_STILT || config.getMode() == ObjectPlaceMode.ERODE_STILT);
|
||||||
|
boolean eroding = config.getMode() == ObjectPlaceMode.ERODE_STILT;
|
||||||
KMap<Position2, Integer> heightmap = config.getSnow() > 0 ? new KMap<>() : null;
|
KMap<Position2, Integer> heightmap = config.getSnow() > 0 ? new KMap<>() : null;
|
||||||
int spinx = rng.imax() / 1000;
|
int spinx = rng.imax() / 1000;
|
||||||
int spiny = rng.imax() / 1000;
|
int spiny = rng.imax() / 1000;
|
||||||
@@ -954,7 +964,7 @@ public class IrisObject extends IrisRegistrant {
|
|||||||
i = config.getRotation().rotate(i.clone(), spinx, spiny, spinz).clone();
|
i = config.getRotation().rotate(i.clone(), spinx, spiny, spinz).clone();
|
||||||
i = config.getTranslate().translate(i.clone(), config.getRotation(), spinx, spiny, spinz).clone();
|
i = config.getTranslate().translate(i.clone(), config.getRotation(), spinx, spiny, spinz).clone();
|
||||||
|
|
||||||
if (stilting && i.getBlockY() < lowest && B.isSolid(data)) {
|
if (stilting && i.getBlockY() < lowest && shouldStilt(data)) {
|
||||||
lowest = i.getBlockY();
|
lowest = i.getBlockY();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1054,6 +1064,45 @@ public class IrisObject extends IrisRegistrant {
|
|||||||
if (stilting) {
|
if (stilting) {
|
||||||
readLock.lock();
|
readLock.lock();
|
||||||
IrisStiltSettings settings = config.getStiltSettings();
|
IrisStiltSettings settings = config.getStiltSettings();
|
||||||
|
|
||||||
|
double erodeCentroidX = 0;
|
||||||
|
double erodeCentroidZ = 0;
|
||||||
|
double erodeMaxDist = 1;
|
||||||
|
if (eroding) {
|
||||||
|
int centroidCount = 0;
|
||||||
|
for (BlockVector g : blocks.keys()) {
|
||||||
|
BlockVector rot = config.getRotation().rotate(g.clone(), spinx, spiny, spinz).clone();
|
||||||
|
rot = config.getTranslate().translate(rot.clone(), config.getRotation(), spinx, spiny, spinz).clone();
|
||||||
|
if (rot.getBlockY() == lowest) {
|
||||||
|
BlockData bd = blocks.get(g);
|
||||||
|
if (bd != null && shouldStilt(bd)) {
|
||||||
|
erodeCentroidX += rot.getX();
|
||||||
|
erodeCentroidZ += rot.getZ();
|
||||||
|
centroidCount++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (centroidCount > 0) {
|
||||||
|
erodeCentroidX /= centroidCount;
|
||||||
|
erodeCentroidZ /= centroidCount;
|
||||||
|
}
|
||||||
|
for (BlockVector g : blocks.keys()) {
|
||||||
|
BlockVector rot = config.getRotation().rotate(g.clone(), spinx, spiny, spinz).clone();
|
||||||
|
rot = config.getTranslate().translate(rot.clone(), config.getRotation(), spinx, spiny, spinz).clone();
|
||||||
|
if (rot.getBlockY() == lowest) {
|
||||||
|
BlockData bd = blocks.get(g);
|
||||||
|
if (bd != null && shouldStilt(bd)) {
|
||||||
|
double dx = rot.getX() - erodeCentroidX;
|
||||||
|
double dz = rot.getZ() - erodeCentroidZ;
|
||||||
|
double dist = Math.sqrt(dx * dx + dz * dz);
|
||||||
|
if (dist > erodeMaxDist) {
|
||||||
|
erodeMaxDist = dist;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
for (BlockVector g : blocks.keys()) {
|
for (BlockVector g : blocks.keys()) {
|
||||||
BlockData sourceData;
|
BlockData sourceData;
|
||||||
try {
|
try {
|
||||||
@@ -1069,13 +1118,18 @@ public class IrisObject extends IrisRegistrant {
|
|||||||
sourceData = AIR;
|
sourceData = AIR;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!B.isSolid(sourceData)) {
|
if (!shouldStilt(sourceData)) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
BlockData d = sourceData;
|
BlockData d = sourceData;
|
||||||
if (settings != null && settings.getPalette() != null) {
|
if (settings != null && settings.getPalette() != null) {
|
||||||
d = config.getStiltSettings().getPalette().get(rng, x, y, z, rdata);
|
d = config.getStiltSettings().getPalette().get(rng, x, y, z, rdata);
|
||||||
|
} else {
|
||||||
|
Material mat = d.getMaterial();
|
||||||
|
if (mat == Material.GRASS_BLOCK || mat == Material.MYCELIUM || mat == Material.PODZOL || mat == Material.DIRT_PATH) {
|
||||||
|
d = Material.DIRT.createBlockData();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
BlockVector i = g.clone();
|
BlockVector i = g.clone();
|
||||||
@@ -1102,7 +1156,7 @@ public class IrisObject extends IrisRegistrant {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (d == null || !B.isSolid(d))
|
if (d == null || !d.getMaterial().isOccluding())
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
xx = x + (int) Math.round(i.getX());
|
xx = x + (int) Math.round(i.getX());
|
||||||
@@ -1127,7 +1181,31 @@ public class IrisObject extends IrisRegistrant {
|
|||||||
if (settings.getYMax() != 0)
|
if (settings.getYMax() != 0)
|
||||||
lowerBound -= Math.min(config.getStiltSettings().getYMax() - (lowest + y - highest), 0);
|
lowerBound -= Math.min(config.getStiltSettings().getYMax() - (lowest + y - highest), 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (eroding) {
|
||||||
|
double dx = i.getX() - erodeCentroidX;
|
||||||
|
double dz = i.getZ() - erodeCentroidZ;
|
||||||
|
double normalizedDist = Math.sqrt(dx * dx + dz * dz) / erodeMaxDist;
|
||||||
|
normalizedDist = Math.min(normalizedDist, 1.0);
|
||||||
|
int totalDepth = (lowest + y) - lowerBound;
|
||||||
|
int erodeDepth = (int) (totalDepth * Math.pow(1.0 - normalizedDist, 1.5));
|
||||||
|
lowerBound = (lowest + y) - erodeDepth;
|
||||||
|
}
|
||||||
|
|
||||||
for (int j = lowest + y; j > lowerBound; j--) {
|
for (int j = lowest + y; j > lowerBound; j--) {
|
||||||
|
if (eroding) {
|
||||||
|
int depth = (lowest + y) - j;
|
||||||
|
int totalDepth = (lowest + y) - lowerBound;
|
||||||
|
double depthRatio = totalDepth > 0 ? (double) depth / totalDepth : 0;
|
||||||
|
if (depthRatio > 0.4) {
|
||||||
|
long hash = ((long) (xx * 341873128712L) ^ ((long) j * 132897987541L) ^ ((long) zz * 735791245321L));
|
||||||
|
double skipChance = (depthRatio - 0.4) / 0.6;
|
||||||
|
if ((Math.abs(hash) % 1000) / 1000.0 < skipChance * 0.7) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (B.isVineBlock(d)) {
|
if (B.isVineBlock(d)) {
|
||||||
MultipleFacing f = (MultipleFacing) d;
|
MultipleFacing f = (MultipleFacing) d;
|
||||||
for (BlockFace face : f.getAllowedFaces()) {
|
for (BlockFace face : f.getAllowedFaces()) {
|
||||||
|
|||||||
@@ -62,6 +62,10 @@ public enum ObjectPlaceMode {
|
|||||||
|
|
||||||
CENTER_STILT,
|
CENTER_STILT,
|
||||||
|
|
||||||
|
@Desc("Erode stilting tapers columns downward like an ice cream cone. Blocks near the center extend deepest while edge blocks drop off first. Blocks are randomly skipped in the lower portion for a rough organic eroded texture.")
|
||||||
|
|
||||||
|
ERODE_STILT,
|
||||||
|
|
||||||
@Desc("Samples the height of the terrain at every x,z position of your object and pushes it down to the surface. It's pretty much like a melt function over the terrain.")
|
@Desc("Samples the height of the terrain at every x,z position of your object and pushes it down to the surface. It's pretty much like a melt function over the terrain.")
|
||||||
|
|
||||||
PAINT
|
PAINT
|
||||||
|
|||||||
@@ -380,6 +380,7 @@ public class BukkitChunkGenerator extends ChunkGenerator implements PlatformChun
|
|||||||
INMS.get().placeStructures(c);
|
INMS.get().placeStructures(c);
|
||||||
setChunkReplacementPhase(phaseRef, effectiveListener, "chunk-load-callback", x, z);
|
setChunkReplacementPhase(phaseRef, effectiveListener, "chunk-load-callback", x, z);
|
||||||
engine.getWorldManager().onChunkLoad(c, true);
|
engine.getWorldManager().onChunkLoad(c, true);
|
||||||
|
world.refreshChunk(c.getX(), c.getZ());
|
||||||
} finally {
|
} finally {
|
||||||
Iris.tickets.removeTicket(c);
|
Iris.tickets.removeTicket(c);
|
||||||
}
|
}
|
||||||
@@ -464,6 +465,7 @@ public class BukkitChunkGenerator extends ChunkGenerator implements PlatformChun
|
|||||||
CompletableFuture.runAsync(() -> {
|
CompletableFuture.runAsync(() -> {
|
||||||
setChunkReplacementPhase(phaseRef, effectiveListener, "chunk-load-callback", x, z);
|
setChunkReplacementPhase(phaseRef, effectiveListener, "chunk-load-callback", x, z);
|
||||||
engine.getWorldManager().onChunkLoad(c, true);
|
engine.getWorldManager().onChunkLoad(c, true);
|
||||||
|
world.refreshChunk(c.getX(), c.getZ());
|
||||||
}, syncExecutor).get();
|
}, syncExecutor).get();
|
||||||
} finally {
|
} finally {
|
||||||
Iris.tickets.removeTicket(c);
|
Iris.tickets.removeTicket(c);
|
||||||
|
|||||||
@@ -86,16 +86,29 @@ import org.jetbrains.annotations.Contract;
|
|||||||
import org.jetbrains.annotations.NotNull;
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
|
||||||
import java.awt.Color;
|
import java.awt.Color;
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
import java.lang.reflect.Field;
|
import java.lang.reflect.Field;
|
||||||
import java.lang.reflect.InvocationTargetException;
|
import java.lang.reflect.InvocationTargetException;
|
||||||
import java.lang.reflect.Method;
|
import java.lang.reflect.Method;
|
||||||
import java.lang.reflect.Modifier;
|
import java.lang.reflect.Modifier;
|
||||||
|
import java.net.URI;
|
||||||
|
import java.net.URL;
|
||||||
|
import java.nio.file.FileSystem;
|
||||||
|
import java.nio.file.FileSystems;
|
||||||
|
import java.nio.file.Files;
|
||||||
|
import java.nio.file.Path;
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.concurrent.Executor;
|
import java.util.concurrent.Executor;
|
||||||
import java.util.concurrent.atomic.AtomicBoolean;
|
import java.util.concurrent.atomic.AtomicBoolean;
|
||||||
import java.util.concurrent.atomic.AtomicInteger;
|
import java.util.concurrent.atomic.AtomicInteger;
|
||||||
|
import java.util.regex.Pattern;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
import java.util.stream.Stream;
|
||||||
|
import java.util.zip.ZipEntry;
|
||||||
|
import java.util.zip.ZipFile;
|
||||||
|
|
||||||
public class NMSBinding implements INMSBinding {
|
public class NMSBinding implements INMSBinding {
|
||||||
private final KMap<Biome, Object> baseBiomeCache = new KMap<>();
|
private final KMap<Biome, Object> baseBiomeCache = new KMap<>();
|
||||||
@@ -903,6 +916,96 @@ public class NMSBinding implements INMSBinding {
|
|||||||
.collect(Collectors.toMap(Pair::getA, Pair::getB, (a, b) -> a, KMap::new));
|
.collect(Collectors.toMap(Pair::getA, Pair::getB, (a, b) -> a, KMap::new));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static final Pattern VANILLA_DATAPACK_ENTRY = Pattern.compile(
|
||||||
|
"^data/minecraft/(?:worldgen/(?:structure|structure_set|template_pool|processor_list)/.+\\.json"
|
||||||
|
+ "|structures?/.+\\.nbt"
|
||||||
|
+ "|tags/worldgen/biome/has_structure/.+\\.json)$"
|
||||||
|
);
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Map<String, byte[]> extractVanillaDatapack() {
|
||||||
|
Map<String, byte[]> entries = new LinkedHashMap<>();
|
||||||
|
File serverJar = resolveServerJar();
|
||||||
|
if (serverJar == null) {
|
||||||
|
Iris.error("Unable to locate server JAR for vanilla datapack extraction.");
|
||||||
|
return entries;
|
||||||
|
}
|
||||||
|
|
||||||
|
Iris.info("Extracting vanilla datapack from " + serverJar.getName() + "...");
|
||||||
|
try (ZipFile zip = new ZipFile(serverJar)) {
|
||||||
|
Enumeration<? extends ZipEntry> zipEntries = zip.entries();
|
||||||
|
while (zipEntries.hasMoreElements()) {
|
||||||
|
ZipEntry entry = zipEntries.nextElement();
|
||||||
|
if (entry.isDirectory()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
String name = entry.getName();
|
||||||
|
if (!VANILLA_DATAPACK_ENTRY.matcher(name).matches()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
String datapackPath = normalizeStructurePath(name);
|
||||||
|
try (InputStream is = zip.getInputStream(entry)) {
|
||||||
|
entries.put(datapackPath, is.readAllBytes());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (IOException e) {
|
||||||
|
Iris.error("Failed to read vanilla datapack entries from " + serverJar.getName());
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
|
||||||
|
Iris.info("Extracted " + entries.size() + " vanilla datapack entries.");
|
||||||
|
return entries;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String normalizeStructurePath(String path) {
|
||||||
|
return path.replace("data/minecraft/structures/", "data/minecraft/structure/");
|
||||||
|
}
|
||||||
|
|
||||||
|
private static File resolveServerJar() {
|
||||||
|
try {
|
||||||
|
URL url = MinecraftServer.class.getProtectionDomain().getCodeSource().getLocation();
|
||||||
|
if (url != null) {
|
||||||
|
File file = Path.of(url.toURI()).toFile();
|
||||||
|
if (file.isFile() && file.getName().endsWith(".jar")) {
|
||||||
|
return file;
|
||||||
|
}
|
||||||
|
if (file.isDirectory()) {
|
||||||
|
return resolveServerJarFromDirectory(file);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (Exception ignored) {
|
||||||
|
}
|
||||||
|
|
||||||
|
String classpath = System.getProperty("java.class.path", "");
|
||||||
|
for (String entry : classpath.split(File.pathSeparator)) {
|
||||||
|
File file = new File(entry);
|
||||||
|
if (file.isFile() && file.getName().endsWith(".jar") && containsVanillaData(file)) {
|
||||||
|
return file;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static File resolveServerJarFromDirectory(File dir) {
|
||||||
|
File[] jars = dir.listFiles((d, name) -> name.endsWith(".jar"));
|
||||||
|
if (jars == null) return null;
|
||||||
|
for (File jar : jars) {
|
||||||
|
if (containsVanillaData(jar)) {
|
||||||
|
return jar;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static boolean containsVanillaData(File jar) {
|
||||||
|
try (ZipFile zip = new ZipFile(jar)) {
|
||||||
|
return zip.getEntry("data/minecraft/worldgen/structure_set/villages.json") != null
|
||||||
|
|| zip.getEntry("data/minecraft/worldgen/structure_set/village_plains.json") != null;
|
||||||
|
} catch (Exception e) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Object createRuntimeLevelStem(Object registryAccess, ChunkGenerator raw) {
|
public Object createRuntimeLevelStem(Object registryAccess, ChunkGenerator raw) {
|
||||||
if (!(registryAccess instanceof RegistryAccess access)) {
|
if (!(registryAccess instanceof RegistryAccess access)) {
|
||||||
|
|||||||
Reference in New Issue
Block a user