diff --git a/BauSystem/BauSystem_Main/build.gradle.kts b/BauSystem/BauSystem_Main/build.gradle.kts index c6316f7d..c387bc36 100644 --- a/BauSystem/BauSystem_Main/build.gradle.kts +++ b/BauSystem/BauSystem_Main/build.gradle.kts @@ -47,7 +47,7 @@ dependencies { compileOnly(libs.netty) implementation(libs.luaj) - implementation(files("$projectDir/../libs/YAPION-SNAPSHOT.jar")) + // implementation(files("$projectDir/../libs/YAPION-SNAPSHOT.jar")) } widener { diff --git a/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/BauSystem.java b/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/BauSystem.java index 5a8cac69..041e4d83 100644 --- a/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/BauSystem.java +++ b/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/BauSystem.java @@ -20,8 +20,7 @@ package de.steamwar.bausystem; import de.steamwar.bausystem.config.BauServer; -import de.steamwar.bausystem.configplayer.Config; -import de.steamwar.bausystem.configplayer.ConfigConverter; +import de.steamwar.bausystem.config.ConfigStore; import de.steamwar.bausystem.features.gui.BauGUI; import de.steamwar.bausystem.features.script.lua.SteamWarLuaPlugin; import de.steamwar.bausystem.features.script.lua.libs.LuaLib; @@ -36,7 +35,6 @@ import de.steamwar.bausystem.region.Region; import de.steamwar.bausystem.region.RegionSystem; import de.steamwar.bausystem.utils.ScoreboardElement; import de.steamwar.bausystem.utils.TickManager; -import de.steamwar.bausystem.worlddata.WorldData; import de.steamwar.command.AbstractValidator; import de.steamwar.command.SWCommandUtils; import de.steamwar.core.CRIUSleepEvent; @@ -112,9 +110,6 @@ public class BauSystem extends JavaPlugin implements Listener { if (any instanceof PanzernAlgorithm) { Panzern.add((PanzernAlgorithm) any); } - if (any instanceof ConfigConverter) { - Config.addConfigConverter((ConfigConverter) any); - } if (any instanceof BoundingBoxLoader) { ((BoundingBoxLoader) any).load(); } @@ -160,9 +155,8 @@ public class BauSystem extends JavaPlugin implements Listener { @Override public void onDisable() { linker.unlink(); - WorldData.write(); RegionSystem.INSTANCE.save(); - Config.getInstance().saveAll(); + ConfigStore.saveAll(); } private AbstractValidator validator(Permission permission) { diff --git a/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/config/ConfigConverter.java b/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/config/ConfigConverter.java new file mode 100644 index 00000000..a76db061 --- /dev/null +++ b/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/config/ConfigConverter.java @@ -0,0 +1,47 @@ +/* + * This file is a part of the SteamWar software. + * + * Copyright (C) 2025 SteamWar.de-Serverteam + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package de.steamwar.bausystem.config; + +/** + * Converts a config of one version into the next version, e.g. ConfigV1 to + * ConfigV2. The converter for the second newest version converts into the + * current version. Converters are registered in the {@link ConfigStore} + * of the config. + * + * @param the config version to convert from + * @param the config version to convert to + */ +public interface ConfigConverter { + + /** + * @return the version this converter converts from + */ + int version(); + + /** + * Converts the config to the next version. If anything goes wrong do not + * silently exit this method, throw an Exception. The {@link ConfigStore} + * will deal with it. + * + * @param config the config to convert + * @return the converted config + */ + T convert(F config); +} diff --git a/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/config/ConfigStore.java b/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/config/ConfigStore.java new file mode 100644 index 00000000..21ff6fcb --- /dev/null +++ b/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/config/ConfigStore.java @@ -0,0 +1,364 @@ +/* + * This file is a part of the SteamWar software. + * + * Copyright (C) 2026 SteamWar.de-Serverteam + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package de.steamwar.bausystem.config; + +import com.google.gson.Gson; +import com.google.gson.JsonObject; +import com.google.gson.JsonParser; +import de.steamwar.sql.UserConfig; +import org.bukkit.Bukkit; +import org.bukkit.entity.Player; + +import java.io.File; +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.StandardCopyOption; +import java.util.*; +import java.util.concurrent.ConcurrentHashMap; +import java.util.function.Function; +import java.util.function.Predicate; +import java.util.function.Supplier; +import java.util.logging.Level; + +/** + * Loads, converts and saves a versioned config stored as JSON. Older versions + * are converted with the registered {@link ConfigConverter}s, legacy formats + * that are not JSON can be read with {@link #legacy(Predicate, Function)}. + *

+ * A store can be bound to where the config is stored: + *

    + *
  • {@link #userConfig(String)}: one config per player in the {@link UserConfig}, use {@link #get(Player)}
  • + *
  • {@link #file(File)}: one config in a file, use {@link #get()}
  • + *
  • unbound: use {@link #load(File)} and {@link #save(File, VersionedConfig)}
  • + *
+ * + * @param the current config version + */ +public class ConfigStore { + + private static final List> STORES = new ArrayList<>(); + + private final Class type; + private final int version; + private final Supplier defaultConfig; + + private final Map> jsonVersions = new HashMap<>(); + private final Map> converters = new HashMap<>(); + private final List legacyFormats = new ArrayList<>(); + private Gson gson = new Gson(); + private Integer unversionedVersion = null; + + private String userConfigKey = null; + private Predicate removeIf = config -> false; + private final Map playerConfigs = new ConcurrentHashMap<>(); + + private File file = null; + private File legacyFile = null; + private Function legacyFileReader = null; + private T fileConfig = null; + + private record LegacyFormat(Predicate matches, Function reader) { + } + + public ConfigStore(Class type, int version, Supplier defaultConfig) { + this.type = type; + this.version = version; + this.defaultConfig = defaultConfig; + jsonVersions.put(version, type); + STORES.add(this); + } + + public ConfigStore gson(Gson gson) { + this.gson = gson; + return this; + } + + /** + * Registers an older config version that is stored as JSON. + */ + public ConfigStore jsonVersion(int version, Class type) { + jsonVersions.put(version, type); + return this; + } + + public ConfigStore converter(ConfigConverter converter) { + if (converters.putIfAbsent(converter.version(), converter) != null) { + throw new IllegalStateException("Duplicate converter for version " + converter.version() + " of " + type.getSimpleName()); + } + return this; + } + + /** + * Registers a legacy format that is not JSON, stored strings matching the predicate are read by the reader. + */ + public ConfigStore legacy(Predicate matches, Function reader) { + legacyFormats.add(new LegacyFormat(matches, reader)); + return this; + } + + /** + * The version of stored JSON without a version field. + */ + public ConfigStore unversioned(int version) { + this.unversionedVersion = version; + return this; + } + + /** + * Binds this store to the {@link UserConfig} with the given key, one config per player. + */ + public ConfigStore userConfig(String key) { + this.userConfigKey = key; + return this; + } + + /** + * Removes the config from the {@link UserConfig} instead of saving it, if the predicate matches. + */ + public ConfigStore removeIf(Predicate removeIf) { + this.removeIf = removeIf; + return this; + } + + /** + * Binds this store to a single file. + */ + public ConfigStore file(File file) { + this.file = file; + return this; + } + + /** + * Legacy file to read, if the file of this store does not exist yet. + */ + public ConfigStore legacyFile(File legacyFile, Function reader) { + this.legacyFile = legacyFile; + this.legacyFileReader = reader; + return this; + } + + /** + * Reads a stored config and converts it to the current version. + * + * @throws RuntimeException if the config could not be read or converted + */ + public T read(String stored) { + for (LegacyFormat legacyFormat : legacyFormats) { + if (legacyFormat.matches().test(stored)) { + return update(legacyFormat.reader().apply(stored)); + } + } + + JsonObject json = JsonParser.parseString(stored).getAsJsonObject(); + int storedVersion; + if (json.has("version")) { + storedVersion = json.get("version").getAsInt(); + } else if (unversionedVersion != null) { + storedVersion = unversionedVersion; + json.addProperty("version", storedVersion); + } else { + throw new IllegalStateException("Missing version of " + type.getSimpleName()); + } + Class storedType = jsonVersions.get(storedVersion); + if (storedType == null) { + throw new IllegalStateException("Unknown version " + storedVersion + " of " + type.getSimpleName()); + } + return update(gson.fromJson(json, storedType)); + } + + public String write(T config) { + return gson.toJson(config); + } + + /** + * Converts a config to the current version. + */ + @SuppressWarnings({"unchecked", "rawtypes"}) + public T update(VersionedConfig config) { + while (config.getVersion() < version) { + ConfigConverter converter = converters.get(config.getVersion()); + if (converter == null) { + throw new IllegalStateException("No converter found for version " + config.getVersion() + " of " + type.getSimpleName()); + } + VersionedConfig converted = converter.convert(config); + if (converted.getVersion() <= config.getVersion()) { + throw new IllegalStateException("Version did not increase after conversion of version " + config.getVersion() + " of " + type.getSimpleName()); + } + config = converted; + } + if (!type.isInstance(config)) { + throw new IllegalStateException("Version " + config.getVersion() + " is not the current version of " + type.getSimpleName()); + } + return type.cast(config); + } + + /** + * @return the config stored in the file, or the default config if the file is missing or empty + * @throws RuntimeException if the config could not be read or converted + */ + public T load(File file) throws IOException { + if (file.length() == 0) { + return defaultConfig.get(); + } + return read(Files.readString(file.toPath(), StandardCharsets.UTF_8)); + } + + public void save(File file, T config) throws IOException { + Files.writeString(file.toPath(), write(config), StandardCharsets.UTF_8); + } + + /** + * Get the config of a player, loads it from the {@link UserConfig} and converts it if necessary. + */ + public T get(Player player) { + requireUserConfig(); + return playerConfigs.computeIfAbsent(player.getUniqueId(), this::loadPlayer); + } + + /** + * Save the config of a player, this does not unload the config. + */ + public void save(Player player) { + requireUserConfig(); + T config = playerConfigs.get(player.getUniqueId()); + if (config != null) { + savePlayer(player.getUniqueId(), config); + } + } + + /** + * Save and unload the config of a player. + */ + public void unload(Player player) { + if (userConfigKey == null) return; + T config = playerConfigs.remove(player.getUniqueId()); + if (config != null) { + savePlayer(player.getUniqueId(), config); + } + } + + private T loadPlayer(UUID uuid) { + String stored = UserConfig.getConfig(uuid, userConfigKey); + if (stored == null) { + return defaultConfig.get(); + } + try { + return read(stored); + } catch (Exception e) { + Bukkit.getLogger().log(Level.SEVERE, "Could not load " + type.getSimpleName() + " of " + uuid, e); + return defaultConfig.get(); + } + } + + private void savePlayer(UUID uuid, T config) { + if (removeIf.test(config)) { + UserConfig.removePlayerConfig(uuid, userConfigKey); + } else { + UserConfig.updatePlayerConfig(uuid, userConfigKey, write(config)); + } + } + + private void requireUserConfig() { + if (userConfigKey == null) { + throw new IllegalStateException(type.getSimpleName() + " is not stored in the UserConfig"); + } + } + + /** + * Get the config of the file of this store, loads it and converts it if necessary. + */ + public T get() { + requireFile(); + if (fileConfig == null) { + fileConfig = loadFile(); + save(); + } + return fileConfig; + } + + /** + * Save the config to the file of this store, if it was loaded. + */ + public void save() { + requireFile(); + if (fileConfig == null) return; + try { + save(file, fileConfig); + } catch (IOException e) { + Bukkit.getLogger().log(Level.SEVERE, "Could not write " + file, e); + } + } + + private T loadFile() { + try { + if (file.length() != 0) { + return load(file); + } + if (legacyFile != null && legacyFile.length() != 0) { + return update(legacyFileReader.apply(Files.readString(legacyFile.toPath(), StandardCharsets.UTF_8))); + } + return defaultConfig.get(); + } catch (Exception e) { + Bukkit.getLogger().log(Level.SEVERE, "Could not load " + file + ", using default " + type.getSimpleName(), e); + backupBrokenFile(); + return defaultConfig.get(); + } + } + + private void backupBrokenFile() { + if (file.length() == 0) return; + try { + Files.copy(file.toPath(), new File(file.getParentFile(), file.getName() + ".broken").toPath(), StandardCopyOption.REPLACE_EXISTING); + } catch (IOException e) { + Bukkit.getLogger().log(Level.SEVERE, "Could not backup broken " + file, e); + } + } + + private void requireFile() { + if (file == null) { + throw new IllegalStateException(type.getSimpleName() + " is not bound to a file"); + } + } + + static void unloadAll(Player player) { + STORES.forEach(store -> store.unload(player)); + } + + /** + * Saves every loaded config and unloads the configs of the players. + */ + public static void saveAll() { + for (ConfigStore store : STORES) { + if (store.userConfigKey != null) { + store.playerConfigs.forEach((uuid, config) -> saveUnchecked(store, uuid, config)); + store.playerConfigs.clear(); + } + if (store.file != null) { + store.save(); + } + } + } + + @SuppressWarnings("unchecked") + private static void saveUnchecked(ConfigStore store, UUID uuid, Object config) { + store.savePlayer(uuid, (T) config); + } +} diff --git a/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/config/ConfigStoreListener.java b/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/config/ConfigStoreListener.java new file mode 100644 index 00000000..67276588 --- /dev/null +++ b/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/config/ConfigStoreListener.java @@ -0,0 +1,45 @@ +/* + * This file is a part of the SteamWar software. + * + * Copyright (C) 2026 SteamWar.de-Serverteam + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package de.steamwar.bausystem.config; + +import de.steamwar.core.CRIUSleepEvent; +import de.steamwar.linkage.Linked; +import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; +import org.bukkit.event.Listener; +import org.bukkit.event.player.PlayerQuitEvent; + +/** + * Saves and unloads the configs of the {@link ConfigStore}s bound to the UserConfig + * when a player quits, configs are loaded on first use. + */ +@Linked +public class ConfigStoreListener implements Listener { + + @EventHandler(priority = EventPriority.MONITOR) + public void onPlayerQuit(PlayerQuitEvent event) { + ConfigStore.unloadAll(event.getPlayer()); + } + + @EventHandler + public void onCRIUSleep(CRIUSleepEvent event) { + ConfigStore.saveAll(); + } +} diff --git a/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/config/VersionedConfig.java b/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/config/VersionedConfig.java new file mode 100644 index 00000000..ca036e06 --- /dev/null +++ b/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/config/VersionedConfig.java @@ -0,0 +1,29 @@ +/* + * This file is a part of the SteamWar software. + * + * Copyright (C) 2026 SteamWar.de-Serverteam + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package de.steamwar.bausystem.config; + +/** + * Common interface of every version of a config stored by a {@link ConfigStore}, + * the current version (e.g. Config) and every older version (ConfigV1, ConfigV2, ...). + */ +public interface VersionedConfig { + + int getVersion(); +} diff --git a/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/config/legacy/YapionItemDeserializer.java b/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/config/legacy/YapionItemDeserializer.java new file mode 100644 index 00000000..1ce4ab25 --- /dev/null +++ b/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/config/legacy/YapionItemDeserializer.java @@ -0,0 +1,95 @@ +/* + * This file is a part of the SteamWar software. + * + * Copyright (C) 2026 SteamWar.de-Serverteam + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package de.steamwar.bausystem.config.legacy; + +import lombok.experimental.UtilityClass; +import org.bukkit.configuration.serialization.ConfigurationSerialization; +import org.bukkit.inventory.ItemStack; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; + +/** + * Deserializes {@link ItemStack}s that were serialized by YAPION, read with {@link YapionReader}. + */ +@UtilityClass +@Deprecated +public class YapionItemDeserializer { + + private static final String TYPE_IDENTIFIER = "@type"; + + public ItemStack deserializeItem(Object yapion) { + if (yapion == null) return null; + Object result = deserialize(yapion); + if (result instanceof ItemStack itemStack) return itemStack; + throw new IllegalArgumentException("Not an ItemStack: " + result); + } + + private Object deserialize(Object yapion) { + if (yapion instanceof List list) { + List result = new ArrayList<>(); + list.forEach(o -> result.add(deserialize(o))); + return result; + } + if (yapion instanceof YapionReader.YapionMap map) { + Map result = new LinkedHashMap<>(); + map.entries().forEach((key, value) -> result.put(deserialize(key), deserialize(value))); + return result; + } + if (yapion instanceof Map object) { + return deserializeObject(object); + } + return yapion; + } + + private Object deserializeObject(Map object) { + Map data = new LinkedHashMap<>(); + object.forEach((key, value) -> { + if (!TYPE_IDENTIFIER.equals(key)) data.put((String) key, deserialize(value)); + }); + + if (!(object.get(TYPE_IDENTIFIER) instanceof String type)) { + return data; + } + + Class javaType = javaUtilClass(type); + if (javaType != null && Collection.class.isAssignableFrom(javaType)) { + return data.getOrDefault("values", new ArrayList<>()); + } + if (javaType != null && Map.class.isAssignableFrom(javaType)) { + return data.containsKey("values") ? data.get("values") : data; + } + + data.put(ConfigurationSerialization.SERIALIZED_TYPE_KEY, type.equals("org.bukkit.inventory.meta.ItemMeta") ? "ItemMeta" : type); + return ConfigurationSerialization.deserializeObject(data); + } + + private Class javaUtilClass(String type) { + if (!type.startsWith("java.util.")) return null; + try { + return Class.forName(type); + } catch (ClassNotFoundException e) { + return null; + } + } +} diff --git a/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/config/legacy/YapionReader.java b/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/config/legacy/YapionReader.java new file mode 100644 index 00000000..c3fb84d4 --- /dev/null +++ b/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/config/legacy/YapionReader.java @@ -0,0 +1,323 @@ +/* + * This file is a part of the SteamWar software. + * + * Copyright (C) 2026 SteamWar.de-Serverteam + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package de.steamwar.bausystem.config.legacy; + +import java.util.ArrayList; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.regex.Pattern; + +/** + * Minimal reader for the legacy YAPION format, only used to migrate old data. + *

+ * The result is built from plain java objects: + *

    + *
  • YAPION objects {@code {key(value)}} become a {@link LinkedHashMap} of String to value
  • + *
  • YAPION arrays {@code [a,b]} become a {@link List}
  • + *
  • YAPION maps {@code <(a):(b)>} become a {@link YapionMap}
  • + *
  • YAPION values {@code (value)} become null, Boolean, Number or String
  • + *
+ */ +@Deprecated +public class YapionReader { + + private static final Pattern INTEGER = Pattern.compile("[-+]?\\d+"); + private static final Pattern DECIMAL = Pattern.compile("[-+]?(\\d+\\.\\d*|\\.\\d+|\\d+)([eE][-+]?\\d+)?"); + + public record YapionMap(Map entries) { + } + + public static Map parse(String yapion) { + YapionReader reader = new YapionReader(yapion); + reader.skipWhitespace(); + Map result = reader.readObject(); + reader.skipWhitespace(); + if (reader.index != yapion.length()) { + throw reader.error("Unexpected trailing content"); + } + return result; + } + + public static double getDouble(Map object, String key, double defaultValue) { + return object.get(key) instanceof Number value ? value.doubleValue() : defaultValue; + } + + public static int getInt(Map object, String key, int defaultValue) { + return object.get(key) instanceof Number value ? value.intValue() : defaultValue; + } + + public static boolean getBoolean(Map object, String key, boolean defaultValue) { + return object.get(key) instanceof Boolean value ? value : defaultValue; + } + + public static String getString(Map object, String key, String defaultValue) { + return object.get(key) instanceof String value ? value : defaultValue; + } + + /** + * @return the entries of the array that are YAPION objects + */ + public static List> getObjects(Map object, String key) { + List> objects = new ArrayList<>(); + if (object.get(key) instanceof List array) { + array.forEach(o -> { + if (o instanceof Map map) objects.add(map); + }); + } + return objects; + } + + private final String input; + private int index = 0; + + private YapionReader(String input) { + this.input = input; + } + + private Object readAny() { + return switch (peek()) { + case '{' -> readObject(); + case '[' -> readArray(); + case '<' -> readMap(); + case '(' -> readValue(); + default -> throw error("Expected '{', '[', '<' or '('"); + }; + } + + private Map readObject() { + expect('{'); + Map object = new LinkedHashMap<>(); + while (true) { + skipWhitespace(); + if (peek() == '}') { + index++; + return object; + } + StringBuilder key = new StringBuilder(); + while (true) { + char c = peek(); + if (c == '(' || c == '{' || c == '[' || c == '<') break; + if (c == '}') throw error("Key without value"); + key.append(readChar()); + } + object.put(key.toString(), readAny()); + } + } + + private List readArray() { + expect('['); + List array = new ArrayList<>(); + skipWhitespace(); + if (peek() == ']') { + index++; + return array; + } + while (true) { + int start = index; + skipWhitespace(); + char c = peek(); + if (c == '{' || c == '[' || c == '<' || c == '(') { + array.add(readAny()); + skipWhitespace(); + } else { + // Whitespace is part of plain values + index = start; + StringBuilder value = new StringBuilder(); + while (peek() != ',' && peek() != ']') { + value.append(readChar()); + } + array.add(typed(value.toString())); + } + char separator = input.charAt(index++); + if (separator == ']') return array; + if (separator != ',') throw error("Expected ',' or ']'"); + } + } + + private YapionMap readMap() { + expect('<'); + Map map = new LinkedHashMap<>(); + while (true) { + skipWhitespace(); + if (peek() == '>') { + index++; + return new YapionMap(map); + } + Object key = readAny(); + skipWhitespace(); + expect(':'); + skipWhitespace(); + map.put(key, readAny()); + } + } + + /** + * YAPION does not escape parentheses in values, balanced parentheses are part of the value. + * A backslash only escapes a backslash, a unicode sequence or a control character, otherwise it is kept. + */ + private Object readValue() { + expect('('); + StringBuilder value = new StringBuilder(); + int depth = 0; + while (true) { + char c = peek(); + if (c == '\\') { + value.append(readValueEscape()); + continue; + } + index++; + if (c == '(') { + depth++; + } else if (c == ')') { + if (depth == 0) break; + depth--; + } + value.append(c); + } + return typed(value.toString()); + } + + private String readValueEscape() { + index++; + if (index >= input.length()) return "\\"; + char escaped = input.charAt(index); + switch (escaped) { + case '\\', '(', ')': + index++; + return String.valueOf(escaped); + case 'n': + index++; + return "\n"; + case 't': + index++; + return "\t"; + case 'r': + index++; + return "\r"; + case 'u': + if (index + 5 <= input.length() && input.substring(index + 1, index + 5).matches("[0-9a-fA-F]{4}")) { + char unicode = (char) Integer.parseInt(input.substring(index + 1, index + 5), 16); + index += 5; + return String.valueOf(unicode); + } + return "\\"; + default: + return "\\"; + } + } + + /** + * Reads one character of a key or plain array value, resolving backslash escapes. + * A backslash that does not escape anything is kept, as the old player config + * collapsed escaped backslashes into a single one. + */ + private char readChar() { + char c = input.charAt(index++); + if (c != '\\') return c; + if (index >= input.length()) throw error("Unterminated escape"); + char escaped = input.charAt(index); + switch (escaped) { + case 'n': + index++; + return '\n'; + case 't': + index++; + return '\t'; + case 'r': + index++; + return '\r'; + case 'u': + if (index + 5 <= input.length() && input.substring(index + 1, index + 5).matches("[0-9a-fA-F]{4}")) { + char unicode = (char) Integer.parseInt(input.substring(index + 1, index + 5), 16); + index += 5; + return unicode; + } + return '\\'; + case '\\', ',', ' ', '(', ')', '{', '}', '[', ']', '<', '>', '"', ':', '@': + index++; + return escaped; + default: + return '\\'; + } + } + + private static Object typed(String value) { + if (value.equals("null")) return null; + if (value.equals("true")) return true; + if (value.equals("false")) return false; + if (value.length() >= 2 && value.startsWith("\"") && value.endsWith("\"")) { + return value.substring(1, value.length() - 1); + } + try { + if (INTEGER.matcher(value).matches()) { + long l = Long.parseLong(value); + if (l >= Integer.MIN_VALUE && l <= Integer.MAX_VALUE) return (int) l; + return l; + } + if (value.length() > 1) { + String number = value.substring(0, value.length() - 1); + switch (value.charAt(value.length() - 1)) { + case 'L': + if (INTEGER.matcher(number).matches()) return Long.parseLong(number); + break; + case 'S': + if (INTEGER.matcher(number).matches()) return Short.parseShort(number); + break; + case 'B': + if (INTEGER.matcher(number).matches()) return Byte.parseByte(number); + break; + case 'F': + if (DECIMAL.matcher(number).matches()) return Float.parseFloat(number); + break; + case 'D': + if (DECIMAL.matcher(number).matches()) return Double.parseDouble(number); + break; + default: + break; + } + } + if (DECIMAL.matcher(value).matches()) return Double.parseDouble(value); + } catch (NumberFormatException e) { + // Not a number, fall through to String + } + return value; + } + + private void skipWhitespace() { + while (index < input.length() && Character.isWhitespace(input.charAt(index))) { + index++; + } + } + + private char peek() { + if (index >= input.length()) throw error("Unexpected end of input"); + return input.charAt(index); + } + + private void expect(char c) { + if (peek() != c) throw error("Expected '" + c + "'"); + index++; + } + + private IllegalArgumentException error(String message) { + return new IllegalArgumentException(message + " at index " + index); + } +} diff --git a/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/config/serializer/ItemStackTypeAdapter.java b/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/config/serializer/ItemStackTypeAdapter.java new file mode 100644 index 00000000..9edda398 --- /dev/null +++ b/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/config/serializer/ItemStackTypeAdapter.java @@ -0,0 +1,63 @@ +/* + * This file is a part of the SteamWar software. + * + * Copyright (C) 2026 SteamWar.de-Serverteam + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package de.steamwar.bausystem.config.serializer; + +import com.google.gson.TypeAdapter; +import com.google.gson.stream.JsonReader; +import com.google.gson.stream.JsonToken; +import com.google.gson.stream.JsonWriter; +import org.bukkit.Bukkit; +import org.bukkit.inventory.ItemStack; + +import java.io.IOException; +import java.util.Base64; +import java.util.logging.Level; + +/** + * Stores an {@link ItemStack} as Base64 of {@link ItemStack#serializeAsBytes()}, which + * contains the data version and gets upgraded on newer Minecraft versions. + * Empty items are stored as null, items that can't be read are deserialized as null. + */ +public class ItemStackTypeAdapter extends TypeAdapter { + + @Override + public void write(JsonWriter out, ItemStack itemStack) throws IOException { + if (itemStack == null || itemStack.isEmpty()) { + out.nullValue(); + return; + } + out.value(Base64.getEncoder().encodeToString(itemStack.serializeAsBytes())); + } + + @Override + public ItemStack read(JsonReader in) throws IOException { + if (in.peek() == JsonToken.NULL) { + in.nextNull(); + return null; + } + String value = in.nextString(); + try { + return ItemStack.deserializeBytes(Base64.getDecoder().decode(value)); + } catch (Exception e) { + Bukkit.getLogger().log(Level.WARNING, "Could not deserialize ItemStack", e); + return null; + } + } +} diff --git a/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/configplayer/Config.java b/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/configplayer/Config.java index 77b8b706..19aef6d7 100644 --- a/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/configplayer/Config.java +++ b/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/configplayer/Config.java @@ -1,7 +1,7 @@ /* * This file is a part of the SteamWar software. * - * Copyright (C) 2025 SteamWar.de-Serverteam + * Copyright (C) 2026 SteamWar.de-Serverteam * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as published by @@ -19,129 +19,77 @@ package de.steamwar.bausystem.configplayer; -import de.steamwar.bausystem.configplayer.serializer.ConfigurationSerializableSerializer; -import de.steamwar.linkage.Linked; -import de.steamwar.sql.UserConfig; +import com.google.gson.GsonBuilder; +import de.steamwar.bausystem.config.ConfigConverter; +import de.steamwar.bausystem.config.ConfigStore; +import de.steamwar.bausystem.config.VersionedConfig; +import de.steamwar.bausystem.config.serializer.ItemStackTypeAdapter; +import de.steamwar.bausystem.configplayer.converter.ConfigV1ToV2Converter; +import de.steamwar.bausystem.features.hotbar.DefaultHotbar; +import lombok.AccessLevel; import lombok.Getter; -import org.bukkit.Bukkit; -import org.bukkit.entity.Player; -import org.bukkit.event.EventHandler; -import org.bukkit.event.Listener; -import org.bukkit.event.player.PlayerJoinEvent; -import org.bukkit.event.player.PlayerQuitEvent; -import yapion.hierarchy.output.StringOutput; -import yapion.hierarchy.types.YAPIONObject; -import yapion.parser.YAPIONParser; -import yapion.serializing.SerializeManager; +import lombok.Setter; +import org.bukkit.inventory.ItemStack; import java.util.HashMap; import java.util.Map; -import java.util.UUID; -import java.util.logging.Level; -@Linked -public class Config implements Listener { +/** + * The current player config. It is stored as JSON, fields missing in the + * stored JSON keep the default value of their initializer, so adding a new + * field does not need a new version. + *

+ * When a field gets removed, renamed or its meaning changes, copy this class + * to ConfigV{@link #VERSION} (without the {@link #STORE}), increase {@link #VERSION}, + * write a {@link ConfigConverter} from the copy to this class and register it in the {@link #STORE}. + */ +@Getter +@Setter +public class Config implements VersionedConfig { - static { - SerializeManager.add(new ConfigurationSerializableSerializer()); - } + public static final int VERSION = 2; - @Getter - private static Config instance; + public static final ConfigStore STORE = new ConfigStore<>(Config.class, VERSION, Config::new) + .gson(new GsonBuilder().registerTypeHierarchyAdapter(ItemStack.class, new ItemStackTypeAdapter()).create()) + .legacy(s -> s.startsWith("{@version(1)"), ConfigV1::parse) + .converter(new ConfigV1ToV2Converter()) + .userConfig("bausystem"); - { - instance = this; - } - - private final Map playerConfigurations = new HashMap<>(); - - private static final Map CONFIG_CONVERTER_MAP = new HashMap<>(); - - public static void addConfigConverter(ConfigConverter configConverter) { - CONFIG_CONVERTER_MAP.putIfAbsent(configConverter.version(), configConverter); - } - - @EventHandler - public void onPlayerJoin(PlayerJoinEvent event) { - get(event.getPlayer()); - } - - @EventHandler - public void onPlayerQuit(PlayerQuitEvent event) { - save(event.getPlayer()); - playerConfigurations.remove(event.getPlayer().getUniqueId()); - } + @Setter(AccessLevel.NONE) + protected int version = VERSION; /** - * Get a PlayerConfig, optionally loads it from the DataBase and migrates it if necessary. - * - * @param player the player from whom to get the config. - * @return the config object + * BauGui item id to inventory slot */ - public YAPIONObject get(Player player) { - UUID uuid = player.getUniqueId(); - if (!playerConfigurations.containsKey(uuid)) { - String s = UserConfig.getConfig(uuid, "bausystem"); - YAPIONObject yapionObject; - if (s == null) { - yapionObject = ConfigCreator.createDefaultConfig(); - } else { - yapionObject = YAPIONParser.parse(s); - } - yapionObject = update(yapionObject); - playerConfigurations.put(uuid, yapionObject); - return yapionObject; - } - return playerConfigurations.get(uuid); - } - - public void saveAll() { - playerConfigurations.forEach((uuid, yapionObject) -> { - String string = yapionObject.toYAPION(new StringOutput()).getResult().replaceAll("\\+", "\\"); - UserConfig.updatePlayerConfig(uuid, "bausystem", string); - }); - playerConfigurations.clear(); - } + protected Map bauGui = defaultBauGui(); + protected int bauGuiSize = 45; /** - * Save a PlayerConfig, this does not remove the key value mapping from the map. - * - * @param player the player to save the config. + * Slots 0-8 are the hotbar, slots 9-12 the armor contents */ - public void save(Player player) { - UUID uuid = player.getUniqueId(); - if (playerConfigurations.containsKey(uuid)) { - YAPIONObject yapionObject = playerConfigurations.get(uuid); - String string = yapionObject.toYAPION(new StringOutput()).getResult().replaceAll("\\\\+", "\\\\"); - UserConfig.updatePlayerConfig(uuid, "bausystem", string); - } - } + protected ItemStack[] hotbar = DefaultHotbar.defaultHotbar(); - private YAPIONObject update(YAPIONObject yapionObject) { - int version = yapionObject.getPlainValue("@version"); - while (version < ConfigCreator.currentVersion) { - ConfigConverter configConverter = CONFIG_CONVERTER_MAP.getOrDefault(version, null); - if (configConverter == null) { - Bukkit.getLogger().log(Level.SEVERE, "No updater found for version " + version); - return ConfigCreator.createDefaultConfig(); - } - try { - configConverter.update(yapionObject); - } catch (Exception e) { - Bukkit.getLogger().log(Level.SEVERE, e.getMessage(), e); - return ConfigCreator.createDefaultConfig(); - } - int newVersion = yapionObject.getPlainValue("@version"); - if (version == newVersion) { - Bukkit.getLogger().log(Level.SEVERE, "Version Tag was the same after conversion"); - return ConfigCreator.createDefaultConfig(); - } - if (newVersion < version) { - Bukkit.getLogger().log(Level.SEVERE, "Version Tag was earlier after conversion"); - return ConfigCreator.createDefaultConfig(); - } - version = newVersion; - } - return yapionObject; + protected boolean nightVision = false; + protected boolean tntClickDetails = true; + protected boolean detonatorAutostart = false; + protected boolean inventoryFill = false; + protected boolean rayVisualizer = false; + protected boolean smartPlace = false; + protected boolean blockCounter = false; + protected boolean depthMessage = true; + + public static Map defaultBauGui() { + // 0: ? | 1: ? | 2: 10 | 3: 3 | 4: 7 | 5: 17 | 6: 15 | 7: ? | 8: ? + // 9: 5 | 10: ? | 11: ? | 12: ? | 13: ? | 14: ? | 15: ? | 16: ? | 17: 16 + // 18: 4 | 19: ? | 20: 19 | 21: 21 | 22: 9 | 23: 0 | 24: 1 | 25: ? | 26: 11 + // 27: 6 | 28: ? | 29: ? | 30: ? | 31: ? | 32: ? | 33: ? | 34: ? | 35: 18 + // 36: ? | 37: 23 | 38: 20 | 39: 8 | 40: 22 | 41: 26 | 42: 12 | 43: 14 | 44: ? + Map bauGui = new HashMap<>(); + bauGui.put(10, 2); bauGui.put(3, 3); bauGui.put(7, 4); bauGui.put(17, 5); bauGui.put(15, 6); + bauGui.put(5, 9); bauGui.put(4, 18); bauGui.put(6, 27); + bauGui.put(16, 17); bauGui.put(11, 26); bauGui.put(18, 35); + bauGui.put(19, 20); bauGui.put(21, 21); bauGui.put(9, 22); bauGui.put(0, 23); bauGui.put(1, 24); + bauGui.put(23, 37); bauGui.put(20, 38); bauGui.put(8, 39); bauGui.put(22, 40); bauGui.put(26, 41); bauGui.put(12, 42); bauGui.put(14, 43); + return bauGui; } } diff --git a/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/configplayer/ConfigConverter.java b/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/configplayer/ConfigConverter.java deleted file mode 100644 index 46119887..00000000 --- a/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/configplayer/ConfigConverter.java +++ /dev/null @@ -1,56 +0,0 @@ -/* - * This file is a part of the SteamWar software. - * - * Copyright (C) 2025 SteamWar.de-Serverteam - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero 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 Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see . - */ - -package de.steamwar.bausystem.configplayer; - -import yapion.hierarchy.types.YAPIONObject; - -/** - * A new {@link ConfigConverter} should be written when you remove anything - * from the Config or modify any mayor part. When you move anything from - * any key to any other key you should write a new {@link ConfigConverter}. - * For adding any new key you should be able to get the default without - * having it in the Config. Anything you need to change you should also - * change the {@link ConfigCreator} accordingly, to produce the new Config. - */ -public interface ConfigConverter { - - /** - * This describes the version this Converter can convert from. The version - * it should convert to is the version 1 above this number. But this is not - * a necessity. In the config Object as parameter given in {@link #update(YAPIONObject)} - * you should update the @version variable in the root object to the - * new version this converter produced. - * - * @return the version number - */ - int version(); - - /** - * This method should update everything needed to go from a lower config - * version to a higher. It should update the @version variable - * accordingly. Anything else is up the implementation. If anything goes wrong - * do not silently exit this method, throw an Exception. The updater Code will - * deal with it. Never leave the inputted object in a corrupted state. - * - * @param config the config object to update - */ - void update(YAPIONObject config); - -} diff --git a/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/configplayer/ConfigCreator.java b/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/configplayer/ConfigCreator.java deleted file mode 100644 index 9a887208..00000000 --- a/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/configplayer/ConfigCreator.java +++ /dev/null @@ -1,61 +0,0 @@ -/* - * This file is a part of the SteamWar software. - * - * Copyright (C) 2025 SteamWar.de-Serverteam - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero 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 Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see . - */ - -package de.steamwar.bausystem.configplayer; - -import de.steamwar.bausystem.features.hotbar.DefaultHotbar; -import lombok.experimental.UtilityClass; -import yapion.hierarchy.types.YAPIONObject; - -@UtilityClass -public class ConfigCreator { - - public static final int currentVersion = 1; - - public YAPIONObject createDefaultConfig() { - YAPIONObject yapionObject = new YAPIONObject(); - // This call should never be touched - yapionObject.add("@version", currentVersion); - - // Any initialising goes into here - yapionObject.add("baugui", defaultBauGui()); - - // Default Hotbar Gui - yapionObject.add("hotbar", DefaultHotbar.defaultHotbar()); - return yapionObject; - } - - private YAPIONObject defaultBauGui() { - YAPIONObject yapionObject = new YAPIONObject(); - - // 0: ? | 1: ? | 2: 10 | 3: 3 | 4: 7 | 5: 17 | 6: 15 | 7: ? | 8: ? - // 9: 5 | 10: ? | 11: ? | 12: ? | 13: ? | 14: ? | 15: ? | 16: ? | 17: 16 - // 18: 4 | 19: ? | 20: 19 | 21: 21 | 22: 9 | 23: 0 | 24: 1 | 25: ? | 26: 11 - // 27: 6 | 28: ? | 29: ? | 30: ? | 31: ? | 32: ? | 33: ? | 34: ? | 35: 18 - // 36: ? | 37: 23 | 38: 20 | 39: 8 | 40: 22 | 41: 26 | 42: 12 | 43: 14 | 44: ? - - yapionObject.add("10", 2).add("3", 3).add("7", 4).add("17", 5).add("15", 6); - yapionObject.add("5", 9).add("4", 18).add("6", 27); - yapionObject.add("16", 17).add("11", 26).add("18", 35); - yapionObject.add("19", 20).add("21", 21).add("9", 22).add("0", 23).add("1", 24); - yapionObject.add("23", 37).add("20", 38).add("8", 39).add("22", 40).add("26", 41).add("12", 42).add("14", 43); - yapionObject.add("size", 45); - return yapionObject; - } -} diff --git a/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/configplayer/ConfigV1.java b/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/configplayer/ConfigV1.java new file mode 100644 index 00000000..ba1f8cd0 --- /dev/null +++ b/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/configplayer/ConfigV1.java @@ -0,0 +1,104 @@ +/* + * This file is a part of the SteamWar software. + * + * Copyright (C) 2026 SteamWar.de-Serverteam + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package de.steamwar.bausystem.configplayer; + +import de.steamwar.bausystem.config.VersionedConfig; +import de.steamwar.bausystem.config.legacy.YapionReader; +import lombok.AccessLevel; +import lombok.Getter; +import lombok.Setter; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * The legacy config that was stored in the YAPION format. + */ +@Getter +@Setter +public class ConfigV1 implements VersionedConfig { + + public static final int VERSION = 1; + + @Setter(AccessLevel.NONE) + protected int version = VERSION; + + /** + * BauGui item id to inventory slot + */ + protected Map bauGui = new HashMap<>(); + protected int bauGuiSize = 45; + + /** + * Minecraft version to the hotbar serialized by YAPION, read with {@link YapionReader}. + * Use {@link de.steamwar.bausystem.config.legacy.YapionItemDeserializer} to get the items. + */ + protected Map> hotbars = new HashMap<>(); + + protected boolean nightVision = false; + protected boolean tntClickDetails = true; + protected boolean detonatorAutostart = false; + protected boolean inventoryFill = false; + protected boolean rayVisualizer = false; + protected boolean smartPlace = false; + protected boolean blockCounter = false; + protected boolean depthMessage = true; + + public static ConfigV1 parse(String yapion) { + Map object = YapionReader.parse(yapion); + ConfigV1 config = new ConfigV1(); + config.version = (Integer) object.get("@version"); + + if (object.get("baugui") instanceof Map bauGui) { + bauGui.forEach((key, value) -> { + if (key.equals("size")) { + config.bauGuiSize = (Integer) value; + } else { + config.bauGui.put(Integer.parseInt((String) key), (Integer) value); + } + }); + } + + object.forEach((key, value) -> { + if (!(value instanceof List hotbar)) return; + if (key.equals("hotbar")) { + // Hotbars were stored without version prior to 1.19 + config.hotbars.putIfAbsent(19, hotbar); + } else if (key.startsWith("hotbar-")) { + config.hotbars.put(Integer.parseInt(key.substring("hotbar-".length())), hotbar); + } + }); + + config.nightVision = getBoolean(object, "nightvision", config.nightVision); + config.tntClickDetails = getBoolean(object, "tnt_click_details", config.tntClickDetails); + config.detonatorAutostart = getBoolean(object, "detonator-autostart", config.detonatorAutostart); + config.inventoryFill = getBoolean(object, "inventoryfill", config.inventoryFill); + config.rayVisualizer = getBoolean(object, "rayvisualizer", config.rayVisualizer); + config.smartPlace = getBoolean(object, "smartPlace", config.smartPlace); + config.blockCounter = getBoolean(object, "blockCounter", config.blockCounter); + config.depthMessage = getBoolean(object, "depth_message", config.depthMessage); + return config; + } + + private static boolean getBoolean(Map object, String key, boolean defaultValue) { + return object.get(key) instanceof Boolean value ? value : defaultValue; + } +} diff --git a/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/configplayer/converter/.gitkeep b/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/configplayer/converter/.gitkeep deleted file mode 100644 index e69de29b..00000000 diff --git a/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/configplayer/converter/ConfigV1ToV2Converter.java b/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/configplayer/converter/ConfigV1ToV2Converter.java new file mode 100644 index 00000000..9886de52 --- /dev/null +++ b/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/configplayer/converter/ConfigV1ToV2Converter.java @@ -0,0 +1,75 @@ +/* + * This file is a part of the SteamWar software. + * + * Copyright (C) 2026 SteamWar.de-Serverteam + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package de.steamwar.bausystem.configplayer.converter; + +import de.steamwar.bausystem.config.ConfigConverter; +import de.steamwar.bausystem.config.legacy.YapionItemDeserializer; +import de.steamwar.bausystem.configplayer.Config; +import de.steamwar.bausystem.configplayer.ConfigV1; +import de.steamwar.core.Core; +import org.bukkit.Bukkit; +import org.bukkit.inventory.ItemStack; + +import java.util.HashMap; +import java.util.List; +import java.util.logging.Level; + +@Deprecated +public class ConfigV1ToV2Converter implements ConfigConverter { + + @Override + public int version() { + return ConfigV1.VERSION; + } + + @Override + public Config convert(ConfigV1 old) { + Config config = new Config(); + config.setBauGui(new HashMap<>(old.getBauGui())); + config.setBauGuiSize(old.getBauGuiSize()); + + List hotbar = old.getHotbars().get(Core.getVersion()); + if (hotbar != null) { + config.setHotbar(convertHotbar(hotbar)); + } + + config.setNightVision(old.isNightVision()); + config.setTntClickDetails(old.isTntClickDetails()); + config.setDetonatorAutostart(old.isDetonatorAutostart()); + config.setInventoryFill(old.isInventoryFill()); + config.setRayVisualizer(old.isRayVisualizer()); + config.setSmartPlace(old.isSmartPlace()); + config.setBlockCounter(old.isBlockCounter()); + config.setDepthMessage(old.isDepthMessage()); + return config; + } + + private ItemStack[] convertHotbar(List hotbar) { + ItemStack[] items = new ItemStack[13]; + for (int i = 0; i < Math.min(hotbar.size(), items.length); i++) { + try { + items[i] = YapionItemDeserializer.deserializeItem(hotbar.get(i)); + } catch (Exception e) { + Bukkit.getLogger().log(Level.WARNING, "Could not convert hotbar item " + i, e); + } + } + return items; + } +} diff --git a/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/configplayer/serializer/ConfigurationSerializableSerializer.java b/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/configplayer/serializer/ConfigurationSerializableSerializer.java deleted file mode 100644 index 2d054c3d..00000000 --- a/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/configplayer/serializer/ConfigurationSerializableSerializer.java +++ /dev/null @@ -1,90 +0,0 @@ -/* - * This file is a part of the SteamWar software. - * - * Copyright (C) 2025 SteamWar.de-Serverteam - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero 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 Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see . - */ - -package de.steamwar.bausystem.configplayer.serializer; - -import org.bukkit.configuration.serialization.ConfigurationSerializable; -import org.bukkit.configuration.serialization.ConfigurationSerialization; -import org.bukkit.inventory.ItemStack; -import org.bukkit.inventory.meta.ItemMeta; -import yapion.hierarchy.api.groups.YAPIONAnyType; -import yapion.hierarchy.types.YAPIONObject; -import yapion.serializing.api.SerializerObject; -import yapion.serializing.data.DeserializeData; -import yapion.serializing.data.SerializeData; - -import java.util.ArrayList; -import java.util.HashMap; -import java.util.Map; - -import static yapion.utils.IdentifierUtils.TYPE_IDENTIFIER; - -public class ConfigurationSerializableSerializer extends SerializerObject { - - @Override - public Class type() { - return ConfigurationSerializable.class; - } - - @Override - public boolean isInterface() { - return true; - } - - @Override - public YAPIONObject serialize(SerializeData serializeData) { - YAPIONObject yapionObject = new YAPIONObject(); - yapionObject.add(TYPE_IDENTIFIER, serializeData.object.getClass().getTypeName()); - if (serializeData.object instanceof ItemStack) { - yapionObject.add(TYPE_IDENTIFIER, ItemStack.class.getTypeName()); - } - if (serializeData.object instanceof ItemMeta) { - yapionObject.add(TYPE_IDENTIFIER, ItemMeta.class.getTypeName()); - } - Map serializeDataMap = serializeData.object.serialize(); - serializeDataMap.forEach((s, o) -> { - YAPIONAnyType yapionAnyType = serializeData.serialize(o); - if (yapionAnyType instanceof YAPIONObject) { - YAPIONObject object = (YAPIONObject) yapionAnyType; - if (object.containsKey(TYPE_IDENTIFIER) && object.getPlainValue(TYPE_IDENTIFIER).equals("com.google.common.collect.RegularImmutableList")) { - object.put(TYPE_IDENTIFIER, ArrayList.class.getTypeName()); - } - } - yapionObject.add(s, yapionAnyType); - }); - return yapionObject; - } - - @Override - public ConfigurationSerializable deserialize(DeserializeData deserializeData) { - Map deserializeDataMap = new HashMap<>(); - deserializeData.object.forEach((s, yapionAnyType) -> { - if (s.equals(TYPE_IDENTIFIER)) { - if (yapionAnyType.toString().equals("(org.bukkit.inventory.meta.ItemMeta)")) { - deserializeDataMap.put("==", "ItemMeta"); - } else { - deserializeDataMap.put("==", deserializeData.deserialize(yapionAnyType)); - } - return; - } - deserializeDataMap.put(s, deserializeData.deserialize(yapionAnyType)); - }); - return ConfigurationSerialization.deserializeObject(deserializeDataMap); - } -} \ No newline at end of file diff --git a/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/cannon/depth/Depth.java b/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/cannon/depth/Depth.java index 89f0daa2..61d16843 100644 --- a/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/cannon/depth/Depth.java +++ b/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/cannon/depth/Depth.java @@ -64,7 +64,7 @@ public class Depth { dimensions.setZ(Math.abs(dimensions.getZ())); RegionUtils.message(region, player -> { - if (Config.getInstance().get(player).getPlainValueOrDefault("depth_message", true)) { + if (Config.STORE.get(player).isDepthMessage()) { player.spigot().sendMessage(getMessage(player, dimensions.getBlockX() + 1, dimensions.getBlockY() + 1, dimensions.getBlockZ() + 1, tntCount)); } }); diff --git a/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/cannon/depth/DepthCommand.java b/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/cannon/depth/DepthCommand.java index b9f71398..8eee56a6 100644 --- a/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/cannon/depth/DepthCommand.java +++ b/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/cannon/depth/DepthCommand.java @@ -34,11 +34,11 @@ public class DepthCommand extends SWCommand { @Register public void toggle(Player player) { - if (Config.getInstance().get(player).getPlainValueOrDefault("depth_message", true)) { - Config.getInstance().get(player).put("depth_message", false); + if (Config.STORE.get(player).isDepthMessage()) { + Config.STORE.get(player).setDepthMessage(false); BauSystem.MESSAGE.send("DEPTH_COUNTER_DISABLE", player); } else { - Config.getInstance().get(player).put("depth_message", true); + Config.STORE.get(player).setDepthMessage(true); BauSystem.MESSAGE.send("DEPTH_COUNTER_ENABLE", player); } } diff --git a/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/detonator/Detonator.java b/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/detonator/Detonator.java index 86932f2e..e04c78e1 100644 --- a/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/detonator/Detonator.java +++ b/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/detonator/Detonator.java @@ -95,7 +95,7 @@ public class Detonator { public static void activateDetonator(DetonatorStorage detonator) { Player p = detonator.getPlayer(); - if (Config.getInstance().get(p).getPlainValueOrDefault("detonator-autostart", false)) { + if (Config.STORE.get(p).isDetonatorAutostart()) { AutostartListener.instance.activate(p); } diff --git a/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/detonator/DetonatorCommand.java b/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/detonator/DetonatorCommand.java index 5c936c49..5ac946e1 100644 --- a/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/detonator/DetonatorCommand.java +++ b/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/detonator/DetonatorCommand.java @@ -76,8 +76,8 @@ public class DetonatorCommand extends SWCommand { @Register(value = "autostart", description = "DETONATOR_HELP_AUTOSTART") public void toggleAutostartTimer(Player p) { - boolean current = Config.getInstance().get(p).getPlainValueOrDefault("detonator-autostart", false); - Config.getInstance().get(p).put("detonator-autostart", !current); + boolean current = Config.STORE.get(p).isDetonatorAutostart(); + Config.STORE.get(p).setDetonatorAutostart(!current); if (!current) { BauSystem.MESSAGE.send("DETONATOR_AUTOSTART_ENABLE", p); } else { diff --git a/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/gui/editor/BauGuiMapping.java b/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/gui/editor/BauGuiMapping.java index 7dfed54d..1c0b56b1 100644 --- a/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/gui/editor/BauGuiMapping.java +++ b/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/gui/editor/BauGuiMapping.java @@ -28,7 +28,6 @@ import lombok.Setter; import org.bukkit.Bukkit; import org.bukkit.entity.Player; import org.bukkit.scheduler.BukkitTask; -import yapion.hierarchy.types.YAPIONObject; import java.util.*; @@ -52,15 +51,15 @@ public class BauGuiMapping { } @Getter - private final YAPIONObject object; + private final Config config; @Getter private final Player owner; @Getter @Setter private boolean saved; - protected BauGuiMapping(YAPIONObject object, Player p) { - this.object = object; + protected BauGuiMapping(Config config, Player p) { + this.config = config; this.owner = p; fromUUID.put(p.getUniqueId(), this); mappings.add(this); @@ -70,19 +69,18 @@ public class BauGuiMapping { BauGuiMapping mapping = fromUUID.get(p.getUniqueId()); if (mapping == null) { - YAPIONObject yapionObject = Config.getInstance().get(p); - mapping = new BauGuiMapping(yapionObject.getObject("baugui"), p); + mapping = new BauGuiMapping(Config.STORE.get(p), p); } return mapping; } public boolean isShown(int id) { - return object.getPlainValueOrDefault(Integer.toString(id), -1) >= 0; + return config.getBauGui().getOrDefault(id, -1) >= 0; } public int getSlot(int id) { - return object.getPlainValue(Integer.toString(id)); + return config.getBauGui().get(id); } public Map getMapping() { @@ -94,7 +92,7 @@ public class BauGuiMapping { } public int getSize() { - return object.getPlainValueOrDefault("size", 45); + return config.getBauGuiSize(); } public void setSize(int size) { @@ -102,7 +100,7 @@ public class BauGuiMapping { return; } - object.add("size", size); + config.setBauGuiSize(size); } private void tick() { @@ -111,16 +109,17 @@ public class BauGuiMapping { public void save() { if (saved) return; - YAPIONObject config = Config.getInstance().get(owner); - config.add("baugui", object); - Config.getInstance().save(owner); + Config current = Config.STORE.get(owner); + current.setBauGui(config.getBauGui()); + current.setBauGuiSize(config.getBauGuiSize()); + Config.STORE.save(owner); saved = true; } private Map internalReadMap() { Map map = new HashMap<>(); for (Map.Entry e : BauGUI.getITEMS().entrySet()) { - Integer value = object.getPlainValueOrDefault(e.getKey().toString(), -1); + Integer value = config.getBauGui().getOrDefault(e.getKey(), -1); if (value == -1) continue; map.put(e.getKey(), value); } @@ -129,10 +128,10 @@ public class BauGuiMapping { private void internalWriteMap(Map map) { for (Map.Entry e : BauGUI.getITEMS().entrySet()) { - object.remove(e.getKey().toString()); + config.getBauGui().remove(e.getKey()); Integer value = map.getOrDefault(e.getKey(), -1); if (value == -1) continue; - object.add(e.getKey().toString(), value); + config.getBauGui().put(e.getKey(), value); } } } diff --git a/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/hotbar/DefaultHotbar.java b/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/hotbar/DefaultHotbar.java index 602f1817..c8340a7c 100644 --- a/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/hotbar/DefaultHotbar.java +++ b/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/hotbar/DefaultHotbar.java @@ -20,39 +20,23 @@ package de.steamwar.bausystem.features.hotbar; import de.steamwar.bausystem.configplayer.Config; -import de.steamwar.core.Core; import lombok.experimental.UtilityClass; +import org.bukkit.Material; import org.bukkit.entity.Player; import org.bukkit.inventory.ItemStack; -import yapion.hierarchy.types.YAPIONArray; -import yapion.hierarchy.types.YAPIONObject; -import yapion.hierarchy.types.YAPIONValue; -import yapion.parser.YAPIONParser; -import yapion.serializing.YAPIONDeserializer; -import yapion.serializing.YAPIONSerializer; +import org.bukkit.inventory.meta.ItemMeta; -import java.util.HashSet; -import java.util.Set; +import java.util.List; @UtilityClass public class DefaultHotbar { - private final YAPIONArray DEFAULT_HOTBAR = YAPIONParser.parse("{[{@type(org.bukkit.inventory.ItemStack)v(2230)type(WOODEN_AXE)meta{@type(org.bukkit.inventory.meta.ItemMeta)meta-type(UNSPECIFIC)display-name(WorldEdit Wand)lore{@type(java.util.ArrayList)values[Left click: select pos #1,Right click: select pos #2]}}},{@type(org.bukkit.inventory.ItemStack)v(2230)type(COMPASS)meta{@type(org.bukkit.inventory.meta.ItemMeta)meta-type(UNSPECIFIC)display-name(Navigation Wand)lore{@type(java.util.ArrayList)values[Left click: jump to location,Right click: pass through walls]}}},null,null,null,null,null,null,{@type(org.bukkit.inventory.ItemStack)v(2230)type(NETHER_STAR)meta{@type(org.bukkit.inventory.meta.ItemMeta)meta-type(UNSPECIFIC)display-name(§eBau GUI)}},null,null,{@type(org.bukkit.inventory.ItemStack)v(3117)type(ELYTRA)},null]}").getArray(""); - public void updateHotbar(Player p) { ItemStack[] hotbar = new ItemStack[13]; System.arraycopy(p.getInventory().getContents(), 0, hotbar, 0, 9); System.arraycopy(p.getInventory().getArmorContents(), 0, hotbar, 9, 4); - YAPIONArray yapionArray = new YAPIONArray(); - for (ItemStack itemStack : hotbar) { - if (itemStack != null) { - yapionArray.add(YAPIONSerializer.serialize(itemStack)); - } else { - yapionArray.add(new YAPIONValue<>(null)); - } - } - Config.getInstance().get(p).add("hotbar-" + Core.getVersion(), yapionArray); - Config.getInstance().save(p); + Config.STORE.get(p).setHotbar(copy(hotbar)); + Config.STORE.save(p); } public void setHotbar(Player p) { @@ -66,28 +50,34 @@ public class DefaultHotbar { } public ItemStack[] getItems(Player p) { - Config.getInstance().get(p).remap("hotbar", "hotbar-19"); - YAPIONArray yapionArray = Config.getInstance().get(p).getYAPIONArrayOrSetDefault("hotbar-" + Core.getVersion(), defaultHotbar()); + return copy(Config.STORE.get(p).getHotbar()); + } + + public ItemStack[] defaultHotbar() { ItemStack[] hotbar = new ItemStack[13]; - Set invalid = new HashSet<>(); - yapionArray.forEach((integer, yapionAnyType) -> { - if (yapionAnyType instanceof YAPIONValue) { - hotbar[integer] = null; - } else { - try { - hotbar[integer] = YAPIONDeserializer.deserialize((YAPIONObject) yapionAnyType); - } catch (Exception e) { - invalid.add(integer); - hotbar[integer] = null; - } - } - }); - invalid.forEach(i -> yapionArray.set(i, new YAPIONValue<>(null))); - if (!invalid.isEmpty()) Config.getInstance().save(p); + hotbar[0] = item(Material.WOODEN_AXE, "WorldEdit Wand", "Left click: select pos #1", "Right click: select pos #2"); + hotbar[1] = item(Material.COMPASS, "Navigation Wand", "Left click: jump to location", "Right click: pass through walls"); + hotbar[8] = item(Material.NETHER_STAR, "§eBau GUI"); + hotbar[11] = new ItemStack(Material.ELYTRA); return hotbar; } - public YAPIONArray defaultHotbar() { - return DEFAULT_HOTBAR; + private ItemStack item(Material material, String name, String... lore) { + ItemStack itemStack = new ItemStack(material); + ItemMeta meta = itemStack.getItemMeta(); + meta.setDisplayName(name); + if (lore.length > 0) { + meta.setLore(List.of(lore)); + } + itemStack.setItemMeta(meta); + return itemStack; + } + + private ItemStack[] copy(ItemStack[] hotbar) { + ItemStack[] copy = new ItemStack[13]; + for (int i = 0; i < Math.min(hotbar.length, copy.length); i++) { + copy[i] = hotbar[i] == null ? null : hotbar[i].clone(); + } + return copy; } } diff --git a/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/inventoryfiller/InventoryFillBauGuiItem.java b/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/inventoryfiller/InventoryFillBauGuiItem.java index 13e3af35..e23d71de 100644 --- a/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/inventoryfiller/InventoryFillBauGuiItem.java +++ b/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/inventoryfiller/InventoryFillBauGuiItem.java @@ -49,7 +49,7 @@ public class InventoryFillBauGuiItem extends BauGuiItem { @Override public ItemStack getItem(Player player) { - String loreKey = Config.getInstance().get(player).getPlainValueOrDefault("inventoryfill", false) ? "OTHER_ITEMS_INVENTORY_FILL_LORE_ACTIVE" : "OTHER_ITEMS_INVENTORY_FILL_LORE_INACTIVE"; + String loreKey = Config.STORE.get(player).isInventoryFill() ? "OTHER_ITEMS_INVENTORY_FILL_LORE_ACTIVE" : "OTHER_ITEMS_INVENTORY_FILL_LORE_INACTIVE"; return new SWItem(Material.HOPPER, BauSystem.MESSAGE.parse("OTHER_ITEMS_INVENTORY_FILL_NAME", player), Collections.singletonList(BauSystem.MESSAGE.parse(loreKey, player)), false, clickType -> { }).getItemStack(); } diff --git a/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/inventoryfiller/InventoryFiller.java b/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/inventoryfiller/InventoryFiller.java index 8312e77c..48341152 100644 --- a/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/inventoryfiller/InventoryFiller.java +++ b/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/inventoryfiller/InventoryFiller.java @@ -40,7 +40,7 @@ public class InventoryFiller implements Listener { @EventHandler public void onPlayerDropItem(PlayerDropItemEvent event) { if (!Permission.BUILD.hasPermission(event.getPlayer())) return; - if (!Config.getInstance().get(event.getPlayer()).getPlainValueOrDefault("inventoryfill", false)) return; + if (!Config.STORE.get(event.getPlayer()).isInventoryFill()) return; if (!event.getPlayer().isSneaking()) return; Block block = event.getPlayer().getTargetBlockExact(5); if (block == null) return; @@ -62,7 +62,7 @@ public class InventoryFiller implements Listener { @EventHandler public void onPlayerItemHeld(PlayerItemHeldEvent event) { if (!Permission.BUILD.hasPermission(event.getPlayer())) return; - if (!Config.getInstance().get(event.getPlayer()).getPlainValueOrDefault("inventoryfill", false)) return; + if (!Config.STORE.get(event.getPlayer()).isInventoryFill()) return; if (!event.getPlayer().isSneaking()) return; ItemStack itemStack = event.getPlayer().getInventory().getItemInMainHand(); if (itemStack.getType() == Material.AIR) return; diff --git a/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/inventoryfiller/InventoryFillerCommand.java b/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/inventoryfiller/InventoryFillerCommand.java index 0e9887ab..4d4b5f05 100644 --- a/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/inventoryfiller/InventoryFillerCommand.java +++ b/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/inventoryfiller/InventoryFillerCommand.java @@ -68,8 +68,8 @@ public class InventoryFillerCommand extends SWCommand { @Register(description = {"INVENTORY_FILL_HELP", "INVENTORY_FILL_INFO"}) public void toggle(Player player) { - boolean inventoryFill = Config.getInstance().get(player).getPlainValueOrDefault("inventoryfill", false); - Config.getInstance().get(player).put("inventoryfill", !inventoryFill); + boolean inventoryFill = Config.STORE.get(player).isInventoryFill(); + Config.STORE.get(player).setInventoryFill(!inventoryFill); if (!inventoryFill) { SWUtils.sendToActionbar(player, BauSystem.MESSAGE.parse("INVENTORY_FILL_ENABLE", player)); BauSystem.MESSAGE.send("INVENTORY_FILL_INFO", player); diff --git a/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/rayvisualizer/RayVisualizerCommand.java b/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/rayvisualizer/RayVisualizerCommand.java index 2732c140..af5d4eb0 100644 --- a/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/rayvisualizer/RayVisualizerCommand.java +++ b/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/rayvisualizer/RayVisualizerCommand.java @@ -167,7 +167,7 @@ public class RayVisualizerCommand extends SWCommand implements Listener { @EventHandler public void onPlayerJoin(PlayerJoinEvent event) { if (!Permission.BUILD.hasPermission(event.getPlayer())) return; - boolean rayvisualizer = Config.getInstance().get(event.getPlayer()).getPlainValueOrDefault("rayvisualizer", false); + boolean rayvisualizer = Config.STORE.get(event.getPlayer()).isRayVisualizer(); if (rayvisualizer) server.addPlayer(event.getPlayer()); } @@ -181,7 +181,7 @@ public class RayVisualizerCommand extends SWCommand implements Listener { Bukkit.getScheduler().runTaskLater(BauSystem.getInstance(), () -> { for (Player player : Bukkit.getOnlinePlayers()) { if (Permission.BUILD.hasPermission(player)) { - boolean rayvisualizer = Config.getInstance().get(player).getPlainValueOrDefault("rayvisualizer", false); + boolean rayvisualizer = Config.STORE.get(player).isRayVisualizer(); if (rayvisualizer) server.addPlayer(player); } else { server.removePlayer(player); @@ -192,8 +192,8 @@ public class RayVisualizerCommand extends SWCommand implements Listener { @Register public void toggle(@Validator Player player) { - boolean rayvisualizer = Config.getInstance().get(player).getPlainValueOrDefault("rayvisualizer", false); - Config.getInstance().get(player).put("rayvisualizer", !rayvisualizer); + boolean rayvisualizer = Config.STORE.get(player).isRayVisualizer(); + Config.STORE.get(player).setRayVisualizer(!rayvisualizer); if (!rayvisualizer) { SWUtils.sendToActionbar(player, BauSystem.MESSAGE.parse("RAY_VISUALIZER_ENABLE", player)); server.addPlayer(player); diff --git a/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/simulator/data/SimulatorElement.java b/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/simulator/data/SimulatorElement.java index 87719f2a..27538d7b 100644 --- a/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/simulator/data/SimulatorElement.java +++ b/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/simulator/data/SimulatorElement.java @@ -21,6 +21,7 @@ package de.steamwar.bausystem.features.simulator.data; import de.steamwar.bausystem.features.simulator.execute.SimulatorAction; import de.steamwar.bausystem.features.simulator.gui.base.SimulatorBaseGui; +import de.steamwar.bausystem.features.simulator.storage.SimulatorConfig; import de.steamwar.inventory.InvCallback; import de.steamwar.inventory.SWItem; import lombok.Getter; @@ -28,7 +29,6 @@ import lombok.Setter; import org.bukkit.Material; import org.bukkit.entity.Player; import org.bukkit.util.Vector; -import yapion.hierarchy.types.YAPIONObject; import java.util.ArrayList; import java.util.Comparator; @@ -121,9 +121,9 @@ public abstract class SimulatorElement { public abstract String getType(); - public void saveExtra(YAPIONObject elementObject) { + public void saveExtra(SimulatorConfig.Element element) { } - public void loadExtra(YAPIONObject elementObject) { + public void loadExtra(SimulatorConfig.Element element) { } } diff --git a/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/simulator/data/SimulatorPhase.java b/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/simulator/data/SimulatorPhase.java index b8c951ab..2eae0d82 100644 --- a/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/simulator/data/SimulatorPhase.java +++ b/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/simulator/data/SimulatorPhase.java @@ -20,10 +20,10 @@ package de.steamwar.bausystem.features.simulator.data; import de.steamwar.bausystem.features.simulator.execute.SimulatorAction; +import de.steamwar.bausystem.features.simulator.storage.SimulatorConfig; import lombok.Getter; import lombok.Setter; import org.bukkit.util.Vector; -import yapion.hierarchy.types.YAPIONObject; import java.util.function.BiConsumer; @@ -38,9 +38,9 @@ public abstract class SimulatorPhase { public abstract void toSimulatorActions(Vector position, BiConsumer tickStart, BiConsumer tickEnd); - public void saveExtra(YAPIONObject phaseObject) { + public void saveExtra(SimulatorConfig.Phase phase) { } - public void loadExtra(YAPIONObject phaseObject) { + public void loadExtra(SimulatorConfig.Phase phase) { } } diff --git a/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/simulator/data/observer/ObserverPhase.java b/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/simulator/data/observer/ObserverPhase.java index f69ff797..73c00d19 100644 --- a/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/simulator/data/observer/ObserverPhase.java +++ b/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/simulator/data/observer/ObserverPhase.java @@ -21,6 +21,7 @@ package de.steamwar.bausystem.features.simulator.data.observer; import de.steamwar.bausystem.features.simulator.data.SimulatorPhase; import de.steamwar.bausystem.features.simulator.execute.SimulatorAction; +import de.steamwar.bausystem.features.simulator.storage.SimulatorConfig; import lombok.Getter; import lombok.NoArgsConstructor; import lombok.Setter; @@ -31,7 +32,6 @@ import org.bukkit.block.BlockFace; import org.bukkit.block.BlockState; import org.bukkit.block.data.type.Observer; import org.bukkit.util.Vector; -import yapion.hierarchy.types.YAPIONObject; import java.util.function.BiConsumer; @@ -70,12 +70,12 @@ public final class ObserverPhase extends SimulatorPhase { } @Override - public void saveExtra(YAPIONObject phaseObject) { - phaseObject.add("orientation", orientation.name()); + public void saveExtra(SimulatorConfig.Phase phase) { + phase.setOrientation(orientation); } @Override - public void loadExtra(YAPIONObject phaseObject) { - orientation = BlockFace.valueOf(phaseObject.getPlainValue("orientation")); + public void loadExtra(SimulatorConfig.Phase phase) { + if (phase.getOrientation() != null) orientation = phase.getOrientation(); } } diff --git a/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/simulator/data/tnt/TNTElement.java b/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/simulator/data/tnt/TNTElement.java index 735ca292..a8353907 100644 --- a/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/simulator/data/tnt/TNTElement.java +++ b/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/simulator/data/tnt/TNTElement.java @@ -24,13 +24,13 @@ import de.steamwar.bausystem.features.simulator.data.SimulatorElement; import de.steamwar.bausystem.features.simulator.data.SimulatorGroup; import de.steamwar.bausystem.features.simulator.gui.SimulatorTNTGui; import de.steamwar.bausystem.features.simulator.gui.base.SimulatorBaseGui; +import de.steamwar.bausystem.features.simulator.storage.SimulatorConfig; import de.steamwar.inventory.InvCallback; import de.steamwar.inventory.SWItem; import lombok.Getter; import org.bukkit.Material; import org.bukkit.entity.Player; import org.bukkit.util.Vector; -import yapion.hierarchy.types.YAPIONObject; public final class TNTElement extends SimulatorElement { @@ -100,14 +100,14 @@ public final class TNTElement extends SimulatorElement { } @Override - public void saveExtra(YAPIONObject elementObject) { - elementObject.add("alignmentX", alignment.getX()); - elementObject.add("alignmentZ", alignment.getZ()); + public void saveExtra(SimulatorConfig.Element element) { + element.setAlignmentX(alignment.getX()); + element.setAlignmentZ(alignment.getZ()); } @Override - public void loadExtra(YAPIONObject elementObject) { - alignment.setX(elementObject.getDoubleOrSetDefault("alignmentX", 0)); - alignment.setZ(elementObject.getDoubleOrSetDefault("alignmentZ", 0)); + public void loadExtra(SimulatorConfig.Element element) { + alignment.setX(element.getAlignmentX() != null ? element.getAlignmentX() : 0); + alignment.setZ(element.getAlignmentZ() != null ? element.getAlignmentZ() : 0); } } diff --git a/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/simulator/data/tnt/TNTPhase.java b/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/simulator/data/tnt/TNTPhase.java index bb5be0ce..bb0f6eb2 100644 --- a/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/simulator/data/tnt/TNTPhase.java +++ b/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/simulator/data/tnt/TNTPhase.java @@ -21,6 +21,7 @@ package de.steamwar.bausystem.features.simulator.data.tnt; import de.steamwar.bausystem.features.simulator.data.SimulatorPhase; import de.steamwar.bausystem.features.simulator.execute.SimulatorAction; +import de.steamwar.bausystem.features.simulator.storage.SimulatorConfig; import de.steamwar.bausystem.region.Region; import de.steamwar.bausystem.region.flags.Flag; import de.steamwar.bausystem.region.flags.FreezeMode; @@ -31,7 +32,6 @@ import org.bukkit.Location; import org.bukkit.World; import org.bukkit.entity.TNTPrimed; import org.bukkit.util.Vector; -import yapion.hierarchy.types.YAPIONObject; import java.util.function.BiConsumer; @@ -83,18 +83,18 @@ public final class TNTPhase extends SimulatorPhase { } @Override - public void saveExtra(YAPIONObject phaseObject) { - phaseObject.add("count", count); - phaseObject.add("xJump", xJump); - phaseObject.add("yJump", yJump); - phaseObject.add("zJump", zJump); + public void saveExtra(SimulatorConfig.Phase phase) { + phase.setCount(count); + phase.setXJump(xJump); + phase.setYJump(yJump); + phase.setZJump(zJump); } @Override - public void loadExtra(YAPIONObject phaseObject) { - count = phaseObject.getPlainValue("count"); - xJump = phaseObject.getPlainValue("xJump"); - yJump = phaseObject.getPlainValue("yJump"); - zJump = phaseObject.getPlainValue("zJump"); + public void loadExtra(SimulatorConfig.Phase phase) { + if (phase.getCount() != null) count = phase.getCount(); + if (phase.getXJump() != null) xJump = phase.getXJump(); + if (phase.getYJump() != null) yJump = phase.getYJump(); + if (phase.getZJump() != null) zJump = phase.getZJump(); } } diff --git a/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/simulator/storage/SimFormatSimulatorLoader.java b/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/simulator/storage/SimFormatSimulatorLoader.java index 5257ce12..9310fa2d 100644 --- a/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/simulator/storage/SimFormatSimulatorLoader.java +++ b/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/simulator/storage/SimFormatSimulatorLoader.java @@ -19,6 +19,7 @@ package de.steamwar.bausystem.features.simulator.storage; +import com.google.gson.JsonParseException; import de.steamwar.bausystem.features.simulator.data.Simulator; import de.steamwar.bausystem.features.simulator.data.SimulatorElement; import de.steamwar.bausystem.features.simulator.data.SimulatorGroup; @@ -29,12 +30,7 @@ import de.steamwar.bausystem.features.simulator.data.redstone.RedstoneElement; import de.steamwar.bausystem.features.simulator.data.redstone.RedstonePhase; import de.steamwar.bausystem.features.simulator.data.tnt.TNTElement; import de.steamwar.bausystem.features.simulator.data.tnt.TNTPhase; -import org.bukkit.Material; import org.bukkit.util.Vector; -import yapion.exceptions.YAPIONException; -import yapion.hierarchy.types.YAPIONArray; -import yapion.hierarchy.types.YAPIONObject; -import yapion.parser.YAPIONParser; import java.io.File; import java.io.IOException; @@ -51,44 +47,41 @@ public class SimFormatSimulatorLoader implements SimulatorLoader { return Optional.empty(); } - YAPIONObject yapionObject; + SimulatorConfig config; try { - yapionObject = YAPIONParser.parse(file); - } catch (YAPIONException | IOException e) { + config = SimulatorConfig.STORE.load(file); + } catch (JsonParseException | IllegalStateException | IOException e) { return Optional.empty(); } String name = file.getName().substring(0, file.getName().length() - ".sim".length()); Simulator simulator = new Simulator(name); - loadSimulator(yapionObject, simulator); + loadSimulator(config, simulator); return Optional.of(Collections.singletonList(simulator)); } - private void loadSimulator(YAPIONObject simulatorObject, Simulator simulator) { - simulator.setMaterial(Material.valueOf(simulatorObject.getPlainValue("material"))); - simulator.setAutoTrace(simulatorObject.getPlainValue("autoTrace")); - simulator.setAutoTestblock(simulatorObject.getPlainValueOrDefault("autoTestblock", false)); + private void loadSimulator(SimulatorConfig config, Simulator simulator) { + if (config.getMaterial() != null) simulator.setMaterial(config.getMaterial()); + simulator.setAutoTrace(config.isAutoTrace()); + simulator.setAutoTestblock(config.isAutoTestblock()); - YAPIONArray groups = simulatorObject.getArray("groups"); - groups.streamObject().forEach(groupObject -> { + config.getGroups().forEach(groupConfig -> { SimulatorGroup simulatorGroup = new SimulatorGroup(); - loadGroup(groupObject, simulatorGroup); + loadGroup(groupConfig, simulatorGroup); simulator.add(simulatorGroup); }); } - private void loadGroup(YAPIONObject groupObject, SimulatorGroup group) { - group.setMaterial(Material.valueOf(groupObject.getPlainValue("material"))); - group.setDisabled(groupObject.getPlainValue("disabled")); + private void loadGroup(SimulatorConfig.Group groupConfig, SimulatorGroup group) { + if (groupConfig.getMaterial() != null) group.setMaterial(groupConfig.getMaterial()); + group.setDisabled(groupConfig.isDisabled()); - YAPIONArray elements = groupObject.getArray("elements"); - elements.streamObject().forEach(elementObject -> { + groupConfig.getElements().forEach(elementConfig -> { SimulatorElement element; Supplier phaseConstructor; - Vector position = new Vector(elementObject.getDouble("x"), elementObject.getDouble("y"), elementObject.getDouble("z")); - String type = elementObject.getPlainValue("type"); - switch (type) { + Vector position = new Vector(elementConfig.getX(), elementConfig.getY(), elementConfig.getZ()); + switch (String.valueOf(elementConfig.getType())) { case "TNT": element = new TNTElement(position); phaseConstructor = TNTPhase::new; @@ -109,28 +102,27 @@ public class SimFormatSimulatorLoader implements SimulatorLoader { if (element == null) { return; } - loadElement(elementObject, element, phaseConstructor); + loadElement(elementConfig, element, phaseConstructor); group.add(element); }); } - private void loadElement(YAPIONObject elementObject, SimulatorElement element, Supplier phaseConstructor) { - element.setMaterial(Material.valueOf(elementObject.getPlainValue("material"))); - element.setDisabled(elementObject.getPlainValue("disabled")); - element.loadExtra(elementObject); + private void loadElement(SimulatorConfig.Element elementConfig, SimulatorElement element, Supplier phaseConstructor) { + if (elementConfig.getMaterial() != null) element.setMaterial(elementConfig.getMaterial()); + element.setDisabled(elementConfig.isDisabled()); + element.loadExtra(elementConfig); - YAPIONArray phases = elementObject.getArray("phases"); - phases.streamObject().forEach(phaseObject -> { + elementConfig.getPhases().forEach(phaseConfig -> { SimulatorPhase phase = phaseConstructor.get(); - loadPhase(phaseObject, phase); + loadPhase(phaseConfig, phase); ((SimulatorElement) element).add(phase); }); } - private void loadPhase(YAPIONObject phaseObject, SimulatorPhase phase) { - phase.setTickOffset(phaseObject.getPlainValue("tickOffset")); - phase.setLifetime(phaseObject.getPlainValue("lifetime")); - phase.setOrder(phaseObject.getPlainValue("order")); - phase.loadExtra(phaseObject); + private void loadPhase(SimulatorConfig.Phase phaseConfig, SimulatorPhase phase) { + phase.setTickOffset(phaseConfig.getTickOffset()); + phase.setLifetime(phaseConfig.getLifetime()); + phase.setOrder(phaseConfig.getOrder()); + phase.loadExtra(phaseConfig); } } diff --git a/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/simulator/storage/SimulatorConfig.java b/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/simulator/storage/SimulatorConfig.java new file mode 100644 index 00000000..a1c56f86 --- /dev/null +++ b/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/simulator/storage/SimulatorConfig.java @@ -0,0 +1,117 @@ +/* + * This file is a part of the SteamWar software. + * + * Copyright (C) 2026 SteamWar.de-Serverteam + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package de.steamwar.bausystem.features.simulator.storage; + +import de.steamwar.bausystem.config.ConfigStore; +import de.steamwar.bausystem.config.VersionedConfig; +import lombok.AccessLevel; +import lombok.Getter; +import lombok.Setter; +import org.bukkit.Material; +import org.bukkit.block.BlockFace; + +import java.util.ArrayList; +import java.util.List; + +/** + * The current stored form of a simulator, saved as JSON in a .sim file. Files + * saved before the version was stored have no version and are version 1. + * Fields missing in the stored JSON keep the default value of their initializer, + * so adding a new field does not need a new version. + *

+ * When a field gets removed, renamed or its meaning changes, copy this class + * to SimulatorConfigV{@link #VERSION} (without the {@link #STORE}), increase {@link #VERSION}, write a + * {@link de.steamwar.bausystem.config.ConfigConverter} from the copy to + * this class and register it in the {@link #STORE}. + */ +@Getter +@Setter +public class SimulatorConfig implements VersionedConfig { + + public static final int VERSION = 1; + + /** + * Files saved before the version was stored are version 1 + */ + public static final ConfigStore STORE = new ConfigStore<>(SimulatorConfig.class, VERSION, SimulatorConfig::new) + .unversioned(1); + + @Setter(AccessLevel.NONE) + protected int version = VERSION; + + protected Material material = Material.BARREL; + protected boolean autoTrace = false; + protected boolean autoTestblock = false; + protected List groups = new ArrayList<>(); + + @Getter + @Setter + public static class Group { + + protected Material material = Material.CHEST; + protected boolean disabled = false; + protected List elements = new ArrayList<>(); + } + + @Getter + @Setter + public static class Element { + + /** + * One of "TNT", "Redstone" or "Observer" + */ + protected String type; + protected Material material; + protected boolean disabled = false; + protected double x; + protected double y; + protected double z; + + /** + * Only used by TNT + */ + protected Double alignmentX; + protected Double alignmentZ; + + protected List phases = new ArrayList<>(); + } + + @Getter + @Setter + public static class Phase { + + protected int tickOffset; + protected int lifetime; + protected int order; + + /** + * Only used by TNT + */ + protected Integer count; + protected Boolean xJump; + protected Boolean yJump; + protected Boolean zJump; + + /** + * Only used by Observer + */ + protected BlockFace orientation; + } +} diff --git a/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/simulator/storage/SimulatorFormatSimulatorLoader.java b/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/simulator/storage/SimulatorFormatSimulatorLoader.java index 65815440..a7f52ede 100644 --- a/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/simulator/storage/SimulatorFormatSimulatorLoader.java +++ b/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/simulator/storage/SimulatorFormatSimulatorLoader.java @@ -19,24 +19,30 @@ package de.steamwar.bausystem.features.simulator.storage; +import de.steamwar.bausystem.config.legacy.YapionReader; import de.steamwar.bausystem.features.simulator.data.Simulator; import de.steamwar.bausystem.features.simulator.data.SimulatorGroup; import de.steamwar.bausystem.features.simulator.data.tnt.TNTElement; import de.steamwar.bausystem.features.simulator.data.tnt.TNTPhase; import org.bukkit.Material; import org.bukkit.util.Vector; -import yapion.exceptions.YAPIONException; -import yapion.hierarchy.types.YAPIONArray; -import yapion.hierarchy.types.YAPIONObject; -import yapion.parser.YAPIONParser; import java.io.File; import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; import java.util.Collections; import java.util.List; +import java.util.Map; import java.util.Optional; -import java.util.stream.Collectors; +import static de.steamwar.bausystem.config.legacy.YapionReader.*; + +/** + * Loads the legacy .simulator files stored as YAPION, they are deleted after loading + * and saved in the current format by the {@link SimulatorSaver}. + */ +@Deprecated public class SimulatorFormatSimulatorLoader implements SimulatorLoader { @Override @@ -45,20 +51,19 @@ public class SimulatorFormatSimulatorLoader implements SimulatorLoader { return Optional.empty(); } - YAPIONObject yapionObject; + Map yapionObject; try { - yapionObject = YAPIONParser.parse(file); - } catch (YAPIONException | IOException e) { + yapionObject = YapionReader.parse(Files.readString(file.toPath(), StandardCharsets.UTF_8)); + } catch (IllegalArgumentException | IOException e) { return Optional.empty(); } String name = file.getName().substring(0, file.getName().length() - ".simulator".length()); Simulator simulator = new Simulator(name); - simulator.setMaterial(Material.valueOf(yapionObject.getStringOrDefault("material", "BARREL"))); + simulator.setMaterial(Material.valueOf(getString(yapionObject, "material", "BARREL"))); - YAPIONArray tntElements = yapionObject.getArrayOrDefault("tntElements", new YAPIONArray()); - for (YAPIONObject element : tntElements.streamObject().collect(Collectors.toList())) { + for (Map element : getObjects(yapionObject, "tntElements")) { if (element.containsKey("elements")) { simulator.add(loadGroup(element)); } else { @@ -70,27 +75,26 @@ public class SimulatorFormatSimulatorLoader implements SimulatorLoader { return Optional.of(Collections.singletonList(simulator)); } - private SimulatorGroup loadGroup(YAPIONObject element) { - double x = element.getDoubleOrDefault("x", 0); - double y = element.getDoubleOrDefault("y", 0); - double z = element.getDoubleOrDefault("z", 0); - int tickOffset = element.getIntOrDefault("tickOffset", 0); + private SimulatorGroup loadGroup(Map element) { + double x = getDouble(element, "x", 0); + double y = getDouble(element, "y", 0); + double z = getDouble(element, "z", 0); + int tickOffset = getInt(element, "tickOffset", 0); SimulatorGroup simulatorGroup = new SimulatorGroup(); - simulatorGroup.setDisabled(element.getBooleanOrDefault("disabled", false)); + simulatorGroup.setDisabled(getBoolean(element, "disabled", false)); - YAPIONArray elements = element.getArrayOrDefault("elements", new YAPIONArray()); - for (YAPIONObject e : elements.streamObject().collect(Collectors.toList())) { + for (Map e : getObjects(element, "elements")) { simulatorGroup.add(loadElement(e, x, y, z, tickOffset)); } return simulatorGroup; } - private TNTElement loadElement(YAPIONObject element, double dx, double dy, double dz, int dTickOffset) { - TNTElement tntElement = new TNTElement(new Vector(element.getDouble("x") + dx, element.getDouble("y") + dy, element.getDouble("z") + dz)); - tntElement.setDisabled(element.getBooleanOrDefault("disabled", false)); - tntElement.setMaterial(Material.valueOf(element.getStringOrDefault("material", "TNT"))); + private TNTElement loadElement(Map element, double dx, double dy, double dz, int dTickOffset) { + TNTElement tntElement = new TNTElement(new Vector(requireDouble(element, "x") + dx, requireDouble(element, "y") + dy, requireDouble(element, "z") + dz)); + tntElement.setDisabled(getBoolean(element, "disabled", false)); + tntElement.setMaterial(Material.valueOf(getString(element, "material", "TNT"))); TNTPhase tntPhase = loadPhase(element); tntPhase.setTickOffset(tntPhase.getTickOffset() + dTickOffset); @@ -99,12 +103,12 @@ public class SimulatorFormatSimulatorLoader implements SimulatorLoader { return tntElement; } - private SimulatorGroup loadElement(YAPIONObject element) { + private SimulatorGroup loadElement(Map element) { SimulatorGroup simulatorGroup = new SimulatorGroup(); - TNTElement tntElement = new TNTElement(new Vector(element.getDouble("x"), element.getDouble("y"), element.getDouble("z"))); - tntElement.setDisabled(element.getBooleanOrDefault("disabled", false)); - tntElement.setMaterial(Material.valueOf(element.getStringOrDefault("material", "TNT"))); + TNTElement tntElement = new TNTElement(new Vector(requireDouble(element, "x"), requireDouble(element, "y"), requireDouble(element, "z"))); + tntElement.setDisabled(getBoolean(element, "disabled", false)); + tntElement.setMaterial(Material.valueOf(getString(element, "material", "TNT"))); simulatorGroup.add(tntElement); tntElement.add(loadPhase(element)); @@ -112,15 +116,15 @@ public class SimulatorFormatSimulatorLoader implements SimulatorLoader { return simulatorGroup; } - private TNTPhase loadPhase(YAPIONObject element) { + private TNTPhase loadPhase(Map element) { TNTPhase tntPhase = new TNTPhase(); - tntPhase.setLifetime(element.getIntOrDefault("fuseTicks", 80)); - tntPhase.setCount(element.getIntOrDefault("count", 1)); - tntPhase.setTickOffset(element.getIntOrDefault("tickOffset", 0)); - tntPhase.setXJump(element.getBooleanOrDefault("xVelocity", false)); - tntPhase.setYJump(element.getBooleanOrDefault("yVelocity", false)); - tntPhase.setZJump(element.getBooleanOrDefault("zVelocity", false)); - switch (element.getStringOrDefault("order", "REPEATER")) { + tntPhase.setLifetime(getInt(element, "fuseTicks", 80)); + tntPhase.setCount(getInt(element, "count", 1)); + tntPhase.setTickOffset(getInt(element, "tickOffset", 0)); + tntPhase.setXJump(getBoolean(element, "xVelocity", false)); + tntPhase.setYJump(getBoolean(element, "yVelocity", false)); + tntPhase.setZJump(getBoolean(element, "zVelocity", false)); + switch (getString(element, "order", "REPEATER")) { case "REPEATER": tntPhase.setOrder(0); break; @@ -133,4 +137,8 @@ public class SimulatorFormatSimulatorLoader implements SimulatorLoader { } return tntPhase; } + + private double requireDouble(Map element, String key) { + return ((Number) element.get(key)).doubleValue(); + } } diff --git a/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/simulator/storage/SimulatorSaver.java b/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/simulator/storage/SimulatorSaver.java index 1f3345e4..bfa278e7 100644 --- a/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/simulator/storage/SimulatorSaver.java +++ b/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/simulator/storage/SimulatorSaver.java @@ -21,9 +21,6 @@ package de.steamwar.bausystem.features.simulator.storage; import de.steamwar.bausystem.features.simulator.data.Simulator; import lombok.experimental.UtilityClass; -import yapion.hierarchy.output.FileOutput; -import yapion.hierarchy.types.YAPIONArray; -import yapion.hierarchy.types.YAPIONObject; import java.io.File; import java.io.IOException; @@ -36,48 +33,42 @@ public class SimulatorSaver { directory.mkdirs(); } - YAPIONObject simulatorObject = new YAPIONObject(); - simulatorObject.add("material", simulator.getMaterial().name()); - simulatorObject.add("autoTrace", simulator.isAutoTrace()); - simulatorObject.add("autoTestblock", simulator.isAutoTestblock()); + SimulatorConfig config = new SimulatorConfig(); + config.setMaterial(simulator.getMaterial()); + config.setAutoTrace(simulator.isAutoTrace()); + config.setAutoTestblock(simulator.isAutoTestblock()); - YAPIONArray groups = new YAPIONArray(); simulator.getGroups().forEach(group -> { - YAPIONObject groupObject = new YAPIONObject(); - groupObject.add("material", group.getMaterial().name()); - groupObject.add("disabled", group.isDisabled()); + SimulatorConfig.Group groupConfig = new SimulatorConfig.Group(); + groupConfig.setMaterial(group.getMaterial()); + groupConfig.setDisabled(group.isDisabled()); - YAPIONArray elements = new YAPIONArray(); group.getElements().forEach(element -> { - YAPIONObject elementObject = new YAPIONObject(); - elementObject.add("type", element.getType()); - elementObject.add("material", element.getMaterial().name()); - elementObject.add("disabled", element.isDisabled()); - elementObject.add("x", element.getPosition().getX()); - elementObject.add("y", element.getPosition().getY()); - elementObject.add("z", element.getPosition().getZ()); - element.saveExtra(elementObject); + SimulatorConfig.Element elementConfig = new SimulatorConfig.Element(); + elementConfig.setType(element.getType()); + elementConfig.setMaterial(element.getMaterial()); + elementConfig.setDisabled(element.isDisabled()); + elementConfig.setX(element.getPosition().getX()); + elementConfig.setY(element.getPosition().getY()); + elementConfig.setZ(element.getPosition().getZ()); + element.saveExtra(elementConfig); - YAPIONArray phases = new YAPIONArray(); element.getPhases().forEach(phase -> { - YAPIONObject phaseObject = new YAPIONObject(); - phaseObject.add("tickOffset", phase.getTickOffset()); - phaseObject.add("lifetime", phase.getLifetime()); - phaseObject.add("order", phase.getOrder()); - phase.saveExtra(phaseObject); - phases.add(phaseObject); + SimulatorConfig.Phase phaseConfig = new SimulatorConfig.Phase(); + phaseConfig.setTickOffset(phase.getTickOffset()); + phaseConfig.setLifetime(phase.getLifetime()); + phaseConfig.setOrder(phase.getOrder()); + phase.saveExtra(phaseConfig); + elementConfig.getPhases().add(phaseConfig); }); - elementObject.add("phases", phases); - elements.add(elementObject); + groupConfig.getElements().add(elementConfig); }); - groupObject.add("elements", elements); - groups.add(groupObject); + config.getGroups().add(groupConfig); }); - simulatorObject.add("groups", groups); File file = new File(directory, simulator.getName() + ".sim"); try { - simulatorObject.toJSONLossy(new FileOutput(file)).close(); + SimulatorConfig.STORE.save(file, config); } catch (IOException e) { // Ignore } diff --git a/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/simulator/storage/YAPIONFormatSimulatorLoader.java b/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/simulator/storage/YAPIONFormatSimulatorLoader.java index 6111eb32..a3c773c0 100644 --- a/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/simulator/storage/YAPIONFormatSimulatorLoader.java +++ b/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/simulator/storage/YAPIONFormatSimulatorLoader.java @@ -19,6 +19,7 @@ package de.steamwar.bausystem.features.simulator.storage; +import de.steamwar.bausystem.config.legacy.YapionReader; import de.steamwar.bausystem.features.simulator.data.Simulator; import de.steamwar.bausystem.features.simulator.data.SimulatorGroup; import de.steamwar.bausystem.features.simulator.data.tnt.TNTElement; @@ -26,18 +27,23 @@ import de.steamwar.bausystem.features.simulator.data.tnt.TNTPhase; import de.steamwar.sql.SteamwarUser; import org.bukkit.Material; import org.bukkit.util.Vector; -import yapion.exceptions.YAPIONException; -import yapion.hierarchy.types.YAPIONArray; -import yapion.hierarchy.types.YAPIONObject; -import yapion.parser.YAPIONParser; import java.io.File; import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; import java.util.ArrayList; import java.util.List; +import java.util.Map; import java.util.Optional; -import java.util.stream.Collectors; +import static de.steamwar.bausystem.config.legacy.YapionReader.*; + +/** + * Loads the legacy per player .yapion files containing multiple simulators, they are + * deleted after loading and saved in the current format by the {@link SimulatorSaver}. + */ +@Deprecated public class YAPIONFormatSimulatorLoader implements SimulatorLoader { @Override @@ -46,10 +52,10 @@ public class YAPIONFormatSimulatorLoader implements SimulatorLoader { return Optional.empty(); } - YAPIONObject yapionObject; + Map yapionObject; try { - yapionObject = YAPIONParser.parse(file); - } catch (YAPIONException | IOException e) { + yapionObject = YapionReader.parse(Files.readString(file.toPath(), StandardCharsets.UTF_8)); + } catch (IllegalArgumentException | IOException e) { return Optional.empty(); } @@ -57,28 +63,28 @@ public class YAPIONFormatSimulatorLoader implements SimulatorLoader { SteamwarUser steamwarUser = SteamwarUser.byId(Integer.parseInt(name)); List simulators = new ArrayList<>(); - for (String s : yapionObject.getKeys()) { + for (String s : yapionObject.keySet()) { String newName = steamwarUser.getUserName() + (s.isEmpty() ? "" : "_" + s); - YAPIONArray content = yapionObject.getArray(s); + List> content = getObjects(yapionObject, s); if (content.isEmpty()) continue; Simulator simulator = new Simulator(newName); - for (YAPIONObject element : content.streamObject().collect(Collectors.toList())) { + for (Map element : content) { SimulatorGroup simulatorGroup = new SimulatorGroup(); simulator.add(simulatorGroup); - TNTElement tntElement = new TNTElement(new Vector(element.getDouble("positionX"), element.getDouble("positionY"), element.getDouble("positionZ"))); - tntElement.setDisabled(element.getBooleanOrDefault("disabled", false)); // IDK if this existed back then? - tntElement.setMaterial(Material.valueOf(element.getStringOrDefault("material", "TNT"))); + TNTElement tntElement = new TNTElement(new Vector(requireDouble(element, "positionX"), requireDouble(element, "positionY"), requireDouble(element, "positionZ"))); + tntElement.setDisabled(getBoolean(element, "disabled", false)); // IDK if this existed back then? + tntElement.setMaterial(Material.valueOf(getString(element, "material", "TNT"))); simulatorGroup.add(tntElement); TNTPhase tntPhase = new TNTPhase(); - tntPhase.setLifetime(element.getIntOrDefault("fuseTicks", 80)); - tntPhase.setCount(element.getIntOrDefault("count", 1)); - tntPhase.setTickOffset(element.getIntOrDefault("tickOffset", 0)); - tntPhase.setXJump(element.getBooleanOrDefault("xVelocity", false)); - tntPhase.setYJump(element.getBooleanOrDefault("yVelocity", false)); - tntPhase.setZJump(element.getBooleanOrDefault("zVelocity", false)); - tntPhase.setOrder(element.getBooleanOrDefault("comparator", false) ? 2 : 0); + tntPhase.setLifetime(getInt(element, "fuseTicks", 80)); + tntPhase.setCount(getInt(element, "count", 1)); + tntPhase.setTickOffset(getInt(element, "tickOffset", 0)); + tntPhase.setXJump(getBoolean(element, "xVelocity", false)); + tntPhase.setYJump(getBoolean(element, "yVelocity", false)); + tntPhase.setZJump(getBoolean(element, "zVelocity", false)); + tntPhase.setOrder(getBoolean(element, "comparator", false) ? 2 : 0); tntElement.add(tntPhase); } simulators.add(simulator); @@ -87,4 +93,8 @@ public class YAPIONFormatSimulatorLoader implements SimulatorLoader { file.delete(); return Optional.of(simulators); } + + private double requireDouble(Map element, String key) { + return ((Number) element.get(key)).doubleValue(); + } } diff --git a/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/slaves/laufbau/LaufbauConfig.java b/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/slaves/laufbau/LaufbauConfig.java new file mode 100644 index 00000000..c422807c --- /dev/null +++ b/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/slaves/laufbau/LaufbauConfig.java @@ -0,0 +1,64 @@ +/* + * This file is a part of the SteamWar software. + * + * Copyright (C) 2026 SteamWar.de-Serverteam + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package de.steamwar.bausystem.features.slaves.laufbau; + +import de.steamwar.bausystem.config.ConfigStore; +import de.steamwar.bausystem.config.VersionedConfig; +import de.steamwar.bausystem.features.slaves.laufbau.converter.LaufbauConfigV1ToV2Converter; +import lombok.AccessLevel; +import lombok.Getter; +import lombok.Setter; + +import java.util.HashSet; +import java.util.Set; + +/** + * The current laufbau config of a player. It is stored as JSON, fields missing + * in the stored JSON keep the default value of their initializer, so adding a + * new field does not need a new version. + *

+ * When a field gets removed, renamed or its meaning changes, copy this class + * to LaufbauConfigV{@link #VERSION} (without the {@link #STORE}), increase {@link #VERSION}, write a + * {@link de.steamwar.bausystem.config.ConfigConverter} from the copy to + * this class and register it in the {@link #STORE}. + */ +@Getter +@Setter +public class LaufbauConfig implements VersionedConfig { + + public static final int VERSION = 2; + + /** + * LaufbauConfigV1 is stored as Base64 of gzipped YAPION, the current version as JSON + */ + public static final ConfigStore STORE = new ConfigStore<>(LaufbauConfig.class, VERSION, LaufbauConfig::new) + .legacy(s -> !s.startsWith("{"), LaufbauConfigV1::parse) + .converter(new LaufbauConfigV1ToV2Converter()) + .userConfig("bausystem-laufbau") + .removeIf(config -> config.getDeactivated().isEmpty()); + + @Setter(AccessLevel.NONE) + protected int version = VERSION; + + /** + * Identifiers of the deactivated {@link BlockBoundingBox}es, see {@link LaufbauUtils#identifier(BlockBoundingBox)} + */ + protected Set deactivated = new HashSet<>(); +} diff --git a/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/slaves/laufbau/LaufbauConfigV1.java b/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/slaves/laufbau/LaufbauConfigV1.java new file mode 100644 index 00000000..d372ed5d --- /dev/null +++ b/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/slaves/laufbau/LaufbauConfigV1.java @@ -0,0 +1,65 @@ +/* + * This file is a part of the SteamWar software. + * + * Copyright (C) 2026 SteamWar.de-Serverteam + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package de.steamwar.bausystem.features.slaves.laufbau; + +import de.steamwar.bausystem.config.VersionedConfig; +import de.steamwar.bausystem.config.legacy.YapionReader; +import lombok.AccessLevel; +import lombok.Getter; +import lombok.Setter; + +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.UncheckedIOException; +import java.nio.charset.StandardCharsets; +import java.util.Base64; +import java.util.HashSet; +import java.util.Set; +import java.util.zip.GZIPInputStream; + +/** + * The legacy laufbau config that was stored as Base64 of the gzipped YAPION, + * an object with the deactivated identifiers as keys. + */ +@Getter +@Setter +@Deprecated +public class LaufbauConfigV1 implements VersionedConfig { + + public static final int VERSION = 1; + + @Setter(AccessLevel.NONE) + protected int version = VERSION; + + protected Set deactivated = new HashSet<>(); + + public static LaufbauConfigV1 parse(String base64) { + String yapion; + try (InputStream inputStream = new GZIPInputStream(new ByteArrayInputStream(Base64.getDecoder().decode(base64)))) { + yapion = new String(inputStream.readAllBytes(), StandardCharsets.UTF_8); + } catch (IOException e) { + throw new UncheckedIOException(e); + } + LaufbauConfigV1 config = new LaufbauConfigV1(); + config.deactivated.addAll(YapionReader.parse(yapion).keySet()); + return config; + } +} diff --git a/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/slaves/laufbau/LaufbauUtils.java b/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/slaves/laufbau/LaufbauUtils.java index a642c22d..45b94482 100644 --- a/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/slaves/laufbau/LaufbauUtils.java +++ b/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/slaves/laufbau/LaufbauUtils.java @@ -20,63 +20,22 @@ package de.steamwar.bausystem.features.slaves.laufbau; import de.steamwar.bausystem.BauSystem; -import de.steamwar.core.SWPlayer; import de.steamwar.inventory.SWItem; -import de.steamwar.sql.UserConfig; -import lombok.Cleanup; import lombok.SneakyThrows; import lombok.experimental.UtilityClass; import org.bukkit.Material; import org.bukkit.entity.Player; import org.bukkit.inventory.meta.ItemMeta; -import yapion.hierarchy.output.StreamOutput; -import yapion.hierarchy.types.YAPIONObject; -import yapion.parser.YAPIONParser; -import yapion.parser.options.StreamOptions; -import java.io.ByteArrayInputStream; -import java.io.ByteArrayOutputStream; import java.util.ArrayList; import java.util.Arrays; -import java.util.Base64; import java.util.List; +import java.util.Set; import java.util.stream.Collectors; -import java.util.zip.GZIPInputStream; -import java.util.zip.GZIPOutputStream; @UtilityClass public class LaufbauUtils { - public static class LaufbauComponent implements SWPlayer.Component { - - private YAPIONObject yapionObject = new YAPIONObject(); - - @Override - @SneakyThrows - public void onMount(SWPlayer player) { - String config = UserConfig.getConfig(player.getPlayer().getUniqueId(), "bausystem-laufbau"); - if (config == null) { - return; - } - byte[] bytes = Base64.getDecoder().decode(config); - @Cleanup GZIPInputStream gzipInputStream = new GZIPInputStream(new ByteArrayInputStream(bytes)); - yapionObject = new YAPIONParser(gzipInputStream, new StreamOptions().stopOnStreamEnd(true)).parse().result(); - } - - @Override - @SneakyThrows - public void onUnmount(SWPlayer player) { - if (yapionObject.isEmpty()) { - UserConfig.removePlayerConfig(player.getPlayer().getUniqueId(), "bausystem-laufbau"); - return; - } - @Cleanup ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); - @Cleanup GZIPOutputStream gzipOutputStream = new GZIPOutputStream(byteArrayOutputStream); - yapionObject.toYAPION(new StreamOutput(gzipOutputStream)).close(); - UserConfig.updatePlayerConfig(player.getPlayer().getUniqueId(), "bausystem-laufbau", Base64.getEncoder().encodeToString(byteArrayOutputStream.toByteArray())); - } - } - public static Cuboid pixelCuboid(double pixelX, double pixelY, double pixelZ, double pixelDX, double pixelDY, double pixelDZ) { return new Cuboid(pixelToBlock(pixelX), pixelToBlock(pixelY), pixelToBlock(pixelZ), pixelToBlock(pixelDX), pixelToBlock(pixelDY), pixelToBlock(pixelDZ)); } @@ -123,7 +82,7 @@ public class LaufbauUtils { return false; } String identifier = identifier(bb); - return SWPlayer.of(p).getComponentOrDefault(LaufbauComponent.class, LaufbauComponent::new).yapionObject.containsKey(identifier); + return LaufbauConfig.STORE.get(p).getDeactivated().contains(identifier); } @SneakyThrows @@ -132,11 +91,9 @@ public class LaufbauUtils { return; } String identifier = identifier(bb); - YAPIONObject yapionObject = SWPlayer.of(p).getComponentOrDefault(LaufbauComponent.class, LaufbauComponent::new).yapionObject; - if (yapionObject.containsKey(identifier)) { - yapionObject.remove(identifier); - } else { - yapionObject.put(identifier, new YAPIONObject()); + Set deactivated = LaufbauConfig.STORE.get(p).getDeactivated(); + if (!deactivated.remove(identifier)) { + deactivated.add(identifier); } } } diff --git a/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/slaves/laufbau/converter/LaufbauConfigV1ToV2Converter.java b/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/slaves/laufbau/converter/LaufbauConfigV1ToV2Converter.java new file mode 100644 index 00000000..6e1c7aac --- /dev/null +++ b/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/slaves/laufbau/converter/LaufbauConfigV1ToV2Converter.java @@ -0,0 +1,40 @@ +/* + * This file is a part of the SteamWar software. + * + * Copyright (C) 2026 SteamWar.de-Serverteam + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package de.steamwar.bausystem.features.slaves.laufbau.converter; + +import de.steamwar.bausystem.config.ConfigConverter; +import de.steamwar.bausystem.features.slaves.laufbau.LaufbauConfig; +import de.steamwar.bausystem.features.slaves.laufbau.LaufbauConfigV1; + +@Deprecated +public class LaufbauConfigV1ToV2Converter implements ConfigConverter { + + @Override + public int version() { + return LaufbauConfigV1.VERSION; + } + + @Override + public LaufbauConfig convert(LaufbauConfigV1 old) { + LaufbauConfig config = new LaufbauConfig(); + config.getDeactivated().addAll(old.getDeactivated()); + return config; + } +} diff --git a/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/smartplace/SmartPlaceCommand.java b/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/smartplace/SmartPlaceCommand.java index 4eac9b8f..b9244573 100644 --- a/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/smartplace/SmartPlaceCommand.java +++ b/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/smartplace/SmartPlaceCommand.java @@ -35,8 +35,8 @@ public class SmartPlaceCommand extends SWCommand { @Register(description = {"SMART_PLACE_HELP", "SMART_PLACE_INFO"}) public void genericToggle(Player p) { - boolean smartPlace = Config.getInstance().get(p).getPlainValueOrDefault("smartPlace", false); - Config.getInstance().get(p).put("smartPlace", !smartPlace); + boolean smartPlace = Config.STORE.get(p).isSmartPlace(); + Config.STORE.get(p).setSmartPlace(!smartPlace); if (!smartPlace) { SWUtils.sendToActionbar(p, BauSystem.MESSAGE.parse("SMART_PLACE_ENABLE", p)); BauSystem.MESSAGE.send("SMART_PLACE_INFO", p); diff --git a/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/smartplace/SmartPlaceListener.java b/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/smartplace/SmartPlaceListener.java index 6f70fc55..f403e265 100644 --- a/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/smartplace/SmartPlaceListener.java +++ b/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/smartplace/SmartPlaceListener.java @@ -87,7 +87,7 @@ public class SmartPlaceListener implements Listener { public SmartPlaceListener() { TinyProtocol.instance.addFilter(ServerboundUseItemOnPacket.class, (player, packet) -> { if (!Permission.BUILD.hasPermission(player)) return packet; - if (!Config.getInstance().get(player).getPlainValueOrDefault("smartPlace", false)) return packet; + if (!Config.STORE.get(player).isSmartPlace()) return packet; RayTraceResult rayTraceResult = player.rayTraceBlocks(6); Block block = rayTraceResult != null ? rayTraceResult.getHitBlock() : null; BlockFace blockFace = rayTraceResult != null ? rayTraceResult.getHitBlockFace() : null; @@ -127,7 +127,7 @@ public class SmartPlaceListener implements Listener { @EventHandler public void onPlayerInteract(PlayerInteractEvent event) { if (!Permission.BUILD.hasPermission(event.getPlayer())) return; - if (!Config.getInstance().get(event.getPlayer()).getPlainValueOrDefault("smartPlace", false)) return; + if (!Config.STORE.get(event.getPlayer()).isSmartPlace()) return; WAS_EXECUTED.add(event.getPlayer()); if (event.getAction() != Action.RIGHT_CLICK_BLOCK) return; if (event.getPlayer().getGameMode() == GameMode.SPECTATOR) return; @@ -146,7 +146,7 @@ public class SmartPlaceListener implements Listener { @EventHandler public void onBlockPlace(BlockPlaceEvent event) { if (!Permission.BUILD.hasPermission(event.getPlayer())) return; - if (!Config.getInstance().get(event.getPlayer()).getPlainValueOrDefault("smartPlace", false)) return; + if (!Config.STORE.get(event.getPlayer()).isSmartPlace()) return; if (!SMART_PLACING.contains(event.getPlayer())) { if (CONTAINERS.contains(event.getBlockAgainst().getType())) { SoundGroup soundGroup = event.getBlockPlaced().getBlockData().getSoundGroup(); diff --git a/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/testblock/blockcounter/BlockCounter.java b/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/testblock/blockcounter/BlockCounter.java index 9e3e53e0..2ca16694 100644 --- a/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/testblock/blockcounter/BlockCounter.java +++ b/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/testblock/blockcounter/BlockCounter.java @@ -35,18 +35,15 @@ public class BlockCounter { public final Map blockCountMap = new HashMap<>(); public boolean isActive(final Player p) { - if (!Config.getInstance().get(p).containsKey("blockCounter")) { - return false; - } - return Config.getInstance().get(p).getPlainValue("blockCounter"); + return Config.STORE.get(p).isBlockCounter(); } public void enable(final Player p) { - Config.getInstance().get(p).put("blockCounter", true); + Config.STORE.get(p).setBlockCounter(true); } public void disable(final Player p) { - Config.getInstance().get(p).put("blockCounter", false); + Config.STORE.get(p).setBlockCounter(false); } public String getMessage(Player player, int count, int tntCount, long tick, long lastTick) { diff --git a/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/tracer/rendering/TraceEntity.java b/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/tracer/rendering/TraceEntity.java index 160e4d58..2ec576f6 100644 --- a/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/tracer/rendering/TraceEntity.java +++ b/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/tracer/rendering/TraceEntity.java @@ -36,13 +36,10 @@ import org.bukkit.entity.Player; import org.bukkit.util.Transformation; import org.joml.Quaternionf; import org.joml.Vector3f; -import yapion.hierarchy.types.YAPIONValue; import java.util.List; import java.util.stream.Collectors; -import static de.steamwar.bausystem.features.util.TNTClickListener.TNT_CLICK_DETAILS; - /** * Wrapper for the rendering of a record bundle */ @@ -102,7 +99,7 @@ public class TraceEntity extends RBlockDisplay { * @param player the player the message should be printed for */ public void printIntoChat(Player player) { - if (!Config.getInstance().get(player).getOrSetDefault(TNT_CLICK_DETAILS, new YAPIONValue<>(true)).asBoolean().orElse(true)) { + if (!Config.STORE.get(player).isTntClickDetails()) { return; } TNTPoint representative = records.get(0); diff --git a/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/util/NightVisionCommand.java b/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/util/NightVisionCommand.java index 2f884047..d43502ad 100644 --- a/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/util/NightVisionCommand.java +++ b/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/util/NightVisionCommand.java @@ -30,7 +30,6 @@ import org.bukkit.event.Listener; import org.bukkit.event.player.PlayerJoinEvent; import org.bukkit.potion.PotionEffect; import org.bukkit.potion.PotionEffectType; -import yapion.hierarchy.types.YAPIONObject; @Linked public class NightVisionCommand extends SWCommand implements Listener { @@ -41,15 +40,15 @@ public class NightVisionCommand extends SWCommand implements Listener { @Register(description = "NIGHT_VISION_HELP") public void genericCommand(Player p) { - YAPIONObject yapionObject = Config.getInstance().get(p); - boolean value = !yapionObject.getBooleanOrDefault("nightvision", false); - yapionObject.put("nightvision", value); + Config config = Config.STORE.get(p); + boolean value = !config.isNightVision(); + config.setNightVision(value); setNightVision(p, value); } @EventHandler public void onPlayerJoin(PlayerJoinEvent event) { - setNightVision(event.getPlayer(), Config.getInstance().get(event.getPlayer()).getBooleanOrDefault("nightvision", false)); + setNightVision(event.getPlayer(), Config.STORE.get(event.getPlayer()).isNightVision()); } private void setNightVision(Player p, boolean value) { diff --git a/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/util/TNTClickListener.java b/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/util/TNTClickListener.java index ca17f6a5..8af264a1 100644 --- a/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/util/TNTClickListener.java +++ b/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/util/TNTClickListener.java @@ -32,26 +32,22 @@ import org.bukkit.event.EventHandler; import org.bukkit.event.Listener; import org.bukkit.event.player.PlayerInteractEntityEvent; import org.bukkit.inventory.EquipmentSlot; -import yapion.hierarchy.types.YAPIONObject; -import yapion.hierarchy.types.YAPIONValue; @Linked public class TNTClickListener extends SWCommand implements Listener { - public static final String TNT_CLICK_DETAILS = "tnt_click_details"; - public TNTClickListener() { super("tntdetails"); } @Register(description = "TNT_DETAILS_COMMAND") public void toggle(Player player) { - YAPIONObject yapionObject = Config.getInstance().get(player); - if (yapionObject.getOrSetDefault(TNT_CLICK_DETAILS, new YAPIONValue<>(true)).asBoolean().orElse(true)) { - yapionObject.put(TNT_CLICK_DETAILS, false); + Config config = Config.STORE.get(player); + if (config.isTntClickDetails()) { + config.setTntClickDetails(false); BauSystem.MESSAGE.send("TNT_DETAILS_OFF", player); } else { - yapionObject.put(TNT_CLICK_DETAILS, true); + config.setTntClickDetails(true); BauSystem.MESSAGE.send("TNT_DETAILS_ON", player); } } @@ -60,7 +56,7 @@ public class TNTClickListener extends SWCommand implements Listener { public void onPlayerInteractEntity(PlayerInteractEntityEvent event) { if (!Permission.BUILD.hasPermission(event.getPlayer())) return; if (event.getHand() != EquipmentSlot.HAND) return; - if (!Config.getInstance().get(event.getPlayer()).getOrSetDefault(TNT_CLICK_DETAILS, new YAPIONValue<>(true)).asBoolean().orElse(true)) { + if (!Config.STORE.get(event.getPlayer()).isTntClickDetails()) { return; } diff --git a/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/warp/Warp.java b/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/warp/Warp.java index 696dac81..f2110927 100644 --- a/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/warp/Warp.java +++ b/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/warp/Warp.java @@ -25,7 +25,6 @@ import lombok.Getter; import org.bukkit.*; import org.bukkit.entity.Player; import org.bukkit.event.player.PlayerTeleportEvent; -import yapion.hierarchy.types.YAPIONObject; import java.util.*; @@ -56,27 +55,22 @@ public class Warp { warpMap.put(name, this); } - public Warp(String name, YAPIONObject object) { + public Warp(String name, WorldData.WarpEntry warp) { this.name = name; - double x = object.getPlainValue("x"); - double y = object.getPlainValue("y"); - double z = object.getPlainValue("z"); - float yaw = object.getPlainValue("yaw"); - float pitch = object.getPlainValue("pitch"); - location = new Location(Bukkit.getWorlds().get(0), x, y, z, yaw, pitch); - mat = Material.getMaterial(object.getPlainValue("material")); + location = new Location(Bukkit.getWorlds().get(0), warp.getX(), warp.getY(), warp.getZ(), warp.getYaw(), warp.getPitch()); + mat = Material.getMaterial(warp.getMaterial()); warpMap.put(name, this); } - public YAPIONObject output() { - YAPIONObject yapionObject = new YAPIONObject(); - yapionObject.add("x", location.getX()); - yapionObject.add("y", location.getY()); - yapionObject.add("z", location.getZ()); - yapionObject.add("yaw", location.getYaw()); - yapionObject.add("pitch", location.getPitch()); - yapionObject.add("material", mat.toString()); - return yapionObject; + public WorldData.WarpEntry output() { + WorldData.WarpEntry warp = new WorldData.WarpEntry(); + warp.setX(location.getX()); + warp.setY(location.getY()); + warp.setZ(location.getZ()); + warp.setYaw(location.getYaw()); + warp.setPitch(location.getPitch()); + warp.setMaterial(mat.toString()); + return warp; } public static Set getWarpNames() { @@ -101,7 +95,7 @@ public class Warp { public void delete() { warpMap.remove(name); - WorldData.getWarpData().remove(name); + WorldData.STORE.get().getWarps().remove(name); } public void teleport(Player player) { diff --git a/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/warp/WarpCommand.java b/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/warp/WarpCommand.java index e20a70b7..0c071276 100644 --- a/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/warp/WarpCommand.java +++ b/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/warp/WarpCommand.java @@ -34,9 +34,9 @@ import net.md_5.bungee.api.chat.ComponentBuilder; import net.md_5.bungee.api.chat.HoverEvent; import net.md_5.bungee.api.chat.TextComponent; import org.bukkit.entity.Player; -import yapion.hierarchy.types.YAPIONObject; import java.util.ArrayList; +import java.util.Map; @Linked public class WarpCommand extends SWCommand implements Disable, Enable { @@ -135,17 +135,15 @@ public class WarpCommand extends SWCommand implements Disable, Enable { @Override public void enable() { - WorldData.getWarpData().forEach((name, yapionAnyType) -> { - new Warp(name, (YAPIONObject) yapionAnyType); - }); + WorldData.STORE.get().getWarps().forEach(Warp::new); Warp.enable(); } @Override public void disable() { - YAPIONObject yapionObject = WorldData.getWarpData(); + Map warps = WorldData.STORE.get().getWarps(); for (Warp warp : Warp.getWarps()) { - yapionObject.add(warp.getName(), warp.output()); + warps.put(warp.getName(), warp.output()); } } } diff --git a/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/region/RegionData.java b/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/region/RegionData.java index a1d94ed4..715dc1d6 100644 --- a/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/region/RegionData.java +++ b/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/region/RegionData.java @@ -20,78 +20,39 @@ package de.steamwar.bausystem.region; import de.steamwar.bausystem.region.flags.Flag; +import de.steamwar.bausystem.worlddata.WorldData; import de.steamwar.sql.SchematicNode; import lombok.NonNull; -import yapion.hierarchy.types.YAPIONObject; -import java.util.ArrayList; import java.util.HashMap; -import java.util.List; import java.util.Map; -import java.util.function.Function; public abstract class RegionData { - private final List> properties = new ArrayList<>(); - - protected final YAPIONObject data; - protected final YAPIONObject flagData; + protected final WorldData.RegionEntry data; + protected final Map flagData; protected final Runnable onChange; protected final Map, Flag.Value> flagMap = new HashMap<>(); - private final class Property { - private final String field; - private final Function loader; - private final Function writer; + private SchematicNode testblockSchematic; - private T value; - - public Property(String field, Function loader, Function writer) { - this.field = field; - this.loader = loader; - this.writer = writer; - properties.add(this); - } - - public void load() { - if (flagData.containsKey(field)) { - value = loader.apply(flagData.getPlainValue(field)); - } else { - value = null; - } - } - - public T get() { - return value; - } - - public void set(T value) { - this.value = value; - if (value == null) { - flagData.remove(field); - } else { - flagData.put(field, writer.apply(value)); - } - } - } - - private Property testblockSchematic = new Property<>("testblockSchematic", SchematicNode::byId, SchematicNode::getId); - - protected RegionData(YAPIONObject data, Runnable onChange) { + protected RegionData(WorldData.RegionEntry data, Runnable onChange) { this.data = data; - this.flagData = data.getObjectOrSetDefault("flagStorage", new YAPIONObject()); + this.flagData = data.getFlags(); this.onChange = onChange; initialize(); for (final Flag flag : Flag.getFlags()) { if (!has(flag).isWritable()) continue; try { - String s = flagData.getPlainValue(flag.name()); + String s = flagData.get(flag.name()); flagMap.put(flag, flag.valueOfValue(s)); } catch (Exception e) { flagMap.put(flag, (Flag.Value) flag.getDefaultValue()); } } - properties.forEach(Property::load); + if (data.getTestblockSchematic() != null) { + testblockSchematic = SchematicNode.byId(data.getTestblockSchematic()); + } } protected void initialize() { @@ -126,7 +87,8 @@ public abstract class RegionData { flagData.remove(flag.name()); } } - properties.forEach(property -> property.set(null)); + testblockSchematic = null; + data.setTestblockSchematic(null); onChange.run(); } @@ -135,11 +97,12 @@ public abstract class RegionData { } public SchematicNode getTestblockSchematic() { - return testblockSchematic.get(); + return testblockSchematic; } public void setTestblockSchematic(SchematicNode schematic) { - testblockSchematic.set(schematic); + testblockSchematic = schematic; + data.setTestblockSchematic(schematic == null ? null : schematic.getId()); onChange.run(); } diff --git a/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/worlddata/SimulatorData.java b/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/worlddata/SimulatorData.java deleted file mode 100644 index 2c1a3573..00000000 --- a/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/worlddata/SimulatorData.java +++ /dev/null @@ -1,101 +0,0 @@ -/* - * This file is a part of the SteamWar software. - * - * Copyright (C) 2025 SteamWar.de-Serverteam - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero 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 Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see . - */ - -package de.steamwar.bausystem.worlddata; - -import de.steamwar.sql.SteamwarUser; -import lombok.SneakyThrows; -import lombok.experimental.UtilityClass; -import org.bukkit.Bukkit; -import org.bukkit.entity.Player; -import yapion.hierarchy.output.FileOutput; -import yapion.hierarchy.types.YAPIONArray; -import yapion.hierarchy.types.YAPIONObject; -import yapion.parser.InputStreamCharsets; -import yapion.parser.YAPIONParser; -import yapion.parser.options.FileOptions; - -import java.io.File; -import java.util.List; - -@UtilityClass -public class SimulatorData { - - private final File simulatorsDir = new File(Bukkit.getWorlds().get(0).getWorldFolder(), "simulators"); - - static { - simulatorsDir.mkdirs(); - } - - @SneakyThrows - public YAPIONObject getSimulator(Player player) { - File file = getFile(player); - if (!file.exists()) { - return new YAPIONObject(); - } - return YAPIONParser.parse(file, new FileOptions().charset(InputStreamCharsets.UTF_8)); - } - - @SneakyThrows - private void internalSaveSimulator(Player player, YAPIONObject yapionObject) { - File file = getFile(player); - yapionObject.toYAPION(new FileOutput(file)).close(); - } - - @SneakyThrows - void saveSimulator(SteamwarUser steamwarUser, YAPIONObject yapionObject) { - File file = new File(simulatorsDir, steamwarUser.getId() + ".yapion"); - yapionObject.toYAPION(new FileOutput(file)).close(); - } - - public List listSimulator(Player player) { - List strings = getSimulator(player).getKeys(); - strings.remove(""); - return strings; - } - - public void saveSimulator(Player player, String name) { - YAPIONObject yapionObject = getSimulator(player); - yapionObject.put(name, yapionObject.getArray("").copy()); - internalSaveSimulator(player, yapionObject); - } - - public void saveSimulator(Player player, YAPIONArray simulator) { - YAPIONObject yapionObject = getSimulator(player); - yapionObject.put("", simulator); - internalSaveSimulator(player, yapionObject); - } - - public void loadSimulator(Player player, String name) { - YAPIONObject yapionObject = getSimulator(player); - yapionObject.put("", yapionObject.getArray(name).copy()); - internalSaveSimulator(player, yapionObject); - } - - public void removeSimulator(Player player, String name) { - YAPIONObject yapionObject = getSimulator(player); - yapionObject.remove(name); - internalSaveSimulator(player, yapionObject); - } - - private File getFile(Player player) { - SteamwarUser steamwarUser = SteamwarUser.get(player.getUniqueId()); - return new File(simulatorsDir, steamwarUser.getId() + ".yapion"); - } -} diff --git a/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/worlddata/WorldData.java b/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/worlddata/WorldData.java index bd661172..d47f097a 100644 --- a/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/worlddata/WorldData.java +++ b/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/worlddata/WorldData.java @@ -1,7 +1,7 @@ /* * This file is a part of the SteamWar software. * - * Copyright (C) 2025 SteamWar.de-Serverteam + * Copyright (C) 2026 SteamWar.de-Serverteam * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as published by @@ -19,58 +19,83 @@ package de.steamwar.bausystem.worlddata; -import lombok.experimental.UtilityClass; +import com.google.gson.GsonBuilder; +import de.steamwar.bausystem.config.ConfigStore; +import de.steamwar.bausystem.config.VersionedConfig; +import de.steamwar.bausystem.worlddata.converter.WorldDataV1ToV2Converter; +import lombok.AccessLevel; +import lombok.Getter; +import lombok.Setter; import org.bukkit.Bukkit; -import yapion.hierarchy.output.FileOutput; -import yapion.hierarchy.types.YAPIONObject; -import yapion.parser.InputStreamCharsets; -import yapion.parser.YAPIONParser; -import yapion.parser.options.StreamOptions; -import java.io.BufferedInputStream; import java.io.File; -import java.io.FileInputStream; -import java.io.IOException; +import java.util.HashMap; +import java.util.Map; -@UtilityClass -public class WorldData { +/** + * The current world data. It is stored as JSON, fields missing in the + * stored JSON keep the default value of their initializer, so adding a new + * field does not need a new version. + *

+ * When a field gets removed, renamed or its meaning changes, copy this class + * to WorldDataV{@link #VERSION} (without the {@link #STORE}), increase {@link #VERSION}, write a + * {@link de.steamwar.bausystem.config.ConfigConverter} from the copy to this class and register it in the {@link #STORE}. + */ +@Getter +@Setter +public class WorldData implements VersionedConfig { - private final File optionsFile = new File(Bukkit.getWorlds().get(0).getWorldFolder(), "options.yapion"); - private YAPIONObject worldData; + public static final int VERSION = 2; - public YAPIONObject getWorldData() { - if (worldData == null) { - read(); - } - return worldData; + public static final ConfigStore STORE = new ConfigStore<>(WorldData.class, VERSION, WorldData::new) + .gson(new GsonBuilder().setPrettyPrinting().create()) + .converter(new WorldDataV1ToV2Converter()) + .file(new File(Bukkit.getWorlds().get(0).getWorldFolder(), "options.json")) + .legacyFile(new File(Bukkit.getWorlds().get(0).getWorldFolder(), "options.yapion"), WorldDataV1::parse); + + @Setter(AccessLevel.NONE) + protected int version = VERSION; + + /** + * Region name to the data of the region + */ + protected Map regions = new HashMap<>(); + protected RegionEntry globalRegion = new RegionEntry(); + + /** + * Warp name to warp + */ + protected Map warps = new HashMap<>(); + + @Getter + @Setter + public static class RegionEntry { + + /** + * Overrides the prototype of the region config, null if not overridden + */ + protected String prototype = null; + + /** + * Flag name to flag value name + */ + protected Map flags = new HashMap<>(); + + /** + * Id of the testblock SchematicNode, null if the default testblock is used + */ + protected Integer testblockSchematic = null; } - public YAPIONObject getRegionsData() { - return getWorldData().getYAPIONObjectOrSetDefault("regions", new YAPIONObject()); - } + @Getter + @Setter + public static class WarpEntry { - public YAPIONObject getWarpData() { - return getWorldData().getYAPIONObjectOrSetDefault("warps", new YAPIONObject()); - } - - private void read() { - worldData = new YAPIONObject(); - if (optionsFile.length() != 0) { - try (BufferedInputStream bufferedInputStream = new BufferedInputStream(new FileInputStream(optionsFile))) { - worldData = YAPIONParser.parse(bufferedInputStream, new StreamOptions().charset(InputStreamCharsets.UTF_8)); - } catch (IOException e) { - // Ignored - } - } else { - write(); - } - } - - public void write() { - try { - worldData.toYAPION(new FileOutput(optionsFile)).close(); - } catch (IOException e) { - e.printStackTrace(); - } + protected double x; + protected double y; + protected double z; + protected float yaw; + protected float pitch; + protected String material; } } diff --git a/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/worlddata/WorldDataV1.java b/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/worlddata/WorldDataV1.java new file mode 100644 index 00000000..b7549246 --- /dev/null +++ b/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/worlddata/WorldDataV1.java @@ -0,0 +1,111 @@ +/* + * This file is a part of the SteamWar software. + * + * Copyright (C) 2026 SteamWar.de-Serverteam + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package de.steamwar.bausystem.worlddata; + +import de.steamwar.bausystem.config.VersionedConfig; +import de.steamwar.bausystem.config.legacy.YapionReader; +import lombok.AccessLevel; +import lombok.Getter; +import lombok.Setter; + +import java.util.HashMap; +import java.util.Map; + +/** + * The legacy world data that was stored in the YAPION format in options.yapion. + */ +@Getter +@Setter +@Deprecated +public class WorldDataV1 implements VersionedConfig { + + public static final int VERSION = 1; + + @Setter(AccessLevel.NONE) + protected int version = VERSION; + + /** + * Region name to the data of the region, the global region is stored with the name "global" + */ + protected Map regions = new HashMap<>(); + + /** + * Warp name to warp + */ + protected Map warps = new HashMap<>(); + + @Getter + @Setter + public static class RegionEntry { + + protected String prototype = null; + + /** + * Flag name to flag value name and "testblockSchematic" to the SchematicNode id + */ + protected Map flagStorage = new HashMap<>(); + } + + @Getter + @Setter + public static class WarpEntry { + + protected double x; + protected double y; + protected double z; + protected float yaw; + protected float pitch; + protected String material; + } + + public static WorldDataV1 parse(String yapion) { + Map object = YapionReader.parse(yapion); + WorldDataV1 worldData = new WorldDataV1(); + + if (object.get("regions") instanceof Map regions) { + regions.forEach((name, value) -> { + if (!(value instanceof Map region)) return; + RegionEntry entry = new RegionEntry(); + if (region.get("prototype") instanceof String prototype) { + entry.prototype = prototype; + } + if (region.get("flagStorage") instanceof Map flagStorage) { + flagStorage.forEach((key, flag) -> entry.flagStorage.put((String) key, flag)); + } + worldData.regions.put((String) name, entry); + }); + } + + if (object.get("warps") instanceof Map warps) { + warps.forEach((name, value) -> { + if (!(value instanceof Map warp)) return; + WarpEntry entry = new WarpEntry(); + entry.x = ((Number) warp.get("x")).doubleValue(); + entry.y = ((Number) warp.get("y")).doubleValue(); + entry.z = ((Number) warp.get("z")).doubleValue(); + entry.yaw = ((Number) warp.get("yaw")).floatValue(); + entry.pitch = ((Number) warp.get("pitch")).floatValue(); + entry.material = (String) warp.get("material"); + worldData.warps.put((String) name, entry); + }); + } + return worldData; + } +} diff --git a/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/worlddata/converter/WorldDataV1ToV2Converter.java b/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/worlddata/converter/WorldDataV1ToV2Converter.java new file mode 100644 index 00000000..31bd3f85 --- /dev/null +++ b/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/worlddata/converter/WorldDataV1ToV2Converter.java @@ -0,0 +1,75 @@ +/* + * This file is a part of the SteamWar software. + * + * Copyright (C) 2026 SteamWar.de-Serverteam + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package de.steamwar.bausystem.worlddata.converter; + +import de.steamwar.bausystem.config.ConfigConverter; +import de.steamwar.bausystem.worlddata.WorldData; +import de.steamwar.bausystem.worlddata.WorldDataV1; + +@Deprecated +public class WorldDataV1ToV2Converter implements ConfigConverter { + + private static final String GLOBAL_REGION = "global"; + private static final String TESTBLOCK_SCHEMATIC = "testblockSchematic"; + + @Override + public int version() { + return WorldDataV1.VERSION; + } + + @Override + public WorldData convert(WorldDataV1 old) { + WorldData worldData = new WorldData(); + old.getRegions().forEach((name, region) -> { + if (name.equals(GLOBAL_REGION)) { + worldData.setGlobalRegion(convert(region)); + } else { + worldData.getRegions().put(name, convert(region)); + } + }); + old.getWarps().forEach((name, warp) -> worldData.getWarps().put(name, convert(warp))); + return worldData; + } + + private WorldData.RegionEntry convert(WorldDataV1.RegionEntry old) { + WorldData.RegionEntry region = new WorldData.RegionEntry(); + region.setPrototype(old.getPrototype()); + old.getFlagStorage().forEach((key, value) -> { + if (value == null) return; + if (key.equals(TESTBLOCK_SCHEMATIC)) { + region.setTestblockSchematic(((Number) value).intValue()); + } else { + region.getFlags().put(key, value.toString()); + } + }); + return region; + } + + private WorldData.WarpEntry convert(WorldDataV1.WarpEntry old) { + WorldData.WarpEntry warp = new WorldData.WarpEntry(); + warp.setX(old.getX()); + warp.setY(old.getY()); + warp.setZ(old.getZ()); + warp.setYaw(old.getYaw()); + warp.setPitch(old.getPitch()); + warp.setMaterial(old.getMaterial()); + return warp; + } +} diff --git a/BauSystem/BauSystem_RegionFixed/src/de/steamwar/bausystem/region/fixed/FixedGlobalRegionData.java b/BauSystem/BauSystem_RegionFixed/src/de/steamwar/bausystem/region/fixed/FixedGlobalRegionData.java index 065e2e20..5412adf7 100644 --- a/BauSystem/BauSystem_RegionFixed/src/de/steamwar/bausystem/region/fixed/FixedGlobalRegionData.java +++ b/BauSystem/BauSystem_RegionFixed/src/de/steamwar/bausystem/region/fixed/FixedGlobalRegionData.java @@ -25,12 +25,12 @@ import de.steamwar.bausystem.region.flags.ColorMode; import de.steamwar.bausystem.region.flags.Flag; import de.steamwar.bausystem.region.flags.ProtectMode; import de.steamwar.bausystem.region.flags.TNTMode; +import de.steamwar.bausystem.worlddata.WorldData; import lombok.NonNull; -import yapion.hierarchy.types.YAPIONObject; public class FixedGlobalRegionData extends RegionData { - public FixedGlobalRegionData(YAPIONObject data, Runnable onChange) { + public FixedGlobalRegionData(WorldData.RegionEntry data, Runnable onChange) { super(data, onChange); } diff --git a/BauSystem/BauSystem_RegionFixed/src/de/steamwar/bausystem/region/fixed/FixedRegion.java b/BauSystem/BauSystem_RegionFixed/src/de/steamwar/bausystem/region/fixed/FixedRegion.java index 4a87922d..913e1a1f 100644 --- a/BauSystem/BauSystem_RegionFixed/src/de/steamwar/bausystem/region/fixed/FixedRegion.java +++ b/BauSystem/BauSystem_RegionFixed/src/de/steamwar/bausystem/region/fixed/FixedRegion.java @@ -26,6 +26,7 @@ import de.steamwar.bausystem.region.flags.Flag; import de.steamwar.bausystem.region.flags.TestblockMode; import de.steamwar.bausystem.utils.FlatteningWrapper; import de.steamwar.bausystem.utils.PasteBuilder; +import de.steamwar.bausystem.worlddata.WorldData; import de.steamwar.core.Core; import de.steamwar.sql.GameModeConfig; import de.steamwar.sql.SchematicType; @@ -139,7 +140,7 @@ public class FixedRegion implements Region { } } - public FixedRegion(String name, FixedRegionData flagStorage, Prototype prototype, YAPIONObject regionConfig, YAPIONObject regionData) { + public FixedRegion(String name, FixedRegionData flagStorage, Prototype prototype, YAPIONObject regionConfig, WorldData.RegionEntry regionData) { this.name = name; uuid = UUID.nameUUIDFromBytes(name.getBytes(StandardCharsets.UTF_8)); this.flagStorage = flagStorage; diff --git a/BauSystem/BauSystem_RegionFixed/src/de/steamwar/bausystem/region/fixed/FixedRegionData.java b/BauSystem/BauSystem_RegionFixed/src/de/steamwar/bausystem/region/fixed/FixedRegionData.java index c2de7042..ffe7c327 100644 --- a/BauSystem/BauSystem_RegionFixed/src/de/steamwar/bausystem/region/fixed/FixedRegionData.java +++ b/BauSystem/BauSystem_RegionFixed/src/de/steamwar/bausystem/region/fixed/FixedRegionData.java @@ -22,12 +22,12 @@ package de.steamwar.bausystem.region.fixed; import de.steamwar.bausystem.region.RegionData; import de.steamwar.bausystem.region.RegionFlagPolicy; import de.steamwar.bausystem.region.flags.Flag; +import de.steamwar.bausystem.worlddata.WorldData; import lombok.NonNull; -import yapion.hierarchy.types.YAPIONObject; public class FixedRegionData extends RegionData { - public FixedRegionData(YAPIONObject data, Runnable onChange) { + public FixedRegionData(WorldData.RegionEntry data, Runnable onChange) { super(data, onChange); } diff --git a/BauSystem/BauSystem_RegionFixed/src/de/steamwar/bausystem/region/fixed/Prototype.java b/BauSystem/BauSystem_RegionFixed/src/de/steamwar/bausystem/region/fixed/Prototype.java index 04cf7ee0..0fe5f2b5 100644 --- a/BauSystem/BauSystem_RegionFixed/src/de/steamwar/bausystem/region/fixed/Prototype.java +++ b/BauSystem/BauSystem_RegionFixed/src/de/steamwar/bausystem/region/fixed/Prototype.java @@ -210,14 +210,14 @@ public class Prototype { } } - public static void generateRegion(String name, YAPIONObject regionConfig, YAPIONObject regionData) { + public static void generateRegion(String name, YAPIONObject regionConfig, WorldData.RegionEntry regionData) { Prototype prototype; - if (regionData.containsKey("prototype", String.class)) { - prototype = PROTOTYPE_MAP.get(regionData.getPlainValue("prototype")); + if (regionData.getPrototype() != null) { + prototype = PROTOTYPE_MAP.get(regionData.getPrototype()); } else { prototype = PROTOTYPE_MAP.get(regionConfig.getPlainValue("prototype")); } - FixedRegionData flagStorage = new FixedRegionData(regionData, WorldData::write); + FixedRegionData flagStorage = new FixedRegionData(regionData, WorldData.STORE::save); FixedRegionSystem.addRegion(new FixedRegion(name, flagStorage, prototype, regionConfig, regionData)); } } diff --git a/BauSystem/BauSystem_RegionFixed/src/de/steamwar/bausystem/region/fixed/loader/RegionLoader.java b/BauSystem/BauSystem_RegionFixed/src/de/steamwar/bausystem/region/fixed/loader/RegionLoader.java index 3bfe538d..d19699e4 100644 --- a/BauSystem/BauSystem_RegionFixed/src/de/steamwar/bausystem/region/fixed/loader/RegionLoader.java +++ b/BauSystem/BauSystem_RegionFixed/src/de/steamwar/bausystem/region/fixed/loader/RegionLoader.java @@ -28,7 +28,6 @@ import org.bukkit.Bukkit; import yapion.hierarchy.diff.DiffChange; import yapion.hierarchy.diff.YAPIONDiff; import yapion.hierarchy.types.YAPIONObject; -import yapion.hierarchy.types.YAPIONType; import yapion.parser.YAPIONParser; import java.io.BufferedInputStream; @@ -55,7 +54,7 @@ public class RegionLoader { } loaded = yapionObject; - YAPIONObject optionsYapionObject = WorldData.getRegionsData(); + WorldData worldData = WorldData.STORE.get(); yapionObject.forEach((key, yapionAnyType) -> { if (key.equals("global")) { return; @@ -65,21 +64,10 @@ public class RegionLoader { } YAPIONObject regionConfig = (YAPIONObject) yapionAnyType; - YAPIONObject regionData = new YAPIONObject(); - if (optionsYapionObject.containsKey(key, YAPIONType.OBJECT)) { - regionData = optionsYapionObject.getObject(key); - } else { - optionsYapionObject.add(key, regionData); - } - + WorldData.RegionEntry regionData = worldData.getRegions().computeIfAbsent(key, k -> new WorldData.RegionEntry()); Prototype.generateRegion(key, regionConfig, regionData); }); - YAPIONObject globalOptions = optionsYapionObject.getObject("global"); - if (globalOptions == null) { - globalOptions = new YAPIONObject(); - optionsYapionObject.add("global", globalOptions); - } - FixedGlobalRegion.setFLAG_STORAGE(new FixedGlobalRegionData(globalOptions, WorldData::write)); + FixedGlobalRegion.setFLAG_STORAGE(new FixedGlobalRegionData(worldData.getGlobalRegion(), WorldData.STORE::save)); } }