417 lines
16 KiB
Java
417 lines
16 KiB
Java
package com.hypixel.hytale.builtin.buildertools.prefabeditor;
|
|
|
|
import com.hypixel.hytale.assetstore.AssetExtraInfo;
|
|
import com.hypixel.hytale.assetstore.AssetRegistry;
|
|
import com.hypixel.hytale.assetstore.AssetStore;
|
|
import com.hypixel.hytale.assetstore.codec.AssetBuilderCodec;
|
|
import com.hypixel.hytale.assetstore.map.DefaultAssetMap;
|
|
import com.hypixel.hytale.assetstore.map.JsonAssetWithMap;
|
|
import com.hypixel.hytale.builtin.buildertools.prefabeditor.commands.PrefabEditLoadCommand;
|
|
import com.hypixel.hytale.builtin.buildertools.prefabeditor.enums.PrefabAlignment;
|
|
import com.hypixel.hytale.builtin.buildertools.prefabeditor.enums.PrefabRootDirectory;
|
|
import com.hypixel.hytale.builtin.buildertools.prefabeditor.enums.PrefabRowSplitMode;
|
|
import com.hypixel.hytale.builtin.buildertools.prefabeditor.enums.PrefabStackingAxis;
|
|
import com.hypixel.hytale.builtin.buildertools.prefabeditor.enums.WorldGenType;
|
|
import com.hypixel.hytale.codec.Codec;
|
|
import com.hypixel.hytale.codec.KeyedCodec;
|
|
import com.hypixel.hytale.codec.codecs.EnumCodec;
|
|
import com.hypixel.hytale.codec.codecs.array.ArrayCodec;
|
|
import com.hypixel.hytale.common.util.PathUtil;
|
|
import com.hypixel.hytale.common.util.StringUtil;
|
|
import com.hypixel.hytale.server.core.Message;
|
|
import com.hypixel.hytale.server.core.asset.AssetModule;
|
|
import com.hypixel.hytale.server.core.asset.type.environment.config.Environment;
|
|
import com.hypixel.hytale.server.core.entity.entities.Player;
|
|
import com.hypixel.hytale.server.core.modules.singleplayer.SingleplayerModule;
|
|
import com.hypixel.hytale.server.core.prefab.PrefabStore;
|
|
import com.hypixel.hytale.server.core.universe.PlayerRef;
|
|
import com.hypixel.hytale.server.core.util.message.MessageFormat;
|
|
import it.unimi.dsi.fastutil.objects.ObjectArrayList;
|
|
import java.io.File;
|
|
import java.io.IOException;
|
|
import java.nio.file.Files;
|
|
import java.nio.file.Path;
|
|
import java.util.List;
|
|
import java.util.Map;
|
|
import java.util.Set;
|
|
import java.util.concurrent.CompletableFuture;
|
|
import java.util.stream.Collectors;
|
|
import java.util.stream.Stream;
|
|
import javax.annotation.Nonnull;
|
|
import javax.annotation.Nullable;
|
|
|
|
public class PrefabEditorCreationSettings
|
|
implements PrefabEditorCreationContext,
|
|
JsonAssetWithMap<String, DefaultAssetMap<String, PrefabEditorCreationSettings>> {
|
|
private static final int RECURSIVE_SEARCH_MAX_DEPTH = 10;
|
|
public static final AssetBuilderCodec<String, PrefabEditorCreationSettings> CODEC = AssetBuilderCodec.builder(
|
|
PrefabEditorCreationSettings.class,
|
|
PrefabEditorCreationSettings::new,
|
|
Codec.STRING,
|
|
(builder, id) -> builder.id = id,
|
|
builder -> builder.id,
|
|
(builder, data) -> builder.data = data,
|
|
builder -> builder.data
|
|
)
|
|
.append(
|
|
new KeyedCodec<>("RootDirectory", new EnumCodec<>(PrefabRootDirectory.class)),
|
|
(o, rootDirectory) -> o.prefabRootDirectory = rootDirectory,
|
|
o -> o.prefabRootDirectory
|
|
)
|
|
.add()
|
|
.append(
|
|
new KeyedCodec<>("UnprocessedPrefabPaths", new ArrayCodec<>(Codec.STRING, String[]::new)),
|
|
(o, unprocessedPrefabPaths) -> o.unprocessedPrefabPaths = List.of(unprocessedPrefabPaths),
|
|
o -> o.unprocessedPrefabPaths.toArray(String[]::new)
|
|
)
|
|
.add()
|
|
.append(new KeyedCodec<>("PasteYLevelGoal", Codec.INTEGER), (o, pasteYLevelGoal) -> o.pasteYLevelGoal = pasteYLevelGoal, o -> o.pasteYLevelGoal)
|
|
.add()
|
|
.append(
|
|
new KeyedCodec<>("BlocksBetweenEachPrefab", Codec.INTEGER),
|
|
(o, blocksBetweenEachPrefab) -> o.blocksBetweenEachPrefab = blocksBetweenEachPrefab,
|
|
o -> o.blocksBetweenEachPrefab
|
|
)
|
|
.add()
|
|
.append(new KeyedCodec<>("WorldGenType", new EnumCodec<>(WorldGenType.class)), (o, worldGenType) -> o.worldGenType = worldGenType, o -> o.worldGenType)
|
|
.add()
|
|
.append(
|
|
new KeyedCodec<>("BlocksAboveSurface", Codec.INTEGER), (o, blocksAboveSurface) -> o.blocksAboveSurface = blocksAboveSurface, o -> o.blocksAboveSurface
|
|
)
|
|
.add()
|
|
.append(
|
|
new KeyedCodec<>("PrefabStackingAxis", new EnumCodec<>(PrefabStackingAxis.class)),
|
|
(o, stackingAxis) -> o.stackingAxis = stackingAxis,
|
|
o -> o.stackingAxis
|
|
)
|
|
.add()
|
|
.append(new KeyedCodec<>("PrefabAlignment", new EnumCodec<>(PrefabAlignment.class)), (o, alignment) -> o.alignment = alignment, o -> o.alignment)
|
|
.add()
|
|
.append(new KeyedCodec<>("RecursiveSearch", Codec.BOOLEAN), (o, recursive) -> o.recursive = recursive, o -> o.recursive)
|
|
.add()
|
|
.append(new KeyedCodec<>("LoadChildren", Codec.BOOLEAN), (o, loadChildren) -> o.loadChildren = loadChildren, o -> o.loadChildren)
|
|
.add()
|
|
.append(new KeyedCodec<>("LoadEntities", Codec.BOOLEAN), (o, loadEntities) -> o.loadEntities = loadEntities, o -> o.loadEntities)
|
|
.add()
|
|
.append(
|
|
new KeyedCodec<>("EnableWorldTicking", Codec.BOOLEAN), (o, enableWorldTicking) -> o.enableWorldTicking = enableWorldTicking, o -> o.enableWorldTicking
|
|
)
|
|
.add()
|
|
.append(
|
|
new KeyedCodec<>("RowSplitMode", new EnumCodec<>(PrefabRowSplitMode.class)), (o, rowSplitMode) -> o.rowSplitMode = rowSplitMode, o -> o.rowSplitMode
|
|
)
|
|
.add()
|
|
.<String>append(new KeyedCodec<>("Environment", Codec.STRING), (o, environment) -> o.environment = environment, o -> o.environment)
|
|
.addValidator(Environment.VALIDATOR_CACHE.getValidator())
|
|
.add()
|
|
.append(new KeyedCodec<>("GrassTint", Codec.STRING), (o, grassTint) -> o.grassTint = grassTint, o -> o.grassTint)
|
|
.add()
|
|
.build();
|
|
private static AssetStore<String, PrefabEditorCreationSettings, DefaultAssetMap<String, PrefabEditorCreationSettings>> ASSET_STORE;
|
|
private String id;
|
|
private AssetExtraInfo.Data data;
|
|
private transient Player player;
|
|
private transient PlayerRef playerRef;
|
|
private PrefabRootDirectory prefabRootDirectory = PrefabRootDirectory.ASSET;
|
|
private final transient List<Path> prefabPaths = new ObjectArrayList();
|
|
private List<String> unprocessedPrefabPaths = new ObjectArrayList();
|
|
private int pasteYLevelGoal = 55;
|
|
private int blocksBetweenEachPrefab = 15;
|
|
private WorldGenType worldGenType = PrefabEditLoadCommand.DEFAULT_WORLD_GEN_TYPE;
|
|
private int blocksAboveSurface = 0;
|
|
private PrefabStackingAxis stackingAxis = PrefabEditLoadCommand.DEFAULT_PREFAB_STACKING_AXIS;
|
|
private PrefabAlignment alignment = PrefabEditLoadCommand.DEFAULT_PREFAB_ALIGNMENT;
|
|
private boolean recursive;
|
|
private boolean loadChildren;
|
|
private boolean loadEntities;
|
|
private boolean enableWorldTicking = false;
|
|
private PrefabRowSplitMode rowSplitMode = PrefabRowSplitMode.BY_ALL_SUBFOLDERS;
|
|
private String environment = "Env_Zone1_Plains";
|
|
private String grassTint = "#5B9E28";
|
|
|
|
public static AssetStore<String, PrefabEditorCreationSettings, DefaultAssetMap<String, PrefabEditorCreationSettings>> getAssetStore() {
|
|
if (ASSET_STORE == null) {
|
|
ASSET_STORE = AssetRegistry.getAssetStore(PrefabEditorCreationSettings.class);
|
|
}
|
|
|
|
return ASSET_STORE;
|
|
}
|
|
|
|
public static DefaultAssetMap<String, PrefabEditorCreationSettings> getAssetMap() {
|
|
return (DefaultAssetMap<String, PrefabEditorCreationSettings>)getAssetStore().getAssetMap();
|
|
}
|
|
|
|
private PrefabEditorCreationSettings() {
|
|
}
|
|
|
|
public PrefabEditorCreationSettings(
|
|
PrefabRootDirectory prefabRootDirectory,
|
|
List<String> unprocessedPrefabPaths,
|
|
int pasteYLevelGoal,
|
|
int blocksBetweenEachPrefab,
|
|
WorldGenType worldGenType,
|
|
int blocksAboveSurface,
|
|
PrefabStackingAxis stackingAxis,
|
|
PrefabAlignment alignment,
|
|
boolean recursive,
|
|
boolean loadChildren,
|
|
boolean loadEntities,
|
|
boolean enableWorldTicking,
|
|
PrefabRowSplitMode rowSplitMode,
|
|
String environment,
|
|
String grassTint
|
|
) {
|
|
this.prefabRootDirectory = prefabRootDirectory;
|
|
this.unprocessedPrefabPaths = unprocessedPrefabPaths;
|
|
this.pasteYLevelGoal = pasteYLevelGoal;
|
|
this.blocksBetweenEachPrefab = blocksBetweenEachPrefab;
|
|
this.worldGenType = worldGenType;
|
|
this.blocksAboveSurface = blocksAboveSurface;
|
|
this.stackingAxis = stackingAxis;
|
|
this.alignment = alignment;
|
|
this.recursive = recursive;
|
|
this.loadChildren = loadChildren;
|
|
this.loadEntities = loadEntities;
|
|
this.enableWorldTicking = enableWorldTicking;
|
|
this.rowSplitMode = rowSplitMode;
|
|
this.environment = environment;
|
|
this.grassTint = grassTint;
|
|
}
|
|
|
|
@Nullable
|
|
PrefabEditorCreationContext finishProcessing(Player editor, PlayerRef playerRef, boolean creatingNewPrefab) {
|
|
this.prefabPaths.clear();
|
|
this.player = editor;
|
|
this.playerRef = playerRef;
|
|
|
|
for (String inputPrefabName : this.unprocessedPrefabPaths) {
|
|
inputPrefabName = StringUtil.stripQuotes(inputPrefabName);
|
|
inputPrefabName = inputPrefabName.replace('/', File.separatorChar);
|
|
inputPrefabName = inputPrefabName.replace('\\', File.separatorChar);
|
|
if (!SingleplayerModule.isOwner(playerRef) && !inputPrefabName.isEmpty() && Path.of(inputPrefabName).isAbsolute()) {
|
|
this.player.sendMessage(Message.translation("server.commands.editprefab.error.absolutePathNotAllowed"));
|
|
return null;
|
|
}
|
|
|
|
if (!inputPrefabName.endsWith(File.separator)) {
|
|
if (!stringEndsWithPrefabPath(inputPrefabName)) {
|
|
inputPrefabName = inputPrefabName + ".prefab.json";
|
|
}
|
|
|
|
try {
|
|
Path rootPath = this.resolveRootPathForInput(inputPrefabName);
|
|
String relativePath = this.getRelativePathForInput(inputPrefabName);
|
|
Path resolvedPath = rootPath.resolve(relativePath);
|
|
if (!SingleplayerModule.isOwner(playerRef) && !PathUtil.isChildOf(rootPath, resolvedPath)) {
|
|
this.player.sendMessage(Message.translation("server.commands.editprefab.error.pathTraversal"));
|
|
return null;
|
|
}
|
|
|
|
this.prefabPaths.add(resolvedPath);
|
|
} catch (Exception var13) {
|
|
var13.printStackTrace();
|
|
this.player.sendMessage(Message.translation("server.commands.editprefab.finishProcessingError").param("error", var13.getMessage()));
|
|
return null;
|
|
}
|
|
} else {
|
|
Path rootPath = this.resolveRootPathForInput(inputPrefabName);
|
|
String relativePath = this.getRelativePathForInput(inputPrefabName);
|
|
Path resolvedDir = rootPath.resolve(relativePath);
|
|
if (!SingleplayerModule.isOwner(playerRef) && !PathUtil.isChildOf(rootPath, resolvedDir)) {
|
|
this.player.sendMessage(Message.translation("server.commands.editprefab.error.pathTraversal"));
|
|
return null;
|
|
}
|
|
|
|
try (Stream<Path> walk = Files.walk(resolvedDir, this.recursive ? 10 : 1)) {
|
|
walk.filter(x$0 -> Files.isRegularFile(x$0)).filter(path -> path.toString().endsWith(".prefab.json")).forEach(this.prefabPaths::add);
|
|
} catch (IOException var15) {
|
|
var15.printStackTrace();
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!creatingNewPrefab) {
|
|
for (Path processedPrefabPath : this.prefabPaths) {
|
|
if (!Files.exists(processedPrefabPath)) {
|
|
this.player
|
|
.sendMessage(Message.translation("server.commands.editprefab.load.error.prefabNotFound").param("path", processedPrefabPath.toString()));
|
|
return null;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (this.prefabPaths.isEmpty()) {
|
|
Message header = Message.translation("server.commands.editprefab.noPrefabsInPath");
|
|
Set<Message> values = this.unprocessedPrefabPaths
|
|
.stream()
|
|
.map(p -> this.prefabRootDirectory.getPrefabPath().resolve(p))
|
|
.map(Path::toString)
|
|
.map(Message::raw)
|
|
.collect(Collectors.toSet());
|
|
this.player.sendMessage(MessageFormat.list(header, values));
|
|
return null;
|
|
} else {
|
|
return this;
|
|
}
|
|
}
|
|
|
|
@Nonnull
|
|
private Path resolveRootPathForInput(@Nonnull String inputPath) {
|
|
if (this.prefabRootDirectory != PrefabRootDirectory.ASSET) {
|
|
return this.prefabRootDirectory.getPrefabPath();
|
|
} else {
|
|
String firstComponent = inputPath.contains(File.separator) ? inputPath.substring(0, inputPath.indexOf(File.separator)) : inputPath;
|
|
|
|
for (PrefabStore.AssetPackPrefabPath packPath : PrefabStore.get().getAllAssetPrefabPaths()) {
|
|
if (packPath.getDisplayName().equals(firstComponent)) {
|
|
return packPath.prefabsPath();
|
|
}
|
|
}
|
|
|
|
return this.prefabRootDirectory.getPrefabPath();
|
|
}
|
|
}
|
|
|
|
@Nonnull
|
|
private String getRelativePathForInput(@Nonnull String inputPath) {
|
|
if (this.prefabRootDirectory != PrefabRootDirectory.ASSET) {
|
|
return inputPath;
|
|
} else {
|
|
String firstComponent = inputPath.contains(File.separator) ? inputPath.substring(0, inputPath.indexOf(File.separator)) : inputPath;
|
|
|
|
for (PrefabStore.AssetPackPrefabPath packPath : PrefabStore.get().getAllAssetPrefabPaths()) {
|
|
if (packPath.getDisplayName().equals(firstComponent)) {
|
|
if (inputPath.contains(File.separator)) {
|
|
return inputPath.substring(inputPath.indexOf(File.separator) + 1);
|
|
}
|
|
|
|
return "";
|
|
}
|
|
}
|
|
|
|
return inputPath;
|
|
}
|
|
}
|
|
|
|
public static boolean stringEndsWithPrefabPath(@Nonnull String input) {
|
|
return input.endsWith(".prefab.json") || input.endsWith(".prefab.json.lpf") || input.endsWith(".lpf");
|
|
}
|
|
|
|
@Nonnull
|
|
public static CompletableFuture<PrefabEditorCreationSettings> load(@Nonnull String name) {
|
|
return CompletableFuture.supplyAsync(() -> getAssetMap().getAsset(name));
|
|
}
|
|
|
|
@Nonnull
|
|
public static CompletableFuture<Void> save(@Nonnull String name, PrefabEditorCreationSettings settings) {
|
|
return CompletableFuture.runAsync(() -> {
|
|
try {
|
|
getAssetStore().writeAssetToDisk(AssetModule.get().getBaseAssetPack(), Map.of(Path.of(name + ".json"), settings));
|
|
} catch (IOException var3) {
|
|
var3.printStackTrace();
|
|
}
|
|
});
|
|
}
|
|
|
|
@Override
|
|
public Player getEditor() {
|
|
return this.player;
|
|
}
|
|
|
|
@Override
|
|
public PlayerRef getEditorRef() {
|
|
return this.playerRef;
|
|
}
|
|
|
|
@Override
|
|
public List<Path> getPrefabPaths() {
|
|
return this.prefabPaths;
|
|
}
|
|
|
|
@Override
|
|
public int getBlocksBetweenEachPrefab() {
|
|
return this.blocksBetweenEachPrefab;
|
|
}
|
|
|
|
@Override
|
|
public int getPasteLevelGoal() {
|
|
return this.pasteYLevelGoal;
|
|
}
|
|
|
|
@Override
|
|
public boolean loadChildPrefabs() {
|
|
return this.loadChildren;
|
|
}
|
|
|
|
@Override
|
|
public boolean shouldLoadEntities() {
|
|
return this.loadEntities;
|
|
}
|
|
|
|
@Override
|
|
public PrefabStackingAxis getStackingAxis() {
|
|
return this.stackingAxis;
|
|
}
|
|
|
|
@Override
|
|
public WorldGenType getWorldGenType() {
|
|
return this.worldGenType;
|
|
}
|
|
|
|
@Override
|
|
public int getBlocksAboveSurface() {
|
|
return this.blocksAboveSurface;
|
|
}
|
|
|
|
@Override
|
|
public PrefabAlignment getAlignment() {
|
|
return this.alignment;
|
|
}
|
|
|
|
public String getId() {
|
|
return this.id;
|
|
}
|
|
|
|
@Override
|
|
public PrefabRootDirectory getPrefabRootDirectory() {
|
|
return this.prefabRootDirectory;
|
|
}
|
|
|
|
@Override
|
|
public List<String> getUnprocessedPrefabPaths() {
|
|
return this.unprocessedPrefabPaths;
|
|
}
|
|
|
|
public int getPasteYLevelGoal() {
|
|
return this.pasteYLevelGoal;
|
|
}
|
|
|
|
public boolean isRecursive() {
|
|
return this.recursive;
|
|
}
|
|
|
|
public boolean isLoadChildren() {
|
|
return this.loadChildren;
|
|
}
|
|
|
|
@Override
|
|
public boolean isWorldTickingEnabled() {
|
|
return this.enableWorldTicking;
|
|
}
|
|
|
|
@Override
|
|
public PrefabRowSplitMode getRowSplitMode() {
|
|
return this.rowSplitMode;
|
|
}
|
|
|
|
@Override
|
|
public String getEnvironment() {
|
|
return this.environment;
|
|
}
|
|
|
|
@Override
|
|
public String getGrassTint() {
|
|
return this.grassTint;
|
|
}
|
|
}
|