package com.hypixel.hytale.builtin.buildertools.utils; import com.hypixel.hytale.component.ComponentType; import com.hypixel.hytale.component.Holder; import com.hypixel.hytale.server.core.asset.type.blocktype.config.BlockType; import com.hypixel.hytale.server.core.asset.type.blocktype.config.Rotation; import com.hypixel.hytale.server.core.modules.prefabspawner.PrefabSpawnerState; import com.hypixel.hytale.server.core.prefab.PrefabLoadException; import com.hypixel.hytale.server.core.prefab.PrefabRotation; import com.hypixel.hytale.server.core.prefab.PrefabWeights; import com.hypixel.hytale.server.core.prefab.selection.buffer.PrefabLoader; import com.hypixel.hytale.server.core.prefab.selection.standard.BlockSelection; import com.hypixel.hytale.server.core.universe.world.meta.BlockStateModule; import com.hypixel.hytale.server.core.universe.world.storage.ChunkStore; import it.unimi.dsi.fastutil.objects.ObjectArrayList; import java.io.IOException; import java.nio.file.Path; import java.util.ArrayList; import java.util.HashSet; import java.util.List; import java.util.Random; import java.util.Set; import java.util.function.BiFunction; import java.util.function.Consumer; import java.util.function.Function; import javax.annotation.Nonnull; import javax.annotation.Nullable; public abstract class RecursivePrefabLoader implements BiFunction { private static final int MAX_RECURSION_DEPTH = 10; protected final Path rootPrefabsDir; protected final Function prefabsLoader; protected final Set visitedFiles = new HashSet<>(); @Nullable protected final ComponentType prefabComponentType = BlockStateModule.get().getComponentType(PrefabSpawnerState.class); private int depthTracker = 0; public RecursivePrefabLoader(Path rootPrefabsDir, Function prefabsLoader) { this.rootPrefabsDir = rootPrefabsDir; this.prefabsLoader = prefabsLoader; } @Nonnull public T apply(@Nonnull String name, @Nonnull Random random) { return this.load(name, random); } @Nonnull public T load(@Nonnull String name, @Nonnull Random random) { return this.load(0, 0, 0, name, PrefabRotation.ROTATION_0, PrefabWeights.NONE, random); } @Nonnull protected T load(int x, int y, int z, @Nonnull String name, PrefabRotation rotation, @Nonnull PrefabWeights weights, @Nonnull Random random) { if (this.depthTracker >= 10) { throw new PrefabLoadException(PrefabLoadException.Type.NOT_FOUND, "Prefab nesting limit exceeded!"); } else { Object var9; try { this.depthTracker++; RecursivePrefabLoader.DistinctCollector prefabs = new RecursivePrefabLoader.DistinctCollector<>(); PrefabLoader.resolvePrefabs(this.rootPrefabsDir, stripSuffix(name), prefabs); if (prefabs.isEmpty()) { throw new PrefabLoadException(PrefabLoadException.Type.NOT_FOUND, "Could not locate prefab: " + name); } if (prefabs.size() == 1) { return this.loadSinglePrefab(x, y, z, prefabs.getFirst(), rotation, random); } if (weights.size() <= 0) { return this.loadRandomPrefab(x, y, z, prefabs, rotation, random); } var9 = this.loadWeightedPrefab(x, y, z, name, prefabs, rotation, weights, random); } catch (IOException var13) { throw new PrefabLoadException(PrefabLoadException.Type.ERROR, var13); } finally { this.depthTracker--; } return (T)var9; } } protected T loadSinglePrefab(int x, int y, int z, @Nonnull Path file, PrefabRotation rotation, Random random) { if (!this.visitedFiles.add(file)) { throw new PrefabLoadException(PrefabLoadException.Type.ERROR, "Cyclic prefab dependency detected: " + file); } else { Object var8; try { String path = this.rootPrefabsDir.relativize(file).toString(); var8 = this.loadPrefab(x, y, z, appendSuffix(path), rotation, random); } finally { this.visitedFiles.remove(file); } return (T)var8; } } protected T loadWeightedPrefab( int x, int y, int z, @Nonnull String name, @Nonnull List files, PrefabRotation rotation, @Nonnull PrefabWeights weights, @Nonnull Random random ) { Path[] prefabs = files.toArray(Path[]::new); Path prefab = weights.get(prefabs, path -> PrefabLoader.resolveRelativeJsonPath(name, path, this.rootPrefabsDir), random); if (prefab != null) { return this.loadSinglePrefab(x, y, z, prefab, rotation, random); } else { throw new PrefabLoadException(PrefabLoadException.Type.ERROR, String.format("Unable to pick weighted prefab! Files: %s, Weights: %s", files, weights)); } } protected T loadRandomPrefab(int x, int y, int z, @Nonnull List files, PrefabRotation rotation, @Nonnull Random random) { Path file = files.get(random.nextInt(files.size())); return this.loadSinglePrefab(x, y, z, file, rotation, random); } protected abstract T loadPrefab(int var1, int var2, int var3, String var4, PrefabRotation var5, Random var6); @Nonnull private static String stripSuffix(@Nonnull String path) { return path.replace(".prefab.json", ""); } @Nonnull private static String appendSuffix(@Nonnull String path) { return path.endsWith(".prefab.json") ? path : path + ".prefab.json"; } public static class BlockSelectionLoader extends RecursivePrefabLoader { public BlockSelectionLoader(Path rootPrefabsDir, @Nonnull Function prefabsLoader) { super(rootPrefabsDir, prefabsLoader.andThen(BlockSelection::cloneSelection)); } @Nonnull protected BlockSelection loadPrefab(int x, int y, int z, String file, @Nonnull PrefabRotation rotation, @Nonnull Random random) { BlockSelection prefab = this.prefabsLoader.apply(file); prefab.setPosition(x, y, z); List children = new ObjectArrayList(); prefab.forEachBlock((dx, dy, dz, block) -> { Holder state = block.holder(); if (state != null) { PrefabSpawnerState spawner = state.getComponent(this.prefabComponentType); if (spawner != null) { BlockType blockType = BlockType.getAssetMap().getAsset(block.blockId()); int childX = x + rotation.getX(dx, dz); int childY = y + dy; int childZ = z + rotation.getZ(dx, dz); String childPath = spawner.getPrefabPath(); PrefabWeights childWeights = spawner.getPrefabWeights(); PrefabRotation childRotation = rotation.add(getRotation(blockType)); BlockSelection child = (BlockSelection)this.load(childX, childY, childZ, childPath, childRotation, childWeights, random); children.add(child); } } }); for (int i = 0; i < children.size(); i++) { prefab.add(children.get(i)); } return prefab; } @Nonnull private static PrefabRotation getRotation(@Nonnull BlockType blockType) { Rotation rotation = blockType.getRotationYawPlacementOffset(); return rotation == null ? PrefabRotation.ROTATION_0 : PrefabRotation.fromRotation(rotation); } } protected static class DistinctCollector extends ArrayList implements Consumer { @Override public void accept(T t) { if (!this.contains(t)) { this.add(t); } } } }