Add DynamicRegionRepository#loadRegions

Add DynamicRegionRepository#loadRegionData
Add DynamicRegionRepository#saveRegion
Add DynamicRegionRepository#deleteRegion
This commit is contained in:
2026-03-01 15:04:35 +01:00
parent 327ea9351a
commit 5795476e23
7 changed files with 350 additions and 125 deletions
@@ -1,4 +1,9 @@
{
"TNT": "DENY",
"FREEZE": "INACTIVE"
"flags": {
"TNT": "DENY",
"FREEZE": "INACTIVE"
},
"properties": {
"testblockSchematic": 0
}
}
@@ -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<Long, Region> 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<Class<? extends DynamicRegion>, RegionConstructorData> constructorDataMap = new HashMap<>();
private static Map<String, Class<? extends DynamicRegion>> identifierDataMap = new HashMap<>();
public static Map<Class<? extends DynamicRegion>, RegionConstructorData> constructorDataMap = new HashMap<>();
public static Map<String, Class<? extends DynamicRegion>> 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<? extends DynamicRegion> 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<Tile> 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<? extends DynamicRegion> 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<? extends RegionData> regionDataClass = constructorData.regionDataClass();
Constructor<? extends RegionData> 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<? extends RegionData>) 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
@@ -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 <https://www.gnu.org/licenses/>.
*/
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<? extends DynamicRegion> 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<Tile> 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<? extends DynamicRegion> 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<? extends RegionData> regionDataClass = constructorData.regionDataClass();
Constructor<? extends RegionData> 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<? extends RegionData>) 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;
}
});
}
}
@@ -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<Tile> fromPoint(Point point) {
return fromXZ(point.getX(), point.getZ());
}
public static Optional<Tile> fromXZ(int x, int z) {
x = (int) Math.floor((x + tileOffset) / (double) tileSize);
z = (int) Math.floor((z + tileOffset) / (double) tileSize);
@@ -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);
}
}
@@ -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 {