hytale-server/com/hypixel/hytale/builtin/buildertools/prefabeditor/PrefabEditorCreationSetting...

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