diff --git a/BauSystem/BauSystem_RegionDynamic/src/de/steamwar/bausystem/region/00000000-0000-0000-0000-000000000000/meta.json b/BauSystem/BauSystem_RegionDynamic/src/de/steamwar/bausystem/region/00000000-0000-0000-0000-000000000000/meta.json index 70c78272..04ea5b03 100644 --- a/BauSystem/BauSystem_RegionDynamic/src/de/steamwar/bausystem/region/00000000-0000-0000-0000-000000000000/meta.json +++ b/BauSystem/BauSystem_RegionDynamic/src/de/steamwar/bausystem/region/00000000-0000-0000-0000-000000000000/meta.json @@ -1,5 +1,6 @@ { "region_identifier": "SpawnRegion", - "tile_x": 0, - "tile_z": 0 + "tiles": [ + { "tile_x": 0, "tile_z": 0 } + ] } \ No newline at end of file diff --git a/BauSystem/BauSystem_RegionDynamic/src/de/steamwar/bausystem/region/DynamicRegionVisualizer.java b/BauSystem/BauSystem_RegionDynamic/src/de/steamwar/bausystem/region/DynamicRegionVisualizer.java index a34290a3..084b8d0c 100644 --- a/BauSystem/BauSystem_RegionDynamic/src/de/steamwar/bausystem/region/DynamicRegionVisualizer.java +++ b/BauSystem/BauSystem_RegionDynamic/src/de/steamwar/bausystem/region/DynamicRegionVisualizer.java @@ -306,7 +306,7 @@ public class DynamicRegionVisualizer implements SWPlayer.Component, Listener { } private void place() { - DynamicRegion dynamicRegion = DynamicRegionRepository.constructRegion(regionType, UUID.randomUUID(), sourceTile.getMinX(), sourceTile.getMinZ()); + DynamicRegion dynamicRegion = DynamicRegionRepository.constructRegion(regionType, sourceTile); if (dynamicRegion == null) { // TODO: Give error to user return; diff --git a/BauSystem/BauSystem_RegionDynamic/src/de/steamwar/bausystem/region/dynamic/DynamicRegion.java b/BauSystem/BauSystem_RegionDynamic/src/de/steamwar/bausystem/region/dynamic/DynamicRegion.java index 803f28cc..b5b2e916 100644 --- a/BauSystem/BauSystem_RegionDynamic/src/de/steamwar/bausystem/region/dynamic/DynamicRegion.java +++ b/BauSystem/BauSystem_RegionDynamic/src/de/steamwar/bausystem/region/dynamic/DynamicRegion.java @@ -19,6 +19,10 @@ package de.steamwar.bausystem.region.dynamic; +import com.google.gson.JsonArray; +import com.google.gson.JsonObject; +import com.google.gson.JsonPrimitive; +import com.google.gson.stream.JsonWriter; import de.steamwar.bausystem.region.DynamicRegionSystem; import de.steamwar.bausystem.region.Point; import de.steamwar.bausystem.region.Region; @@ -29,34 +33,121 @@ import lombok.Getter; import lombok.NonNull; import org.bukkit.Location; -import java.util.HashSet; -import java.util.List; -import java.util.Set; -import java.util.UUID; +import java.io.IOException; +import java.util.*; public abstract class DynamicRegion implements Region { protected final UUID id; - protected final int minX; - protected final int minZ; @Getter protected RegionData regionData = null; - protected DynamicRegion(UUID id, int minX, int minZ) { - this.id = id; - this.minX = minX; - this.minZ = minZ; + /** + * This Constructor should be used if a Region is placed newly onto the world! + * + * @param tile this parameter is never used but forces the implementor to have it as a parameter + */ + protected DynamicRegion(Tile tile) { + this.id = UUID.randomUUID(); } /** - * This method must be called from the super constructor in a way that everything is already initialized. + * This constructor is used for loading the Region from a file + * + * @param id + * @param tileData this parameter is never used but forces the implementor to have it as a parameter */ - protected final void finishInit() { - DynamicRegionSystem.INSTANCE.add(this); + protected DynamicRegion(UUID id, JsonArray tileData) { + this.id = id; + } + + private static Tile readTile(JsonObject tileData, String prefix) { + JsonPrimitive xData = tileData.getAsJsonPrimitive(prefix + "_x"); + JsonPrimitive zData = tileData.getAsJsonPrimitive(prefix + "_z"); + if (xData == null || zData == null) return null; + return Tile.fromTile(xData.getAsInt(), zData.getAsInt()) + .orElse(null); + } + + protected static Tile readTile(JsonArray tileData) { + if (tileData.size() != 1) return null; + try { + return readTile(tileData.get(0).getAsJsonObject(), "tile"); + } catch (IllegalStateException e) { + return null; + } + } + + protected static List> readQuantizedTiles(JsonArray tileData) { + List> list = new ArrayList<>(); + for (int i = 0; i < tileData.size(); i++) { + JsonObject tileObject; + try { + tileObject = tileData.get(i).getAsJsonObject(); + } catch (IllegalArgumentException e) { + return Collections.emptyList(); + } + + Tile tile = readTile(tileObject, "tile"); + if (tile != null) { + list.add(new Pair<>(tile, null)); + continue; + } + + Tile minTile = readTile(tileObject, "min"); + Tile maxTile = readTile(tileObject, "max"); + if (minTile == null || maxTile == null) { + return Collections.emptyList(); + } + list.add(new Pair<>(minTile, maxTile)); + } + return list; + } + + private static void writeTile(JsonWriter writer, Tile tile, String prefix) throws IOException { + writer.name(prefix + "_x"); + writer.value(tile.getTileX()); + writer.name(prefix + "_z"); + writer.value(tile.getTileZ()); + } + + protected static void writeTile(JsonWriter writer, Tile tile) throws IOException { + writer.beginObject(); + writeTile(writer, tile, "tile"); + writer.endObject(); + } + + protected static void writeQuantizedTiles(JsonWriter writer, List> list) throws IOException { + for (Pair pair : list) { + writer.beginObject(); + if (pair.getValue() == null) { + writeTile(writer, pair.getKey(), "tile"); + } else { + writeTile(writer, pair.getKey(), "min"); + writeTile(writer, pair.getValue(), "max"); + } + writer.endObject(); + } + } + + /** + * This method should be called when a Region is created and needs to be saved afterward + */ + protected final void finishCreate() { + finishLoad(); save(); } + /** + * This method should be called when a Region is loaded from file! + */ + protected final void finishLoad() { + DynamicRegionSystem.INSTANCE.add(this); + } + + public abstract void writeData(JsonWriter writer) throws IOException; + public final void updateNeighbours() { List> list = DynamicRegionSystem.INSTANCE.getNeighbours(this) .filter(data -> data.getKey() instanceof PathRegion) @@ -74,7 +165,7 @@ public abstract class DynamicRegion implements Region { // Updating world state for all neighbouring PathRegions list.forEach(data -> { if (needsFullReset.contains(data.getKey().getID())) { - data.getKey().getArea().reset(null,null, false); // TODO: Implement! + data.getKey().getArea().reset(null, null, false); // TODO: Implement! } else { data.getKey().update(this, data.getValue().opposite()); } 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 index eba78ae7..0777b263 100644 --- a/BauSystem/BauSystem_RegionDynamic/src/de/steamwar/bausystem/region/dynamic/DynamicRegionRepository.java +++ b/BauSystem/BauSystem_RegionDynamic/src/de/steamwar/bausystem/region/dynamic/DynamicRegionRepository.java @@ -19,10 +19,7 @@ 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.*; import com.google.gson.stream.JsonWriter; import de.steamwar.bausystem.region.*; import de.steamwar.bausystem.region.dynamic.path.PathRegion; @@ -31,7 +28,6 @@ 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; @@ -71,8 +67,7 @@ public class DynamicRegionRepository { 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 META_FILES_TILES = "tiles"; public static final String FLAG_FILE_NAME = "flags.json"; public static final String BACKUPS_DIR_NAME = "backups"; @@ -119,12 +114,8 @@ public class DynamicRegionRepository { 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)"); @@ -138,13 +129,12 @@ public class DynamicRegionRepository { continue; } - Tile tile = Tile.fromTile(tileX, tileZ).orElse(null); - if (tile == null) { + JsonArray tileData = metaData.getAsJsonArray(META_FILES_TILES); + if (tileData == null) { RegionSystem.LOGGER.log(Level.SEVERE, "Failed to read region metadata file (tile is no longer in bounds)"); continue; } - Location minTileLocation = tile.getMinLocation(); - constructRegion(regionClass, regionUUID, minTileLocation.getBlockX(), minTileLocation.getBlockZ()); + constructRegion(regionClass, regionUUID, tileData); } // Calculate Garden State for all PathRegions @@ -154,17 +144,35 @@ public class DynamicRegionRepository { }); } - public static DynamicRegion constructRegion(Class clazz, UUID uuid, int minX, int minZ) { + public static DynamicRegion constructRegion(Class clazz, Tile tile) { Constructor regionConstructor; try { - regionConstructor = clazz.getConstructor(UUID.class, int.class, int.class); + regionConstructor = clazz.getConstructor(Tile.class); + } catch (NoSuchMethodException e) { + RegionSystem.LOGGER.log(Level.SEVERE, "Failed to create region (region constructor not found)"); + return null; + } + + try { + return regionConstructor.newInstance(tile); + } catch (InstantiationException | IllegalAccessException | IllegalArgumentException | + InvocationTargetException e) { + RegionSystem.LOGGER.log(Level.SEVERE, "Failed to create region (invalid data)"); + return null; + } + } + + public static DynamicRegion constructRegion(Class clazz, UUID uuid, JsonArray tileData) { + Constructor regionConstructor; + try { + regionConstructor = clazz.getConstructor(UUID.class, JsonArray.class); } catch (NoSuchMethodException e) { RegionSystem.LOGGER.log(Level.SEVERE, "Failed to read region metadata file (region constructor not found)"); return null; } try { - return regionConstructor.newInstance(uuid, minX, minZ); + return regionConstructor.newInstance(uuid, tileData); } catch (InstantiationException | IllegalAccessException | IllegalArgumentException | InvocationTargetException e) { RegionSystem.LOGGER.log(Level.SEVERE, "Failed to read region metadata file (invalid data)"); @@ -236,17 +244,18 @@ public class DynamicRegionRepository { } public static void saveRegion(Region region) { + if (!(region.getType().isGlobal() || region instanceof DynamicRegion)) { + throw new IllegalArgumentException(); + } + File regionDirectory = new File(REGION_DATA_FOLDER, region.getID().toString()); if (!regionDirectory.exists()) { regionDirectory.mkdir(); } - if (!region.getType().isGlobal()) { + if (region instanceof DynamicRegion dynamicRegion) { RegionConstructorData constructorData = DynamicRegionSystem.constructorDataMap.get(region.getClass()); - Point point = region.getArea().getMinPoint(false); - Tile tile = Tile.fromPoint(point).get(); - - writeMetaFile(regionDirectory, constructorData, tile); + writeMetaFile(regionDirectory, constructorData, dynamicRegion); } writeFlagsFile(regionDirectory, region.getRegionData()); @@ -261,17 +270,17 @@ public class DynamicRegionRepository { } @SneakyThrows - private static void writeMetaFile(File regionDirectory, RegionConstructorData constructorData, Tile tile) { + private static void writeMetaFile(File regionDirectory, RegionConstructorData constructorData, DynamicRegion dynamicRegion) { @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.name(META_FILES_TILES); + jsonWriter.beginArray(); + dynamicRegion.writeData(jsonWriter); + jsonWriter.endArray(); jsonWriter.endObject(); } diff --git a/BauSystem/BauSystem_RegionDynamic/src/de/steamwar/bausystem/region/dynamic/MultiTileArea.java b/BauSystem/BauSystem_RegionDynamic/src/de/steamwar/bausystem/region/dynamic/MultiTileArea.java index df0f439c..40fe7763 100644 --- a/BauSystem/BauSystem_RegionDynamic/src/de/steamwar/bausystem/region/dynamic/MultiTileArea.java +++ b/BauSystem/BauSystem_RegionDynamic/src/de/steamwar/bausystem/region/dynamic/MultiTileArea.java @@ -29,31 +29,44 @@ import org.jetbrains.annotations.Nullable; import java.util.*; import java.util.function.BiConsumer; +import java.util.logging.Logger; public abstract class MultiTileArea implements Region.Area { + private final Logger LOGGER = Logger.getLogger(this.getClass().getTypeName()); + protected final Set tiles = new HashSet<>(); protected MultiTileArea() { } - protected MultiTileArea(Set initialTiles) { - tiles.addAll(initialTiles); + protected MultiTileArea(List> list) { + for (Pair pair : list) { + Tile from = pair.getKey(); + Tile to = pair.getValue(); + if (to == null) { + tiles.add(from); + continue; + } + + for (int x = from.getTileX(); x <= to.getTileX(); x++) { + for (int z = from.getTileZ(); z <= to.getTileZ(); z++) { + tiles.add(Tile.fromTile(x, z).orElseThrow()); + } + } + } } - public boolean addTile(Tile tile) { - boolean result = tiles.add(tile); - quantize(); - return result; + public void addTile(Tile tile) { + tiles.add(tile); } public boolean removeTile(Tile tile) { - boolean result = tiles.remove(tile); - quantize(); - return result; + tiles.remove(tile); + return tiles.isEmpty(); } - public void quantize() { + public List> quantize() { long time = System.currentTimeMillis(); Quantizer quantizer = new Quantizer(tiles); List> results = new ArrayList<>(); @@ -62,9 +75,9 @@ public abstract class MultiTileArea implements Region.Area { if (pair == null) break; results.add(pair); } - // [{"min_tile_x":0,"min_tile_z":0,"max_tile_x":0,"max_tile_z":0}] time = System.currentTimeMillis() - time; - System.out.println(tiles.size() + ": " + results.size() + " in " + time + "ms"); + LOGGER.info(tiles.size() + ": " + results.size() + " in " + time + "ms"); + return results; } @Override diff --git a/BauSystem/BauSystem_RegionDynamic/src/de/steamwar/bausystem/region/dynamic/path_special/PathArea.java b/BauSystem/BauSystem_RegionDynamic/src/de/steamwar/bausystem/region/dynamic/path_special/PathArea.java new file mode 100644 index 00000000..a260c370 --- /dev/null +++ b/BauSystem/BauSystem_RegionDynamic/src/de/steamwar/bausystem/region/dynamic/path_special/PathArea.java @@ -0,0 +1,203 @@ +/* + * 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.path_special; + +import de.steamwar.bausystem.BauSystem; +import de.steamwar.bausystem.region.DynamicRegionSystem; +import de.steamwar.bausystem.region.Point; +import de.steamwar.bausystem.region.dynamic.MultiTileArea; +import de.steamwar.bausystem.region.dynamic.PasteUtils; +import de.steamwar.bausystem.region.dynamic.Tile; +import de.steamwar.bausystem.region.dynamic.VariantSelector; +import de.steamwar.bausystem.region.dynamic.path.*; +import de.steamwar.bausystem.shared.Pair; +import de.steamwar.bausystem.utils.PasteBuilder; +import lombok.NonNull; +import org.bukkit.Bukkit; +import org.bukkit.Location; + +import java.io.File; +import java.util.List; + +import static de.steamwar.bausystem.region.RegionType.ConnectionType.*; + +public class PathArea extends MultiTileArea { + + private static final File PATH_DIR = new File(Bukkit.getWorlds().get(0).getWorldFolder(), "sections/path"); + private static final File FALLBACK_SCHEM = new File(PATH_DIR, "Fallback.schem"); + + private static final VariantSelector CENTER_NORMAL = VariantSelector.Get(new File(PATH_DIR, "center/normal")); + + private static final VariantSelector SIDE_GLOBAL = VariantSelector.Get(new File(PATH_DIR, "side/global")); + private static final VariantSelector CORNER_INNER_GLOBAL = VariantSelector.Get(new File(PATH_DIR, "cinner/global")); + private static final VariantSelector CORNER_OUTER_GLOBAL = VariantSelector.Get(new File(PATH_DIR, "couter/global")); + + private static final VariantSelector SIDE_WATER = VariantSelector.Get(new File(PATH_DIR, "side/water")); + private static final VariantSelector CORNER_INNER_WATER = VariantSelector.Get(new File(PATH_DIR, "cinner/water")); + private static final VariantSelector CORNER_OUTER_WATER = VariantSelector.Get(new File(PATH_DIR, "couter/water")); + + private static final VariantSelector SIDE_CLOSED = VariantSelector.Get(new File(PATH_DIR, "side/closed")); + private static final VariantSelector CORNER_INNER_CLOSED = VariantSelector.Get(new File(PATH_DIR, "cinner/closed")); + private static final VariantSelector CORNER_OUTER_CLOSED = VariantSelector.Get(new File(PATH_DIR, "couter/closed")); + + private static final VariantSelector GARDEN = VariantSelector.Get(new File(PATH_DIR, "garden")); + private static final VariantSelector SIDE_GARDEN = VariantSelector.Get(new File(PATH_DIR, "side/garden")); + private static final VariantSelector SIDE_GARDEN_CONNECTED = VariantSelector.Get(new File(PATH_DIR, "side/garden_connected")); + private static final VariantSelector CORNER_INNER_GARDEN = VariantSelector.Get(new File(PATH_DIR, "cinner/garden")); + private static final VariantSelector CORNER_OUTER_GARDEN = VariantSelector.Get(new File(PATH_DIR, "couter/garden")); + + private static final SelectorSide SELECTOR_SIDE = new SelectorSide() + .Case(Path, CENTER_NORMAL) + .Case(Global, SIDE_GLOBAL) + .Case(Water, SIDE_WATER) + .Case(Closed, SIDE_CLOSED) + .Case(Garden, SIDE_GARDEN_CONNECTED); + + private static final SelectorCorner SELECTOR_CORNER = new SelectorCorner() + // Path to Path + .Case(Path, Path, Path, CENTER_NORMAL, RotationCorrection.UsingOrdinal) + // Path to Global + .Case(Path, Global, Global, SIDE_GLOBAL, RotationCorrection.WithCorrection) + .Case(Global, Path, Path, SIDE_GLOBAL) + .Case(Global, Path, Global, SIDE_GLOBAL) + .Case(Path, Global, Path, SIDE_GLOBAL, RotationCorrection.WithCorrection) + .Case(Global, Global, Global, CORNER_OUTER_GLOBAL, RotationCorrection.UsingOrdinal) + .Case(Global, Global, Path, CORNER_OUTER_GLOBAL, RotationCorrection.UsingOrdinal) + .Case(Path, Path, Global, CORNER_INNER_GLOBAL, RotationCorrection.UsingOrdinal) + // Path to Water + .Case(Path, Water, Water, SIDE_WATER, RotationCorrection.WithCorrection) + .Case(Water, Path, Path, SIDE_WATER) + .Case(Water, Path, Water, SIDE_WATER) + .Case(Path, Water, Path, SIDE_WATER, RotationCorrection.WithCorrection) + .Case(Water, Water, Water, CORNER_OUTER_WATER, RotationCorrection.UsingOrdinal) + .Case(Water, Water, Path, CORNER_OUTER_WATER, RotationCorrection.UsingOrdinal) + .Case(Path, Path, Water, CORNER_INNER_WATER, RotationCorrection.UsingOrdinal) + // Path to Closed + .Case(Path, Closed, Closed, SIDE_CLOSED, RotationCorrection.WithCorrection) + .Case(Closed, Path, Path, SIDE_CLOSED) + .Case(Closed, Path, Closed, SIDE_CLOSED) + .Case(Path, Closed, Path, SIDE_CLOSED, RotationCorrection.WithCorrection) + .Case(Closed, Closed, Closed, CORNER_OUTER_CLOSED, RotationCorrection.UsingOrdinal) + .Case(Closed, Closed, Path, CORNER_OUTER_CLOSED, RotationCorrection.UsingOrdinal) + .Case(Path, Path, Closed, CORNER_INNER_CLOSED, RotationCorrection.UsingOrdinal) + // Path to Garden + .Case(Path, Garden, Garden, SIDE_GARDEN, RotationCorrection.WithCorrection) + .Case(Garden, Path, Path, SIDE_GARDEN) + .Case(Garden, Path, Garden, SIDE_GARDEN) + .Case(Path, Garden, Path, SIDE_GARDEN, RotationCorrection.WithCorrection) + .Case(Garden, Garden, Garden, CORNER_OUTER_GARDEN, RotationCorrection.UsingOrdinal) + .Case(Garden, Garden, Path, CORNER_OUTER_GARDEN, RotationCorrection.UsingOrdinal) + .Case(Path, Path, Garden, CORNER_INNER_GARDEN, RotationCorrection.UsingOrdinal) + ; + + public PathArea() { + } + + public PathArea(List> list) { + super(list); + } + + public void reset(@NonNull Tile tile, PathSide side) { + if (!tiles.contains(tile)) return; + File resetFile = null; + VariantSelector selector = SELECTOR_SIDE.Select(tile, side); + if (selector != null) resetFile = selector.select().orElse(null); + if (selector == null || resetFile == null) { + if (!BauSystem.DEV_SERVER) return; + resetFile = FALLBACK_SCHEM; + } + + Point minPoint = Point.fromLocation(tile.getMinLocation()); + PasteUtils.paste(resetFile, minPoint.add(side.pasteOffsetX, 0, side.pasteOffsetZ), side.rotate); + } + + public void reset(@NonNull Tile tile, PathCorner corner) { + if (!tiles.contains(tile)) return; + File resetFile = null; + Pair pair = SELECTOR_CORNER.Select(tile, corner); + VariantSelector selector = pair.getKey(); + RotationCorrection rotationCorrection = pair.getValue(); + if (selector != null) resetFile = selector.select().orElse(null); + if (selector == null || resetFile == null) { + if (!BauSystem.DEV_SERVER) return; + resetFile = FALLBACK_SCHEM; + rotationCorrection = RotationCorrection.Unchanged; + } + + int rotate = corner.rotate; + switch (rotationCorrection) { + case Unchanged: + break; + case UsingOrdinal: + rotate = corner.ordinal() * 90; + break; + case WithCorrection: + rotate += corner.rotateCorrection; + break; + } + + Point minPoint = Point.fromLocation(tile.getMinLocation()); + PasteUtils.paste(resetFile, minPoint.add(corner.pasteOffsetX, 0, corner.pasteOffsetZ), rotate); + } + + @Override + public void place(Location location, PasteBuilder pasteBuilder, boolean extension) { + Tile tile = Tile.fromLocation(location).orElse(null); + if (tile == null || !tiles.contains(tile)) return; + Point minPoint = Point.fromLocation(tile.getMinLocation()); + + if (isGarden(tile)) { + File resetFile = GARDEN.select().orElse(null); + if (resetFile != null) { + PasteUtils.paste(resetFile, minPoint, 0); + } + return; + } + + File resetFile = CENTER_NORMAL.select().orElse(null); + if (resetFile != null) { + PasteUtils.paste(resetFile, minPoint.add(7, 0, 7), 0); + } + + for (PathSide side : PathSide.values()) { + reset(tile, side); + } + + for (PathCorner corner : PathCorner.values()) { + reset(tile, corner); + } + } + + private boolean isGarden(Tile tile) { + for (int x = -1; x <= 1; x++) { + for (int z = -1; z <= 1; z++) { + if (x == 0 && z == 0) continue; + Tile t = tile.add(x, z).orElse(null); + if (t == null) { + return false; + } + if (!DynamicRegionSystem.INSTANCE.get(t).getType().isPath()) { + return false; + } + } + } + return true; + } +} diff --git a/BauSystem/BauSystem_RegionDynamic/src/de/steamwar/bausystem/region/dynamic/path_special/PathRegion.java b/BauSystem/BauSystem_RegionDynamic/src/de/steamwar/bausystem/region/dynamic/path_special/PathRegion.java new file mode 100644 index 00000000..6e3b45ad --- /dev/null +++ b/BauSystem/BauSystem_RegionDynamic/src/de/steamwar/bausystem/region/dynamic/path_special/PathRegion.java @@ -0,0 +1,137 @@ +/* + * 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.path_special; + +import com.google.gson.JsonArray; +import com.google.gson.stream.JsonWriter; +import de.steamwar.bausystem.region.*; +import de.steamwar.bausystem.region.dynamic.*; +import de.steamwar.bausystem.region.dynamic.path.PathCorner; +import de.steamwar.bausystem.region.dynamic.path.PathSide; +import de.steamwar.sql.GameModeConfig; +import lombok.NonNull; +import org.bukkit.Location; +import org.bukkit.Material; + +import java.io.IOException; +import java.util.UUID; + +public class PathRegion extends DynamicRegion { + + private final PathArea area; + + public PathRegion(Tile minTile, PathArea area) { + super(minTile); + this.area = new PathArea(); + regionData = new PathRegionData(this); + finishCreate(); + } + + public PathRegion(UUID id, JsonArray tileData) { + super(id, tileData); + area = new PathArea(readQuantizedTiles(tileData)); + regionData = new PathRegionData(this); + finishLoad(); + } + + @Override + public void writeData(JsonWriter writer) throws IOException { + writeQuantizedTiles(writer, area.quantize()); + } + + public void add(Tile tile) { + area.addTile(tile); + save(); + } + + public void update(Tile toUpdate, NeighbourDirection toDirection) { + for (PathSide side : toDirection.getSideUpdates()) { + area.reset(toUpdate, side); + } + for (PathCorner corner : toDirection.getCornerUpdates()) { + area.reset(toUpdate, corner); + } + } + + @Override + public @NonNull RegionType getType() { + return RegionType.PATH; + } + + @Override + public @NonNull Area getArea() { + return area; + } + + @Override + public @NonNull Area getBuildArea() { + return Area.EMPTY; + } + + @Override + public @NonNull Area getTestblockArea() { + return Area.EMPTY; + } + + @Override + public @NonNull GameModeConfig getGameModeConfig() { + return GameModeConfig.getDefaults(); + } + + @Override + public @NonNull RegionHistory getHistory() { + return RegionHistory.EMPTY; + } + + @Override + public @NonNull RegionBackups getBackups() { + return RegionBackups.EMPTY; + } + + @Override + public void save() { + DynamicRegionRepository.saveRegion(this); + } + + @Override + public void load(RegionData regionData) { + DynamicRegionRepository.loadRegionData(this, regionData); + } + + @Override + public void delete(Location location) { + Tile tile = Tile.fromLocation(location).orElse(null); + if (tile == null) return; + boolean delete = area.removeTile(tile); + if (delete) { + DynamicRegionSystem.INSTANCE.remove(this); + DynamicRegionRepository.deleteRegion(this); + } + + Point minPoint = Point.fromLocation(tile.getMinLocation()); + Point maxPoint = Point.fromLocation(tile.getMaxLocation()); + PasteUtils.reset(minPoint, maxPoint); + + this.updateNeighbours(); + if (!delete) { + save(); + } + } +} diff --git a/BauSystem/BauSystem_RegionDynamic/src/de/steamwar/bausystem/region/dynamic/path_special/PathRegionData.java b/BauSystem/BauSystem_RegionDynamic/src/de/steamwar/bausystem/region/dynamic/path_special/PathRegionData.java new file mode 100644 index 00000000..c173be0a --- /dev/null +++ b/BauSystem/BauSystem_RegionDynamic/src/de/steamwar/bausystem/region/dynamic/path_special/PathRegionData.java @@ -0,0 +1,57 @@ +/* + * 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.path_special; + +import de.steamwar.bausystem.region.RegionData; +import de.steamwar.bausystem.region.RegionFlagPolicy; +import de.steamwar.bausystem.region.flags.FireMode; +import de.steamwar.bausystem.region.flags.Flag; +import de.steamwar.bausystem.region.flags.ProtectMode; +import de.steamwar.bausystem.region.flags.TNTMode; +import de.steamwar.core.Core; +import lombok.NonNull; + +public class PathRegionData extends RegionData { + + public PathRegionData(PathRegion pathRegion) { + super(pathRegion); + } + + @Override + protected void initialize() { + flagMap.put(Flag.TNT, TNTMode.DENY); + flagMap.put(Flag.FIRE, FireMode.DENY); + flagMap.put(Flag.PROTECT, ProtectMode.INACTIVE); + } + + @Override + public @NonNull & Flag.Value> RegionFlagPolicy has(@NonNull Flag flag) { + if (flag.oneOf(Flag.TNT, Flag.FIRE, Flag.PROTECT)) { + return RegionFlagPolicy.READ_ONLY; + } + if (flag.oneOf(Flag.ITEMS) && Core.getVersion() >= 20) { + return RegionFlagPolicy.WRITABLE; + } + if (flag.oneOf(Flag.FREEZE)) { + return RegionFlagPolicy.WRITABLE; + } + return RegionFlagPolicy.NOT_APPLICABLE; + } +}