diff --git a/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/region/RegionBackups.java b/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/region/RegionBackups.java index f8798971..24dc1143 100644 --- a/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/region/RegionBackups.java +++ b/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/region/RegionBackups.java @@ -49,7 +49,7 @@ public interface RegionBackups { private final String name; @NonNull - private final RegionData data; + private final RegionData regionData; @CheckReturnValue public abstract boolean load(); diff --git a/BauSystem/BauSystem_RegionDynamic/src/de/steamwar/bausystem/region/00000000-0000-0000-0000-000000000000/flags.json b/BauSystem/BauSystem_RegionDynamic/src/de/steamwar/bausystem/region/00000000-0000-0000-0000-000000000000/flags.json index 38df8ecc..8b031f3a 100644 --- a/BauSystem/BauSystem_RegionDynamic/src/de/steamwar/bausystem/region/00000000-0000-0000-0000-000000000000/flags.json +++ b/BauSystem/BauSystem_RegionDynamic/src/de/steamwar/bausystem/region/00000000-0000-0000-0000-000000000000/flags.json @@ -1,4 +1,9 @@ { - "TNT": "DENY", - "FREEZE": "INACTIVE" + "flags": { + "TNT": "DENY", + "FREEZE": "INACTIVE" + }, + "properties": { + "testblockSchematic": 0 + } } \ No newline at end of file diff --git a/BauSystem/BauSystem_RegionDynamic/src/de/steamwar/bausystem/region/DynamicRegionSystem.java b/BauSystem/BauSystem_RegionDynamic/src/de/steamwar/bausystem/region/DynamicRegionSystem.java index 3602da91..b38f73d1 100644 --- a/BauSystem/BauSystem_RegionDynamic/src/de/steamwar/bausystem/region/DynamicRegionSystem.java +++ b/BauSystem/BauSystem_RegionDynamic/src/de/steamwar/bausystem/region/DynamicRegionSystem.java @@ -19,38 +19,23 @@ package de.steamwar.bausystem.region; -import com.google.gson.JsonIOException; -import com.google.gson.JsonObject; -import com.google.gson.JsonParser; -import com.google.gson.JsonSyntaxException; import de.steamwar.bausystem.BauSystem; import de.steamwar.bausystem.region.dynamic.DynamicRegion; +import de.steamwar.bausystem.region.dynamic.DynamicRegionRepository; import de.steamwar.bausystem.region.dynamic.RegionConstructorData; import de.steamwar.bausystem.region.dynamic.Tile; import de.steamwar.bausystem.region.dynamic.global.GlobalRegion; import lombok.NonNull; -import org.bukkit.Bukkit; import org.bukkit.Location; import java.io.*; -import java.lang.reflect.Constructor; -import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Parameter; import java.util.*; import java.util.function.Function; -import java.util.logging.Level; import java.util.stream.Collectors; import java.util.stream.Stream; public class DynamicRegionSystem implements RegionSystem { - public static final File REGION_DATA_FOLDER = new File(Bukkit.getWorlds().get(0).getWorldFolder(), "steamwar_regions"); - public static final String META_FILE_NAME = "meta.json"; - - static { - REGION_DATA_FOLDER.mkdirs(); - } - public static DynamicRegionSystem INSTANCE; private static final Map regionCache = new LinkedHashMap<>(16, 0.75f, true) { @@ -74,8 +59,8 @@ public class DynamicRegionSystem implements RegionSystem { regionTypeMap.getOrDefault(region.getType(), Collections.emptySet()).remove(region); } - private static Map, RegionConstructorData> constructorDataMap = new HashMap<>(); - private static Map> identifierDataMap = new HashMap<>(); + public static Map, RegionConstructorData> constructorDataMap = new HashMap<>(); + public static Map> identifierDataMap = new HashMap<>(); @Override public void load() { @@ -98,107 +83,7 @@ public class DynamicRegionSystem implements RegionSystem { .stream() .collect(Collectors.toUnmodifiableMap(entry -> entry.getValue().identifier(), Map.Entry::getKey)); - // Loading all saved regions from the files - File[] regions = REGION_DATA_FOLDER.listFiles(); - for (File region : regions) { - UUID regionUUID; - try { - regionUUID = UUID.fromString(region.getName()); - } catch (IllegalArgumentException e) { - LOGGER.log(Level.WARNING, "Failed to resolve region id: " + region.getName()); - continue; - } - if (regionUUID == GLOBAL_REGION_ID) { - continue; - } - - File metaFile = new File(region, META_FILE_NAME); - if (!metaFile.exists() || !metaFile.isFile()) { - LOGGER.log(Level.WARNING, "Failed to resolve " + region.getName()); - continue; - } - - JsonObject metaData; - try { - metaData = JsonParser.parseReader(new FileReader(metaFile)).getAsJsonObject(); - } catch (JsonIOException e) { - LOGGER.log(Level.SEVERE, "Failed to read region metadata file (unknown)"); - continue; - } catch (JsonSyntaxException | IllegalStateException e) { - LOGGER.log(Level.SEVERE, "Failed to read region metadata file (invalid json)"); - continue; - } catch (FileNotFoundException e) { - LOGGER.log(Level.SEVERE, "Failed to read region metadata file (not found)"); - continue; - } - - int tileX; - int tileZ; - String identifier; - try { - tileX = metaData.getAsJsonPrimitive("tile_x").getAsInt(); - tileZ = metaData.getAsJsonPrimitive("tile_z").getAsInt(); - identifier = metaData.getAsJsonPrimitive("region_identifier").getAsString(); - } catch (ClassCastException | NumberFormatException e) { - LOGGER.log(Level.SEVERE, "Failed to read region metadata file (invalid json)"); - continue; - } - - Class regionClass = identifierDataMap.get(identifier); - if (regionClass == null) { - LOGGER.log(Level.SEVERE, "Failed to read region metadata file (region no longer exists)"); - continue; - } - RegionConstructorData constructorData = constructorDataMap.get(regionClass); - - Optional optionalTile = Tile.fromTile(tileX, tileZ); - if (optionalTile.isEmpty()) { - LOGGER.log(Level.SEVERE, "Failed to read region metadata file (tile is no longer in bounds)"); - continue; - } - Tile tile = optionalTile.get(); - Location minTileLocation = tile.getMinLocation(); - - Constructor regionConstructor; - try { - regionConstructor = regionClass.getConstructor(UUID.class, int.class, int.class); - } catch (NoSuchMethodException e) { - LOGGER.log(Level.SEVERE, "Failed to read region metadata file (region constructor not found)"); - continue; - } - - Class regionDataClass = constructorData.regionDataClass(); - Constructor regionDataConstructor = null; - for (Constructor constructor : regionDataClass.getConstructors()) { - if (constructor.getParameters().length != 1) continue; - Parameter parameter = constructor.getParameters()[0]; - if (parameter.getType().isAssignableFrom(regionClass)) { - regionDataConstructor = (Constructor) constructor; - break; - } - } - if (regionDataConstructor == null) { - LOGGER.log(Level.SEVERE, "Failed to read region metadata file (region data constructor not found)"); - continue; - } - - DynamicRegion dynamicRegion; - try { - dynamicRegion = regionConstructor.newInstance(regionUUID, minTileLocation.getBlockX(), minTileLocation.getBlockZ()); - } catch (InstantiationException | IllegalAccessException | IllegalArgumentException | InvocationTargetException e) { - LOGGER.log(Level.SEVERE, "Failed to read region metadata file (invalid data)"); - continue; - } - - RegionData regionData; - try { - regionData = regionDataConstructor.newInstance(dynamicRegion); - } catch (InstantiationException | IllegalAccessException | IllegalArgumentException | InvocationTargetException e) { - LOGGER.log(Level.SEVERE, "Failed to instantiate region data (invalid data)"); - continue; - } - dynamicRegion.setRegionData(regionData); - } + DynamicRegionRepository.loadRegions(); } @Override diff --git a/BauSystem/BauSystem_RegionDynamic/src/de/steamwar/bausystem/region/dynamic/DynamicRegionRepository.java b/BauSystem/BauSystem_RegionDynamic/src/de/steamwar/bausystem/region/dynamic/DynamicRegionRepository.java new file mode 100644 index 00000000..f6df631a --- /dev/null +++ b/BauSystem/BauSystem_RegionDynamic/src/de/steamwar/bausystem/region/dynamic/DynamicRegionRepository.java @@ -0,0 +1,328 @@ +/* + * 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.region.dynamic; + +import com.google.gson.JsonIOException; +import com.google.gson.JsonObject; +import com.google.gson.JsonParser; +import com.google.gson.JsonSyntaxException; +import com.google.gson.stream.JsonWriter; +import de.steamwar.bausystem.region.*; +import de.steamwar.bausystem.region.flags.Flag; +import lombok.Cleanup; +import lombok.SneakyThrows; +import lombok.experimental.UtilityClass; +import org.bukkit.Bukkit; +import org.bukkit.Location; + +import java.io.*; +import java.lang.reflect.Constructor; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Parameter; +import java.nio.file.FileVisitResult; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.SimpleFileVisitor; +import java.nio.file.attribute.BasicFileAttributes; +import java.util.Optional; +import java.util.UUID; +import java.util.logging.Level; + +@UtilityClass +public class DynamicRegionRepository { + + // Example regions: + // steamwar_regions/ + // \- 00000000-0000-0000-0000-000000000000/ + // | \- flags.json + // \- 9494bbf6-b22c-4050-b62b-4be0594ed8ba/ + // \- meta.json + // \- flags.json + // \- backups/ + // \- MANUAL/ + // | \- 2026.03.01 14:40:00/ + // | \- flags.json + // | \- backup.schem + // \- AUTOMATIC/ + // \- 2026.02.30 12:00:00/ + // | \- flags.json + // | \- backup.schem + // \- 2026.02.28 19:39:00/ + // \- flags.json + // \- backup.schem + + public static final File REGION_DATA_FOLDER = new File(Bukkit.getWorlds().get(0).getWorldFolder(), "steamwar_regions"); + + public static final String META_FILE_NAME = "meta.json"; + public static final String META_FILE_REGION_IDENTIFIER = "region_identifier"; + public static final String META_FILE_TILE_X = "tile_x"; + public static final String META_FILE_TILE_Z = "tile_z"; + + public static final String FLAG_FILE_NAME = "flags.json"; + public static final String BACKUPS_DIR_NAME = "backups"; + public static final String BACKUP_FILE_NAME = "backup.schem"; + public static final String FLAGS_KEY = "flags"; + public static final String PROPERTIES_KEY = "properties"; + + static { + REGION_DATA_FOLDER.mkdirs(); + } + + public static void loadRegions() { + // Loading all saved regions from the files + File[] regions = REGION_DATA_FOLDER.listFiles(); + for (File region : regions) { + UUID regionUUID; + try { + regionUUID = UUID.fromString(region.getName()); + } catch (IllegalArgumentException e) { + RegionSystem.LOGGER.log(Level.WARNING, "Failed to resolve region id: " + region.getName()); + continue; + } + if (regionUUID == RegionSystem.GLOBAL_REGION_ID) { + continue; + } + + File metaFile = new File(region, META_FILE_NAME); + if (!metaFile.exists() || !metaFile.isFile()) { + RegionSystem.LOGGER.log(Level.WARNING, "Failed to resolve " + region.getName()); + continue; + } + + JsonObject metaData; + try { + metaData = JsonParser.parseReader(new FileReader(metaFile)).getAsJsonObject(); + } catch (JsonIOException e) { + RegionSystem.LOGGER.log(Level.SEVERE, "Failed to read region metadata file (unknown)"); + continue; + } catch (JsonSyntaxException | IllegalStateException e) { + RegionSystem.LOGGER.log(Level.SEVERE, "Failed to read region metadata file (invalid json)"); + continue; + } catch (FileNotFoundException e) { + RegionSystem.LOGGER.log(Level.SEVERE, "Failed to read region metadata file (not found)"); + continue; + } + + int tileX; + int tileZ; + String identifier; + try { + tileX = metaData.getAsJsonPrimitive(META_FILE_TILE_X).getAsInt(); + tileZ = metaData.getAsJsonPrimitive(META_FILE_TILE_Z).getAsInt(); + identifier = metaData.getAsJsonPrimitive(META_FILE_REGION_IDENTIFIER).getAsString(); + } catch (ClassCastException | NumberFormatException e) { + RegionSystem.LOGGER.log(Level.SEVERE, "Failed to read region metadata file (invalid json)"); + continue; + } + + // TODO: Maybe add static method to DynamicRegionSystem + Class regionClass = DynamicRegionSystem.identifierDataMap.get(identifier); + if (regionClass == null) { + RegionSystem.LOGGER.log(Level.SEVERE, "Failed to read region metadata file (region no longer exists)"); + continue; + } + RegionConstructorData constructorData = DynamicRegionSystem.constructorDataMap.get(regionClass); + + Optional optionalTile = Tile.fromTile(tileX, tileZ); + if (optionalTile.isEmpty()) { + RegionSystem.LOGGER.log(Level.SEVERE, "Failed to read region metadata file (tile is no longer in bounds)"); + continue; + } + Tile tile = optionalTile.get(); + Location minTileLocation = tile.getMinLocation(); + + Constructor regionConstructor; + try { + regionConstructor = regionClass.getConstructor(UUID.class, int.class, int.class); + } catch (NoSuchMethodException e) { + RegionSystem.LOGGER.log(Level.SEVERE, "Failed to read region metadata file (region constructor not found)"); + continue; + } + + Class regionDataClass = constructorData.regionDataClass(); + Constructor regionDataConstructor = null; + for (Constructor constructor : regionDataClass.getConstructors()) { + if (constructor.getParameters().length != 1) continue; + Parameter parameter = constructor.getParameters()[0]; + if (parameter.getType().isAssignableFrom(regionClass)) { + regionDataConstructor = (Constructor) constructor; + break; + } + } + if (regionDataConstructor == null) { + RegionSystem.LOGGER.log(Level.SEVERE, "Failed to read region metadata file (region data constructor not found)"); + continue; + } + + DynamicRegion dynamicRegion; + try { + dynamicRegion = regionConstructor.newInstance(regionUUID, minTileLocation.getBlockX(), minTileLocation.getBlockZ()); + } catch (InstantiationException | IllegalAccessException | IllegalArgumentException | + InvocationTargetException e) { + RegionSystem.LOGGER.log(Level.SEVERE, "Failed to read region metadata file (invalid data)"); + continue; + } + + RegionData regionData; + try { + regionData = regionDataConstructor.newInstance(dynamicRegion); + } catch (InstantiationException | IllegalAccessException | IllegalArgumentException | InvocationTargetException e) { + RegionSystem.LOGGER.log(Level.SEVERE, "Failed to instantiate region data (invalid data)"); + continue; + } + dynamicRegion.setRegionData(regionData); + } + } + + public static void loadRegionData(Region region) { + File regionDirectory = new File(REGION_DATA_FOLDER, region.getID().toString()); + if (!regionDirectory.exists()) return; + loadRegionData(regionDirectory, region.getRegionData()); + } + + public static void loadRegionData(Region region, RegionBackups.Backup backup) { + File regionDirectory = new File(REGION_DATA_FOLDER, region.getID().toString()); + if (!regionDirectory.exists()) return; + File backupsDirectory = new File(regionDirectory, BACKUPS_DIR_NAME); + if (!backupsDirectory.exists()) return; + File backupsTypeDirectory = new File(backupsDirectory, backup.getType().name()); + if (!backupsTypeDirectory.exists()) return; + File backupDirectory = new File(backupsTypeDirectory, backup.getName()); + if (!backupDirectory.exists()) return; + loadRegionData(backupDirectory, backup.getRegionData()); + } + + private static void loadRegionData(File regionDirectory, RegionData regionData) { + JsonObject flagsData; + try { + flagsData = JsonParser.parseReader(new FileReader(new File(regionDirectory, FLAG_FILE_NAME))).getAsJsonObject(); + } catch (JsonIOException e) { + RegionSystem.LOGGER.log(Level.SEVERE, "Failed to read region metadata file (unknown)"); + return; + } catch (JsonSyntaxException | IllegalStateException e) { + RegionSystem.LOGGER.log(Level.SEVERE, "Failed to read region metadata file (invalid json)"); + return; + } catch (FileNotFoundException e) { + RegionSystem.LOGGER.log(Level.SEVERE, "Failed to read region metadata file (not found)"); + return; + } + + JsonObject flags = flagsData.getAsJsonObject(FLAGS_KEY); + for (String key : flags.keySet()) { + Flag flag; + try { + flag = Flag.valueOf(key); + } catch (IllegalArgumentException e) { + continue; + } + String value = flags.getAsJsonPrimitive(key).getAsString(); + Flag.Value flagValue; + try { + flagValue = flag.valueOfValue(value); + } catch (IllegalArgumentException e) { + continue; + } + regionData.getBackedMap().put(flag, flagValue); + } + + JsonObject properties = flagsData.getAsJsonObject(PROPERTIES_KEY); + // TODO: Implement! + } + + public static void saveRegion(Region region) { + File regionDirectory = new File(REGION_DATA_FOLDER, region.getID().toString()); + if (!regionDirectory.exists()) { + regionDirectory.mkdir(); + } + + if (!region.getType().isGlobal()) { + RegionConstructorData constructorData = DynamicRegionSystem.constructorDataMap.get(region.getClass()); + Point point = region.getArea().getMinPoint(false); + Tile tile = Tile.fromPoint(point).get(); + + writeMetaFile(regionDirectory, constructorData, tile); + } + + writeFlagsFile(regionDirectory, region.getRegionData()); + // TODO: Write backups -> Directly on create! + } + + @SneakyThrows + private static void writeMetaFile(File regionDirectory, RegionConstructorData constructorData, Tile tile) { + @Cleanup + JsonWriter jsonWriter = new JsonWriter(new FileWriter(new File(regionDirectory, META_FILE_NAME))); + jsonWriter.setIndent(" "); + jsonWriter.beginObject(); + jsonWriter.name(META_FILE_REGION_IDENTIFIER); + jsonWriter.value(constructorData.identifier()); + jsonWriter.name(META_FILE_TILE_X); + jsonWriter.value(tile.getTileX()); + jsonWriter.name(META_FILE_TILE_Z); + jsonWriter.value(tile.getTileZ()); + jsonWriter.endObject(); + } + + @SneakyThrows + private static void writeFlagsFile(File regionDirectory, RegionData regionData) { + @Cleanup + JsonWriter jsonWriter = new JsonWriter(new FileWriter(new File(regionDirectory, FLAG_FILE_NAME))); + jsonWriter.setIndent(" "); + jsonWriter.beginObject(); + { + jsonWriter.name(FLAGS_KEY); + jsonWriter.beginObject(); + for (Flag flag : Flag.getFlags()) { + if (!regionData.has(flag).isApplicable()) continue; + jsonWriter.name(flag.name()); + jsonWriter.value(regionData.get(flag).nameWithDefault()); + } + jsonWriter.endObject(); + } + { + jsonWriter.name(PROPERTIES_KEY); + jsonWriter.beginObject(); + // TODO: Write out needed properties! + jsonWriter.endObject(); + } + jsonWriter.endObject(); + } + + public static void deleteRegion(Region region) { + File regionDirectory = new File(REGION_DATA_FOLDER, region.getID().toString()); + deleteDir(regionDirectory); + } + + @SneakyThrows + private static void deleteDir(File file) { + Files.walkFileTree(file.toPath(), new SimpleFileVisitor<>() { + @Override + public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException { + Files.delete(file); + return FileVisitResult.CONTINUE; + } + + @Override + public FileVisitResult postVisitDirectory(Path dir, IOException exc) throws IOException { + Files.delete(dir); + return FileVisitResult.CONTINUE; + } + }); + } +} diff --git a/BauSystem/BauSystem_RegionDynamic/src/de/steamwar/bausystem/region/dynamic/Tile.java b/BauSystem/BauSystem_RegionDynamic/src/de/steamwar/bausystem/region/dynamic/Tile.java index cca15ab5..a9f26176 100644 --- a/BauSystem/BauSystem_RegionDynamic/src/de/steamwar/bausystem/region/dynamic/Tile.java +++ b/BauSystem/BauSystem_RegionDynamic/src/de/steamwar/bausystem/region/dynamic/Tile.java @@ -19,10 +19,13 @@ package de.steamwar.bausystem.region.dynamic; +import de.steamwar.bausystem.region.Point; +import lombok.Getter; import org.bukkit.Location; import java.util.Optional; +@Getter public class Tile { public static final int tileSize = 19; @@ -49,6 +52,10 @@ public class Tile { return fromXZ(location.getBlockX(), location.getBlockZ()); } + public static Optional fromPoint(Point point) { + return fromXZ(point.getX(), point.getZ()); + } + public static Optional fromXZ(int x, int z) { x = (int) Math.floor((x + tileOffset) / (double) tileSize); z = (int) Math.floor((z + tileOffset) / (double) tileSize); diff --git a/BauSystem/BauSystem_RegionDynamic/src/de/steamwar/bausystem/region/dynamic/global/GlobalRegion.java b/BauSystem/BauSystem_RegionDynamic/src/de/steamwar/bausystem/region/dynamic/global/GlobalRegion.java index 6aaf45f1..5f281176 100644 --- a/BauSystem/BauSystem_RegionDynamic/src/de/steamwar/bausystem/region/dynamic/global/GlobalRegion.java +++ b/BauSystem/BauSystem_RegionDynamic/src/de/steamwar/bausystem/region/dynamic/global/GlobalRegion.java @@ -21,6 +21,7 @@ package de.steamwar.bausystem.region.dynamic.global; import com.sk89q.worldedit.extent.clipboard.Clipboard; import de.steamwar.bausystem.region.*; +import de.steamwar.bausystem.region.dynamic.DynamicRegionRepository; import de.steamwar.bausystem.utils.PasteBuilder; import de.steamwar.sql.GameModeConfig; import lombok.NonNull; @@ -135,11 +136,11 @@ public class GlobalRegion implements Region { @Override public void saveRegion() { - + DynamicRegionRepository.saveRegion(this); } @Override public void loadRegion() { - + DynamicRegionRepository.loadRegionData(this); } } diff --git a/BauSystem/BauSystem_RegionDynamic/src/de/steamwar/bausystem/region/dynamic/global/GlobalRegionData.java b/BauSystem/BauSystem_RegionDynamic/src/de/steamwar/bausystem/region/dynamic/global/GlobalRegionData.java index 12322639..be9b957b 100644 --- a/BauSystem/BauSystem_RegionDynamic/src/de/steamwar/bausystem/region/dynamic/global/GlobalRegionData.java +++ b/BauSystem/BauSystem_RegionDynamic/src/de/steamwar/bausystem/region/dynamic/global/GlobalRegionData.java @@ -27,7 +27,6 @@ import de.steamwar.bausystem.region.flags.ProtectMode; import de.steamwar.bausystem.region.flags.TNTMode; import de.steamwar.core.Core; import lombok.NonNull; -import yapion.hierarchy.types.YAPIONObject; public class GlobalRegionData extends RegionData {