diff --git a/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/region/Region.java b/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/region/Region.java index bd44975d..52acdac4 100644 --- a/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/region/Region.java +++ b/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/region/Region.java @@ -24,6 +24,7 @@ import de.steamwar.bausystem.utils.FlatteningWrapper; import de.steamwar.bausystem.utils.PasteBuilder; import de.steamwar.sql.GameModeConfig; import lombok.NonNull; +import org.bukkit.Bukkit; import org.bukkit.Location; import org.bukkit.Material; @@ -82,6 +83,9 @@ public interface Region extends RegionDataStore { interface Area { + int WORLD_MIN_Y = Bukkit.getWorlds().get(0).getMinHeight(); + int WORLD_MAX_Y = Bukkit.getWorlds().get(0).getMaxHeight(); + Area EMPTY = new Area() { @Override public boolean isEmpty() { 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 3e0ff06c..285d11b5 100644 --- a/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/region/RegionData.java +++ b/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/region/RegionData.java @@ -41,7 +41,7 @@ public abstract class RegionData { protected RegionData(RegionDataStore store) { this.store = store; initialize(); - store.loadRegion(); + store.loadRegion(this); } public final void setStore(RegionDataStore store) { diff --git a/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/region/RegionDataStore.java b/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/region/RegionDataStore.java index 4784ad72..3523f193 100644 --- a/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/region/RegionDataStore.java +++ b/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/region/RegionDataStore.java @@ -21,7 +21,7 @@ package de.steamwar.bausystem.region; public interface RegionDataStore { void saveRegion(); - void loadRegion(); + void loadRegion(RegionData regionData); default void deleteRegion() { } } diff --git a/BauSystem/BauSystem_RegionDynamic/build.gradle.kts b/BauSystem/BauSystem_RegionDynamic/build.gradle.kts index b4cccb47..d94d9de5 100644 --- a/BauSystem/BauSystem_RegionDynamic/build.gradle.kts +++ b/BauSystem/BauSystem_RegionDynamic/build.gradle.kts @@ -32,6 +32,7 @@ java { dependencies { compileOnly(libs.classindex) + annotationProcessor(libs.classindex) compileOnly(project(":BauSystem:BauSystem_Main", "default")) compileOnly(project(":SpigotCore", "default")) diff --git a/BauSystem/BauSystem_RegionDynamic/src/de/steamwar/bausystem/region/DynamicRegionCommand.java b/BauSystem/BauSystem_RegionDynamic/src/de/steamwar/bausystem/region/DynamicRegionCommand.java new file mode 100644 index 00000000..ab53c48e --- /dev/null +++ b/BauSystem/BauSystem_RegionDynamic/src/de/steamwar/bausystem/region/DynamicRegionCommand.java @@ -0,0 +1,66 @@ +/* + * This file is a part of the SteamWar software. + * + * Copyright (C) 2020 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; + +import de.steamwar.bausystem.features.region.RegionCommand; +import de.steamwar.bausystem.region.dynamic.DynamicRegion; +import de.steamwar.bausystem.region.dynamic.Tile; +import de.steamwar.bausystem.region.dynamic.path.PathRegion; +import de.steamwar.bausystem.region.dynamic.path.PathRegionData; +import de.steamwar.bausystem.utils.PasteBuilder; +import de.steamwar.command.AbstractSWCommand; +import de.steamwar.command.SWCommand; +import org.bukkit.entity.Player; + +import java.util.Optional; +import java.util.UUID; +import java.util.stream.Collectors; + +@AbstractSWCommand.PartOf(RegionCommand.class) +public class DynamicRegionCommand extends SWCommand { + + public DynamicRegionCommand() { + super(""); + } + + @Register({"dynamic", "place"}) + public void placeRegion(Player player) { + Region region = DynamicRegionSystem.INSTANCE.get(player.getLocation()); + if (!region.getType().isGlobal()) return; + Optional optionalTile = Tile.fromLocation(player.getLocation()); + if (optionalTile.isEmpty()) return; + Tile tile = optionalTile.get(); + + // TODO: Replace! + PathRegion dynamicRegion = new PathRegion(UUID.randomUUID(), tile.getMinX(), tile.getMinZ()); + dynamicRegion.setRegionData(new PathRegionData(dynamicRegion)); + + dynamicRegion.getArea().reset(new PasteBuilder(new PasteBuilder.FileProvider(dynamicRegion.getArea().getResetFile())), false); + DynamicRegionSystem.INSTANCE.getNeighbours(dynamicRegion).collect(Collectors.toList()) + .forEach(r -> r.update(dynamicRegion)); + } + + @Register({"dynamic", "delete"}) + public void deleteRegion(Player player) { + Region region = DynamicRegionSystem.INSTANCE.get(player.getLocation()); + if (region.getType().isCannotDelete()) return; + ((DynamicRegion) region).delete(); + } +} 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 b38f73d1..89d22d6c 100644 --- a/BauSystem/BauSystem_RegionDynamic/src/de/steamwar/bausystem/region/DynamicRegionSystem.java +++ b/BauSystem/BauSystem_RegionDynamic/src/de/steamwar/bausystem/region/DynamicRegionSystem.java @@ -28,7 +28,8 @@ import de.steamwar.bausystem.region.dynamic.global.GlobalRegion; import lombok.NonNull; import org.bukkit.Location; -import java.io.*; +import java.io.BufferedReader; +import java.io.InputStreamReader; import java.util.*; import java.util.function.Function; import java.util.stream.Collectors; @@ -55,7 +56,7 @@ public class DynamicRegionSystem implements RegionSystem { public void remove(DynamicRegion region) { regionCache.clear(); - regionMap.remove(region.getId()); + regionMap.remove(region.getID()); regionTypeMap.getOrDefault(region.getType(), Collections.emptySet()).remove(region); } @@ -84,6 +85,7 @@ public class DynamicRegionSystem implements RegionSystem { .collect(Collectors.toUnmodifiableMap(entry -> entry.getValue().identifier(), Map.Entry::getKey)); DynamicRegionRepository.loadRegions(); + new DynamicRegionCommand(); } @Override @@ -151,8 +153,10 @@ public class DynamicRegionSystem implements RegionSystem { return neighbours.stream(); } - public Stream getNeighbours(Region region) { - return getNeighbours(region, false, true, regionMap.values()); + public Stream getNeighbours(Region region) { + return getNeighbours(region, false, true, regionMap.values()) + .filter(DynamicRegion.class::isInstance) + .map(DynamicRegion.class::cast); } public Stream getConnectedRegions(Region region) { 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 334f5a94..83e3e2df 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,17 +19,24 @@ package de.steamwar.bausystem.region.dynamic; +import com.sk89q.worldedit.EditSession; +import com.sk89q.worldedit.WorldEdit; +import com.sk89q.worldedit.bukkit.BukkitAdapter; +import com.sk89q.worldedit.regions.CuboidRegion; +import com.sk89q.worldedit.world.block.BlockTypes; import de.steamwar.bausystem.region.DynamicRegionSystem; +import de.steamwar.bausystem.region.Point; import de.steamwar.bausystem.region.Region; import de.steamwar.bausystem.region.RegionData; import lombok.Getter; import lombok.NonNull; +import org.bukkit.Bukkit; import java.util.UUID; +import java.util.stream.Collectors; public abstract class DynamicRegion implements Region { - @Getter protected final UUID id; protected final int minX; protected final int minZ; @@ -42,20 +49,42 @@ public abstract class DynamicRegion implements Region { this.minX = minX; this.minZ = minZ; DynamicRegionSystem.INSTANCE.add(this); - // TODO: Implement further } public void update(DynamicRegion updateFrom) { + // TODO: Implement further } public void setRegionData(@NonNull RegionData regionData) { - regionData.setStore(this); this.regionData = regionData; + regionData.setStore(this); } public void delete() { if (getType().isCannotDelete()) return; DynamicRegionSystem.INSTANCE.remove(this); - // TODO: Implement! + DynamicRegionRepository.deleteRegion(this); + + Point minPoint = getArea().getMinPoint(false); + Point maxPoint = getArea().getMaxPoint(false); + + EditSession editSession = WorldEdit.getInstance() + .newEditSessionBuilder() + .world(BukkitAdapter.adapt(Bukkit.getWorlds().get(0))) + .checkMemory(false) + .allowedRegionsEverywhere() + .limitUnlimited() + .changeSetNull() + .build(); + editSession.setBlocks((com.sk89q.worldedit.regions.Region) new CuboidRegion(minPoint.toBlockVector3(), maxPoint.toBlockVector3()), BlockTypes.AIR.getDefaultState()); + editSession.close(); + + DynamicRegionSystem.INSTANCE.getNeighbours(this).collect(Collectors.toList()) + .forEach(r -> r.update(this)); + } + + @Override + public @NonNull UUID getID() { + return id; } } 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 f6df631a..823dc68d 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 @@ -35,7 +35,6 @@ 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; @@ -156,48 +155,22 @@ public class DynamicRegionRepository { 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()); + 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) { + public static void loadRegionData(Region region, RegionData regionData) { File regionDirectory = new File(REGION_DATA_FOLDER, region.getID().toString()); if (!regionDirectory.exists()) return; - loadRegionData(regionDirectory, region.getRegionData()); + loadRegionData(regionDirectory, regionData); } - public static void loadRegionData(Region region, RegionBackups.Backup backup) { + public static void loadRegionData(Region region, RegionBackups.Backup backup, RegionData regionData) { File regionDirectory = new File(REGION_DATA_FOLDER, region.getID().toString()); if (!regionDirectory.exists()) return; File backupsDirectory = new File(regionDirectory, BACKUPS_DIR_NAME); @@ -206,7 +179,7 @@ public class DynamicRegionRepository { if (!backupsTypeDirectory.exists()) return; File backupDirectory = new File(backupsTypeDirectory, backup.getName()); if (!backupDirectory.exists()) return; - loadRegionData(backupDirectory, backup.getRegionData()); + loadRegionData(backupDirectory, regionData); } private static void loadRegionData(File regionDirectory, RegionData regionData) { @@ -281,27 +254,26 @@ public class DynamicRegionRepository { @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.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(); + jsonWriter.close(); } public static void deleteRegion(Region region) { diff --git a/BauSystem/BauSystem_RegionDynamic/src/de/steamwar/bausystem/region/dynamic/RegionConstructorData.java b/BauSystem/BauSystem_RegionDynamic/src/de/steamwar/bausystem/region/dynamic/RegionConstructorData.java index 4468dd78..483241e1 100644 --- a/BauSystem/BauSystem_RegionDynamic/src/de/steamwar/bausystem/region/dynamic/RegionConstructorData.java +++ b/BauSystem/BauSystem_RegionDynamic/src/de/steamwar/bausystem/region/dynamic/RegionConstructorData.java @@ -19,7 +19,6 @@ package de.steamwar.bausystem.region.dynamic; -import de.steamwar.bausystem.region.RegionData; import org.atteo.classindex.IndexAnnotated; import java.lang.annotation.ElementType; @@ -35,5 +34,4 @@ public @interface RegionConstructorData { int widthX(); int widthZ(); boolean placeable() default true; - Class regionDataClass(); } 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 a9f26176..b3594eb6 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 @@ -28,7 +28,7 @@ import java.util.Optional; @Getter public class Tile { - public static final int tileSize = 19; + public static final int tileSize = 21; public static final int tileOffset = tileSize / 2; public static final int maxTile = 1023; public static final int minTile = -maxTile; @@ -78,6 +78,22 @@ public class Tile { return getMinZ(tileZ); } + public static int getMaxX(int tileX) { + return tileX * tileSize + tileOffset; + } + + public int getMaxX() { + return getMaxX(tileX); + } + + public static int getMaxZ(int tileZ) { + return tileZ * tileSize + tileOffset; + } + + public int getMaxZ() { + return getMaxZ(tileZ); + } + public static Location getMinLocation(int tileX, int tileZ) { return new Location(null, getMinX(tileX), 0, getMinZ(tileZ)); } @@ -86,6 +102,22 @@ public class Tile { return getMinLocation(tileX, tileZ); } + public static Location getMaxLocation(int tileX, int tileZ) { + return new Location(null, getMaxX(tileX), 0, getMaxZ(tileZ)); + } + + public Location getMaxLocation() { + return getMaxLocation(tileX, tileZ); + } + + public static Location getCenterLocation(int tileX, int tileZ) { + return new Location(null, tileX * tileSize, 0, tileZ * tileSize); + } + + public Location getCenterLocation() { + return getCenterLocation(tileX, tileZ); + } + public Optional add(int offsetX, int offsetZ) { return fromTile(tileX + offsetX, tileZ + offsetZ); } diff --git a/BauSystem/BauSystem_RegionDynamic/src/de/steamwar/bausystem/region/dynamic/VariantSelector.java b/BauSystem/BauSystem_RegionDynamic/src/de/steamwar/bausystem/region/dynamic/VariantSelector.java new file mode 100644 index 00000000..63c1b720 --- /dev/null +++ b/BauSystem/BauSystem_RegionDynamic/src/de/steamwar/bausystem/region/dynamic/VariantSelector.java @@ -0,0 +1,94 @@ +/* + * 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 lombok.NonNull; + +import java.io.File; +import java.time.LocalDate; +import java.time.Month; +import java.util.*; + +public abstract class VariantSelector { + + private static final Random RANDOM = new Random(); + public static final VariantSelector EMPTY = new VariantSelector() { + @Override + public Optional select(UUID regionID, int drift) { + return Optional.empty(); + } + }; + + private VariantSelector() { + } + + public abstract Optional select(UUID regionID, int drift); + + public final VariantSelector or(VariantSelector other) { + if (this == EMPTY) return other; + VariantSelector self = this; + return new VariantSelector() { + @Override + public Optional select(UUID regionID, int drift) { + return self.select(regionID, drift).or(() -> other.select(regionID, drift)); + } + }; + } + + public final VariantSelector atDate(int day, Month month) { + if (this == EMPTY) return this; + VariantSelector self = this; + return new VariantSelector() { + @Override + public Optional select(UUID regionID, int drift) { + LocalDate date = LocalDate.now(); + if (date.getDayOfMonth() == day && date.getMonth() == month) { + return self.select(regionID, drift); + } else { + return Optional.empty(); + } + } + }; + } + + public static VariantSelector Get(@NonNull File directory) { + final File[] files = directory.listFiles(); + if (files == null || files.length == 0) return EMPTY; + if (files.length == 1) { + final File file = files[0]; + return new VariantSelector() { + @Override + public Optional select(UUID regionID, int drift) { + return Optional.of(file); + } + }; + } + + Arrays.sort(files, Comparator.comparing(File::getName)); + final int filesCount = files.length; + return new VariantSelector() { + @Override + public Optional select(UUID regionID, int drift) { + RANDOM.setSeed(regionID.getLeastSignificantBits() ^ regionID.getMostSignificantBits() ^ drift); + return Optional.of(files[RANDOM.nextInt(filesCount)]); + } + }; + } +} 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 5f281176..78679a52 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 @@ -140,7 +140,7 @@ public class GlobalRegion implements Region { } @Override - public void loadRegion() { - DynamicRegionRepository.loadRegionData(this); + public void loadRegion(RegionData regionData) { + DynamicRegionRepository.loadRegionData(this, regionData); } } 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 be9b957b..86c9957f 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 @@ -21,7 +21,6 @@ package de.steamwar.bausystem.region.dynamic.global; import de.steamwar.bausystem.region.RegionData; import de.steamwar.bausystem.region.RegionFlagPolicy; -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; @@ -37,13 +36,12 @@ public class GlobalRegionData extends RegionData { @Override protected void initialize() { flagMap.put(Flag.TNT, TNTMode.DENY); - flagMap.put(Flag.COLOR, ColorMode.YELLOW); flagMap.put(Flag.PROTECT, ProtectMode.INACTIVE); } @Override public @NonNull & Flag.Value> RegionFlagPolicy has(@NonNull Flag flag) { - if (flag.oneOf(Flag.COLOR, Flag.PROTECT)) { + if (flag.oneOf(Flag.PROTECT)) { return RegionFlagPolicy.READ_ONLY; } if (flag.oneOf(Flag.ITEMS) && Core.getVersion() >= 20) { diff --git a/BauSystem/BauSystem_RegionDynamic/src/de/steamwar/bausystem/region/dynamic/path/PathArea.java b/BauSystem/BauSystem_RegionDynamic/src/de/steamwar/bausystem/region/dynamic/path/PathArea.java new file mode 100644 index 00000000..7d4e74b7 --- /dev/null +++ b/BauSystem/BauSystem_RegionDynamic/src/de/steamwar/bausystem/region/dynamic/path/PathArea.java @@ -0,0 +1,238 @@ +/* + * 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; + +import com.sk89q.worldedit.bukkit.BukkitAdapter; +import com.sk89q.worldedit.extent.clipboard.Clipboard; +import com.sk89q.worldedit.math.BlockVector3; +import com.sk89q.worldedit.math.transform.AffineTransform; +import de.steamwar.bausystem.region.DynamicRegionSystem; +import de.steamwar.bausystem.region.Point; +import de.steamwar.bausystem.region.Region; +import de.steamwar.bausystem.region.RegionType; +import de.steamwar.bausystem.region.dynamic.Tile; +import de.steamwar.bausystem.region.dynamic.VariantSelector; +import de.steamwar.bausystem.utils.FlatteningWrapper; +import de.steamwar.bausystem.utils.PasteBuilder; +import lombok.NonNull; +import lombok.RequiredArgsConstructor; +import org.bukkit.Bukkit; +import org.jetbrains.annotations.Nullable; + +import java.io.File; +import java.util.Optional; +import java.util.UUID; + +public class PathArea implements Region.Area { + + private static final File PATH_DIR = new File(Bukkit.getWorlds().get(0).getWorldFolder(), "sections/path"); + + 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")); + + @RequiredArgsConstructor + private enum Side { + North(0, -1, 7, 0, 0), + South(0, 1, 7, 14, 180), + West(-1, 0, 0, 7, 90), + East(1, 0, 14, 7, 270), + ; + + private final int tileOffsetX; + private final int tileOffsetZ; + private final int pasteOffsetX; + private final int pasteOffsetZ; + private final int rotate; + } + + @RequiredArgsConstructor + private enum Corner { + NorthEast(Side.North, Side.East, 14, 0, 0, -90), + NorthWest(Side.North, Side.West, 0, 0, 0, 90), + SouthEast(Side.South, Side.East, 14, 14, 180, 90), + SouthWest(Side.South, Side.West, 0, 14, 180, -90), + ; + + private final Side side1; + private final Side side2; + private final int pasteOffsetX; + private final int pasteOffsetZ; + private final int rotate; + private final int rotateCorrection; + } + + private final UUID regionIdentifier; + private final Tile tile; + private final Point minPoint; + private final Point maxPoint; + private final Point copyPoint; + + public PathArea(Tile tile, PathRegion region) { + this.regionIdentifier = region.getID(); + this.tile = tile; + + minPoint = Point.fromLocation(tile.getMinLocation()).setY(WORLD_MIN_Y); + maxPoint = Point.fromLocation(tile.getMaxLocation()).setY(WORLD_MAX_Y); + copyPoint = Point.fromLocation(tile.getCenterLocation()); + } + + @Override + public @NonNull Point getMinPoint(boolean extension) { + return minPoint; + } + + @Override + public @NonNull Point getMaxPoint(boolean extension) { + return maxPoint; + } + + @Override + public @NonNull Point getCopyPoint() { + return copyPoint; + } + + @Override + public @Nullable File getResetFile() { + return null; // I know what I do! + } + + @Override + public void reset(PasteBuilder pasteBuilder, boolean extension) { + if (surroundedByPath()) { + // TODO: Implement Garden case! + } + + File resetFile = CENTER_NORMAL.select(regionIdentifier, 0).orElse(null); + if (resetFile != null) { + paste(resetFile, minPoint.add(7, 0, 7), 0); + } + + for (Side side : Side.values()) { + RegionType.ConnectionType connectionType = getConnectionType(side, null); + + VariantSelector selector = switch (connectionType) { + case Path -> CENTER_NORMAL; + case Water -> null; + case Closed -> null; + case Global -> SIDE_GLOBAL; + }; + if (selector == null) { + continue; + } + + resetFile = selector.select(regionIdentifier, side.ordinal() + side.rotate).orElse(null); + if (resetFile == null) { + continue; + } + + paste(resetFile, minPoint.add(side.pasteOffsetX, 0, side.pasteOffsetZ), side.rotate); + } + + for (Corner corner : Corner.values()) { + RegionType.ConnectionType connectionType1 = getConnectionType(corner.side1, null); + RegionType.ConnectionType connectionType2 = getConnectionType(corner.side2, null); + RegionType.ConnectionType connectionType3 = getConnectionType(corner.side1, corner.side2); + + VariantSelector selector = null; + int rotate = corner.rotate; + if (connectionType1 == RegionType.ConnectionType.Path && connectionType2 == connectionType3) { + selector = switch (connectionType2) { + case Global -> SIDE_GLOBAL; + case Closed -> null; + case Water -> null; + case Path -> null; + }; + rotate += corner.rotateCorrection; + } + if (connectionType1 == RegionType.ConnectionType.Global && connectionType2 == connectionType3) { + selector = switch (connectionType2) { + case Global -> null; + case Closed -> null; + case Water -> null; + case Path -> SIDE_GLOBAL; + }; + } + + if (connectionType2 == RegionType.ConnectionType.Path && connectionType1 == connectionType3) { + selector = switch (connectionType1) { + case Global -> SIDE_GLOBAL; + case Closed -> null; + case Water -> null; + case Path -> null; + }; + } + if (connectionType2 == RegionType.ConnectionType.Global && connectionType1 == connectionType3) { + selector = switch (connectionType1) { + case Global -> null; + case Closed -> null; + case Water -> null; + case Path -> SIDE_GLOBAL; + }; + rotate += corner.rotateCorrection; + } + if (connectionType1 == RegionType.ConnectionType.Path && connectionType1 == connectionType2 && connectionType1 == connectionType3) { + selector = CENTER_NORMAL; + rotate = 0; + } + if (selector == null) { + continue; + } + + resetFile = selector.select(regionIdentifier, corner.side1.ordinal() * corner.side2.ordinal() + corner.side1.rotate * corner.side2.rotate).orElse(null); + if (resetFile == null) { + continue; + } + + paste(resetFile, minPoint.add(corner.pasteOffsetX, 0, corner.pasteOffsetZ), rotate); + } + } + + private boolean surroundedByPath() { + for (int x = -1; x <= 1; x++) { + for (int z = -1; z <= 1; z++) { + if (x == 0 && z == 0) continue; + Optional optionalTile = this.tile.add(x, z); + if (optionalTile.isEmpty()) { + return false; + } + if (DynamicRegionSystem.INSTANCE.get(optionalTile.get().getCenterLocation()).getType() != RegionType.PATH) { + return false; + } + } + } + return true; + } + + private RegionType.ConnectionType getConnectionType(Side side, Side optionalSide) { + Optional optionalTile = this.tile.add(side.tileOffsetX, side.tileOffsetZ); + if (optionalSide != null) { + optionalTile = optionalTile.flatMap(tile -> tile.add(optionalSide.tileOffsetX, optionalSide.tileOffsetZ)); + } + return optionalTile.map(tile -> DynamicRegionSystem.INSTANCE.get(tile.getCenterLocation()).getType().getConnectionType()) + .orElse(RegionType.ConnectionType.Global); + } + + private void paste(File file, Point minPoint, int rotate) { + Clipboard clipboard = FlatteningWrapper.impl.loadSchematic(file); + BlockVector3 offset = clipboard.getRegion().getMinimumPoint().subtract(clipboard.getOrigin()); + BlockVector3 to = minPoint.toBlockVector3().subtract(offset); + clipboard.paste(BukkitAdapter.adapt(Bukkit.getWorlds().get(0)), to, false, true, new AffineTransform().rotateY(rotate)); + } +} diff --git a/BauSystem/BauSystem_RegionDynamic/src/de/steamwar/bausystem/region/dynamic/path/PathRegion.java b/BauSystem/BauSystem_RegionDynamic/src/de/steamwar/bausystem/region/dynamic/path/PathRegion.java new file mode 100644 index 00000000..9e3ce322 --- /dev/null +++ b/BauSystem/BauSystem_RegionDynamic/src/de/steamwar/bausystem/region/dynamic/path/PathRegion.java @@ -0,0 +1,101 @@ +/* + * 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; + +import de.steamwar.bausystem.region.RegionBackups; +import de.steamwar.bausystem.region.RegionData; +import de.steamwar.bausystem.region.RegionHistory; +import de.steamwar.bausystem.region.RegionType; +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.sql.GameModeConfig; +import lombok.NonNull; +import org.bukkit.Material; + +import java.util.UUID; + +@RegionConstructorData( + identifier = "path", + widthX = Tile.tileSize, + widthZ = Tile.tileSize +) +public class PathRegion extends DynamicRegion { + + private final PathArea area; + + public PathRegion(UUID id, int minX, int minZ) { + super(id, minX, minZ); + area = new PathArea(Tile.fromXZ(minX, minZ).orElseThrow(), this); + regionData = new PathRegionData(this); + } + + @Override + public void update(DynamicRegion updateFrom) { + // TODO: Improve + area.reset(null, false); + } + + @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 saveRegion() { + DynamicRegionRepository.saveRegion(this); + } + + @Override + public void loadRegion(RegionData regionData) { + DynamicRegionRepository.loadRegionData(this, regionData); + } +} diff --git a/BauSystem/BauSystem_RegionDynamic/src/de/steamwar/bausystem/region/dynamic/path/PathRegionData.java b/BauSystem/BauSystem_RegionDynamic/src/de/steamwar/bausystem/region/dynamic/path/PathRegionData.java new file mode 100644 index 00000000..1f04d83c --- /dev/null +++ b/BauSystem/BauSystem_RegionDynamic/src/de/steamwar/bausystem/region/dynamic/path/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; + +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; + } +} diff --git a/BauSystem/BauSystem_RegionFixed/src/de/steamwar/bausystem/region/fixed/FixedGlobalRegion.java b/BauSystem/BauSystem_RegionFixed/src/de/steamwar/bausystem/region/fixed/FixedGlobalRegion.java index 3f31baa8..3f9d21e8 100644 --- a/BauSystem/BauSystem_RegionFixed/src/de/steamwar/bausystem/region/fixed/FixedGlobalRegion.java +++ b/BauSystem/BauSystem_RegionFixed/src/de/steamwar/bausystem/region/fixed/FixedGlobalRegion.java @@ -147,7 +147,7 @@ public final class FixedGlobalRegion implements Region { } @Override - public void loadRegion() { + public void loadRegion(RegionData regionData) { FixedRegionDataUtils.loadRegionData("global", FLAG_STORAGE); } } 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 d9c8c06b..f19e46ec 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 @@ -143,7 +143,7 @@ public class FixedRegion implements Region { } @Override - public void loadRegion() { + public void loadRegion(RegionData regionData) { } @Override @@ -407,7 +407,7 @@ public class FixedRegion implements Region { } @Override - public void loadRegion() { + public void loadRegion(RegionData regionData) { FixedRegionDataUtils.loadRegionData(name, flagStorage); } } diff --git a/settings.gradle.kts b/settings.gradle.kts index a5a193b4..53f6b7da 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -166,7 +166,7 @@ include( "BauSystem", "BauSystem:BauSystem_Main", "BauSystem:BauSystem_RegionDynamic", - "BauSystem:BauSystem_RegionDynamic2", + // "BauSystem:BauSystem_RegionDynamic2", "BauSystem:BauSystem_RegionFixed" )