Compare commits

..
Author SHA1 Message Date
YoyoNow 09a1c9480d Update DynamicRegionSystem 2026-03-24 18:00:12 +01:00
YoyoNow f8dd7f0321 Update PathRegion 2026-03-23 15:40:26 +01:00
YoyoNow 9c47942237 Fix constructors 2026-03-23 12:19:10 +01:00
YoyoNow 60a65a92c2 Improve DynamicRegion 2026-03-22 22:00:34 +01:00
YoyoNow 9006a34a3b Remove stability from VariantSelector 2026-03-22 16:05:25 +01:00
YoyoNow 268f450ee8 Add initial MultiTileArea 2026-03-22 15:21:27 +01:00
YoyoNow a7b41eb4e8 Improve Region.Area 2026-03-22 15:21:17 +01:00
YoyoNow 8b03093af7 Reduce usable tile area
Fix DynamicRegionVisualizer throwing exceptions
2026-03-21 22:52:10 +01:00
YoyoNow 2fa0d4c5b4 Update yOffset to WS175PlotRegion and WS230PlotRegion 2026-03-21 22:25:36 +01:00
YoyoNow b2906488c5 Implement MiWG7PlotRegion
Implement WS230PlotRegion
2026-03-21 20:51:34 +01:00
YoyoNow 7a9e39a12a Fix MWGDisplayRegion, WG45DisplayRegion, WS175DisplayRegion, WS175PlotRegion
Fix AreaBlock.create
2026-03-21 20:31:01 +01:00
YoyoNow c1d617c963 Remove old code 2026-03-21 18:13:37 +01:00
YoyoNow cad6c32276 Add further TODOs 2026-03-21 18:12:32 +01:00
YoyoNow 14d62a5dd1 Fix WS175Utils and WS230Utils 2026-03-21 18:08:31 +01:00
YoyoNow 6f78ad2354 Implement WG45PlotRegion
Implement WS175PlotRegion
2026-03-21 18:05:47 +01:00
YoyoNow cc7ba8de19 Add TestblockScoreboardElement
Add WireframeCommand
Implement MWGPlotRegion
2026-03-21 17:42:14 +01:00
YoyoNow 7ee9ad1fe0 Remove unneeded variables 2026-03-20 08:51:57 +01:00
YoyoNow 46c791c072 Improve non final variables to be final now 2026-03-20 08:47:23 +01:00
YoyoNow 9f3d9a1afc Fix GameModeConfig for WG45PlotRegion, WS175PlotRegion and WS230PlotRegion 2026-03-18 21:29:09 +01:00
YoyoNow a870c854df Add initial regions 2026-03-18 14:06:02 +01:00
YoyoNow a92cebe441 Fix DynamicRegionVisualizer 2026-03-17 21:02:18 +01:00
YoyoNow 0e2f3c58f2 Add supervisor permission on DynamicRegionVisualizer 2026-03-17 19:16:49 +01:00
YoyoNow 3b44b2f8a8 Add DynamicRegionVisualizer 2026-03-17 19:14:53 +01:00
YoyoNow 183bc3fc5d Add DynamicRegionVisualizer 2026-03-16 22:16:23 +01:00
YoyoNow af2e4c113b Add RInteraction 2026-03-16 22:16:10 +01:00
YoyoNow c1a42e8860 Fix ChunkHider21 2026-03-16 21:42:53 +01:00
YoyoNow 4f5257a13e Add name to RegionConstructorData 2026-03-16 21:08:26 +01:00
YoyoNow e1a3355415 Add MiWG7DisplayRegion 2026-03-16 21:08:26 +01:00
YoyoNow fc2d5caad4 Implement rest of PlotRegionBackups 2026-03-16 21:08:26 +01:00
YoyoNow 1204ecbcdc Implement more in PlotRegionBackups 2026-03-16 21:08:26 +01:00
YoyoNow 2392b6c42f Improve PlotRegionBackups 2026-03-16 21:08:26 +01:00
YoyoNow 6888fb4247 Initial rudimentary implementation (not done) of PlotRegionBackups 2026-03-16 21:08:26 +01:00
YoyoNow 4841694eae Replace RegionDataStore.saveRegion to save
Replace RegionDataStore.loadRegion to load
Replace RegionDataStore.deleteRegion to delete
2026-03-16 21:08:26 +01:00
YoyoNow 0d83f21a31 Use RegionDataStore.deleteRegion instead of custom method in DynamicRegion 2026-03-16 21:08:26 +01:00
YoyoNow cb5f82d7a3 Fix PlotRegionBackups 2026-03-16 21:08:26 +01:00
YoyoNow 242e4a7044 Add PlotRegionBackups
Update DisplayRegionData constructor
2026-03-16 21:08:26 +01:00
YoyoNow 4324576a61 Remove Region.getFloorLevel
Remove Flag.Changed from SpecialRegionData
2026-03-16 21:08:26 +01:00
YoyoNow 479f391a46 Add DisplayRegionData
Add PlotRegionData
Add directories for mode regions
2026-03-16 21:08:26 +01:00
YoyoNow 59468b25e3 Improve SpecialArea.reset 2026-03-16 21:08:26 +01:00
YoyoNow fd8c99f8ef Add Region.Area.place which is called ba Region.Area.reset 2026-03-16 21:08:26 +01:00
YoyoNow ea2891ebc6 Implement rest of Path Connections 2026-03-16 21:08:26 +01:00
YoyoNow 4636948b3e Improve Flag.COLOR 2026-03-16 21:08:26 +01:00
YoyoNow 22399a79f7 Implement Garden updates for Path Regions correctly 2026-03-16 21:08:26 +01:00
YoyoNow a91925f64c Change some instance creations to casts 2026-03-16 21:08:26 +01:00
YoyoNow 60d0cc8236 Cleanup and add comments 2026-03-16 21:08:26 +01:00
YoyoNow 7862dafa85 Fix DynamicRegion.updateNeighbours 2026-03-16 21:08:26 +01:00
YoyoNow 5dbe2b6caa Improve update propagation performance 2026-03-16 21:08:26 +01:00
YoyoNow 92bcfd00e2 Fix RegionData.clear removing initialized flags 2026-03-16 21:08:26 +01:00
YoyoNow 7c23af3a7d Add TNT only making Damage in spawning Region 2026-03-16 21:08:26 +01:00
YoyoNow 800705d7fd Fix RegionFlagPolicy 2026-03-16 21:08:26 +01:00
YoyoNow 7b0427de6a Add Flag.WATER_DESTROY to needed RegionData's
Fix ScoreboardElement showing stuff not applicable in region
2026-03-16 21:08:26 +01:00
YoyoNow 74e09abf8f Fix WetRegion -> WetRegionData
Fix DynamicRegionSystem.get
Add Fallback.schem for PathArea
2026-03-16 21:08:26 +01:00
YoyoNow dff2e088a2 Add Garden handling for PathRegion 2026-03-16 21:08:26 +01:00
YoyoNow d0ceaa0031 Fix DynamicRegionSystem#get(Tile) 2026-03-16 21:08:26 +01:00
YoyoNow 0d1cbe4cf7 Fix CORNER_OUTER_GLOBAL case 2026-03-16 21:08:26 +01:00
YoyoNow d585f31f02 Add CornerInnerGlobal and CornerOuterGlobal
Improve the selecting code
2026-03-16 21:08:26 +01:00
YoyoNow 78d17b0af5 Fix runtime errors 2026-03-16 21:08:26 +01:00
YoyoNow ed1f05c800 Add DryRegion and WetRegion
Add SpecialArea
Add SpecialRegionData
Add RegionData.connectedRegions
Add PasteUtils
2026-03-16 21:08:26 +01:00
YoyoNow 30c0255fb2 Improve several things 2026-03-16 21:08:26 +01:00
YoyoNow c8bfcd2dc3 Improve rotation of PathArea#reset in CENTER_NORMAL case 2026-03-16 21:08:26 +01:00
YoyoNow dc9c60f712 Fix DynamicRegionRepository.loadRegions with GlobalRegion 2026-03-16 21:08:26 +01:00
YoyoNow 174ff8c02f Add PathRegion, PathArea and PathRegionData
Add VariantSelector
Add DynamicRegionCommand
Fix RegionData constructor
2026-03-16 21:08:26 +01:00
YoyoNow 62122f46ab Add DynamicRegionRepository#loadRegions
Add DynamicRegionRepository#loadRegionData
Add DynamicRegionRepository#saveRegion
Add DynamicRegionRepository#deleteRegion
2026-03-16 21:08:26 +01:00
YoyoNow 598d2019a9 Improve API of RegionDataStore, RegionBackups.Backup 2026-03-16 21:08:26 +01:00
YoyoNow 58d05b5d30 Improve RegionData#setStore 2026-03-16 21:08:26 +01:00
YoyoNow ebc5fcb3d8 Add Tile.tileOffset constant 2026-03-16 21:08:26 +01:00
YoyoNow bc7f5c4d4d Add Region loading to DynamicRegionSystem 2026-03-16 21:08:26 +01:00
YoyoNow b5c2cfd6ab Fix RegionSystem.GLOBAL_REGION_ID
Improve RegionUtils.forEachInRegion
2026-03-16 21:08:25 +01:00
YoyoNow 69d472f48b Add FixedRegionDataUtils 2026-03-16 21:08:25 +01:00
YoyoNow daa5d00268 Add RegionDataStore 2026-03-16 21:08:25 +01:00
YoyoNow eea422aaf6 Add DynamicRegion 2026-03-16 21:08:25 +01:00
YoyoNow 1efedcce9c Add GlobalRegionData 2026-03-16 21:08:25 +01:00
YoyoNow 423826b059 Add GlobalRegion 2026-03-16 21:08:25 +01:00
YoyoNow 8346828e3e Start rebuilt 2026-03-16 21:08:25 +01:00
YoyoNow 566a72a19a Implement WorldIdentifier 2026-03-16 21:08:25 +01:00
YoyoNow f123cc7c0c Improve RegionData
Fix BauScoreboard
Fix TestblockCommand
2026-03-16 21:08:25 +01:00
YoyoNow fae26f2fe0 Optimize some stuff and fix some other stuff 2026-03-16 21:08:25 +01:00
YoyoNow 1c49fa6ca0 Fix bobby in DynamicRegionSystem 2026-03-16 21:08:25 +01:00
YoyoNow 4e408d020b Fix bobby in DynamicRegionSystem 2026-03-16 21:08:25 +01:00
YoyoNow 60f03c6c57 Add GameModeConfig.EMPTY 2026-03-16 21:08:25 +01:00
YoyoNow 134cda6b4f Add Display regions 2026-03-16 21:08:25 +01:00
YoyoNow 8456c4d7ad Use VariantSelector 2026-03-16 21:08:25 +01:00
YoyoNow 35fd9eec30 Add VariantSelector 2026-03-16 21:08:25 +01:00
YoyoNow a9fc7b55c3 Update stuff 2026-03-16 21:08:25 +01:00
YoyoNow 34e86f5289 Add VariantSelector 2026-03-16 21:08:25 +01:00
YoyoNow 45a8376e4a Fix RegionDataRepository.saveFlagStorage 2026-03-16 21:08:25 +01:00
YoyoNow 108cddd467 Improve some stuff 2026-03-16 21:08:25 +01:00
YoyoNow d425bb2830 Remove sout 2026-03-16 21:08:25 +01:00
YoyoNow b837aa828c Fix Warp WorldSpawn 2026-03-16 21:08:25 +01:00
YoyoNow 230359d016 Add MicroWarGear21Region
Add MiniWarGear21Region
Add WarGear21Region
Add WarShip21Region
2026-03-16 21:08:25 +01:00
YoyoNow e1cf983c37 Fix PathAreaTile 2026-03-16 21:08:25 +01:00
YoyoNow 2b3e437704 Add PathRegion 2026-03-16 21:08:25 +01:00
YoyoNow dec5e619b3 Add SpawnPathRegion
Add SpawnRegion
2026-03-16 21:08:25 +01:00
YoyoNow 99b2488870 Fix RegionDataRepository
Add DefaultFlagStorage
2026-03-16 21:08:25 +01:00
YoyoNow 1f8126e48a Add RegionDataRepository 2026-03-16 21:08:25 +01:00
YoyoNow 7145e3fd49 Add DynamicRegionSystem 2026-03-16 21:08:25 +01:00
YoyoNow 5e19629df5 Fix build 2026-03-15 12:54:43 +01:00
YoyoNow ca70c6685c Remove Lunar client support
They currently have a problem with their maven repository
2026-03-15 12:52:56 +01:00
YoyoNow f00bd153fe Add GameModeConfig#Schematic#ReplacementsWithoutBlockUpdates
Add GameModeConfig#Schematic#ReplacementsWithBlockUpdates
2026-03-15 12:49:13 +01:00
YoyoNow c1221e5cf5 Remove SWTSI (SteamWar Teamserver Integration) 2026-03-13 21:16:13 +01:00
YoyoNow 236944ff69 Remove useless System.out 2026-03-13 21:12:34 +01:00
YoyoNow ab85c72fe3 Fix DesignEndStone
Closes: #292
Closes: #288
2026-03-13 21:08:00 +01:00
YoyoNow a750185df0 Fix stop not working for DevServer starter 2026-03-02 12:10:18 +01:00
YoyoNow 008ff1091f Hotfix DevCommand 2026-03-02 11:59:52 +01:00
YoyoNow 5d24581038 Fix WaterRemover.handleEntityExplode 2026-03-01 21:47:22 +01:00
YoyoNow bce07a4ac8 Add GameModeConfig.ArenaConfig.WaterDamage for hard water damage by setting air or normal handling 2026-03-01 21:36:29 +01:00
Chaoscaot 30b7bbc283 Fix WebPW Command
Signed-off-by: Chaoscaot <max@maxsp.de>
2026-02-09 09:28:14 +01:00
Chaoscaot 46a11af6ca Fix Ban Command
Signed-off-by: Chaoscaot <max@maxsp.de>
2026-02-02 19:12:34 +01:00
118 changed files with 6193 additions and 735 deletions
@@ -39,6 +39,7 @@ import com.sk89q.worldedit.math.transform.AffineTransform;
import com.sk89q.worldedit.regions.CuboidRegion; import com.sk89q.worldedit.regions.CuboidRegion;
import com.sk89q.worldedit.regions.selector.CuboidRegionSelector; import com.sk89q.worldedit.regions.selector.CuboidRegionSelector;
import com.sk89q.worldedit.session.ClipboardHolder; import com.sk89q.worldedit.session.ClipboardHolder;
import com.sk89q.worldedit.util.SideEffectSet;
import com.sk89q.worldedit.world.World; import com.sk89q.worldedit.world.World;
import com.sk89q.worldedit.world.block.BaseBlock; import com.sk89q.worldedit.world.block.BaseBlock;
import com.sk89q.worldedit.world.block.BlockTypes; import com.sk89q.worldedit.world.block.BlockTypes;
@@ -114,6 +115,9 @@ public class FlatteningWrapper15 implements FlatteningWrapper {
@Override @Override
public Clipboard loadSchematic(File file) { public Clipboard loadSchematic(File file) {
if (file == null) {
return null;
}
Clipboard clipboard; Clipboard clipboard;
try (ClipboardReader reader = Objects.requireNonNull(ClipboardFormats.findByFile(file)).getReader(new FileInputStream(file))) { try (ClipboardReader reader = Objects.requireNonNull(ClipboardFormats.findByFile(file)).getReader(new FileInputStream(file))) {
clipboard = reader.read(); clipboard = reader.read();
@@ -167,14 +171,19 @@ public class FlatteningWrapper15 implements FlatteningWrapper {
ClipboardHolder ch = new ClipboardHolder(clipboard); ClipboardHolder ch = new ClipboardHolder(clipboard);
BlockVector3 dimensions = clipboard.getDimensions(); BlockVector3 dimensions = clipboard.getDimensions();
BlockVector3 v = BlockVector3.at(pasteBuilder.getPastPoint().getX(), pasteBuilder.getPastPoint().getY(), pasteBuilder.getPastPoint().getZ()); BlockVector3 v;
BlockVector3 offset = clipboard.getRegion().getMinimumPoint().subtract(clipboard.getOrigin()); BlockVector3 offset = clipboard.getRegion().getMinimumPoint().subtract(clipboard.getOrigin());
if (pasteBuilder.getPastPoint() != null) {
v = pasteBuilder.getPastPoint().toBlockVector3();
if (pasteBuilder.isRotate()) { if (pasteBuilder.isRotate()) {
ch.setTransform(new AffineTransform().rotateY(180)); ch.setTransform(new AffineTransform().rotateY(180));
v = v.add(dimensions.getX() / 2, 0, dimensions.getZ() / 2).subtract(offset.multiply(-1, 1, -1)).subtract(0, 0, 1); v = v.add(dimensions.getX() / 2, 0, dimensions.getZ() / 2).subtract(offset.multiply(-1, 1, -1)).subtract(0, 0, 1);
} else { } else {
v = v.subtract(dimensions.getX() / 2, 0, dimensions.getZ() / 2).subtract(offset); v = v.subtract(dimensions.getX() / 2, 0, dimensions.getZ() / 2).subtract(offset);
} }
} else {
v = pasteBuilder.getMinPoint().toBlockVector3().subtract(offset);
}
pastePoint.set(v); pastePoint.set(v);
if (pasteBuilder.isReset()) { if (pasteBuilder.isReset()) {
@@ -183,6 +192,7 @@ public class FlatteningWrapper15 implements FlatteningWrapper {
e.setBlocks(new CuboidRegion(pasteBuilder.getMinPoint().toBlockVector3(), pasteBuilder.getMaxPoint().toBlockVector3().withY(pasteBuilder.getWaterLevel())), Objects.requireNonNull(BlockTypes.WATER).getDefaultState().toBaseBlock()); e.setBlocks(new CuboidRegion(pasteBuilder.getMinPoint().toBlockVector3(), pasteBuilder.getMaxPoint().toBlockVector3().withY(pasteBuilder.getWaterLevel())), Objects.requireNonNull(BlockTypes.WATER).getDefaultState().toBaseBlock());
} }
} }
e.setSideEffectApplier(SideEffectSet.none());
Operations.completeBlindly(ch.createPaste(e).to(v).ignoreAirBlocks(pasteBuilder.isIgnoreAir()).build()); Operations.completeBlindly(ch.createPaste(e).to(v).ignoreAirBlocks(pasteBuilder.isIgnoreAir()).build());
return e; return e;
} catch (WorldEditException e) { } catch (WorldEditException e) {
@@ -39,7 +39,6 @@ import de.steamwar.bausystem.utils.TickManager;
import de.steamwar.bausystem.worlddata.WorldData; import de.steamwar.bausystem.worlddata.WorldData;
import de.steamwar.command.AbstractValidator; import de.steamwar.command.AbstractValidator;
import de.steamwar.command.SWCommandUtils; import de.steamwar.command.SWCommandUtils;
import de.steamwar.core.CRIUSleepEvent;
import de.steamwar.core.Core; import de.steamwar.core.Core;
import de.steamwar.core.WorldEditRendererCUIEditor; import de.steamwar.core.WorldEditRendererCUIEditor;
import de.steamwar.core.WorldIdentifier; import de.steamwar.core.WorldIdentifier;
@@ -52,8 +51,6 @@ import org.bukkit.Bukkit;
import org.bukkit.GameRule; import org.bukkit.GameRule;
import org.bukkit.command.CommandSender; import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player; import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
import org.bukkit.event.Listener;
import org.bukkit.plugin.Plugin; import org.bukkit.plugin.Plugin;
import org.bukkit.plugin.java.JavaPlugin; import org.bukkit.plugin.java.JavaPlugin;
import org.bukkit.scheduler.BukkitRunnable; import org.bukkit.scheduler.BukkitRunnable;
@@ -66,7 +63,7 @@ import java.util.concurrent.atomic.AtomicReference;
import java.util.function.Consumer; import java.util.function.Consumer;
import java.util.logging.Level; import java.util.logging.Level;
public class BauSystem extends JavaPlugin implements Listener { public class BauSystem extends JavaPlugin {
// This should be treated as final! // This should be treated as final!
public static Message MESSAGE; public static Message MESSAGE;
@@ -140,16 +137,10 @@ public class BauSystem extends JavaPlugin implements Listener {
WorldIdentifier.set("bau/" + Core.getVersion() + "/" + identifier); WorldIdentifier.set("bau/" + Core.getVersion() + "/" + identifier);
} }
@EventHandler
public void onCRIUSleep(CRIUSleepEvent event) {
RegionSystem.INSTANCE.save();
}
@Override @Override
public void onDisable() { public void onDisable() {
linker.unlink(); linker.unlink();
WorldData.write(); WorldData.write();
RegionSystem.INSTANCE.save();
Config.getInstance().saveAll(); Config.getInstance().saveAll();
} }
@@ -56,6 +56,7 @@ public class BauInfoBauGuiItem extends BauGuiItem {
Region region = Region.getRegion(player.getLocation()); Region region = Region.getRegion(player.getLocation());
List<String> stringList = new ArrayList<>(); List<String> stringList = new ArrayList<>();
for (Flag flag : Flag.getFlags()) { for (Flag flag : Flag.getFlags()) {
if (flag == Flag.CHANGED) continue;
if (!region.getRegionData().has(flag).isApplicable()) continue; if (!region.getRegionData().has(flag).isApplicable()) continue;
FlagOptional<?> value = region.getRegionData().get(flag); FlagOptional<?> value = region.getRegionData().get(flag);
if (value.isPresent()) { if (value.isPresent()) {
@@ -31,10 +31,7 @@ import org.bukkit.Material;
import org.bukkit.World; import org.bukkit.World;
import org.bukkit.entity.Player; import org.bukkit.entity.Player;
import java.util.ArrayList; import java.util.*;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors; import java.util.stream.Collectors;
public class DesignEndStone { public class DesignEndStone {
@@ -56,11 +53,10 @@ public class DesignEndStone {
this.maxY = region.getBuildArea().getMaxPoint(false).getY(); this.maxY = region.getBuildArea().getMaxPoint(false).getY();
this.maxZ = region.getBuildArea().getMaxPoint(false).getZ(); this.maxZ = region.getBuildArea().getMaxPoint(false).getZ();
limited = region.getGameModeConfig().Schematic.Limited limited = Arrays.stream(Material.values())
.entrySet() .filter(Material::isBlock)
.stream() .filter(material -> !material.isLegacy())
.filter(entry -> entry.getValue() == 0) .filter(material -> material.getBlastResistance() > region.getGameModeConfig().Schematic.MaxDesignBlastResistance)
.flatMap(entry -> entry.getKey().stream())
.collect(Collectors.toSet()); .collect(Collectors.toSet());
calculateFromBottom = region.getGameModeConfig().Arena.NoFloor; calculateFromBottom = region.getGameModeConfig().Arena.NoFloor;
@@ -63,11 +63,11 @@ public class ColorCommand extends SWCommand {
} }
region.getRegionData().set(Flag.COLOR, color); region.getRegionData().set(Flag.COLOR, color);
try { try {
PasteBuilder pasteBuilder = new PasteBuilder(new PasteBuilder.FileProvider(region.getArea().getResetFile())) PasteBuilder pasteBuilder = new PasteBuilder(PasteBuilder.ClipboardProvider.EMPTY)
.ignoreAir(true) .ignoreAir(true)
.onlyColors(true) .onlyColors(true)
.color(color); .color(color);
region.getArea().reset(pasteBuilder, false); region.getArea().reset(p.getLocation(), pasteBuilder, false);
RegionUtils.message(region, "REGION_REGION_COLORED"); RegionUtils.message(region, "REGION_REGION_COLORED");
RegionUtils.message(region, "REGION_REGION_COLORED_FAILED"); RegionUtils.message(region, "REGION_REGION_COLORED_FAILED");
} catch (SecurityException e) { } catch (SecurityException e) {
@@ -56,6 +56,7 @@ public class FireListener implements Listener, ScoreboardElement {
@Override @Override
public String get(Region region, Player p) { public String get(Region region, Player p) {
if (region.getRegionData().has(Flag.FIRE).notVisibleInScoreboard()) return null;
if (region.getRegionData().get(Flag.FIRE).isWithDefault(FireMode.DENY)) return null; if (region.getRegionData().get(Flag.FIRE).isWithDefault(FireMode.DENY)) return null;
return "§e" + BauSystem.MESSAGE.parse(Flag.FIRE.getChatValue(), p) + "§8: " + BauSystem.MESSAGE.parse(region.getRegionData().get(Flag.FIRE).getWithDefault().getChatValue(), p); return "§e" + BauSystem.MESSAGE.parse(Flag.FIRE.getChatValue(), p) + "§8: " + BauSystem.MESSAGE.parse(region.getRegionData().get(Flag.FIRE).getWithDefault().getChatValue(), p);
} }
@@ -235,6 +235,7 @@ public class FreezeListener implements Listener, ScoreboardElement {
@Override @Override
public String get(Region region, Player p) { public String get(Region region, Player p) {
if (region.getRegionData().has(Flag.FREEZE).notVisibleInScoreboard()) return null;
if (region.getRegionData().get(Flag.FREEZE).isWithDefault(FreezeMode.INACTIVE)) return null; if (region.getRegionData().get(Flag.FREEZE).isWithDefault(FreezeMode.INACTIVE)) return null;
return "§e" + BauSystem.MESSAGE.parse(Flag.FREEZE.getChatValue(), p) + "§8: " + BauSystem.MESSAGE.parse(region.getRegionData().get(Flag.FREEZE).getWithDefault().getChatValue(), p); return "§e" + BauSystem.MESSAGE.parse(Flag.FREEZE.getChatValue(), p) + "§8: " + BauSystem.MESSAGE.parse(region.getRegionData().get(Flag.FREEZE).getWithDefault().getChatValue(), p);
} }
@@ -54,6 +54,7 @@ public class ItemsListener implements Listener, ScoreboardElement {
@Override @Override
public String get(Region region, Player p) { public String get(Region region, Player p) {
if (region.getRegionData().has(Flag.ITEMS).notVisibleInScoreboard()) return null;
if (region.getRegionData().get(Flag.ITEMS).isWithDefault(ItemMode.INACTIVE)) return null; if (region.getRegionData().get(Flag.ITEMS).isWithDefault(ItemMode.INACTIVE)) return null;
return "§e" + BauSystem.MESSAGE.parse(Flag.ITEMS.getChatValue(), p) + "§8: " + BauSystem.MESSAGE.parse(region.getRegionData().get(Flag.ITEMS).getWithDefault().getChatValue(), p); return "§e" + BauSystem.MESSAGE.parse(Flag.ITEMS.getChatValue(), p) + "§8: " + BauSystem.MESSAGE.parse(region.getRegionData().get(Flag.ITEMS).getWithDefault().getChatValue(), p);
} }
@@ -54,6 +54,7 @@ public class NoGravityListener implements Listener, ScoreboardElement {
@Override @Override
public String get(Region region, Player p) { public String get(Region region, Player p) {
if (region.getRegionData().has(Flag.NO_GRAVITY).notVisibleInScoreboard()) return null;
if (region.getRegionData().get(Flag.NO_GRAVITY).isWithDefault(NoGravityMode.INACTIVE)) return null; if (region.getRegionData().get(Flag.NO_GRAVITY).isWithDefault(NoGravityMode.INACTIVE)) return null;
return "§e" + BauSystem.MESSAGE.parse(Flag.NO_GRAVITY.getChatValue(), p) + "§8: " + BauSystem.MESSAGE.parse(region.getRegionData().get(Flag.NO_GRAVITY).getWithDefault().getChatValue(), p); return "§e" + BauSystem.MESSAGE.parse(Flag.NO_GRAVITY.getChatValue(), p) + "§8: " + BauSystem.MESSAGE.parse(region.getRegionData().get(Flag.NO_GRAVITY).getWithDefault().getChatValue(), p);
} }
@@ -70,6 +70,7 @@ public class ProtectListener implements Listener, ScoreboardElement {
@Override @Override
public String get(Region region, Player p) { public String get(Region region, Player p) {
if (region.getRegionData().has(Flag.PROTECT).notVisibleInScoreboard()) return null;
if (region.getRegionData().get(Flag.PROTECT).isWithDefault(ProtectMode.INACTIVE)) return null; if (region.getRegionData().get(Flag.PROTECT).isWithDefault(ProtectMode.INACTIVE)) return null;
return "§e" + BauSystem.MESSAGE.parse(Flag.PROTECT.getChatValue(), p) + "§8: " + BauSystem.MESSAGE.parse(region.getRegionData().get(Flag.PROTECT).getWithDefault().getChatValue(), p); return "§e" + BauSystem.MESSAGE.parse(Flag.PROTECT.getChatValue(), p) + "§8: " + BauSystem.MESSAGE.parse(region.getRegionData().get(Flag.PROTECT).getWithDefault().getChatValue(), p);
} }
@@ -30,6 +30,7 @@ import de.steamwar.bausystem.BauSystem;
import de.steamwar.bausystem.features.util.SelectCommand; import de.steamwar.bausystem.features.util.SelectCommand;
import de.steamwar.bausystem.region.Point; import de.steamwar.bausystem.region.Point;
import de.steamwar.bausystem.region.Region; import de.steamwar.bausystem.region.Region;
import de.steamwar.bausystem.region.RegionHistory;
import de.steamwar.bausystem.region.RegionUtils; import de.steamwar.bausystem.region.RegionUtils;
import de.steamwar.bausystem.region.flags.Flag; import de.steamwar.bausystem.region.flags.Flag;
import de.steamwar.bausystem.shared.Pair; import de.steamwar.bausystem.shared.Pair;
@@ -71,7 +72,10 @@ public class RegionCommand extends SWCommand {
@Register(value = "undo", description = "REGION_REGION_HELP_UNDO") @Register(value = "undo", description = "REGION_REGION_HELP_UNDO")
public void undoCommand(@Validator Player p) { public void undoCommand(@Validator Player p) {
Region region = Region.getRegion(p.getLocation()); Region region = Region.getRegion(p.getLocation());
if (checkGlobalRegion(region, p)) return; if (region.getHistory() == RegionHistory.EMPTY) {
BauSystem.MESSAGE.send("REGION_REGION_NO_REGION", p);
return;
}
if (region.getHistory().undo()) { if (region.getHistory().undo()) {
RegionUtils.message(region, "REGION_REGION_UNDID"); RegionUtils.message(region, "REGION_REGION_UNDID");
@@ -83,7 +87,8 @@ public class RegionCommand extends SWCommand {
@Register(value = "redo", description = "REGION_REGION_HELP_REDO") @Register(value = "redo", description = "REGION_REGION_HELP_REDO")
public void redoCommand(@Validator Player p) { public void redoCommand(@Validator Player p) {
Region region = Region.getRegion(p.getLocation()); Region region = Region.getRegion(p.getLocation());
if (checkGlobalRegion(region, p)) { if (region.getHistory() == RegionHistory.EMPTY) {
BauSystem.MESSAGE.send("REGION_REGION_NO_REGION", p);
return; return;
} }
@@ -100,32 +105,10 @@ public class RegionCommand extends SWCommand {
if(checkGlobalRegion(region, p)) return; if(checkGlobalRegion(region, p)) return;
try { try {
PasteBuilder pasteBuilder = new PasteBuilder(new PasteBuilder.FileProvider(region.getArea().getResetFile())) PasteBuilder pasteBuilder = new PasteBuilder(PasteBuilder.ClipboardProvider.EMPTY)
.ignoreAir(true) .ignoreAir(true)
.color(region.getRegionData().get(Flag.COLOR).getWithDefault()); .color(region.getRegionData().get(Flag.COLOR).getWithDefault());
region.getArea().reset(pasteBuilder, false); region.getArea().reset(p.getLocation(), pasteBuilder, false);
RegionUtils.message(region, "REGION_REGION_RESTORED");
} catch (SecurityException e) {
BauSystem.MESSAGE.send("REGION_REGION_FAILED_RESTORE", p);
Bukkit.getLogger().log(Level.WARNING, "Failed restore", e);
}
}
@Register(value = "restore", description = "REGION_REGION_HELP_RESTORE_SCHEMATIC")
public void schematicRestoreCommand(@Validator Player p, SchematicNode node) {
Region region = Region.getRegion(p.getLocation());
if (checkGlobalRegion(region, p)) return;
if(node.isDir()) {
BauSystem.MESSAGE.send("ONLY_SCHEMS", p);
return;
}
try {
PasteBuilder pasteBuilder = new PasteBuilder(new PasteBuilder.SchematicProvider(node))
.ignoreAir(true)
.color(region.getRegionData().get(Flag.COLOR).getWithDefault());
region.getArea().reset(pasteBuilder, false);
RegionUtils.message(region, "REGION_REGION_RESTORED"); RegionUtils.message(region, "REGION_REGION_RESTORED");
} catch (SecurityException e) { } catch (SecurityException e) {
BauSystem.MESSAGE.send("REGION_REGION_FAILED_RESTORE", p); BauSystem.MESSAGE.send("REGION_REGION_FAILED_RESTORE", p);
@@ -29,9 +29,6 @@ import de.steamwar.bausystem.utils.PasteBuilder;
import de.steamwar.command.SWCommand; import de.steamwar.command.SWCommand;
import de.steamwar.linkage.Linked; import de.steamwar.linkage.Linked;
import de.steamwar.linkage.LinkedInstance; import de.steamwar.linkage.LinkedInstance;
import de.steamwar.sql.Punishment;
import de.steamwar.sql.SchematicNode;
import de.steamwar.sql.SteamwarUser;
import org.bukkit.Bukkit; import org.bukkit.Bukkit;
import org.bukkit.entity.Player; import org.bukkit.entity.Player;
@@ -52,9 +49,9 @@ public class ResetCommand extends SWCommand {
Region region = regionCheck(p); Region region = regionCheck(p);
if (region == null) return; if (region == null) return;
try { try {
PasteBuilder pasteBuilder = new PasteBuilder(new PasteBuilder.FileProvider(region.getArea().getResetFile())) PasteBuilder pasteBuilder = new PasteBuilder(PasteBuilder.ClipboardProvider.EMPTY)
.color(region.getRegionData().get(Flag.COLOR).getWithDefault()); .color(region.getRegionData().get(Flag.COLOR).getWithDefault());
region.getArea().reset(pasteBuilder, false); region.getArea().reset(p.getLocation(), pasteBuilder, false);
region.getRegionData().clear(); region.getRegionData().clear();
RegionUtils.message(region, "REGION_RESET_RESETED"); RegionUtils.message(region, "REGION_RESET_RESETED");
} catch (SecurityException e) { } catch (SecurityException e) {
@@ -63,35 +60,6 @@ public class ResetCommand extends SWCommand {
} }
} }
@Register(description = "REGION_RESET_HELP_SCHEMATIC")
public void schematicResetCommand(@Validator Player p, SchematicNode node) {
Region region = regionCheck(p);
if (region == null) return;
if (!p.getUniqueId().equals(bauServer.getOwner())) {
if (Punishment.isPunished(SteamwarUser.get(bauServer.getOwner()), Punishment.PunishmentType.NoSchemReceiving, punishment -> BauSystem.MESSAGE.send("REGION_TB_NO_SCHEMRECEIVING", p, punishment.getEndTime()))) {
return;
}
if (Punishment.isPunished(SteamwarUser.get(p.getUniqueId()), Punishment.PunishmentType.NoSchemSharing, punishment -> BauSystem.MESSAGE.send("REGION_TB_NO_SCHEMSHARING", p, punishment.getEndTime()))) {
return;
}
}
if (node.isDir()) {
BauSystem.MESSAGE.send("ONLY_SCHEMS", p);
return;
}
try {
PasteBuilder pasteBuilder = new PasteBuilder(new PasteBuilder.SchematicProvider(node))
.color(region.getRegionData().get(Flag.COLOR).getWithDefault());
region.getArea().reset(pasteBuilder, true);
RegionUtils.message(region, "REGION_RESET_RESETED");
} catch (SecurityException e) {
BauSystem.MESSAGE.send("REGION_RESET_ERROR", p);
Bukkit.getLogger().log(Level.WARNING, "Failed reset", e);
}
}
private Region regionCheck(Player player) { private Region regionCheck(Player player) {
Region region = Region.getRegion(player.getLocation()); Region region = Region.getRegion(player.getLocation());
if (region == RegionSystem.INSTANCE.getGlobalRegion()) { if (region == RegionSystem.INSTANCE.getGlobalRegion()) {
@@ -21,13 +21,16 @@ package de.steamwar.bausystem.features.region;
import de.steamwar.bausystem.BauSystem; import de.steamwar.bausystem.BauSystem;
import de.steamwar.bausystem.region.Region; import de.steamwar.bausystem.region.Region;
import de.steamwar.bausystem.region.RegionSystem;
import de.steamwar.bausystem.region.RegionUtils; import de.steamwar.bausystem.region.RegionUtils;
import de.steamwar.bausystem.region.flags.Flag; import de.steamwar.bausystem.region.flags.Flag;
import de.steamwar.bausystem.region.flags.TNTMode; import de.steamwar.bausystem.region.flags.TNTMode;
import de.steamwar.bausystem.utils.ScoreboardElement; import de.steamwar.bausystem.utils.ScoreboardElement;
import de.steamwar.linkage.Linked; import de.steamwar.linkage.Linked;
import org.bukkit.Material; import org.bukkit.Material;
import org.bukkit.NamespacedKey;
import org.bukkit.block.Block; import org.bukkit.block.Block;
import org.bukkit.entity.Entity;
import org.bukkit.entity.Player; import org.bukkit.entity.Player;
import org.bukkit.entity.TNTPrimed; import org.bukkit.entity.TNTPrimed;
import org.bukkit.event.EventHandler; import org.bukkit.event.EventHandler;
@@ -35,12 +38,26 @@ import org.bukkit.event.EventPriority;
import org.bukkit.event.Listener; import org.bukkit.event.Listener;
import org.bukkit.event.block.BlockExplodeEvent; import org.bukkit.event.block.BlockExplodeEvent;
import org.bukkit.event.entity.EntityExplodeEvent; import org.bukkit.event.entity.EntityExplodeEvent;
import org.bukkit.event.entity.EntitySpawnEvent;
import org.bukkit.persistence.PersistentDataType;
import java.util.List; import java.util.List;
import java.util.Objects;
import java.util.UUID;
@Linked @Linked
public class TNTListener implements Listener, ScoreboardElement { public class TNTListener implements Listener, ScoreboardElement {
private static final NamespacedKey SPAWN_REGION_ID = Objects.requireNonNull(NamespacedKey.fromString("spawn_region_id", BauSystem.getInstance()));
@EventHandler
public void onEntitySpawn(EntitySpawnEvent event) {
Entity entity = event.getEntity();
if (!(entity instanceof TNTPrimed)) return;
Region region = Region.getRegion(entity.getLocation());
entity.getPersistentDataContainer().set(SPAWN_REGION_ID, PersistentDataType.STRING, region.getID().toString());
}
private void explode(List<Block> blockList, boolean destroy) { private void explode(List<Block> blockList, boolean destroy) {
blockList.removeIf(block -> { blockList.removeIf(block -> {
Region region = Region.getRegion(block.getLocation()); Region region = Region.getRegion(block.getLocation());
@@ -71,6 +88,25 @@ public class TNTListener implements Listener, ScoreboardElement {
event.blockList().clear(); event.blockList().clear();
return; return;
} }
Entity entity = event.getEntity();
Region currentRegion = Region.getRegion(entity.getLocation());
String spawningRegionIdString = entity.getPersistentDataContainer().get(SPAWN_REGION_ID, PersistentDataType.STRING);
UUID spawningRegionId = spawningRegionIdString == null ? null : UUID.fromString(spawningRegionIdString);
// TNT Only created block damage in the Region it spawned in or in a connected special region!
if (!currentRegion.getID().equals(spawningRegionId)) {
if (!currentRegion.getType().isSpecial()) {
event.blockList().clear();
return;
}
if (RegionSystem.INSTANCE.getConnectedRegions(currentRegion)
.noneMatch(region -> region.getID().equals(spawningRegionId))) {
event.blockList().clear();
return;
}
}
explode(event.blockList(), true); explode(event.blockList(), true);
} }
@@ -86,6 +122,7 @@ public class TNTListener implements Listener, ScoreboardElement {
@Override @Override
public String get(Region region, Player p) { public String get(Region region, Player p) {
if (region.getRegionData().has(Flag.TNT).notVisibleInScoreboard()) return null;
if (region.getRegionData().get(Flag.TNT).isWithDefault(TNTMode.ALLOW)) return null; if (region.getRegionData().get(Flag.TNT).isWithDefault(TNTMode.ALLOW)) return null;
return "§e" + BauSystem.MESSAGE.parse(Flag.TNT.getChatValue(), p) + "§8: " + BauSystem.MESSAGE.parse(region.getRegionData().get(Flag.TNT).getWithDefault().getChatValue(), p); return "§e" + BauSystem.MESSAGE.parse(Flag.TNT.getChatValue(), p) + "§8: " + BauSystem.MESSAGE.parse(region.getRegionData().get(Flag.TNT).getWithDefault().getChatValue(), p);
} }
@@ -21,9 +21,11 @@ package de.steamwar.bausystem.features.region;
import de.steamwar.bausystem.BauSystem; import de.steamwar.bausystem.BauSystem;
import de.steamwar.bausystem.config.BauServer; import de.steamwar.bausystem.config.BauServer;
import de.steamwar.bausystem.region.Point;
import de.steamwar.bausystem.region.Region; import de.steamwar.bausystem.region.Region;
import de.steamwar.bausystem.region.RegionUtils; import de.steamwar.bausystem.region.RegionUtils;
import de.steamwar.bausystem.region.flags.Flag; import de.steamwar.bausystem.region.flags.Flag;
import de.steamwar.bausystem.region.flags.TestblockMode;
import de.steamwar.bausystem.region.utils.RegionExtensionType; import de.steamwar.bausystem.region.utils.RegionExtensionType;
import de.steamwar.bausystem.utils.PasteBuilder; import de.steamwar.bausystem.utils.PasteBuilder;
import de.steamwar.command.PreviousArguments; import de.steamwar.command.PreviousArguments;
@@ -106,13 +108,7 @@ public class TestblockCommand extends SWCommand {
region.getRegionData().setTestblockSchematic(node); region.getRegionData().setTestblockSchematic(node);
} }
PasteBuilder.ClipboardProvider clipboardProvider; PasteBuilder.ClipboardProvider clipboardProvider = node == null ? PasteBuilder.ClipboardProvider.EMPTY : PasteBuilder.ClipboardProvider.schematic(node);
if (node == null) {
clipboardProvider = new PasteBuilder.FileProvider(region.getTestblockArea().getResetFile());
} else {
clipboardProvider = new PasteBuilder.SchematicProvider(node);
}
try { try {
PasteBuilder pasteBuilder = new PasteBuilder(clipboardProvider) PasteBuilder pasteBuilder = new PasteBuilder(clipboardProvider)
.ignoreAir(ignoreAir) .ignoreAir(ignoreAir)
@@ -120,7 +116,7 @@ public class TestblockCommand extends SWCommand {
.removeTNT(removeTNT) .removeTNT(removeTNT)
.removeWater(removeWater) .removeWater(removeWater)
.color(region.getRegionData().get(Flag.COLOR).getWithDefault()); .color(region.getRegionData().get(Flag.COLOR).getWithDefault());
region.getTestblockArea().reset(pasteBuilder, regionExtensionType == RegionExtensionType.EXTENSION); region.getTestblockArea().reset(p.getLocation(), pasteBuilder, regionExtensionType == RegionExtensionType.EXTENSION);
RegionUtils.message(region, "REGION_TB_DONE"); RegionUtils.message(region, "REGION_TB_DONE");
} catch (SecurityException e) { } catch (SecurityException e) {
BauSystem.MESSAGE.send("REGION_TB_ERROR", p); BauSystem.MESSAGE.send("REGION_TB_ERROR", p);
@@ -173,6 +169,17 @@ public class TestblockCommand extends SWCommand {
private Region regionCheck(Player player) { private Region regionCheck(Player player) {
Region region = Region.getRegion(player.getLocation()); Region region = Region.getRegion(player.getLocation());
if (region.getRegionData().has(Flag.TESTBLOCK).isWritable() && region.getRegionData().get(Flag.TESTBLOCK).isWithDefault(TestblockMode.NO_VALUE)) {
Point minPoint = region.getArea().getMinPoint(false);
Point maxPoint = region.getArea().getMaxPoint(false);
// TODO: Check if empty!
int half = minPoint.getZ() + (maxPoint.getZ() - minPoint.getZ()) / 2;
if (player.getLocation().getBlockZ() <= half) {
region.getRegionData().set(Flag.TESTBLOCK, TestblockMode.SOUTH);
} else {
region.getRegionData().set(Flag.TESTBLOCK, TestblockMode.NORTH);
}
}
if (region.getTestblockArea().isEmpty()) { if (region.getTestblockArea().isEmpty()) {
BauSystem.MESSAGE.send("REGION_TB_NO_REGION", player); BauSystem.MESSAGE.send("REGION_TB_NO_REGION", player);
return null; return null;
@@ -0,0 +1,49 @@
/*
* This file is a part of the SteamWar software.
*
* Copyright (C) 2025 SteamWar.de-Serverteam
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package de.steamwar.bausystem.features.region;
import de.steamwar.bausystem.BauSystem;
import de.steamwar.bausystem.region.Region;
import de.steamwar.bausystem.region.flags.Flag;
import de.steamwar.bausystem.region.flags.TestblockMode;
import de.steamwar.bausystem.utils.ScoreboardElement;
import de.steamwar.linkage.Linked;
import org.bukkit.entity.Player;
@Linked
public class TestblockScoreboardElement implements ScoreboardElement {
@Override
public ScoreboardGroup getGroup() {
return ScoreboardGroup.REGION;
}
@Override
public int order() {
return 6;
}
@Override
public String get(Region region, Player p) {
if (region.getRegionData().has(Flag.TESTBLOCK).notVisibleInScoreboard()) return null;
if (region.getRegionData().get(Flag.TESTBLOCK).is(TestblockMode.NO_VALUE)) return null;
return "§e" + BauSystem.MESSAGE.parse(Flag.TESTBLOCK.getChatValue(), p) + "§8: " + BauSystem.MESSAGE.parse(region.getRegionData().get(Flag.TESTBLOCK).getWithDefault().getChatValue(), p);
}
}
@@ -51,6 +51,7 @@ public class WaterDestroyListener implements Listener, ScoreboardElement {
@Override @Override
public String get(Region region, Player p) { public String get(Region region, Player p) {
if (region.getRegionData().has(Flag.WATER_DESTROY).notVisibleInScoreboard()) return null;
if (region.getRegionData().get(Flag.WATER_DESTROY).isWithDefault(WaterDestroyMode.ALLOW)) return null; if (region.getRegionData().get(Flag.WATER_DESTROY).isWithDefault(WaterDestroyMode.ALLOW)) return null;
return "§e" + BauSystem.MESSAGE.parse(Flag.WATER_DESTROY.getChatValue(), p) + "§8: " + BauSystem.MESSAGE.parse(region.getRegionData().get(Flag.WATER_DESTROY).getWithDefault().getChatValue(), p); return "§e" + BauSystem.MESSAGE.parse(Flag.WATER_DESTROY.getChatValue(), p) + "§8: " + BauSystem.MESSAGE.parse(region.getRegionData().get(Flag.WATER_DESTROY).getWithDefault().getChatValue(), p);
} }
@@ -50,16 +50,8 @@ public class ResetBauGuiItem extends BauGuiItem {
@Override @Override
public boolean click(ClickType click, Player p) { public boolean click(ClickType click, Player p) {
if (click == ClickType.LEFT) {
p.closeInventory(); p.closeInventory();
resetCommand.genericResetCommand(p); resetCommand.genericResetCommand(p);
} else {
SchematicSelector selector = new SchematicSelector(p, SchematicSelector.selectSchematic(), node -> {
p.closeInventory();
resetCommand.schematicResetCommand(p, node);
});
selector.open();
}
return false; return false;
} }
@@ -33,12 +33,12 @@ public class StabFinalizer extends StabStep {
@Override @Override
protected void start() { protected void start() {
try { try {
PasteBuilder.ClipboardProvider clipboardProvider = new PasteBuilder.ClipboardProviderImpl(data.clipboard); PasteBuilder.ClipboardProvider clipboardProvider = PasteBuilder.ClipboardProvider.clipboard(data.clipboard);
PasteBuilder pasteBuilder = new PasteBuilder(clipboardProvider); PasteBuilder pasteBuilder = new PasteBuilder(clipboardProvider);
if (data.region.getRegionData().has(Flag.COLOR).isReadable()) { if (data.region.getRegionData().has(Flag.COLOR).isReadable()) {
pasteBuilder.color(data.region.getRegionData().get(Flag.COLOR).getWithDefault()); pasteBuilder.color(data.region.getRegionData().get(Flag.COLOR).getWithDefault());
} }
data.region.getTestblockArea().reset(pasteBuilder, true); data.region.getTestblockArea().reset(null, pasteBuilder, true);
} catch (SecurityException e) { } catch (SecurityException e) {
stop(); stop();
throw e; throw e;
@@ -71,12 +71,12 @@ public class StabGenerator extends StabStep implements Listener {
@Override @Override
protected void start() { protected void start() {
try { try {
PasteBuilder.ClipboardProvider clipboardProvider = new PasteBuilder.ClipboardProviderImpl(data.clipboard); PasteBuilder.ClipboardProvider clipboardProvider = PasteBuilder.ClipboardProvider.clipboard(data.clipboard);
PasteBuilder pasteBuilder = new PasteBuilder(clipboardProvider); PasteBuilder pasteBuilder = new PasteBuilder(clipboardProvider);
if (data.region.getRegionData().has(Flag.COLOR).isReadable()) { if (data.region.getRegionData().has(Flag.COLOR).isReadable()) {
pasteBuilder.color(data.region.getRegionData().get(Flag.COLOR).getWithDefault()); pasteBuilder.color(data.region.getRegionData().get(Flag.COLOR).getWithDefault());
} }
data.region.getTestblockArea().reset(pasteBuilder, true); data.region.getTestblockArea().reset(null, pasteBuilder, true);
} catch (SecurityException e) { } catch (SecurityException e) {
stop(); stop();
throw e; throw e;
@@ -22,6 +22,7 @@ package de.steamwar.bausystem.features.warp;
import de.steamwar.bausystem.region.RegionSystem; import de.steamwar.bausystem.region.RegionSystem;
import de.steamwar.bausystem.worlddata.WorldData; import de.steamwar.bausystem.worlddata.WorldData;
import lombok.Getter; import lombok.Getter;
import lombok.Setter;
import org.bukkit.*; import org.bukkit.*;
import org.bukkit.entity.Player; import org.bukkit.entity.Player;
import org.bukkit.event.player.PlayerTeleportEvent; import org.bukkit.event.player.PlayerTeleportEvent;
@@ -35,14 +36,20 @@ public class Warp {
private static Map<String, Warp> warpMap = new HashMap<>(); private static Map<String, Warp> warpMap = new HashMap<>();
public static void enable() { public static void enable() {
Warp worldSpawn = new Warp("WorldSpawn"); Warp worldSpawn = new Warp("WorldSpawn") {
worldSpawn.setLocation(RegionSystem.INSTANCE.getWorldSpawn()); @Override
public Location getLocation() {
return RegionSystem.INSTANCE.getWorldSpawn();
}
};
worldSpawn.setMat(Material.NETHER_STAR); worldSpawn.setMat(Material.NETHER_STAR);
warpMap.put("WorldSpawn", worldSpawn); warpMap.put("WorldSpawn", worldSpawn);
} }
private String name; private String name;
@Setter
private Location location; private Location location;
@Setter
private Material mat; private Material mat;
private Warp(String name) { private Warp(String name) {
@@ -91,21 +98,13 @@ public class Warp {
return warpMap.get(name); return warpMap.get(name);
} }
public void setMat(Material mat) {
this.mat = mat;
}
public void setLocation(Location location) {
this.location = location;
}
public void delete() { public void delete() {
warpMap.remove(name); warpMap.remove(name);
WorldData.getWarpData().remove(name); WorldData.getWarpData().remove(name);
} }
public void teleport(Player player) { public void teleport(Player player) {
player.teleport(location, PlayerTeleportEvent.TeleportCause.PLUGIN); player.teleport(getLocation(), PlayerTeleportEvent.TeleportCause.PLUGIN);
player.playSound(location, Sound.ENTITY_ENDERMAN_TELEPORT, SoundCategory.PLAYERS, 1, 1); player.playSound(getLocation(), Sound.ENTITY_ENDERMAN_TELEPORT, SoundCategory.PLAYERS, 1, 1);
} }
} }
@@ -119,11 +119,8 @@ public class BauScoreboard implements Listener {
@Override @Override
public String getTitle() { public String getTitle() {
Region region = Region.getRegion(player.getLocation()); Region region = Region.getRegion(player.getLocation());
if (region.getType().isGlobal()) return "§eSteam§8War"; if (region.getRegionData().has(Flag.COLOR).notVisibleInScoreboard()) return "§eSteam§8War";
String colorCode = "§e"; String colorCode = "§" + region.getRegionData().get(Flag.COLOR).getWithDefault().getColorCode();
if (region.getRegionData().has(Flag.COLOR).isReadable()) {
colorCode = "§" + region.getRegionData().get(Flag.COLOR).orElse(ColorMode.PINK).getColorCode();
}
return colorCode + "■ §eSteam§8War " + colorCode + "■"; // ■ return colorCode + "■ §eSteam§8War " + colorCode + "■"; // ■
} }
}); });
@@ -28,6 +28,7 @@ import org.bukkit.scheduler.BukkitRunnable;
import java.util.Iterator; import java.util.Iterator;
import java.util.Optional; import java.util.Optional;
import java.util.stream.Collectors;
@Linked @Linked
public class BackupScheduler implements Enable { public class BackupScheduler implements Enable {
@@ -43,6 +44,7 @@ public class BackupScheduler implements Enable {
Iterator<Region> regionsToBackup = RegionSystem.INSTANCE.getRegions() Iterator<Region> regionsToBackup = RegionSystem.INSTANCE.getRegions()
.filter(region -> region.getRegionData().has(Flag.CHANGED).isReadable()) .filter(region -> region.getRegionData().has(Flag.CHANGED).isReadable())
.filter(region -> region.getRegionData().get(Flag.CHANGED).isWithDefault(ChangedMode.HAS_CHANGE)) .filter(region -> region.getRegionData().get(Flag.CHANGED).isWithDefault(ChangedMode.HAS_CHANGE))
.collect(Collectors.toList())
.iterator(); .iterator();
if (!regionsToBackup.hasNext()) return; if (!regionsToBackup.hasNext()) return;
doBackup(regionsToBackup); doBackup(regionsToBackup);
@@ -48,14 +48,26 @@ public class Point {
return new Point(blockVector3.getBlockX(), blockVector3.getBlockY(), blockVector3.getBlockZ()); return new Point(blockVector3.getBlockX(), blockVector3.getBlockY(), blockVector3.getBlockZ());
} }
public Point setY(int y) {
return new Point(this.x, y, this.z);
}
public Point add(int x, int y, int z) { public Point add(int x, int y, int z) {
return new Point(this.x + x, this.y + y, this.z + z); return new Point(this.x + x, this.y + y, this.z + z);
} }
public Point add(Point point) {
return add(point.x, point.y, point.z);
}
public Point subtract(int x, int y, int z) { public Point subtract(int x, int y, int z) {
return new Point(this.x - x, this.y - y, this.z - z); return new Point(this.x - x, this.y - y, this.z - z);
} }
public Point subtract(Point point) {
return subtract(point.x, point.y, point.z);
}
public Point divide(int factor) { public Point divide(int factor) {
return new Point(x / factor, y / factor, z / factor); return new Point(x / factor, y / factor, z / factor);
} }
@@ -24,16 +24,16 @@ import de.steamwar.bausystem.utils.FlatteningWrapper;
import de.steamwar.bausystem.utils.PasteBuilder; import de.steamwar.bausystem.utils.PasteBuilder;
import de.steamwar.sql.GameModeConfig; import de.steamwar.sql.GameModeConfig;
import lombok.NonNull; import lombok.NonNull;
import org.bukkit.Bukkit;
import org.bukkit.Location; import org.bukkit.Location;
import org.bukkit.Material; import org.bukkit.Material;
import javax.annotation.Nullable; import javax.annotation.Nullable;
import java.io.File;
import java.util.UUID; import java.util.UUID;
import java.util.function.BiConsumer; import java.util.function.BiConsumer;
import java.util.stream.Stream; import java.util.stream.Stream;
public interface Region { public interface Region extends RegionDataStore {
static Stream<Region> getRegions() { static Stream<Region> getRegions() {
return RegionSystem.INSTANCE.getRegions(); return RegionSystem.INSTANCE.getRegions();
@@ -76,6 +76,9 @@ public interface Region {
interface Area { interface Area {
int WORLD_MIN_Y = Bukkit.getWorlds().get(0).getMinHeight();
int WORLD_MAX_Y = Bukkit.getWorlds().get(0).getMaxHeight();
Area EMPTY = new Area() { Area EMPTY = new Area() {
@Override @Override
public boolean isEmpty() { public boolean isEmpty() {
@@ -108,12 +111,7 @@ public interface Region {
} }
@Override @Override
public File getResetFile() { public void place(Location location, PasteBuilder pasteBuilder, boolean extension) {
return null;
}
@Override
public void reset(PasteBuilder pasteBuilder, boolean extension) {
} }
@Override @Override
@@ -148,15 +146,24 @@ public interface Region {
return true; return true;
} }
default boolean inRegion(int x, int z, boolean extension) {
Point minPoint = getMinPoint(extension);
Point maxPoint = getMaxPoint(extension);
if (x < minPoint.getX() || x > maxPoint.getX()) return false;
if (z < minPoint.getZ() || z > maxPoint.getZ()) return false;
return true;
}
@Nullable @Nullable
default Clipboard copy(boolean extension) { default Clipboard copy(boolean extension) {
return FlatteningWrapper.impl.copy(getMinPoint(extension), getMaxPoint(extension), getCopyPoint()); return FlatteningWrapper.impl.copy(getMinPoint(extension), getMaxPoint(extension), getCopyPoint());
} }
@Nullable default void reset(Location location, PasteBuilder pasteBuilder, boolean extension) {
File getResetFile(); place(location, pasteBuilder, extension);
}
void reset(PasteBuilder pasteBuilder, boolean extension); void place(Location location, PasteBuilder pasteBuilder, boolean extension);
default void forEachChunk(BiConsumer<Integer, Integer> executor) { default void forEachChunk(BiConsumer<Integer, Integer> executor) {
Point minPoint = getMinPoint(false); Point minPoint = getMinPoint(false);
@@ -22,14 +22,19 @@ package de.steamwar.bausystem.region;
import lombok.Getter; import lombok.Getter;
import lombok.NonNull; import lombok.NonNull;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import org.bukkit.Location;
import javax.annotation.CheckReturnValue; import javax.annotation.CheckReturnValue;
import javax.annotation.Nullable; import javax.annotation.Nullable;
import java.time.format.DateTimeFormatter;
import java.util.List; import java.util.List;
import java.util.Optional; import java.util.Optional;
import java.util.function.Function;
public interface RegionBackups { public interface RegionBackups {
DateTimeFormatter FORMATTER = DateTimeFormatter.ofPattern("yyyy.MM.dd' 'HH:mm:ss");
@RequiredArgsConstructor @RequiredArgsConstructor
enum BackupType { enum BackupType {
MANUAL(5), MANUAL(5),
@@ -39,22 +44,44 @@ public interface RegionBackups {
public final int maxBackups; public final int maxBackups;
} }
@RequiredArgsConstructor
@Getter @Getter
abstract class Backup { abstract class Backup implements RegionDataStore, Comparable<Backup> {
@NonNull @NonNull
private final BackupType type; protected final BackupType type;
@NonNull @NonNull
private final String name; protected final String name;
@NonNull @NonNull
private final RegionData data; protected final RegionData regionData;
protected Backup(@NonNull BackupType type, @NonNull String name, @NonNull Function<Backup, RegionData> regionDataConstructor) {
this.type = type;
this.name = name;
regionData = regionDataConstructor.apply(this);
}
@CheckReturnValue @CheckReturnValue
public abstract boolean load(); public abstract boolean load();
public abstract void delete(); @Override
public final void save() {
}
public abstract long getCreationTime();
@Override
public int compareTo(Backup o) {
return Long.compare(getCreationTime(), o.getCreationTime());
}
@Override
public final void load(RegionData regionData) {
}
@SuppressWarnings("java:S3038") // This forces everybody to implement 'delete' for Backups!
@Override
public abstract void delete(Location location);
} }
@CheckReturnValue @CheckReturnValue
@@ -64,7 +91,7 @@ public interface RegionBackups {
List<Backup> list(); List<Backup> list();
@Nullable @Nullable
Backup get(String name); Backup get(@NonNull String name);
RegionBackups EMPTY = new RegionBackups() { RegionBackups EMPTY = new RegionBackups() {
@Override @Override
@@ -79,7 +106,7 @@ public interface RegionBackups {
@Nullable @Nullable
@Override @Override
public Backup get(String name) { public Backup get(@NonNull String name) {
return null; return null;
} }
}; };
@@ -22,81 +22,42 @@ package de.steamwar.bausystem.region;
import de.steamwar.bausystem.region.flags.Flag; import de.steamwar.bausystem.region.flags.Flag;
import de.steamwar.sql.SchematicNode; import de.steamwar.sql.SchematicNode;
import lombok.NonNull; import lombok.NonNull;
import yapion.hierarchy.types.YAPIONObject;
import java.util.ArrayList; import java.util.*;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.function.Function; import java.util.function.Function;
import java.util.stream.Stream;
public abstract class RegionData { public abstract class RegionData {
protected RegionDataStore store;
private final List<Property<?, ?>> properties = new ArrayList<>(); private final List<Property<?, ?>> properties = new ArrayList<>();
protected final YAPIONObject data;
protected final YAPIONObject flagData;
protected final Runnable onChange;
protected final Map<Flag<?>, Flag.Value<?>> flagMap = new HashMap<>(); protected final Map<Flag<?>, Flag.Value<?>> flagMap = new HashMap<>();
// TODO: These should be turned into an ECS like System because not every Region has them or should have them!
protected final Property<SchematicNode, Integer> testblockSchematic = new Property<>("testblockSchematic", SchematicNode::byId, SchematicNode::getId);
private final class Property<T, K> { protected RegionData(RegionDataStore store) {
private final String field; this.store = store;
private final Function<K, T> loader;
private final Function<T, K> writer;
private T value;
public Property(String field, Function<K, T> loader, Function<T, K> writer) {
this.field = field;
this.loader = loader;
this.writer = writer;
properties.add(this);
}
public void load() {
if (flagData.containsKey(field)) {
value = loader.apply(flagData.getPlainValue(field));
} else {
value = null;
}
}
public T get() {
return value;
}
public void set(T value) {
this.value = value;
if (value == null) {
flagData.remove(field);
} else {
flagData.put(field, writer.apply(value));
}
}
}
private Property<SchematicNode, Integer> testblockSchematic = new Property<>("testblockSchematic", SchematicNode::byId, SchematicNode::getId);
protected RegionData(YAPIONObject data, Runnable onChange) {
this.data = data;
this.flagData = data.getObjectOrSetDefault("flagStorage", new YAPIONObject());
this.onChange = onChange;
initialize(); initialize();
for (final Flag flag : Flag.getFlags()) { store.load(this);
if (!has(flag).isWritable()) continue;
try {
String s = flagData.getPlainValue(flag.name());
flagMap.put(flag, flag.valueOfValue(s));
} catch (Exception e) {
flagMap.put(flag, (Flag.Value<?>) flag.getDefaultValue());
} }
}
properties.forEach(Property::load); public final void setStore(RegionDataStore store) {
this.store = store;
store.save();
} }
protected void initialize() { protected void initialize() {
} }
/**
* All connected Regions are required to have the same type as their RegionData.
*/
protected Stream<Region> connectedRegions() {
return Stream.empty();
}
@NonNull @NonNull
public abstract <T extends Enum<T> & Flag.Value<T>> RegionFlagPolicy has(@NonNull Flag<T> flag); public abstract <T extends Enum<T> & Flag.Value<T>> RegionFlagPolicy has(@NonNull Flag<T> flag);
@@ -105,11 +66,15 @@ public abstract class RegionData {
*/ */
public final <T extends Enum<T> & Flag.Value<T>> boolean set(@NonNull Flag<T> flag, @NonNull T value) { public final <T extends Enum<T> & Flag.Value<T>> boolean set(@NonNull Flag<T> flag, @NonNull T value) {
if (has(flag).isWritable()) { if (has(flag).isWritable()) {
if (flagMap.put(flag, value) != value) { boolean needsSave = flagMap.put(flag, value) != value;
flagData.put(flag.name(), value.name()); if (needsSave) store.save();
onChange.run();
return true; connectedRegions().forEach(region -> {
if (region.getRegionData().flagMap.put(flag, value) != value) {
region.save();
} }
});
return needsSave;
} }
return false; return false;
} }
@@ -120,33 +85,94 @@ public abstract class RegionData {
} }
public final void clear() { public final void clear() {
Set<Flag> remove = new HashSet<>();
for (Flag flag : Flag.getFlags()) { for (Flag flag : Flag.getFlags()) {
if (has(flag).isWritable()) { if (has(flag).isWritable()) {
flagMap.remove(flag); flagMap.remove(flag);
flagData.remove(flag.name()); remove.add(flag);
} }
} }
initialize();
properties.forEach(property -> property.set(null)); properties.forEach(property -> property.set(null));
onChange.run(); store.save();
connectedRegions().forEach(region -> {
region.getRegionData().flagMap.keySet().removeAll(remove);
region.getRegionData().initialize();
region.getRegionData().properties.forEach(property -> property.set(null));
region.save();
});
}
/**
* This method only copies all flags and properties from this into other without saving other afterward.
* TODO: If {@link #connectedRegions()} is overridden this method will not work correctly!
*/
public final void copyInto(RegionData other) {
if (this == other) return;
other.flagMap.clear();
other.flagMap.putAll(flagMap);
// TODO: This might not be correct, needs to be investigated
other.properties.clear();
other.properties.addAll(properties);
} }
public final Map<Flag<?>, Flag.Value<?>> getBackedMap() { public final Map<Flag<?>, Flag.Value<?>> getBackedMap() {
return flagMap; return flagMap;
} }
public SchematicNode getTestblockSchematic() { public final List<Property<Object, Object>> getBackedProperties() {
return (List) properties;
}
public final SchematicNode getTestblockSchematic() {
return testblockSchematic.get(); return testblockSchematic.get();
} }
public void setTestblockSchematic(SchematicNode schematic) { public final void setTestblockSchematic(SchematicNode schematic) {
testblockSchematic.set(schematic); testblockSchematic.set(schematic);
onChange.run(); store.save();
} }
@Override @Override
public final String toString() { public final String toString() {
return getClass().getSimpleName() + "{" + StringBuilder st = new StringBuilder();
"flagMap=" + flagMap + st.append(getClass().getSimpleName()).append("{");
'}'; st.append("flagMap=").append(flagMap);
for (Property<?, ?> p : properties) {
st.append(p);
}
st.append("}");
return st.toString();
}
public final class Property<T, K> {
public final String field;
public final Function<K, T> loader;
public final Function<T, K> writer;
private T value;
private Property(String field, Function<K, T> loader, Function<T, K> writer) {
this.field = field;
this.loader = loader;
this.writer = writer;
properties.add(this);
}
public T get() {
return value;
}
public void set(T value) {
this.value = value;
}
@Override
public String toString() {
Object value = this.value;
if (value != null) value = writer.apply((T) value);
return ", " + field + "=" + value;
}
} }
} }
@@ -1,7 +1,7 @@
/* /*
* This file is a part of the SteamWar software. * This file is a part of the SteamWar software.
* *
* Copyright (C) 2026 SteamWar.de-Serverteam * Copyright (C) 2025 SteamWar.de-Serverteam
* *
* This program is free software: you can redistribute it and/or modify * 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 * it under the terms of the GNU Affero General Public License as published by
@@ -17,11 +17,13 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
package de.steamwar.logger package de.steamwar.bausystem.region;
import de.steamwar.sql.AuditLog import org.bukkit.Location;
import de.steamwar.sql.SteamwarUser
object SWLogger { public interface RegionDataStore {
fun startCommand(user: SteamwarUser) = LogEntry(AuditLog.Type.COMMAND, user) void save();
void load(RegionData regionData);
default void delete(Location location) {
}
} }
@@ -35,4 +35,8 @@ public enum RegionFlagPolicy {
public boolean isApplicable() { public boolean isApplicable() {
return readable || writable; return readable || writable;
} }
public boolean notVisibleInScoreboard() {
return !writable;
}
} }
@@ -19,6 +19,8 @@
package de.steamwar.bausystem.region; package de.steamwar.bausystem.region;
import de.steamwar.bausystem.BauSystem;
import de.steamwar.core.Core;
import lombok.NonNull; import lombok.NonNull;
import org.bukkit.Location; import org.bukkit.Location;
@@ -26,10 +28,15 @@ import javax.annotation.CheckReturnValue;
import java.lang.reflect.InvocationTargetException; import java.lang.reflect.InvocationTargetException;
import java.util.Optional; import java.util.Optional;
import java.util.UUID; import java.util.UUID;
import java.util.logging.Logger;
import java.util.stream.Stream; import java.util.stream.Stream;
public interface RegionSystem { public interface RegionSystem {
Logger LOGGER = BauSystem.getInstance().getLogger();
UUID GLOBAL_REGION_ID = new UUID(0, 0);
RegionSystem INSTANCE = init(); RegionSystem INSTANCE = init();
/** /**
@@ -37,11 +44,6 @@ public interface RegionSystem {
*/ */
void load(); void load();
/**
* Saves anything that should be written to file on either CRIUSleepEvent or plugin disable.
*/
void save();
/** /**
* Returns the Location to teleport players to when they first join or Warp to "WorldSpawn" * Returns the Location to teleport players to when they first join or Warp to "WorldSpawn"
*/ */
@@ -74,22 +76,34 @@ public interface RegionSystem {
@NonNull @NonNull
Stream<Region> getRegions(); Stream<Region> getRegions();
/**
* Only contains Regions of the same Type as the one you inputted.
*/
@NonNull
Stream<Region> getConnectedRegions(Region region);
private static RegionSystem init() { private static RegionSystem init() {
if (Core.getVersion() >= 21) {
// TODO: Add some kind of detection if the DynamicRegionSystem should be used!
try {
return (RegionSystem) Class.forName("de.steamwar.bausystem.region.DynamicRegionSystem").getConstructor().newInstance();
} catch (ClassNotFoundException | NoSuchMethodException | InstantiationException | IllegalAccessException |
InvocationTargetException e) {
// Ignore
}
}
try { try {
return (RegionSystem) Class.forName("de.steamwar.bausystem.region.FixedRegionSystem").getConstructor().newInstance(); return (RegionSystem) Class.forName("de.steamwar.bausystem.region.FixedRegionSystem").getConstructor().newInstance();
} catch (ClassNotFoundException | NoSuchMethodException | InstantiationException | IllegalAccessException | } catch (ClassNotFoundException | NoSuchMethodException | InstantiationException | IllegalAccessException |
InvocationTargetException e) { InvocationTargetException e) {
// Ignore
}
return new RegionSystem() { return new RegionSystem() {
@Override @Override
public void load() { public void load() {
throw new UnsupportedOperationException(); throw new UnsupportedOperationException();
} }
@Override
public void save() {
throw new UnsupportedOperationException();
}
@Override @Override
public @NonNull Location getWorldSpawn() { public @NonNull Location getWorldSpawn() {
throw new UnsupportedOperationException(); throw new UnsupportedOperationException();
@@ -114,7 +128,11 @@ public interface RegionSystem {
public Stream<Region> getRegions() { public Stream<Region> getRegions() {
throw new UnsupportedOperationException(); throw new UnsupportedOperationException();
} }
@Override
public @NonNull Stream<Region> getConnectedRegions(Region region) {
throw new UnsupportedOperationException();
}
}; };
} }
} }
}
@@ -26,9 +26,50 @@ import lombok.RequiredArgsConstructor;
@Getter @Getter
public enum RegionType { public enum RegionType {
GLOBAL(true), GLOBAL(ConnectionType.Global),
NORMAL(false), /**
* This should not be used by the DynamicRegionSystem
*/
NORMAL(ConnectionType.Closed),
SPAWN(ConnectionType.Closed),
SPAWN_PATH(ConnectionType.Path),
SPAWN_EXTENSION(ConnectionType.Closed),
PATH(ConnectionType.Path),
DRY(ConnectionType.Closed),
DRY_SPECIAL(ConnectionType.Closed),
WET(ConnectionType.Water),
WET_SPECIAL(ConnectionType.Water),
; ;
private final boolean global; private final ConnectionType connectionType;
public boolean isGlobal() {
return this == GLOBAL;
}
public boolean isDeletable() {
return this == NORMAL || this == SPAWN_EXTENSION || this == PATH || this == DRY || this == DRY_SPECIAL || this == WET || this == WET_SPECIAL;
}
public boolean isSpecial() {
return this == DRY_SPECIAL || this == WET_SPECIAL;
}
public boolean isSpawn() {
return this == SPAWN || this == SPAWN_PATH || this == SPAWN_EXTENSION;
}
public boolean isPath() {
return this == PATH || this == SPAWN_PATH;
}
public enum ConnectionType {
Closed,
Path,
Water,
Global,
Garden,
}
} }
@@ -28,6 +28,7 @@ import org.bukkit.entity.Player;
import java.util.function.Consumer; import java.util.function.Consumer;
import java.util.function.Function; import java.util.function.Function;
import java.util.stream.Stream;
@UtilityClass @UtilityClass
public class RegionUtils { public class RegionUtils {
@@ -58,10 +59,12 @@ public class RegionUtils {
} }
public void forEachInRegion(Region region, Consumer<Player> consumer) { public void forEachInRegion(Region region, Consumer<Player> consumer) {
Bukkit.getOnlinePlayers() Stream<? extends Player> players = Bukkit.getOnlinePlayers().stream();
.stream() if (region.getType().isGlobal()) {
.filter(player -> region.getArea().inRegion(player.getLocation(), false)) players = players.filter(player -> Region.getRegion(player.getLocation()).getType().isGlobal());
.filter(player -> !region.getType().isGlobal() || Region.getRegion(player.getLocation()).getType().isGlobal()) } else {
.forEach(consumer); players = players.filter(player -> region.getArea().inRegion(player.getLocation(), false));
}
players.forEach(consumer);
} }
} }
@@ -42,7 +42,7 @@ import java.util.function.BiPredicate;
@Getter @Getter
public class PasteBuilder { public class PasteBuilder {
private final ClipboardProvider clipboardProvider; private ClipboardProvider clipboardProvider;
private Point pastPoint; private Point pastPoint;
private boolean rotate; private boolean rotate;
private boolean ignoreAir; private boolean ignoreAir;
@@ -53,10 +53,20 @@ public class PasteBuilder {
private List<BiPredicate<BaseBlock, String>> predicates = new ArrayList<>(); private List<BiPredicate<BaseBlock, String>> predicates = new ArrayList<>();
private List<BiConsumer<Clipboard, BlockVector3>> mappers = new ArrayList<>(); private List<BiConsumer<Clipboard, BlockVector3>> mappers = new ArrayList<>();
public PasteBuilder() {
clipboardProvider = ClipboardProvider.EMPTY;
}
public PasteBuilder(@NonNull ClipboardProvider clipboardProvider) { public PasteBuilder(@NonNull ClipboardProvider clipboardProvider) {
this.clipboardProvider = clipboardProvider; this.clipboardProvider = clipboardProvider;
} }
public PasteBuilder with(@NonNull ClipboardProvider clipboardProvider) {
if (this.clipboardProvider != ClipboardProvider.EMPTY) return this;
this.clipboardProvider = clipboardProvider;
return this;
}
public PasteBuilder pastePoint(Point point) { public PasteBuilder pastePoint(Point point) {
this.pastPoint = point; this.pastPoint = point;
return this; return this;
@@ -92,6 +102,16 @@ public class PasteBuilder {
return this; return this;
} }
private PasteBuilder predicates(List<BiPredicate<BaseBlock, String>> predicates) {
this.predicates = predicates;
return this;
}
public PasteBuilder mappers(List<BiConsumer<Clipboard, BlockVector3>> mappers) {
this.mappers = mappers;
return this;
}
public PasteBuilder only(BiPredicate<BaseBlock, String> predicate) { public PasteBuilder only(BiPredicate<BaseBlock, String> predicate) {
predicates.add(predicate); predicates.add(predicate);
return this; return this;
@@ -182,13 +202,27 @@ public class PasteBuilder {
} }
public EditSession run() { public EditSession run() {
if (pastPoint == null) { if (pastPoint != null || minPoint != null) {
throw new IllegalStateException("pastePoint is null");
}
return FlatteningWrapper.impl.paste(this); return FlatteningWrapper.impl.paste(this);
} }
throw new IllegalStateException("pastePoint is null");
}
public interface ClipboardProvider { public interface ClipboardProvider {
ClipboardProvider EMPTY = new EmptyProvider();
static ClipboardProvider file(File file) {
return new FileProvider(file);
}
static ClipboardProvider schematic(SchematicNode schematic) {
return new SchematicProvider(schematic);
}
static ClipboardProvider clipboard(Clipboard clipboard) {
return new ClipboardProviderImpl(clipboard);
}
Clipboard getClipboard(); Clipboard getClipboard();
default <T extends ClipboardProvider> boolean is(Class<T> clazz) { default <T extends ClipboardProvider> boolean is(Class<T> clazz) {
@@ -200,12 +234,22 @@ public class PasteBuilder {
} }
} }
private static class EmptyProvider implements ClipboardProvider {
private EmptyProvider() {
}
@Override
public Clipboard getClipboard() {
return null;
}
}
@Getter @Getter
public static class FileProvider implements ClipboardProvider { public static class FileProvider implements ClipboardProvider {
private final File file; private final File file;
private final Clipboard clipboard; private final Clipboard clipboard;
public FileProvider(File file) { private FileProvider(File file) {
this.file = file; this.file = file;
this.clipboard = FlatteningWrapper.impl.loadSchematic(file); this.clipboard = FlatteningWrapper.impl.loadSchematic(file);
} }
@@ -221,7 +265,7 @@ public class PasteBuilder {
private final SchematicNode schematic; private final SchematicNode schematic;
private final Clipboard clipboard; private final Clipboard clipboard;
public SchematicProvider(SchematicNode schematic) { private SchematicProvider(SchematicNode schematic) {
this.schematic = schematic; this.schematic = schematic;
try { try {
this.clipboard = new SchematicData(schematic).load(); this.clipboard = new SchematicData(schematic).load();
@@ -240,7 +284,7 @@ public class PasteBuilder {
public static class ClipboardProviderImpl implements ClipboardProvider { public static class ClipboardProviderImpl implements ClipboardProvider {
private final Clipboard clipboard; private final Clipboard clipboard;
public ClipboardProviderImpl(Clipboard clipboard) { private ClipboardProviderImpl(Clipboard clipboard) {
this.clipboard = clipboard; this.clipboard = clipboard;
} }
@@ -0,0 +1,49 @@
/*
* This file is a part of the SteamWar software.
*
* Copyright (C) 2024 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/>.
*/
plugins {
steamwar.java
}
tasks.compileJava {
options.isWarnings = false
}
java {
sourceCompatibility = JavaVersion.VERSION_17
targetCompatibility = JavaVersion.VERSION_17
}
dependencies {
compileOnly(libs.classindex)
annotationProcessor(libs.classindex)
compileOnly(project(":BauSystem:BauSystem_Main", "default"))
compileOnly(project(":SpigotCore", "default"))
compileOnly(libs.spigotapi)
compileOnly(libs.axiom)
compileOnly(libs.authlib)
compileOnly(libs.viaapi)
compileOnly(libs.nms20)
compileOnly(libs.fawe18)
implementation(libs.luaj)
}
@@ -0,0 +1,77 @@
/*
* 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.features.region;
import de.steamwar.bausystem.BauSystem;
import de.steamwar.bausystem.region.Point;
import de.steamwar.bausystem.region.Region;
import de.steamwar.bausystem.region.RegionUtils;
import de.steamwar.bausystem.region.flags.Flag;
import de.steamwar.bausystem.region.flags.TestblockMode;
import de.steamwar.bausystem.utils.PasteBuilder;
import de.steamwar.command.SWCommand;
import org.bukkit.Bukkit;
import org.bukkit.entity.Player;
import java.util.logging.Level;
public class WireframeCommand extends SWCommand {
public WireframeCommand() {
super("wireframe");
}
@Register
public void wireframeCommand(@Validator Player p) {
Region region = regionCheck(p);
if (region == null) return;
try {
PasteBuilder pasteBuilder = new PasteBuilder(PasteBuilder.ClipboardProvider.EMPTY)
.ignoreAir(true)
.color(region.getRegionData().get(Flag.COLOR).getWithDefault());
region.getBuildArea().reset(p.getLocation(), pasteBuilder, false);
RegionUtils.message(region, "REGION_TB_DONE");
} catch (SecurityException e) {
BauSystem.MESSAGE.send("REGION_TB_ERROR", p);
Bukkit.getLogger().log(Level.WARNING, "Failed testblock", e);
}
}
private Region regionCheck(Player player) {
Region region = Region.getRegion(player.getLocation());
if (region.getRegionData().has(Flag.TESTBLOCK).isWritable() && region.getRegionData().get(Flag.TESTBLOCK).isWithDefault(TestblockMode.NO_VALUE)) {
Point minPoint = region.getArea().getMinPoint(false);
Point maxPoint = region.getArea().getMaxPoint(false);
// TODO: Check if empty!
int half = minPoint.getZ() + (maxPoint.getZ() - minPoint.getZ()) / 2;
if (player.getLocation().getBlockZ() <= half) {
region.getRegionData().set(Flag.TESTBLOCK, TestblockMode.SOUTH);
} else {
region.getRegionData().set(Flag.TESTBLOCK, TestblockMode.NORTH);
}
}
if (region.getTestblockArea().isEmpty()) {
BauSystem.MESSAGE.send("REGION_TB_NO_REGION", player);
return null;
}
return region;
}
}
@@ -0,0 +1,9 @@
{
"flags": {
"TNT": "DENY",
"FREEZE": "INACTIVE"
},
"properties": {
"testblockSchematic": 0
}
}
@@ -0,0 +1,6 @@
{
"region_identifier": "SpawnRegion",
"tiles": [
{ "tile_x": 0, "tile_z": 0 }
]
}
@@ -0,0 +1,50 @@
/*
* 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 <https://www.gnu.org/licenses/>.
*/
package de.steamwar.bausystem.region;
import de.steamwar.bausystem.Permission;
import de.steamwar.bausystem.features.region.RegionCommand;
import de.steamwar.bausystem.region.dynamic.Tile;
import de.steamwar.command.AbstractSWCommand;
import de.steamwar.command.SWCommand;
import de.steamwar.core.SWPlayer;
import org.bukkit.entity.Player;
@AbstractSWCommand.PartOf(RegionCommand.class)
public class DynamicRegionCommand extends SWCommand {
public DynamicRegionCommand() {
super("");
}
@Register({"dynamic"})
public void visualizeRegions(Player player) {
Tile tile = Tile.fromLocation(player.getLocation()).orElse(null);
if (tile == null) return;
SWPlayer swPlayer = SWPlayer.of(player);
if (swPlayer.hasComponent(DynamicRegionVisualizer.class)) {
swPlayer.removeComponent(DynamicRegionVisualizer.class);
} else if (Permission.SUPERVISOR.hasPermission(player)) {
swPlayer.setComponent(new DynamicRegionVisualizer());
} else {
// TODO: Add Message
}
}
}
@@ -0,0 +1,249 @@
/*
* 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 <https://www.gnu.org/licenses/>.
*/
package de.steamwar.bausystem.region;
import de.steamwar.bausystem.BauSystem;
import de.steamwar.bausystem.features.region.WireframeCommand;
import de.steamwar.bausystem.region.dynamic.*;
import de.steamwar.bausystem.region.dynamic.global.GlobalRegion;
import de.steamwar.bausystem.shared.Pair;
import lombok.NonNull;
import lombok.RequiredArgsConstructor;
import org.bukkit.Bukkit;
import org.bukkit.Location;
import org.bukkit.Material;
import org.jetbrains.annotations.NotNull;
import javax.annotation.Nullable;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.util.*;
import java.util.function.Function;
import java.util.stream.Collectors;
import java.util.stream.Stream;
public class DynamicRegionSystem implements RegionSystem {
private static final int TILE_SIZE_ADJUSTED = Tile.tileSize - 1;
public static DynamicRegionSystem INSTANCE;
private static final Map<Long, Region> regionCache = new LinkedHashMap<>(16, 0.75f, true) {
@Override
protected boolean removeEldestEntry(Map.Entry<Long, Region> eldest) {
return size() > 8192; // Tweak this number if needed!
}
}; // Will be cleared on region add/delete/remove!
private static final Map<UUID, Region> regionMap = new HashMap<>();
private static final Map<RegionType, Set<Region>> regionTypeMap = new EnumMap<>(RegionType.class);
public void add(DynamicRegion region) {
regionCache.clear();
regionMap.put(region.getID(), region);
regionTypeMap.computeIfAbsent(region.getType(), __ -> new HashSet<>()).add(region);
}
public void remove(DynamicRegion region) {
regionCache.clear();
regionMap.remove(region.getID());
regionTypeMap.getOrDefault(region.getType(), Collections.emptySet()).remove(region);
}
public static Map<Class<? extends DynamicRegion>, RegionConstructorData> constructorDataMap = new HashMap<>();
public static Map<String, Class<? extends DynamicRegion>> identifierDataMap = new HashMap<>();
@Override
public void load() {
INSTANCE = this;
// Loading all Region Constructor Data that are defined inside the Code
constructorDataMap = new BufferedReader(new InputStreamReader(BauSystem.getInstance().getClass().getResourceAsStream("/META-INF/annotations/de.steamwar.bausystem.region.dynamic.RegionConstructorData")))
.lines()
.map(s -> {
try {
return Class.forName(s, false, BauSystem.getInstance().getClass().getClassLoader());
} catch (ClassNotFoundException | NoClassDefFoundError e) {
throw new SecurityException(e.getMessage(), e);
}
})
.filter(DynamicRegion.class::isAssignableFrom)
.map(clazz -> (Class<? extends DynamicRegion>) clazz)
.collect(Collectors.toUnmodifiableMap(Function.identity(), clazz -> clazz.getAnnotation(RegionConstructorData.class)));
identifierDataMap = constructorDataMap.entrySet()
.stream()
.collect(Collectors.toUnmodifiableMap(entry -> entry.getValue().identifier(), Map.Entry::getKey));
DynamicRegionRepository.loadRegions();
new DynamicRegionCommand();
new WireframeCommand();
}
@Override
public @NonNull Location getWorldSpawn() {
return Bukkit.getWorlds().get(0).getSpawnLocation(); // TODO: Temporary
}
@Override
public @NonNull Region getGlobalRegion() {
return GlobalRegion.INSTANCE;
}
public Set<Tile> getTilesOfRegion(@NonNull Region region) {
Point minPoint = region.getArea().getMinPoint(false);
Point maxPoint = region.getArea().getMaxPoint(false);
Set<Tile> tiles = new HashSet<>();
for (int x = minPoint.getX(); x < maxPoint.getX(); x += Tile.tileSize) {
for (int z = minPoint.getZ(); z < maxPoint.getZ(); z += Tile.tileSize) {
tiles.add(Tile.fromXZ(x, z).orElse(null));
}
}
tiles.remove(null);
return Collections.unmodifiableSet(tiles);
}
public @NonNull Region get(@Nullable Tile tile) {
if (tile == null) return getGlobalRegion();
return get(tile.getCenterLocation().getBlockX(), tile.getCenterLocation().getBlockZ(), true, regionMap.values());
}
private Region get(int x, int z, boolean fastCache, Collection<Region> regions) {
Tile tile = Tile.fromXZ(x, z).orElse(null);
if (tile == null) {
return getGlobalRegion();
}
if (regionCache.containsKey(tile.getId())) {
Region region = regionCache.get(tile.getId());
if (fastCache || regions.contains(region)) return region;
}
Region region = regions.stream()
.filter(rg -> rg.getArea().inRegion(x, z, false))
.findFirst()
.orElseGet(this::getGlobalRegion);
if (fastCache || regions.contains(region)) {
regionCache.put(tile.getId(), region);
}
return region;
}
@Override
public @NonNull Region get(@NonNull Location location) {
return get(location.getBlockX(), location.getBlockZ(), true, regionMap.values());
}
@Override
public Optional<Region> getRegion(@NonNull UUID id) {
return Optional.ofNullable(regionMap.get(id));
}
@Override
public @NonNull Stream<Region> getRegions() {
return regionMap.values().stream();
}
public @NonNull Stream<Region> getRegionsByType(RegionType type) {
return regionTypeMap.getOrDefault(type, Collections.emptySet()).stream();
}
@RequiredArgsConstructor
public static class Neighbour<T extends Region> {
public final T region;
public final Tile tile;
public final NeighbourDirection direction;
public <O extends Region> Neighbour<O> as(Class<O> clazz) {
if (!clazz.isInstance(region)) {
return null;
} else {
return (Neighbour<O>) this;
}
}
@Override
public boolean equals(Object o) {
if (!(o instanceof Neighbour<?> neighbour)) return false;
return Objects.equals(tile, neighbour.tile) && direction == neighbour.direction;
}
@Override
public int hashCode() {
return Objects.hash(tile, direction);
}
}
private Stream<Neighbour<Region>> getNeighbours2(Region region, boolean noCorners, boolean fastCache, Collection<Region> regions) {
throw new UnsupportedOperationException();
}
private Stream<Pair<Region, NeighbourDirection>> getNeighbours(Region region, boolean noCorners, boolean fastCache, Collection<Region> regions) {
Point minPoint = region.getArea().getMinPoint(false).subtract(TILE_SIZE_ADJUSTED, 0, TILE_SIZE_ADJUSTED);
Point maxPoint = region.getArea().getMaxPoint(false).add(Tile.tileSize, 0, Tile.tileSize);
Set<Pair<Region, NeighbourDirection>> neighbours = new HashSet<>();
for (int x = minPoint.getX() + (noCorners ? TILE_SIZE_ADJUSTED : 0); x <= maxPoint.getX() - (noCorners ? Tile.tileSize : 0); x += Tile.tileSize) {
NeighbourDirection minZ = NeighbourDirection.North;
if (!noCorners) {
if (x == minPoint.getX()) minZ = NeighbourDirection.NorthWest;
if (x > maxPoint.getX() - TILE_SIZE_ADJUSTED) minZ = NeighbourDirection.NorthEast;
}
neighbours.add(new Pair<>(get(x, minPoint.getZ(), fastCache, regions), minZ));
NeighbourDirection maxZ = NeighbourDirection.South;
if (!noCorners) {
if (x == minPoint.getX()) maxZ = NeighbourDirection.SouthWest;
if (x > maxPoint.getX() - TILE_SIZE_ADJUSTED) maxZ = NeighbourDirection.SouthEast;
}
neighbours.add(new Pair<>(get(x, maxPoint.getZ(), fastCache, regions), maxZ));
}
for (int z = minPoint.getZ() + TILE_SIZE_ADJUSTED; z <= maxPoint.getZ() - Tile.tileSize; z += Tile.tileSize) {
neighbours.add(new Pair<>(get(minPoint.getX(), z, fastCache, regions), NeighbourDirection.West));
neighbours.add(new Pair<>(get(maxPoint.getX(), z, fastCache, regions), NeighbourDirection.East));
}
neighbours.removeIf(data -> data.getKey().getType().isGlobal());
return neighbours.stream();
}
public Stream<Pair<DynamicRegion, NeighbourDirection>> getNeighbours(Region region) {
return getNeighbours(region, false, true, regionMap.values())
.filter(data -> data.getKey() instanceof DynamicRegion)
.map(data -> (Pair<DynamicRegion, NeighbourDirection>) (Pair) data);
}
@Override
@NotNull
public Stream<Region> getConnectedRegions(Region region) {
Set<Region> regions = regionTypeMap.get(region.getType());
Set<Region> connected = new HashSet<>();
LinkedHashSet<Region> current = new LinkedHashSet<>();
current.add(region);
while (!current.isEmpty()) {
Region r = current.removeFirst();
if (!connected.add(r)) continue;
getNeighbours(r, true, false, regions)
.map(Pair::getKey)
.forEach(current::add);
}
return connected.stream();
}
}
@@ -0,0 +1,331 @@
/*
* 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;
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.utils.PasteBuilder;
import de.steamwar.core.SWPlayer;
import de.steamwar.entity.*;
import de.steamwar.inventory.SWInventory;
import de.steamwar.inventory.SWItem;
import de.steamwar.inventory.SWListInv;
import org.bukkit.Bukkit;
import org.bukkit.Location;
import org.bukkit.Material;
import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
import org.bukkit.event.Listener;
import org.bukkit.event.player.PlayerMoveEvent;
import java.util.*;
import java.util.stream.Collectors;
public class DynamicRegionVisualizer implements SWPlayer.Component, Listener {
private final REntityServer entityServer;
private Player player;
private Location sourceLocation;
private Tile sourceTile;
private Placement placement = null;
public DynamicRegionVisualizer() {
this.entityServer = new REntityServer();
}
@Override
public void onMount(SWPlayer player) {
this.player = player.getPlayer();
sourceTile = Tile.fromLocation(player.getLocation()).orElse(null);
if (sourceTile == null) {
player.removeComponent(DynamicRegionVisualizer.class);
return;
}
sourceLocation = player.getLocation().add(0, -5, 0).getBlock().getLocation();
entityServer.addPlayer(player.getPlayer());
Bukkit.getPluginManager().registerEvents(this, BauSystem.getInstance());
renderTiles(0, 0);
}
@Override
public void onUnmount(SWPlayer player) {
Bukkit.getPluginManager().registerEvents(this, BauSystem.getInstance());
entityServer.close();
}
@EventHandler
public void onPlayerMove(PlayerMoveEvent event) {
if (event.getPlayer() != player) return;
Location position = event.getTo().getBlock().getLocation();
int dx = position.getBlockX() - sourceLocation.getBlockX();
int dz = position.getBlockZ() - sourceLocation.getBlockZ();
renderTiles(dx, dz);
}
private void renderTiles(int dx, int dz) {
Set<Tile> tileSet = entityServer.getEntitiesByType(CTile.class)
.stream()
.filter(cTile -> {
if (Math.abs(cTile.tile.getTileX() - dx) > 40 || Math.abs(cTile.tile.getTileZ() - dz) > 40) {
cTile.die();
return false;
} else {
return true;
}
})
.map(cTile -> cTile.tile)
.collect(Collectors.toSet());
for (int x = dx - 20; x <= dx + 20; x++) {
for (int z = dz - 20; z <= dz + 20; z++) {
Tile tile = sourceTile.add(x, z).orElse(null);
if (tile == null || tileSet.contains(tile)) continue;
new CTile(entityServer, tile);
}
}
}
private void resetTiles(Set<Tile> tiles) {
entityServer.getEntitiesByType(CTile.class)
.stream()
.filter(cTile -> tiles.contains(cTile.tile))
.peek(CTile::die)
.map(cTile -> cTile.tile)
.forEach(tile -> {
new CTile(entityServer, tile);
});
if (placement != null) {
placement.check();
}
}
private class CTile extends CEntity {
private final Tile tile;
public CTile(REntityServer server, Tile tile) {
super(server);
this.tile = tile;
RegionType regionType = DynamicRegionSystem.INSTANCE.get(tile).getType();
Material material = switch (regionType) {
case SPAWN, SPAWN_EXTENSION -> Material.LODESTONE;
case SPAWN_PATH, PATH -> Material.DIRT_PATH;
case DRY, DRY_SPECIAL -> Material.IRON_BLOCK;
case WET, WET_SPECIAL -> Material.LAPIS_BLOCK;
default -> Material.WHITE_CARPET;
};
Location location = sourceLocation.clone().add(tile.getTileX() - sourceTile.getTileX(), 0, tile.getTileZ() - sourceTile.getTileZ());
if (tile.equals(Tile.ZERO)) {
CCubedTextDisplay spawn = new CCubedTextDisplay(entityServer, location);
spawn.setText("§eSPAWN");
spawn.setBackgroundColor(0);
spawn.setShadowed(false);
entities.add(spawn);
} else if (tile.equals(sourceTile)) {
CCubedTextDisplay origin = new CCubedTextDisplay(entityServer, location);
origin.setText("§eORIGIN");
origin.setBackgroundColor(0);
origin.setShadowed(false);
entities.add(origin);
}
RBlockDisplay blockDisplay = new RBlockDisplay(entityServer, location);
blockDisplay.setBlock(material.createBlockData());
entities.add(blockDisplay);
RInteraction interaction = new RInteraction(entityServer, location.clone().add(0.5, 0, 0.5));
interaction.setInteraction((player, entityAction) -> {
if (placement != null) {
placement.click(tile, regionType.isGlobal());
return;
}
if (!regionType.isGlobal()) {
SWInventory inv = new SWInventory(player, 9, "Delete Region: " + tile.display());
inv.setItem(0, new SWItem(SWItem.getDye(10), "§8Cancel", click -> {
player.closeInventory();
}));
inv.setItem(8, new SWItem(SWItem.getDye(1), "§cDelete", click -> {
player.closeInventory();
Region region = DynamicRegionSystem.INSTANCE.get(tile);
Set<Tile> tiles = DynamicRegionSystem.INSTANCE.getTilesOfRegion(region);
region.delete(tile.getCenterLocation());
SWPlayer.allWithSingleComponent(DynamicRegionVisualizer.class)
.forEach(pair -> {
pair.getComponent().resetTiles(tiles);
});
}));
inv.open();
} else {
List<SWListInv.SWListEntry<Map.Entry<Class<? extends DynamicRegion>, RegionConstructorData>>> entries = new ArrayList<>();
DynamicRegionSystem.constructorDataMap.entrySet()
.stream()
.filter(entry -> entry.getValue().placeable())
.sorted(Comparator.comparing(entry -> entry.getValue().name()))
.forEach(entry -> {
entries.add(new SWListInv.SWListEntry<>(new SWItem(entry.getValue().material(), entry.getValue().name()), entry));
});
SWListInv<Map.Entry<Class<? extends DynamicRegion>, RegionConstructorData>> listInv = new SWListInv<>(player, "Select Region for: " + tile.display(), entries, (click, entry) -> {
new Placement(entry.getKey(), entry.getValue(), tile);
player.closeInventory();
});
listInv.open();
}
});
entities.add(interaction);
}
}
private class Placement {
private final Class<? extends DynamicRegion> regionType;
private final RegionConstructorData constructorData;
private CWireframe wireframe;
private Tile sourceTile;
private int dx;
private int dz;
private boolean valid = false;
private Location getMinLocation() {
Tile tile = sourceTile.add(-DynamicRegionVisualizer.this.sourceTile.getTileX(), -DynamicRegionVisualizer.this.sourceTile.getTileZ()).orElse(null);
if (tile == null) tile = Tile.ZERO;
return sourceLocation.clone().add(tile.getTileX(), 0, tile.getTileZ());
}
private Location getMaxLocation() {
Tile tile = sourceTile.add(-DynamicRegionVisualizer.this.sourceTile.getTileX(), -DynamicRegionVisualizer.this.sourceTile.getTileZ()).orElse(null);
if (tile == null) tile = Tile.ZERO;
return sourceLocation.clone().add(tile.getTileX(), 0, tile.getTileZ()).add(dx, 0, dz);
}
public Placement(Class<? extends DynamicRegion> regionType, RegionConstructorData constructorData, Tile sourceTile) {
this.regionType = regionType;
this.constructorData = constructorData;
this.sourceTile = sourceTile;
dx = constructorData.widthX() / Tile.tileSize - 1;
dz = constructorData.widthZ() / Tile.tileSize - 1;
if (dx == 0 && dz == 0) {
place();
return;
}
placement = this;
wireframe = new CWireframe(entityServer);
check();
}
private void check() {
wireframe.setPos1And2(getMinLocation(), getMaxLocation());
for (int x = 0; x <= dx; x++) {
for (int z = 0; z <= dz; z++) {
Tile tile = sourceTile.add(x, z).orElse(null);
if (tile == null || !DynamicRegionSystem.INSTANCE.get(tile).getType().isGlobal()) {
wireframe.setBlock(Material.RED_CONCRETE.createBlockData());
valid = false;
return;
}
}
}
wireframe.setBlock(Material.LIME_CONCRETE.createBlockData());
valid = true;
}
public void click(Tile tile, boolean global) {
if (tile.getTileX() >= sourceTile.getTileX() && tile.getTileX() <= sourceTile.getTileX() + dx && tile.getTileZ() >= sourceTile.getTileZ() && tile.getTileZ() <= sourceTile.getTileZ() + dz) {
SWInventory inv = new SWInventory(player, 9, "Place Region: " + constructorData.name());
inv.setItem(0, new SWItem(SWItem.getDye(1), "§cDeselect", click -> {
placement = null;
wireframe.die();
player.closeInventory();
}));
if (valid) {
inv.setItem(8, new SWItem(SWItem.getDye(10), "§aPlace", click -> {
player.closeInventory();
place();
}));
} else {
inv.setItem(8, new SWItem(SWItem.getDye(8), "§8Place", click -> {
}));
}
inv.open();
return;
}
if (!global) {
return;
}
Set<Tile> tiles = new HashSet<>();
for (int x = 0; x <= dx; x++) {
for (int z = 0; z <= dz; z++) {
tiles.add(Tile.fromTile(x + sourceTile.getTileX(), z + sourceTile.getTileZ()).orElse(null));
}
}
tiles.remove(null);
Tile selected = tiles.stream().min(Comparator.comparing(current -> {
int dx = current.getTileX() - tile.getTileX();
int dz = current.getTileZ() - tile.getTileZ();
return dx * dx + dz * dz;
}))
.orElse(null);
if (selected == null) return;
int dx = tile.getTileX() - selected.getTileX();
int dz = tile.getTileZ() - selected.getTileZ();
Tile newSourceTile = sourceTile.add(dx, dz).orElse(null);
if (newSourceTile == null) return;
sourceTile = newSourceTile;
check();
}
private void place() {
DynamicRegion dynamicRegion = DynamicRegionRepository.constructRegion(regionType, sourceTile);
if (dynamicRegion == null) {
// TODO: Give error to user
return;
}
dynamicRegion.getArea().place(sourceTile.getCenterLocation(), new PasteBuilder(), false);
dynamicRegion.updateNeighbours();
placement = null;
if (wireframe != null) wireframe.die();
Set<Tile> tiles = new HashSet<>();
for (int x = 0; x <= dx; x++) {
for (int z = 0; z <= dz; z++) {
tiles.add(Tile.fromTile(x + sourceTile.getTileX(), z + sourceTile.getTileZ()).orElse(null));
}
}
tiles.remove(null);
SWPlayer.allWithSingleComponent(DynamicRegionVisualizer.class)
.forEach(pair -> pair.getComponent().resetTiles(tiles));
}
}
}
@@ -0,0 +1,210 @@
/*
* This file is a part of the SteamWar software.
*
* Copyright (C) 2025 SteamWar.de-Serverteam
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
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;
import de.steamwar.bausystem.region.RegionData;
import de.steamwar.bausystem.region.dynamic.path.PathArea;
import de.steamwar.bausystem.region.dynamic.path.PathRegion;
import de.steamwar.bausystem.shared.Pair;
import lombok.Getter;
import lombok.NonNull;
import org.bukkit.Location;
import java.io.IOException;
import java.util.*;
public abstract class DynamicRegion implements Region {
protected final UUID id;
@Getter
protected RegionData regionData = null;
/**
* 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 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 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<Pair<Tile, Tile>> readQuantizedTiles(JsonArray tileData) {
List<Pair<Tile, Tile>> 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<Pair<Tile, Tile>> list) throws IOException {
for (Pair<Tile, Tile> 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<Pair<PathRegion, NeighbourDirection>> list = DynamicRegionSystem.INSTANCE.getNeighbours(this)
.filter(data -> data.getKey() instanceof PathRegion)
.map(data -> (Pair<PathRegion, NeighbourDirection>) (Pair) data)
.toList();
// Calculate Garden State for all neighbouring PathRegions
Set<UUID> needsFullReset = new HashSet<>();
list.forEach(data -> {
boolean previousGardenState = data.getKey().isGarden();
data.getKey().calculateGardenState();
if (data.getKey().isGarden() != previousGardenState) {
needsFullReset.add(data.getKey().getID());
}
});
// 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!
} else {
data.getKey().update(this, data.getValue().opposite());
}
});
// All full reset regions need to update their neighbours!
needsFullReset.forEach(uuid -> {
Region region = DynamicRegionSystem.INSTANCE.getRegion(uuid).orElse(null);
if (!(region instanceof DynamicRegion dynamicRegion)) return;
DynamicRegionSystem.INSTANCE.getNeighbours(dynamicRegion)
.filter(data -> data.getKey() instanceof PathRegion)
.map(data -> (Pair<PathRegion, NeighbourDirection>) (Pair) data)
.forEach(data -> {
if (data.getKey().isGarden()) return;
data.getKey().update(dynamicRegion, data.getValue().opposite());
});
});
}
public void setRegionData(@NonNull RegionData regionData) {
this.regionData = regionData;
regionData.setStore(this);
}
@Override
public void delete(Location location) {
if (!getType().isDeletable()) return;
DynamicRegionSystem.INSTANCE.remove(this);
DynamicRegionRepository.deleteRegion(this);
Point minPoint = getArea().getMinPoint(false);
Point maxPoint = getArea().getMaxPoint(false);
PasteUtils.reset(minPoint, maxPoint);
this.updateNeighbours();
}
@Override
public @NonNull UUID getID() {
return id;
}
}
@@ -0,0 +1,329 @@
/*
* 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.*;
import com.google.gson.stream.JsonWriter;
import de.steamwar.bausystem.region.*;
import de.steamwar.bausystem.region.dynamic.path.PathRegion;
import de.steamwar.bausystem.region.flags.Flag;
import lombok.Cleanup;
import lombok.SneakyThrows;
import lombok.experimental.UtilityClass;
import org.bukkit.Bukkit;
import java.io.*;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
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.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_FILES_TILES = "tiles";
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.equals(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;
}
String identifier;
try {
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;
}
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;
}
constructRegion(regionClass, regionUUID, tileData);
}
}
public static DynamicRegion constructRegion(Class<? extends DynamicRegion> clazz, Tile tile) {
Constructor<? extends DynamicRegion> regionConstructor;
try {
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<? extends DynamicRegion> clazz, UUID uuid, JsonArray tileData) {
Constructor<? extends DynamicRegion> 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, tileData);
} catch (InstantiationException | IllegalAccessException | IllegalArgumentException |
InvocationTargetException e) {
RegionSystem.LOGGER.log(Level.SEVERE, "Failed to read region metadata file (invalid data)");
return null;
}
}
public static File getRegionDirectory(Region region) {
return new File(REGION_DATA_FOLDER, region.getID().toString());
}
public static File getBackupsTypeDirectory(Region region, RegionBackups.BackupType backupType) {
File regionDirectory = getRegionDirectory(region);
File backupsDirectory = new File(regionDirectory, BACKUPS_DIR_NAME);
return new File(backupsDirectory, backupType.name());
}
public static File getBackupDirectory(Region region, RegionBackups.Backup backup) {
return new File(getBackupsTypeDirectory(region, backup.getType()), backup.getName());
}
public static void loadRegionData(Region region, RegionData regionData) {
File regionDirectory = getRegionDirectory(region);
if (!regionDirectory.exists()) return;
loadRegionData(regionDirectory, regionData);
}
public static void loadRegionData(Region region, RegionBackups.Backup backup, RegionData regionData) {
File backupDirectory = getBackupDirectory(region, backup);
if (!backupDirectory.exists()) return;
loadRegionData(backupDirectory, regionData);
}
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) {
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 instanceof DynamicRegion dynamicRegion) {
RegionConstructorData constructorData = DynamicRegionSystem.constructorDataMap.get(region.getClass());
writeMetaFile(regionDirectory, constructorData, dynamicRegion);
}
writeFlagsFile(regionDirectory, region.getRegionData());
}
public static void saveBackup(Region region, RegionBackups.Backup backup) {
File backupDirectory = getBackupDirectory(region, backup);
if (!backupDirectory.exists()) {
backupDirectory.mkdirs();
}
writeFlagsFile(backupDirectory, backup.getRegionData());
}
@SneakyThrows
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_FILES_TILES);
jsonWriter.beginArray();
dynamicRegion.writeData(jsonWriter);
jsonWriter.endArray();
jsonWriter.endObject();
}
@SneakyThrows
private static void writeFlagsFile(File directory, RegionData regionData) {
JsonWriter jsonWriter = new JsonWriter(new FileWriter(new File(directory, 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();
jsonWriter.close();
}
public static void deleteRegion(Region region) {
deleteDir(getRegionDirectory(region));
}
public static void deleteBackup(Region region, RegionBackups.Backup backup) {
deleteDir(getBackupDirectory(region, backup));
}
@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;
}
});
}
}
@@ -0,0 +1,221 @@
/*
* 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.sk89q.worldedit.extent.clipboard.Clipboard;
import de.steamwar.bausystem.region.Point;
import de.steamwar.bausystem.region.Region;
import de.steamwar.bausystem.shared.Pair;
import lombok.NonNull;
import org.bukkit.Location;
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<Tile> tiles = new HashSet<>();
protected MultiTileArea() {
}
protected MultiTileArea(List<Pair<Tile, Tile>> list) {
for (Pair<Tile, Tile> 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());
}
}
}
}
/**
* @return {@code true} if the {@link MultiTileArea} did not already contain the {@link Tile}.
*/
public boolean addTile(Tile tile) {
return tiles.add(tile);
}
/**
* @return {@code true} if the {@link MultiTileArea} did not already contain any {@link Tile} passed as argument.
*/
public boolean addTiles(Set<Tile> tiles) {
return this.tiles.addAll(tiles);
}
/**
* @return {@code true} if the {@link MultiTileArea} no longer has any {@link Tile}'s associated with it.
*/
public boolean removeTile(Tile tile) {
tiles.remove(tile);
return tiles.isEmpty();
}
public List<Pair<Tile, Tile>> quantize() {
long time = System.currentTimeMillis();
Quantizer quantizer = new Quantizer(tiles);
List<Pair<Tile, Tile>> results = new ArrayList<>();
while (true) {
Pair<Tile, Tile> pair = quantizer.next();
if (pair == null) break;
results.add(pair);
}
time = System.currentTimeMillis() - time;
LOGGER.info(tiles.size() + ": " + results.size() + " in " + time + "ms");
return results;
}
@Override
public @NonNull Point getMinPoint(boolean extension) {
return Point.ZERO;
}
@Override
public @NonNull Point getMaxPoint(boolean extension) {
return Point.ZERO;
}
@Override
public @NonNull Point getCopyPoint() {
return Point.ZERO;
}
@Override
public boolean inRegion(Location location, boolean extension) {
return inRegion(location.getBlockX(), location.getBlockZ(), extension);
}
@Override
public boolean inRegion(int x, int z, boolean extension) {
Tile tile = Tile.fromXZ(x, z).orElse(null);
return tiles.contains(tile);
}
@Override
public @Nullable Clipboard copy(boolean extension) {
return null;
}
@Override
public void forEachChunk(BiConsumer<Integer, Integer> executor) {
}
@Override
public boolean isChunkOutside(int chunkX, int chunkZ) {
return true;
}
private static class Quantizer {
private Map<Tile, Integer> tileToNeighbourCount = new HashMap<>();
public Quantizer(Set<Tile> tiles) {
for (Tile tile : tiles) {
int count = (int) tile.neighboursRing()
.filter(tiles::contains)
.count();
tileToNeighbourCount.put(tile, count);
}
}
public Pair<Tile, Tile> next() {
Map.Entry<Tile, Integer> entry = tileToNeighbourCount.entrySet()
.stream()
.max(Map.Entry.comparingByValue())
.orElse(null);
if (entry == null) return null;
Tile minTile = entry.getKey();
Tile maxTile = entry.getKey();
tileToNeighbourCount.remove(entry.getKey());
while (true) {
Pair<Set<Tile>, Integer> neg_x = tryExpand(minTile, maxTile, -1, 0);
Pair<Set<Tile>, Integer> pos_x = tryExpand(minTile, maxTile, 1, 0);
Pair<Set<Tile>, Integer> neg_z = tryExpand(minTile, maxTile, 0, -1);
Pair<Set<Tile>, Integer> pos_z = tryExpand(minTile, maxTile, 0, 1);
if (neg_x.getValue() == 0 && pos_x.getValue() == 0 && neg_z.getValue() == 0 && pos_z.getValue() == 0) {
break;
}
Set<Tile> remove;
if (neg_x.getValue() >= pos_x.getValue() && neg_x.getValue() >= neg_z.getValue() && neg_x.getValue() >= pos_z.getValue()) {
minTile = minTile.add(-1, 0).orElseThrow();
remove = neg_x.getKey();
} else if (pos_x.getValue() >= neg_x.getValue() && pos_x.getValue() >= neg_z.getValue() && pos_x.getValue() >= pos_z.getValue()) {
maxTile = maxTile.add(1, 0).orElseThrow();
remove = pos_x.getKey();
} else if (neg_z.getValue() >= neg_x.getValue() && neg_z.getValue() >= pos_x.getValue() && neg_z.getValue() >= pos_z.getValue()) {
minTile = minTile.add(0, -1).orElseThrow();
remove = neg_z.getKey();
} else {
maxTile = maxTile.add(0, 1).orElseThrow();
remove = pos_z.getKey();
}
remove.forEach(tileToNeighbourCount::remove);
}
return new Pair<>(minTile, minTile.equals(maxTile) ? null : maxTile);
}
private static final Pair<Set<Tile>, Integer> EMPTY = new Pair<>(Collections.emptySet(), 0);
private Pair<Set<Tile>, Integer> tryExpand(Tile minTile, Tile maxTile, int dx, int dz) {
Set<Tile> expaned = new HashSet<>();
if (dx == 0) {
for (int x = minTile.getTileX(); x <= maxTile.getTileX(); x++) {
int z = (dz < 0 ? minTile.getTileZ() : maxTile.getTileZ()) + dz;
Tile tile = Tile.fromTile(x, z).orElse(null);
if (tile == null) return EMPTY;
if (!tileToNeighbourCount.containsKey(tile)) return EMPTY;
expaned.add(tile);
}
} else if (dz == 0) {
for (int z = minTile.getTileZ(); z <= maxTile.getTileZ(); z++) {
int x = (dx < 0 ? minTile.getTileX() : maxTile.getTileX()) + dx;
Tile tile = Tile.fromTile(x, z).orElse(null);
if (tile == null) return EMPTY;
if (!tileToNeighbourCount.containsKey(tile)) return EMPTY;
expaned.add(tile);
}
} else {
throw new IllegalStateException();
}
int total = expaned.stream()
.mapToInt(tileToNeighbourCount::get)
.sum();
return new Pair<>(expaned, total);
}
}
}
@@ -0,0 +1,59 @@
/*
* 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 de.steamwar.bausystem.region.dynamic.path.PathCorner;
import de.steamwar.bausystem.region.dynamic.path.PathSide;
import lombok.AllArgsConstructor;
import lombok.Getter;
import java.util.Set;
@AllArgsConstructor
@Getter
public enum NeighbourDirection {
North(0, -1, Set.of(PathSide.North), Set.of(PathCorner.NorthEast, PathCorner.NorthWest)),
South(0, 1, Set.of(PathSide.South), Set.of(PathCorner.SouthEast, PathCorner.SouthWest)),
West(-1, 0, Set.of(PathSide.West), Set.of(PathCorner.NorthWest, PathCorner.SouthWest)),
East(1, 0, Set.of(PathSide.East), Set.of(PathCorner.NorthEast, PathCorner.SouthEast)),
NorthWest(-1, -1, Set.of(), Set.of(PathCorner.NorthWest)),
NorthEast(1, -1, Set.of(), Set.of(PathCorner.NorthEast)),
SouthWest(-1, 1, Set.of(), Set.of(PathCorner.SouthWest)),
SouthEast(1, 1, Set.of(), Set.of(PathCorner.SouthEast)),
;
private final int tileOffsetX;
private final int tileOffsetZ;
private final Set<PathSide> sideUpdates;
private final Set<PathCorner> cornerUpdates;
public NeighbourDirection opposite() {
return switch (this) {
case North -> South;
case South -> North;
case East -> West;
case West -> East;
case NorthWest -> SouthEast;
case NorthEast -> SouthWest;
case SouthWest -> NorthEast;
case SouthEast -> NorthWest;
};
}
}
@@ -0,0 +1,62 @@
/*
* 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.sk89q.worldedit.EditSession;
import com.sk89q.worldedit.WorldEdit;
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 com.sk89q.worldedit.regions.CuboidRegion;
import com.sk89q.worldedit.world.block.BlockTypes;
import de.steamwar.bausystem.region.Point;
import de.steamwar.bausystem.utils.FlatteningWrapper;
import lombok.experimental.UtilityClass;
import org.bukkit.Bukkit;
import java.io.File;
@UtilityClass
public class PasteUtils {
public static void reset(Point minPoint, Point maxPoint) {
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();
}
public static EditSession paste(File file, Point minPoint, int rotate) {
try (Clipboard clipboard = FlatteningWrapper.impl.loadSchematic(file)) {
BlockVector3 offset = clipboard.getRegion().getMinimumPoint().subtract(clipboard.getOrigin());
BlockVector3 to = minPoint.toBlockVector3().subtract(offset);
return clipboard.paste(BukkitAdapter.adapt(Bukkit.getWorlds().get(0)), to, false, true, new AffineTransform().rotateY(rotate));
} catch (SecurityException exception) {
return null;
}
}
}
@@ -0,0 +1,40 @@
/*
* This file is a part of the SteamWar software.
*
* Copyright (C) 2025 SteamWar.de-Serverteam
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package de.steamwar.bausystem.region.dynamic;
import org.atteo.classindex.IndexAnnotated;
import org.bukkit.Material;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@IndexAnnotated
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface RegionConstructorData {
String identifier();
String name();
Material material();
int widthX();
int widthZ();
boolean placeable() default true;
}
@@ -0,0 +1,182 @@
/*
* This file is a part of the SteamWar software.
*
* Copyright (C) 2025 SteamWar.de-Serverteam
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package de.steamwar.bausystem.region.dynamic;
import de.steamwar.bausystem.region.Point;
import lombok.EqualsAndHashCode;
import lombok.Getter;
import org.bukkit.Location;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import java.util.stream.Stream;
@Getter
@EqualsAndHashCode
public class Tile {
public static final Tile ZERO = new Tile(0, 0);
public static final int tileSize = 21;
public static final int tileOffset = tileSize / 2;
public static final int maxTile = 255;
public static final int minTile = -maxTile;
public static final int tilesPerAxis = maxTile * 2 + 1;
private final int tileX;
private final int tileZ;
private Tile(int tileX, int tileZ) {
this.tileX = tileX;
this.tileZ = tileZ;
}
public static Optional<Tile> fromTile(int tileX, int tileZ) {
if (tileX < minTile || tileZ < minTile) return Optional.empty();
if (tileX > maxTile || tileZ > maxTile) return Optional.empty();
return Optional.of(new Tile(tileX, tileZ));
}
public static Optional<Tile> fromLocation(Location location) {
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);
return fromTile(x, z);
}
public static int getMinX(int tileX) {
return tileX * tileSize - tileOffset;
}
public int getMinX() {
return getMinX(tileX);
}
public static int getMinZ(int tileZ) {
return tileZ * tileSize - tileOffset;
}
public int getMinZ() {
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));
}
public Location getMinLocation() {
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<Tile> add(int offsetX, int offsetZ) {
return fromTile(tileX + offsetX, tileZ + offsetZ);
}
public static long getId(int tileX, int tileZ) {
return (tileX + maxTile) * tilesPerAxis + tileZ + maxTile;
}
public long getId() {
return getId(tileX, tileZ);
}
public Stream<Tile> neighboursPlus() {
List<Tile> tiles = new ArrayList<>();
add(-1, 0).ifPresent(tiles::add);
add(1, 0).ifPresent(tiles::add);
add(0, -1).ifPresent(tiles::add);
add(0, 1).ifPresent(tiles::add);
return tiles.stream();
}
public Stream<Tile> neighboursRing() {
List<Tile> tiles = new ArrayList<>();
add(-1, -1).ifPresent(tiles::add);
add(-1, 0).ifPresent(tiles::add);
add(-1, 1).ifPresent(tiles::add);
add(0, -1).ifPresent(tiles::add);
add(0, 1).ifPresent(tiles::add);
add(1, -1).ifPresent(tiles::add);
add(1, 0).ifPresent(tiles::add);
add(1, 1).ifPresent(tiles::add);
return tiles.stream();
}
@Override
public String toString() {
return tileX + ":" + tileZ;
}
public String display() {
StringBuilder st = new StringBuilder();
if (tileX >= 0) {
st.append("+");
}
st.append(tileX);
st.append(" / ");
if (tileZ >= 0) {
st.append("+");
}
st.append(tileZ);
return st.toString();
}
}
@@ -0,0 +1,96 @@
/*
* 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 lombok.NonNull;
import java.io.File;
import java.time.LocalDate;
import java.time.Month;
import java.util.Arrays;
import java.util.Comparator;
import java.util.Optional;
import java.util.Random;
public abstract class VariantSelector {
private static final Random RANDOM = new Random();
public static final VariantSelector EMPTY = new VariantSelector() {
@Override
public Optional<File> select() {
return Optional.empty();
}
};
private VariantSelector() {
}
public abstract Optional<File> select();
public final VariantSelector or(VariantSelector other) {
if (this == EMPTY) return other;
VariantSelector self = this;
return new VariantSelector() {
@Override
public Optional<File> select() {
return self.select().or(() -> other.select());
}
};
}
public final VariantSelector atDate(int day, Month month) {
if (this == EMPTY) return this;
VariantSelector self = this;
return new VariantSelector() {
@Override
public Optional<File> select() {
LocalDate date = LocalDate.now();
if (date.getDayOfMonth() == day && date.getMonth() == month) {
return self.select();
} 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<File> select() {
return Optional.of(file);
}
};
}
Arrays.sort(files, Comparator.comparing(File::getName));
final int filesCount = files.length;
return new VariantSelector() {
@Override
public Optional<File> select() {
return Optional.of(files[RANDOM.nextInt(filesCount)]);
}
};
}
}
@@ -0,0 +1,140 @@
/*
* This file is a part of the SteamWar software.
*
* Copyright (C) 2025 SteamWar.de-Serverteam
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
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;
import org.bukkit.Location;
import org.bukkit.Material;
import javax.annotation.Nullable;
import java.io.File;
import java.util.UUID;
import java.util.function.BiConsumer;
public class GlobalRegion implements Region {
public static final GlobalRegion INSTANCE = new GlobalRegion();
private static final Point MIN_POINT = new Point(Integer.MIN_VALUE, Integer.MIN_VALUE, Integer.MIN_VALUE);
private static final Point MAX_POINT = new Point(Integer.MAX_VALUE, Integer.MAX_VALUE, Integer.MAX_VALUE);
private static final Region.Area GLOBAL_AREA = new Region.Area() {
@Override
public @NonNull Point getMinPoint(boolean extension) {
return MIN_POINT;
}
@Override
public @NonNull Point getMaxPoint(boolean extension) {
return MAX_POINT;
}
@Override
public @NonNull Point getCopyPoint() {
return Point.ZERO;
}
@Override
public boolean inRegion(Location location, boolean extension) {
return true;
}
@Nullable
@Override
public Clipboard copy(boolean extension) {
return null;
}
@Override
public void place(Location location, PasteBuilder pasteBuilder, boolean extension) {
}
@Override
public void forEachChunk(BiConsumer<Integer, Integer> executor) {
}
@Override
public boolean isChunkOutside(int chunkX, int chunkZ) {
return false;
}
};
private static final GlobalRegionData REGION_DATA = new GlobalRegionData(INSTANCE);
@Override
public @NonNull UUID getID() {
return RegionSystem.GLOBAL_REGION_ID;
}
@Override
public @NonNull RegionType getType() {
return RegionType.GLOBAL;
}
@Override
public @NonNull RegionData getRegionData() {
return REGION_DATA;
}
@Override
public @NonNull Area getArea() {
return GLOBAL_AREA;
}
@Override
public @NonNull Area getBuildArea() {
return Area.EMPTY;
}
@Override
public @NonNull Area getTestblockArea() {
return Area.EMPTY;
}
@Override
public @NonNull GameModeConfig<Material, String> 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);
}
}
@@ -0,0 +1,55 @@
/*
* This file is a part of the SteamWar software.
*
* Copyright (C) 2025 SteamWar.de-Serverteam
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
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.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 GlobalRegionData extends RegionData {
public GlobalRegionData(GlobalRegion globalRegion) {
super(globalRegion);
}
@Override
protected void initialize() {
flagMap.put(Flag.TNT, TNTMode.DENY);
flagMap.put(Flag.PROTECT, ProtectMode.INACTIVE);
}
@Override
public @NonNull <T extends Enum<T> & Flag.Value<T>> RegionFlagPolicy has(@NonNull Flag<T> flag) {
if (flag.oneOf(Flag.PROTECT)) {
return RegionFlagPolicy.READ_ONLY;
}
if (flag.oneOf(Flag.ITEMS) && Core.getVersion() >= 20) {
return RegionFlagPolicy.WRITABLE;
}
if (flag.oneOf(Flag.TNT, Flag.FIRE, Flag.FREEZE, Flag.WATER_DESTROY)) {
return RegionFlagPolicy.WRITABLE;
}
return RegionFlagPolicy.NOT_APPLICABLE;
}
}
@@ -0,0 +1,171 @@
/*
* 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.modes;
import com.sk89q.worldedit.EditSession;
import de.steamwar.bausystem.region.Point;
import de.steamwar.bausystem.region.Region;
import de.steamwar.bausystem.region.dynamic.VariantSelector;
import de.steamwar.bausystem.shared.Pair;
import de.steamwar.bausystem.utils.PasteBuilder;
import lombok.AccessLevel;
import lombok.AllArgsConstructor;
import lombok.NonNull;
import org.bukkit.Location;
@AllArgsConstructor(access = AccessLevel.PRIVATE)
public class AreaBlock implements Region.Area {
public enum CopyLocation {
/**
* If the CopyLocation is located in between the two Area.
*/
CENTER,
/**
* If the CopyLocation is located on the side of the Area.
*/
SIDE
}
/**
* Returns North and then South Region in the Pair.
*/
public static Pair<AreaBlock, AreaBlock> create(Region region, int yOffset, Point size, Point minExtension, Point maxExtension, CopyLocation copyLocation, int distance) {
Point regionMinPoint = region.getArea().getMinPoint(false);
Point regionMaxPoint = region.getArea().getMaxPoint(false);
int regionSizeX = regionMaxPoint.getX() - regionMinPoint.getX() + 1;
int regionSizeZ = regionMaxPoint.getZ() - regionMinPoint.getZ() + 1;
int tempSizeZ = switch (copyLocation) {
case CENTER -> size.getZ() * 2 + distance;
case SIDE -> size.getZ() + distance;
};
// Calculate Offset Region to North
int minOffsetX = regionSizeX / 2 - size.getX() / 2;
int minOffsetZ = regionSizeZ / 2 - tempSizeZ / 2;
// Calculate North Points
Point northMinPoint = regionMinPoint.add(minOffsetX, yOffset, minOffsetZ);
Point northMaxPoint = northMinPoint.add(size).subtract(1, 1, 1);
Point northMinPointExtension = northMinPoint.subtract(minExtension);
Point northMaxPointExtension = northMaxPoint.add(maxExtension);
Point northCopyPoint = switch (copyLocation) {
case CENTER -> northMinPoint.add(size.getX() / 2, 0, size.getZ());
case SIDE -> northMinPoint.add(-1, 0, size.getZ() / 2);
};
// System.out.println(northMinPoint + " " + northMaxPoint + " @ " + northCopyPoint);
// fill(northCopyPoint.add(0, -1, 0), northCopyPoint.add(0, -1, 0), Material.GOLD_BLOCK);
// fill(northMinPointExtension, northMaxPointExtension, Material.RED_STAINED_GLASS);
// fill(northMinPoint, northMaxPoint, Material.RED_WOOL);
// Calculate Offset North to South
minOffsetZ += distance;
switch (copyLocation) {
case CENTER -> minOffsetZ += size.getZ();
case SIDE -> minOffsetZ -= 1;
}
// Calculate South Points
Point southMinPoint = regionMinPoint.add(minOffsetX, yOffset, minOffsetZ);
Point southMaxPoint = southMinPoint.add(size).subtract(1, 1, 1);
Point southMinPointExtension = southMinPoint.subtract(minExtension);
Point southMaxPointExtension = southMaxPoint.add(maxExtension);
Point southCopyPoint = switch (copyLocation) {
case CENTER -> southMinPoint.add(size.getX() / 2, 0, -1);
case SIDE -> southMinPoint.add(-1, 0, size.getZ() / 2);
};
// System.out.println(southMinPoint + " " + southMaxPoint + " @ " + southCopyPoint);
// fill(southCopyPoint.add(0, -1, 0), southCopyPoint.add(0, -1, 0), Material.GOLD_BLOCK);
// fill(southMinPointExtension, southMaxPointExtension, Material.GREEN_STAINED_GLASS);
// fill(southMinPoint, southMaxPoint, Material.GREEN_WOOL);
AreaBlock northArea = new AreaBlock(region, northMinPoint, northMinPointExtension, northMaxPoint, northMaxPointExtension, northCopyPoint);
AreaBlock southArea = new AreaBlock(region, southMinPoint, southMinPointExtension, southMaxPoint, southMaxPointExtension, southCopyPoint);
return new Pair<>(northArea, southArea);
}
public static AreaBlock create(Region region, int yOffset, Point minExtension, Point maxExtension) {
return null; // TODO: Is this needed?
}
/*
private static final World WORLD = Bukkit.getWorlds().get(0);
private static void fill(Point minPoint, Point maxPoint, Material material) {
BlockData blockData = material.createBlockData();
for (int x = minPoint.getX(); x <= maxPoint.getX(); x++) {
for (int z = minPoint.getZ(); z <= maxPoint.getZ(); z++) {
for (int y = minPoint.getY(); y <= maxPoint.getY(); y++) {
WORLD.setBlockData(x, y, z, blockData);
}
}
}
}
*/
private final Region region;
private final Point minPoint;
private final Point minPointExtension;
private final Point maxPoint;
private final Point maxPointExtension;
private final Point copyPoint;
private VariantSelector selector = null;
private AreaBlock(Region region, Point minPoint, Point minPointExtension, Point maxPoint, Point maxPointExtension, Point copyPoint) {
this.region = region;
this.minPoint = minPoint;
this.minPointExtension = minPointExtension;
this.maxPoint = maxPoint;
this.maxPointExtension = maxPointExtension;
this.copyPoint = copyPoint;
}
public AreaBlock withSelector(VariantSelector selector) {
this.selector = selector;
return this;
}
@Override
public @NonNull Point getMinPoint(boolean extension) {
return extension ? minPointExtension : minPoint;
}
@Override
public @NonNull Point getMaxPoint(boolean extension) {
return extension ? maxPointExtension : maxPoint;
}
@Override
public @NonNull Point getCopyPoint() {
return copyPoint;
}
@Override
public void place(Location location, PasteBuilder pasteBuilder, boolean extension) {
EditSession editSession = pasteBuilder
.with(PasteBuilder.ClipboardProvider.file(selector.select().orElse(null)))
.pastePoint(copyPoint)
.run();
region.getHistory()
.remember(editSession);
}
}
@@ -0,0 +1,73 @@
/*
* 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.modes;
import de.steamwar.bausystem.region.Point;
import de.steamwar.bausystem.region.Region;
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.utils.PasteBuilder;
import lombok.Getter;
import lombok.NonNull;
import org.bukkit.Location;
import java.io.File;
public class AreaTile implements Region.Area {
@Getter
private final Tile tile;
private final Point minPoint;
private final Point maxPoint;
private final Point copyPoint;
private final VariantSelector selector;
public AreaTile(Tile tile, int tileX, int tileZ, VariantSelector selector) {
this.tile = tile;
minPoint = Point.fromLocation(tile.getMinLocation()).setY(WORLD_MIN_Y);
maxPoint = Point.fromLocation(tile.add(tileX - 1, tileZ - 1).orElseThrow().getMaxLocation()).setY(WORLD_MAX_Y);
int x = minPoint.getX() + (maxPoint.getX() - minPoint.getX()) / 2;
int z = minPoint.getZ() + (maxPoint.getZ() - minPoint.getZ()) / 2;
copyPoint = new Point(x, 0, z);
this.selector = selector;
}
@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 void place(Location location, PasteBuilder pasteBuilder, boolean extension) {
File resetFile = selector.select().orElse(null);
if (resetFile != null) PasteUtils.paste(resetFile, minPoint, 0);
}
}
@@ -0,0 +1,54 @@
/*
* 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.modes;
import de.steamwar.bausystem.region.RegionData;
import de.steamwar.bausystem.region.RegionFlagPolicy;
import de.steamwar.bausystem.region.dynamic.DynamicRegion;
import de.steamwar.bausystem.region.flags.Flag;
import de.steamwar.bausystem.region.flags.TNTMode;
import de.steamwar.core.Core;
import lombok.NonNull;
public class DisplayRegionData extends RegionData {
public DisplayRegionData(DynamicRegion region) {
super(region);
}
@Override
protected void initialize() {
flagMap.put(Flag.TNT, TNTMode.DENY);
}
@Override
public @NonNull <T extends Enum<T> & Flag.Value<T>> RegionFlagPolicy has(@NonNull Flag<T> flag) {
if (flag.oneOf(Flag.FIRE, Flag.FREEZE, Flag.NO_GRAVITY, Flag.WATER_DESTROY)) {
return RegionFlagPolicy.WRITABLE;
}
if (flag.oneOf(Flag.ITEMS) && Core.getVersion() >= 20) {
return RegionFlagPolicy.WRITABLE;
}
if (flag.oneOf(Flag.TNT, Flag.PROTECT)) {
return RegionFlagPolicy.READ_ONLY;
}
return RegionFlagPolicy.NOT_APPLICABLE;
}
}
@@ -0,0 +1,158 @@
/*
* 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.modes;
import com.sk89q.worldedit.EditSession;
import com.sk89q.worldedit.extent.clipboard.Clipboard;
import com.sk89q.worldedit.extent.clipboard.io.BuiltInClipboardFormat;
import com.sk89q.worldedit.extent.clipboard.io.ClipboardWriter;
import de.steamwar.bausystem.region.RegionBackups;
import de.steamwar.bausystem.region.RegionData;
import de.steamwar.bausystem.region.dynamic.DynamicRegion;
import de.steamwar.bausystem.region.dynamic.DynamicRegionRepository;
import de.steamwar.bausystem.region.dynamic.PasteUtils;
import lombok.NonNull;
import org.bukkit.Bukkit;
import org.bukkit.Location;
import org.jetbrains.annotations.Nullable;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.time.LocalDateTime;
import java.util.*;
import java.util.function.Function;
import java.util.logging.Level;
public class PlotRegionBackups implements RegionBackups {
private final DynamicRegion region;
private final Function<Backup, RegionData> regionDataConstructor;
private final Map<BackupType, List<Backup>> backups = new EnumMap<>(BackupType.class);
/**
* @param region
* @param regionDataConstructor construct the regionData copying the values from the region into the returned value.
*/
public PlotRegionBackups(DynamicRegion region, Function<Backup, RegionData> regionDataConstructor) {
this.region = region;
this.regionDataConstructor = regionDataConstructor;
// Load all Backups of the Region
for (BackupType backupType : BackupType.values()) {
File backupsTypeDirectory = DynamicRegionRepository.getBackupsTypeDirectory(region, backupType);
if (!backupsTypeDirectory.exists()) continue;
File[] backupsDirectory = backupsTypeDirectory.listFiles();
if (backupsDirectory == null) continue;
List<Backup> backupList = backups.computeIfAbsent(backupType, __ -> new ArrayList<>());
for (File backupDirectory : backupsDirectory) {
backupList.add(new BackupImpl(backupType, region, backupDirectory));
}
}
}
@Override
public Optional<Backup> create(BackupType backupType) {
List<Backup> backupList = backups.computeIfAbsent(backupType, __ -> new ArrayList<>());
// Cleanup backups if there are too many!
backupList.sort(Backup::compareTo);
while (backupList.size() >= backupType.maxBackups) {
backupList.removeFirst().delete(null);
}
// Create backup and save!
Backup backup = new BackupImpl(backupType, region);
backupList.add(backup);
return Optional.of(backup);
}
@Override
public @NonNull List<Backup> list() {
return backups.values()
.stream()
.flatMap(List::stream)
.toList();
}
@Override
public @Nullable Backup get(@NonNull String name) {
return backups.values()
.stream()
.flatMap(List::stream)
.filter(backup -> backup.getName().equals(name))
.findFirst()
.orElse(null);
}
private final class BackupImpl extends Backup {
private static final String SCHEM_FILE = "backup.schem";
private final DynamicRegion region;
public BackupImpl(@NonNull BackupType type, DynamicRegion region) {
super(type, LocalDateTime.now().format(FORMATTER), regionDataConstructor);
region.getRegionData().copyInto(regionData);
this.region = region;
File backupDirectory = DynamicRegionRepository.getBackupDirectory(region, this);
backupDirectory.mkdirs();
DynamicRegionRepository.saveBackup(region, this);
Clipboard clipboard = region.getArea().copy(false);
try (ClipboardWriter writer = BuiltInClipboardFormat.SPONGE_SCHEMATIC.getWriter(new FileOutputStream(new File(backupDirectory, SCHEM_FILE)))) {
writer.write(clipboard);
} catch (IOException e) {
Bukkit.getLogger().log(Level.SEVERE, e.getMessage(), e);
}
}
public BackupImpl(@NonNull BackupType type, DynamicRegion region, File backupDirectory) {
super(type, backupDirectory.getName(), regionDataConstructor);
this.region = region;
DynamicRegionRepository.loadRegionData(region, this, regionData);
}
@Override
public boolean load() {
File file = new File(DynamicRegionRepository.getBackupDirectory(region, this), SCHEM_FILE);
if (!file.exists()) return false;
EditSession editSession = PasteUtils.paste(file, region.getArea().getMinPoint(false), 0);
if (editSession == null) return false;
region.getHistory().remember(editSession);
regionData.copyInto(region.getRegionData());
return true;
}
@Override
public long getCreationTime() {
return DynamicRegionRepository.getBackupDirectory(region, this).lastModified();
}
@Override
public void delete(Location location) {
backups.getOrDefault(type, Collections.emptyList())
.remove(this);
DynamicRegionRepository.deleteBackup(region, this);
}
}
}
@@ -0,0 +1,53 @@
/*
* 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.modes;
import de.steamwar.bausystem.region.RegionData;
import de.steamwar.bausystem.region.RegionDataStore;
import de.steamwar.bausystem.region.RegionFlagPolicy;
import de.steamwar.bausystem.region.flags.Flag;
import de.steamwar.bausystem.region.flags.TNTMode;
import de.steamwar.bausystem.region.flags.TestblockMode;
import de.steamwar.core.Core;
import lombok.NonNull;
public class PlotRegionData extends RegionData {
public PlotRegionData(RegionDataStore store) {
super(store);
}
@Override
protected void initialize() {
flagMap.put(Flag.TNT, TNTMode.ALLOW);
flagMap.put(Flag.TESTBLOCK, TestblockMode.NO_VALUE);
}
@Override
public @NonNull <T extends Enum<T> & Flag.Value<T>> RegionFlagPolicy has(@NonNull Flag<T> flag) {
if (flag.oneOf(Flag.TNT, Flag.PROTECT, Flag.FIRE, Flag.FREEZE, Flag.NO_GRAVITY, Flag.WATER_DESTROY, Flag.TESTBLOCK, Flag.CHANGED)) {
return RegionFlagPolicy.WRITABLE;
}
if (flag.oneOf(Flag.ITEMS) && Core.getVersion() >= 20) {
return RegionFlagPolicy.WRITABLE;
}
return RegionFlagPolicy.NOT_APPLICABLE;
}
}
@@ -0,0 +1,122 @@
/*
* 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.modes.microwargear_7;
import com.google.gson.JsonArray;
import com.google.gson.stream.JsonWriter;
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.*;
import de.steamwar.bausystem.region.dynamic.modes.AreaTile;
import de.steamwar.bausystem.region.dynamic.modes.DisplayRegionData;
import de.steamwar.sql.GameModeConfig;
import lombok.NonNull;
import org.bukkit.Bukkit;
import org.bukkit.Material;
import java.io.File;
import java.io.IOException;
import java.util.UUID;
@RegionConstructorData(
identifier = "microwargear_display_7",
name = "MicroWarGearDisplay",
material = Material.STONE_BUTTON,
widthX = MiWG7DisplayRegion.TILE_X * Tile.tileSize,
widthZ = MiWG7DisplayRegion.TILE_Z * Tile.tileSize
)
public class MiWG7DisplayRegion extends DynamicRegion {
protected static final int TILE_X = 1;
protected static final int TILE_Z = 1;
private static final File DIRECTORY = new File(Bukkit.getWorlds().get(0).getWorldFolder(), "sections/microwargear_7/display");
private static final VariantSelector SELECTOR = VariantSelector.Get(DIRECTORY);
private final AreaTile area;
public MiWG7DisplayRegion(Tile tile) {
this(UUID.randomUUID(), tile);
finishCreate();
}
public MiWG7DisplayRegion(UUID id, JsonArray tileData) {
this(id, readTile(tileData));
finishLoad();
}
private MiWG7DisplayRegion(UUID id, Tile tile) {
super(id, null);
area = new AreaTile(tile, TILE_X, TILE_Z, SELECTOR);
regionData = new DisplayRegionData(this);
}
@Override
public void writeData(JsonWriter writer) throws IOException {
writeTile(writer, area.getTile());
}
@Override
public @NonNull RegionType getType() {
return RegionType.DRY;
}
@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<Material, String> getGameModeConfig() {
return MiWG7Utils.GAME_MODE_CONFIG;
}
@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);
}
}
@@ -0,0 +1,144 @@
/*
* 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.modes.microwargear_7;
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.modes.AreaBlock;
import de.steamwar.bausystem.region.dynamic.modes.AreaTile;
import de.steamwar.bausystem.region.dynamic.modes.PlotRegionBackups;
import de.steamwar.bausystem.region.dynamic.modes.PlotRegionData;
import de.steamwar.bausystem.region.flags.Flag;
import de.steamwar.bausystem.shared.Pair;
import de.steamwar.sql.GameModeConfig;
import lombok.NonNull;
import org.bukkit.Bukkit;
import org.bukkit.Material;
import java.io.File;
import java.io.IOException;
import java.util.UUID;
@RegionConstructorData(
identifier = "microwargear_plot_7",
name = "MicroWarGearPlot",
material = Material.STONE_BUTTON,
widthX = Tile.tileSize * MiWG7PlotRegion.TILE_X,
widthZ = Tile.tileSize * MiWG7PlotRegion.TILE_Z
)
public class MiWG7PlotRegion extends DynamicRegion {
protected static final int TILE_X = 3;
protected static final int TILE_Z = 5;
private static final File DIRECTORY = new File(Bukkit.getWorlds().get(0).getWorldFolder(), "sections/microwargear_7/plot");
private static final VariantSelector REGION = VariantSelector.Get(new File(DIRECTORY, "region"));
private static final VariantSelector TESTBLOCK = VariantSelector.Get(new File(DIRECTORY, "testblock"));
private static final VariantSelector WIREFRAME = VariantSelector.Get(new File(DIRECTORY, "wireframe"));
private final AreaTile area;
private final AreaBlock northArea;
private final AreaBlock southArea;
private final RegionHistory history;
private final RegionBackups backups;
public MiWG7PlotRegion(Tile tile) {
this(UUID.randomUUID(), tile);
finishCreate();
}
public MiWG7PlotRegion(UUID id, JsonArray tileData) {
this(id, readTile(tileData));
finishLoad();
}
private MiWG7PlotRegion(UUID id, Tile tile) {
super(id, null);
area = new AreaTile(tile, TILE_X, TILE_Z, REGION);
Pair<AreaBlock, AreaBlock> pair = AreaBlock.create(this, 36, new Point(7, 7, 7), new Point(7, 0, 7), new Point(7, 7, 7), AreaBlock.CopyLocation.CENTER, 50);
northArea = pair.getKey();
southArea = pair.getValue();
regionData = new PlotRegionData(this);
history = new RegionHistory.Impl(10);
backups = new PlotRegionBackups(this, PlotRegionData::new);
}
@Override
public void writeData(JsonWriter writer) throws IOException {
writeTile(writer, area.getTile());
}
@Override
public @NonNull RegionType getType() {
return RegionType.DRY;
}
@Override
public @NonNull Area getArea() {
return area;
}
@Override
public @NonNull Area getBuildArea() {
return switch (regionData.get(Flag.TESTBLOCK).getWithDefault()) {
case NO_VALUE -> Area.EMPTY;
case NORTH -> southArea.withSelector(WIREFRAME);
case SOUTH -> northArea.withSelector(WIREFRAME);
};
}
@Override
public @NonNull Area getTestblockArea() {
return switch (regionData.get(Flag.TESTBLOCK).getWithDefault()) {
case NO_VALUE -> Area.EMPTY;
case NORTH -> northArea.withSelector(TESTBLOCK);
case SOUTH -> southArea.withSelector(TESTBLOCK);
};
}
@Override
public @NonNull GameModeConfig<Material, String> getGameModeConfig() {
return MiWG7Utils.GAME_MODE_CONFIG;
}
@Override
public @NonNull RegionHistory getHistory() {
return history;
}
@Override
public @NonNull RegionBackups getBackups() {
return backups;
}
@Override
public void save() {
DynamicRegionRepository.saveRegion(this);
}
@Override
public void load(RegionData regionData) {
DynamicRegionRepository.loadRegionData(this, regionData);
}
}
@@ -0,0 +1,35 @@
/*
* 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.modes.microwargear_7;
import de.steamwar.sql.GameModeConfig;
import lombok.experimental.UtilityClass;
import org.bukkit.Material;
@UtilityClass
public class MiWG7Utils {
public static final GameModeConfig<Material, String> GAME_MODE_CONFIG;
static {
GameModeConfig<Material, String> config = GameModeConfig.getByGameName("MicroWarGear");
GAME_MODE_CONFIG = config != null ? config : GameModeConfig.getDefaults();
}
}
@@ -0,0 +1,122 @@
/*
* 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.modes.miniwargear;
import com.google.gson.JsonArray;
import com.google.gson.stream.JsonWriter;
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.*;
import de.steamwar.bausystem.region.dynamic.modes.AreaTile;
import de.steamwar.bausystem.region.dynamic.modes.DisplayRegionData;
import de.steamwar.sql.GameModeConfig;
import lombok.NonNull;
import org.bukkit.Bukkit;
import org.bukkit.Material;
import java.io.File;
import java.io.IOException;
import java.util.UUID;
@RegionConstructorData(
identifier = "miniwargear_display",
name = "MiniWarGearDisplay",
material = Material.END_STONE_BRICK_SLAB,
widthX = MWGDisplayRegion.TILE_X * Tile.tileSize,
widthZ = MWGDisplayRegion.TILE_Z * Tile.tileSize
)
public class MWGDisplayRegion extends DynamicRegion {
protected static final int TILE_X = 3;
protected static final int TILE_Z = 3;
private static final File DIRECTORY = new File(Bukkit.getWorlds().get(0).getWorldFolder(), "sections/miniwargear/display");
private static final VariantSelector SELECTOR = VariantSelector.Get(DIRECTORY);
private final AreaTile area;
public MWGDisplayRegion(Tile tile) {
this(UUID.randomUUID(), tile);
finishCreate();
}
public MWGDisplayRegion(UUID id, JsonArray tileData) {
this(id, readTile(tileData));
finishLoad();
}
private MWGDisplayRegion(UUID id, Tile tile) {
super(id, null);
area = new AreaTile(tile, TILE_X, TILE_Z, SELECTOR);
regionData = new DisplayRegionData(this);
}
@Override
public void writeData(JsonWriter writer) throws IOException {
writeTile(writer, area.getTile());
}
@Override
public @NonNull RegionType getType() {
return RegionType.DRY;
}
@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<Material, String> getGameModeConfig() {
return MWGUtils.GAME_MODE_CONFIG;
}
@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);
}
}
@@ -0,0 +1,144 @@
/*
* 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.modes.miniwargear;
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.modes.AreaBlock;
import de.steamwar.bausystem.region.dynamic.modes.AreaTile;
import de.steamwar.bausystem.region.dynamic.modes.PlotRegionBackups;
import de.steamwar.bausystem.region.dynamic.modes.PlotRegionData;
import de.steamwar.bausystem.region.flags.Flag;
import de.steamwar.bausystem.shared.Pair;
import de.steamwar.sql.GameModeConfig;
import lombok.NonNull;
import org.bukkit.Bukkit;
import org.bukkit.Material;
import java.io.File;
import java.io.IOException;
import java.util.UUID;
@RegionConstructorData(
identifier = "miniwargear_plot",
name = "MiniWarGearPlot",
material = Material.END_STONE_BRICK_SLAB,
widthX = Tile.tileSize * MWGPlotRegion.TILE_X,
widthZ = Tile.tileSize * MWGPlotRegion.TILE_Z
)
public class MWGPlotRegion extends DynamicRegion {
protected static final int TILE_X = 3;
protected static final int TILE_Z = 6;
private static final File DIRECTORY = new File(Bukkit.getWorlds().get(0).getWorldFolder(), "sections/miniwargear/plot");
private static final VariantSelector REGION = VariantSelector.Get(new File(DIRECTORY, "region"));
private static final VariantSelector TESTBLOCK = VariantSelector.Get(new File(DIRECTORY, "testblock"));
private static final VariantSelector WIREFRAME = VariantSelector.Get(new File(DIRECTORY, "wireframe"));
private final AreaTile area;
private final AreaBlock northArea;
private final AreaBlock southArea;
private final RegionHistory history;
private final RegionBackups backups;
public MWGPlotRegion(Tile tile) {
this(UUID.randomUUID(), tile);
finishCreate();
}
public MWGPlotRegion(UUID id, JsonArray tileData) {
this(id, readTile(tileData));
finishLoad();
}
private MWGPlotRegion(UUID id, Tile tile) {
super(id, null);
area = new AreaTile(tile, TILE_X, TILE_Z, REGION);
Pair<AreaBlock, AreaBlock> pair = AreaBlock.create(this, 36, new Point(37, 26, 22), new Point(7, 0, 7), new Point(7, 7, 7), AreaBlock.CopyLocation.CENTER, 50);
northArea = pair.getKey();
southArea = pair.getValue();
regionData = new PlotRegionData(this);
history = new RegionHistory.Impl(10);
backups = new PlotRegionBackups(this, PlotRegionData::new);
}
@Override
public void writeData(JsonWriter writer) throws IOException {
writeTile(writer, area.getTile());
}
@Override
public @NonNull RegionType getType() {
return RegionType.DRY;
}
@Override
public @NonNull Area getArea() {
return area;
}
@Override
public @NonNull Area getBuildArea() {
return switch (regionData.get(Flag.TESTBLOCK).getWithDefault()) {
case NO_VALUE -> Area.EMPTY;
case NORTH -> southArea.withSelector(WIREFRAME);
case SOUTH -> northArea.withSelector(WIREFRAME);
};
}
@Override
public @NonNull Area getTestblockArea() {
return switch (regionData.get(Flag.TESTBLOCK).getWithDefault()) {
case NO_VALUE -> Area.EMPTY;
case NORTH -> northArea.withSelector(TESTBLOCK);
case SOUTH -> southArea.withSelector(TESTBLOCK);
};
}
@Override
public @NonNull GameModeConfig<Material, String> getGameModeConfig() {
return MWGUtils.GAME_MODE_CONFIG;
}
@Override
public @NonNull RegionHistory getHistory() {
return history;
}
@Override
public @NonNull RegionBackups getBackups() {
return backups;
}
@Override
public void save() {
DynamicRegionRepository.saveRegion(this);
}
@Override
public void load(RegionData regionData) {
DynamicRegionRepository.loadRegionData(this, regionData);
}
}
@@ -0,0 +1,35 @@
/*
* 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.modes.miniwargear;
import de.steamwar.sql.GameModeConfig;
import lombok.experimental.UtilityClass;
import org.bukkit.Material;
@UtilityClass
public class MWGUtils {
public static final GameModeConfig<Material, String> GAME_MODE_CONFIG;
static {
GameModeConfig<Material, String> config = GameModeConfig.getByGameName("MiniWarGear");
GAME_MODE_CONFIG = config != null ? config : GameModeConfig.getDefaults();
}
}
@@ -0,0 +1,122 @@
/*
* 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.modes.wargear_45;
import com.google.gson.JsonArray;
import com.google.gson.stream.JsonWriter;
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.*;
import de.steamwar.bausystem.region.dynamic.modes.AreaTile;
import de.steamwar.bausystem.region.dynamic.modes.DisplayRegionData;
import de.steamwar.sql.GameModeConfig;
import lombok.NonNull;
import org.bukkit.Bukkit;
import org.bukkit.Material;
import java.io.File;
import java.io.IOException;
import java.util.UUID;
@RegionConstructorData(
identifier = "wargear_display_45",
name = "WarGearDisplay 45",
material = Material.END_STONE_BRICKS,
widthX = WG45DisplayRegion.TILE_X * Tile.tileSize,
widthZ = WG45DisplayRegion.TILE_Z * Tile.tileSize
)
public class WG45DisplayRegion extends DynamicRegion {
protected static final int TILE_X = 5;
protected static final int TILE_Z = 5;
private static final File DIRECTORY = new File(Bukkit.getWorlds().get(0).getWorldFolder(), "sections/wargear_45/display");
private static final VariantSelector SELECTOR = VariantSelector.Get(DIRECTORY);
private final AreaTile area;
public WG45DisplayRegion(Tile tile) {
this(UUID.randomUUID(), tile);
finishCreate();
}
public WG45DisplayRegion(UUID id, JsonArray tileData) {
this(id, readTile(tileData));
finishLoad();
}
private WG45DisplayRegion(UUID id, Tile tile) {
super(id, null);
area = new AreaTile(tile, TILE_X, TILE_Z, SELECTOR);
regionData = new DisplayRegionData(this);
}
@Override
public void writeData(JsonWriter writer) throws IOException {
writeTile(writer, area.getTile());
}
@Override
public @NonNull RegionType getType() {
return RegionType.DRY;
}
@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<Material, String> getGameModeConfig() {
return WG45Utils.GAME_MODE_CONFIG;
}
@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);
}
}
@@ -0,0 +1,144 @@
/*
* 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.modes.wargear_45;
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.modes.AreaBlock;
import de.steamwar.bausystem.region.dynamic.modes.AreaTile;
import de.steamwar.bausystem.region.dynamic.modes.PlotRegionBackups;
import de.steamwar.bausystem.region.dynamic.modes.PlotRegionData;
import de.steamwar.bausystem.region.flags.Flag;
import de.steamwar.bausystem.shared.Pair;
import de.steamwar.sql.GameModeConfig;
import lombok.NonNull;
import org.bukkit.Bukkit;
import org.bukkit.Material;
import java.io.File;
import java.io.IOException;
import java.util.UUID;
@RegionConstructorData(
identifier = "wargear_plot_45",
name = "WarGearPlot 45",
material = Material.END_STONE_BRICKS,
widthX = Tile.tileSize * WG45PlotRegion.TILE_X,
widthZ = Tile.tileSize * WG45PlotRegion.TILE_Z
)
public class WG45PlotRegion extends DynamicRegion {
protected static final int TILE_X = 7;
protected static final int TILE_Z = 10;
private static final File DIRECTORY = new File(Bukkit.getWorlds().get(0).getWorldFolder(), "sections/wargear_45/plot");
private static final VariantSelector REGION = VariantSelector.Get(new File(DIRECTORY, "region"));
private static final VariantSelector TESTBLOCK = VariantSelector.Get(new File(DIRECTORY, "testblock"));
private static final VariantSelector WIREFRAME = VariantSelector.Get(new File(DIRECTORY, "wireframe"));
private final AreaTile area;
private final AreaBlock northArea;
private final AreaBlock southArea;
private final RegionHistory history;
private final RegionBackups backups;
public WG45PlotRegion(Tile tile) {
this(UUID.randomUUID(), tile);
finishCreate();
}
public WG45PlotRegion(UUID id, JsonArray tileData) {
this(id, readTile(tileData));
finishLoad();
}
private WG45PlotRegion(UUID id, Tile tile) {
super(id, null);
area = new AreaTile(tile, TILE_X, TILE_Z, REGION);
Pair<AreaBlock, AreaBlock> pair = AreaBlock.create(this, 36, new Point(67, 41, 47), new Point(16, 0, 16), new Point(16, 16, 16), AreaBlock.CopyLocation.CENTER, 50);
northArea = pair.getKey();
southArea = pair.getValue();
regionData = new PlotRegionData(this);
history = new RegionHistory.Impl(10);
backups = new PlotRegionBackups(this, PlotRegionData::new);
}
@Override
public void writeData(JsonWriter writer) throws IOException {
writeTile(writer, area.getTile());
}
@Override
public @NonNull RegionType getType() {
return RegionType.DRY;
}
@Override
public @NonNull Area getArea() {
return area;
}
@Override
public @NonNull Area getBuildArea() {
return switch (regionData.get(Flag.TESTBLOCK).getWithDefault()) {
case NO_VALUE -> Area.EMPTY;
case NORTH -> southArea.withSelector(WIREFRAME);
case SOUTH -> northArea.withSelector(WIREFRAME);
};
}
@Override
public @NonNull Area getTestblockArea() {
return switch (regionData.get(Flag.TESTBLOCK).getWithDefault()) {
case NO_VALUE -> Area.EMPTY;
case NORTH -> northArea.withSelector(TESTBLOCK);
case SOUTH -> southArea.withSelector(TESTBLOCK);
};
}
@Override
public @NonNull GameModeConfig<Material, String> getGameModeConfig() {
return WG45Utils.GAME_MODE_CONFIG;
}
@Override
public @NonNull RegionHistory getHistory() {
return history;
}
@Override
public @NonNull RegionBackups getBackups() {
return backups;
}
@Override
public void save() {
DynamicRegionRepository.saveRegion(this);
}
@Override
public void load(RegionData regionData) {
DynamicRegionRepository.loadRegionData(this, regionData);
}
}
@@ -0,0 +1,35 @@
/*
* 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.modes.wargear_45;
import de.steamwar.sql.GameModeConfig;
import lombok.experimental.UtilityClass;
import org.bukkit.Material;
@UtilityClass
public class WG45Utils {
public static final GameModeConfig<Material, String> GAME_MODE_CONFIG;
static {
GameModeConfig<Material, String> config = GameModeConfig.getByGameName("WarGear");
GAME_MODE_CONFIG = config != null ? config : GameModeConfig.getDefaults();
}
}
@@ -0,0 +1,122 @@
/*
* 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.modes.warship_175;
import com.google.gson.JsonArray;
import com.google.gson.stream.JsonWriter;
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.*;
import de.steamwar.bausystem.region.dynamic.modes.AreaTile;
import de.steamwar.bausystem.region.dynamic.modes.DisplayRegionData;
import de.steamwar.sql.GameModeConfig;
import lombok.NonNull;
import org.bukkit.Bukkit;
import org.bukkit.Material;
import java.io.File;
import java.io.IOException;
import java.util.UUID;
@RegionConstructorData(
identifier = "warship_display_175",
name = "WarShipDisplay 175",
material = Material.BIRCH_BOAT,
widthX = WS175DisplayRegion.TILE_X * Tile.tileSize,
widthZ = WS175DisplayRegion.TILE_Z * Tile.tileSize
)
public class WS175DisplayRegion extends DynamicRegion {
protected static final int TILE_X = 11;
protected static final int TILE_Z = 3;
private static final File DIRECTORY = new File(Bukkit.getWorlds().get(0).getWorldFolder(), "sections/warship_175/display");
private static final VariantSelector SELECTOR = VariantSelector.Get(DIRECTORY);
private final AreaTile area;
public WS175DisplayRegion(Tile tile) {
this(UUID.randomUUID(), tile);
finishCreate();
}
public WS175DisplayRegion(UUID id, JsonArray tileData) {
this(id, readTile(tileData));
finishLoad();
}
private WS175DisplayRegion(UUID id, Tile tile) {
super(id, null);
area = new AreaTile(tile, TILE_X, TILE_Z, SELECTOR);
regionData = new DisplayRegionData(this);
}
@Override
public void writeData(JsonWriter writer) throws IOException {
writeTile(writer, area.getTile());
}
@Override
public @NonNull RegionType getType() {
return RegionType.WET;
}
@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<Material, String> getGameModeConfig() {
return WS175Utils.GAME_MODE_CONFIG;
}
@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);
}
}
@@ -0,0 +1,144 @@
/*
* 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.modes.warship_175;
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.modes.AreaBlock;
import de.steamwar.bausystem.region.dynamic.modes.AreaTile;
import de.steamwar.bausystem.region.dynamic.modes.PlotRegionBackups;
import de.steamwar.bausystem.region.dynamic.modes.PlotRegionData;
import de.steamwar.bausystem.region.flags.Flag;
import de.steamwar.bausystem.shared.Pair;
import de.steamwar.sql.GameModeConfig;
import lombok.NonNull;
import org.bukkit.Bukkit;
import org.bukkit.Material;
import java.io.File;
import java.io.IOException;
import java.util.UUID;
@RegionConstructorData(
identifier = "warship_plot_175",
name = "WarShipPlot 175",
material = Material.BIRCH_BOAT,
widthX = Tile.tileSize * WS175PlotRegion.TILE_X,
widthZ = Tile.tileSize * WS175PlotRegion.TILE_Z
)
public class WS175PlotRegion extends DynamicRegion {
protected static final int TILE_X = 11;
protected static final int TILE_Z = 10;
private static final File DIRECTORY = new File(Bukkit.getWorlds().get(0).getWorldFolder(), "sections/warship_175/plot");
private static final VariantSelector REGION = VariantSelector.Get(new File(DIRECTORY, "region"));
private static final VariantSelector TESTBLOCK = VariantSelector.Get(new File(DIRECTORY, "testblock"));
private static final VariantSelector WIREFRAME = VariantSelector.Get(new File(DIRECTORY, "wireframe"));
private final AreaTile area;
private final AreaBlock northArea;
private final AreaBlock southArea;
private final RegionHistory history;
private final RegionBackups backups;
public WS175PlotRegion(Tile tile) {
this(UUID.randomUUID(), tile);
finishCreate();
}
public WS175PlotRegion(UUID id, JsonArray tileData) {
this(id, readTile(tileData));
finishLoad();
}
private WS175PlotRegion(UUID id, Tile tile) {
super(id, null);
area = new AreaTile(tile, TILE_X, TILE_Z, REGION);
Pair<AreaBlock, AreaBlock> pair = AreaBlock.create(this, 25, new Point(175, 58, 39), new Point(12, 0, 8), new Point(12, 0, 8), AreaBlock.CopyLocation.SIDE, 132);
northArea = pair.getKey();
southArea = pair.getValue();
regionData = new PlotRegionData(this);
history = new RegionHistory.Impl(10);
backups = new PlotRegionBackups(this, PlotRegionData::new);
}
@Override
public void writeData(JsonWriter writer) throws IOException {
writeTile(writer, area.getTile());
}
@Override
public @NonNull RegionType getType() {
return RegionType.WET;
}
@Override
public @NonNull Area getArea() {
return area;
}
@Override
public @NonNull Area getBuildArea() {
return switch (regionData.get(Flag.TESTBLOCK).getWithDefault()) {
case NO_VALUE -> Area.EMPTY;
case NORTH -> southArea.withSelector(WIREFRAME);
case SOUTH -> northArea.withSelector(WIREFRAME);
};
}
@Override
public @NonNull Area getTestblockArea() {
return switch (regionData.get(Flag.TESTBLOCK).getWithDefault()) {
case NO_VALUE -> Area.EMPTY;
case NORTH -> northArea.withSelector(TESTBLOCK);
case SOUTH -> southArea.withSelector(TESTBLOCK);
};
}
@Override
public @NonNull GameModeConfig<Material, String> getGameModeConfig() {
return WS175Utils.GAME_MODE_CONFIG;
}
@Override
public @NonNull RegionHistory getHistory() {
return history;
}
@Override
public @NonNull RegionBackups getBackups() {
return backups;
}
@Override
public void save() {
DynamicRegionRepository.saveRegion(this);
}
@Override
public void load(RegionData regionData) {
DynamicRegionRepository.loadRegionData(this, regionData);
}
}
@@ -0,0 +1,35 @@
/*
* 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.modes.warship_175;
import de.steamwar.sql.GameModeConfig;
import lombok.experimental.UtilityClass;
import org.bukkit.Material;
@UtilityClass
public class WS175Utils {
public static final GameModeConfig<Material, String> GAME_MODE_CONFIG;
static {
GameModeConfig<Material, String> config = GameModeConfig.getByGameName("WarShip21");
GAME_MODE_CONFIG = config != null ? config : GameModeConfig.getDefaults();
}
}
@@ -0,0 +1,122 @@
/*
* 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.modes.warship_230;
import com.google.gson.JsonArray;
import com.google.gson.stream.JsonWriter;
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.*;
import de.steamwar.bausystem.region.dynamic.modes.AreaTile;
import de.steamwar.bausystem.region.dynamic.modes.DisplayRegionData;
import de.steamwar.sql.GameModeConfig;
import lombok.NonNull;
import org.bukkit.Bukkit;
import org.bukkit.Material;
import java.io.File;
import java.io.IOException;
import java.util.UUID;
@RegionConstructorData(
identifier = "warship_display_230",
name = "WarShipDisplay 230",
material = Material.BIRCH_BOAT,
widthX = WS230DisplayRegion.TILE_X * Tile.tileSize,
widthZ = WS230DisplayRegion.TILE_Z * Tile.tileSize
)
public class WS230DisplayRegion extends DynamicRegion {
protected static final int TILE_X = 13;
protected static final int TILE_Z = 3;
private static final File DIRECTORY = new File(Bukkit.getWorlds().get(0).getWorldFolder(), "sections/warship_230/display");
private static final VariantSelector SELECTOR = VariantSelector.Get(DIRECTORY);
private final AreaTile area;
public WS230DisplayRegion(Tile tile) {
this(UUID.randomUUID(), tile);
finishCreate();
}
public WS230DisplayRegion(UUID id, JsonArray tileData) {
this(id, readTile(tileData));
finishLoad();
}
private WS230DisplayRegion(UUID id, Tile tile) {
super(id, null);
area = new AreaTile(tile, TILE_X, TILE_Z, SELECTOR);
regionData = new DisplayRegionData(this);
}
@Override
public void writeData(JsonWriter writer) throws IOException {
writeTile(writer, area.getTile());
}
@Override
public @NonNull RegionType getType() {
return RegionType.WET;
}
@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<Material, String> getGameModeConfig() {
return WS230Utils.GAME_MODE_CONFIG;
}
@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);
}
}
@@ -0,0 +1,144 @@
/*
* 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.modes.warship_230;
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.modes.AreaBlock;
import de.steamwar.bausystem.region.dynamic.modes.AreaTile;
import de.steamwar.bausystem.region.dynamic.modes.PlotRegionBackups;
import de.steamwar.bausystem.region.dynamic.modes.PlotRegionData;
import de.steamwar.bausystem.region.flags.Flag;
import de.steamwar.bausystem.shared.Pair;
import de.steamwar.sql.GameModeConfig;
import lombok.NonNull;
import org.bukkit.Bukkit;
import org.bukkit.Material;
import java.io.File;
import java.io.IOException;
import java.util.UUID;
@RegionConstructorData(
identifier = "warship_plot_230",
name = "WarShipPlot 230",
material = Material.BIRCH_BOAT,
widthX = Tile.tileSize * WS230PlotRegion.TILE_X,
widthZ = Tile.tileSize * WS230PlotRegion.TILE_Z
)
public class WS230PlotRegion extends DynamicRegion {
protected static final int TILE_X = 13;
protected static final int TILE_Z = 10;
private static final File DIRECTORY = new File(Bukkit.getWorlds().get(0).getWorldFolder(), "sections/warship_230/plot");
private static final VariantSelector REGION = VariantSelector.Get(new File(DIRECTORY, "region"));
private static final VariantSelector TESTBLOCK = VariantSelector.Get(new File(DIRECTORY, "testblock"));
private static final VariantSelector WIREFRAME = VariantSelector.Get(new File(DIRECTORY, "wireframe"));
private final AreaTile area;
private final AreaBlock northArea;
private final AreaBlock southArea;
private final RegionHistory history;
private final RegionBackups backups;
public WS230PlotRegion(Tile tile) {
this(UUID.randomUUID(), tile);
finishCreate();
}
public WS230PlotRegion(UUID id, JsonArray tileData) {
this(id, readTile(tileData));
finishLoad();
}
private WS230PlotRegion(UUID id, Tile tile) {
super(id, null);
area = new AreaTile(tile, TILE_X, TILE_Z, REGION);
Pair<AreaBlock, AreaBlock> pair = AreaBlock.create(this, 25, new Point(230, 58, 43), new Point(12, 0, 8), new Point(12, 0, 8), AreaBlock.CopyLocation.SIDE, 136);
northArea = pair.getKey();
southArea = pair.getValue();
regionData = new PlotRegionData(this);
history = new RegionHistory.Impl(10);
backups = new PlotRegionBackups(this, PlotRegionData::new);
}
@Override
public void writeData(JsonWriter writer) throws IOException {
writeTile(writer, area.getTile());
}
@Override
public @NonNull RegionType getType() {
return RegionType.WET;
}
@Override
public @NonNull Area getArea() {
return area;
}
@Override
public @NonNull Area getBuildArea() {
return switch (regionData.get(Flag.TESTBLOCK).getWithDefault()) {
case NO_VALUE -> Area.EMPTY;
case NORTH -> southArea.withSelector(WIREFRAME);
case SOUTH -> northArea.withSelector(WIREFRAME);
};
}
@Override
public @NonNull Area getTestblockArea() {
return switch (regionData.get(Flag.TESTBLOCK).getWithDefault()) {
case NO_VALUE -> Area.EMPTY;
case NORTH -> northArea.withSelector(TESTBLOCK);
case SOUTH -> southArea.withSelector(TESTBLOCK);
};
}
@Override
public @NonNull GameModeConfig<Material, String> getGameModeConfig() {
return WS230Utils.GAME_MODE_CONFIG;
}
@Override
public @NonNull RegionHistory getHistory() {
return history;
}
@Override
public @NonNull RegionBackups getBackups() {
return backups;
}
@Override
public void save() {
DynamicRegionRepository.saveRegion(this);
}
@Override
public void load(RegionData regionData) {
DynamicRegionRepository.loadRegionData(this, regionData);
}
}
@@ -0,0 +1,35 @@
/*
* 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.modes.warship_230;
import de.steamwar.sql.GameModeConfig;
import lombok.experimental.UtilityClass;
import org.bukkit.Material;
@UtilityClass
public class WS230Utils {
public static final GameModeConfig<Material, String> GAME_MODE_CONFIG;
static {
GameModeConfig<Material, String> config = GameModeConfig.getByGameName("WarShip20");
GAME_MODE_CONFIG = config != null ? config : GameModeConfig.getDefaults();
}
}
@@ -0,0 +1,221 @@
/*
* 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.path;
import de.steamwar.bausystem.BauSystem;
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.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.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 java.util.Optional;
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<Pair<Tile, Tile>> 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<VariantSelector, RotationCorrection> 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);
}
}
public static 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;
}
protected static RegionType.ConnectionType getConnectionType(Tile tile, PathSide side, PathSide optionalSide) {
Optional<Tile> optionalTile = tile.add(side.tileOffsetX, side.tileOffsetZ);
if (optionalSide != null) {
optionalTile = optionalTile.flatMap(t -> t.add(optionalSide.tileOffsetX, optionalSide.tileOffsetZ));
}
if (optionalTile.isEmpty()) {
return RegionType.ConnectionType.Global;
}
tile = optionalTile.get();
Region region = DynamicRegionSystem.INSTANCE.get(tile.getCenterLocation());
if (region instanceof PathRegion) {
return isGarden(tile) ? Garden : Path;
}
return region.getType().getConnectionType();
}
}
@@ -0,0 +1,38 @@
/*
* 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.path;
import lombok.RequiredArgsConstructor;
@RequiredArgsConstructor
public enum PathCorner {
NorthEast(PathSide.North, PathSide.East, 14, 0, 0, -90),
NorthWest(PathSide.North, PathSide.West, 0, 0, 0, 90),
SouthWest(PathSide.South, PathSide.West, 0, 14, 180, -90),
SouthEast(PathSide.South, PathSide.East, 14, 14, 180, 90),
;
public final PathSide side1;
public final PathSide side2;
public final int pasteOffsetX;
public final int pasteOffsetZ;
public final int rotate;
public final int rotateCorrection;
}
@@ -0,0 +1,143 @@
/*
* 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.path;
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.sql.GameModeConfig;
import lombok.NonNull;
import org.bukkit.Location;
import org.bukkit.Material;
import java.io.IOException;
import java.util.UUID;
@RegionConstructorData(
identifier = "path",
name = "Path",
material = Material.DIRT_PATH,
widthX = Tile.tileSize,
widthZ = Tile.tileSize
)
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) {
if (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<Material, String> 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();
}
}
}
@@ -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 <https://www.gnu.org/licenses/>.
*/
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 <T extends Enum<T> & Flag.Value<T>> RegionFlagPolicy has(@NonNull Flag<T> 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;
}
}
@@ -0,0 +1,37 @@
/*
* 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.path;
import lombok.RequiredArgsConstructor;
@RequiredArgsConstructor
public enum PathSide {
North(0, -1, 7, 0, 0),
South(0, 1, 7, 14, 180),
West(-1, 0, 0, 7, 90),
East(1, 0, 14, 7, 270),
;
public final int tileOffsetX;
public final int tileOffsetZ;
public final int pasteOffsetX;
public final int pasteOffsetZ;
public final int rotate;
}
@@ -17,6 +17,10 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
package de.steamwar package de.steamwar.bausystem.region.dynamic.path;
data class ServerInfo(val name: String, val version: Int, val checkpointed: Boolean) public enum RotationCorrection {
Unchanged,
WithCorrection,
UsingOrdinal,
}
@@ -0,0 +1,68 @@
/*
* 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.path;
import de.steamwar.bausystem.region.RegionType;
import de.steamwar.bausystem.region.dynamic.Tile;
import de.steamwar.bausystem.region.dynamic.VariantSelector;
import de.steamwar.bausystem.shared.Pair;
public class SelectorCorner {
private static final int BITS;
private static final int SELECTOR_COUNT;
static {
int values = RegionType.ConnectionType.values().length;
BITS = (int) Math.ceil(Math.log(values) / Math.log(2));
SELECTOR_COUNT = (int) Math.pow(2, BITS * 3D);
}
private VariantSelector[] selectors = new VariantSelector[SELECTOR_COUNT];
private RotationCorrection[] rotationCorrections = new RotationCorrection[SELECTOR_COUNT];
private int getIndex(RegionType.ConnectionType left, RegionType.ConnectionType right, RegionType.ConnectionType diagonal) {
int index = left.ordinal();
index = index << BITS | right.ordinal();
return index << BITS | diagonal.ordinal();
}
public SelectorCorner Case(RegionType.ConnectionType left, RegionType.ConnectionType right, RegionType.ConnectionType diagonal, VariantSelector selector) {
int index = getIndex(left, right, diagonal);
selectors[index] = selector;
rotationCorrections[index] = RotationCorrection.Unchanged;
return this;
}
public SelectorCorner Case(RegionType.ConnectionType left, RegionType.ConnectionType right, RegionType.ConnectionType diagonal, VariantSelector selector, RotationCorrection rotationCorrection) {
int index = getIndex(left, right, diagonal);
selectors[index] = selector;
rotationCorrections[index] = rotationCorrection;
return this;
}
public Pair<VariantSelector, RotationCorrection> Select(Tile tile, PathCorner corner) {
RegionType.ConnectionType left = PathArea.getConnectionType(tile, corner.side1, null);
RegionType.ConnectionType right = PathArea.getConnectionType(tile, corner.side2, null);
RegionType.ConnectionType diagonal = PathArea.getConnectionType(tile, corner.side1, corner.side2);
int index = getIndex(left, right, diagonal);
return new Pair<>(selectors[index], rotationCorrections[index]);
}
}
@@ -0,0 +1,39 @@
/*
* 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.path;
import de.steamwar.bausystem.region.RegionType;
import de.steamwar.bausystem.region.dynamic.Tile;
import de.steamwar.bausystem.region.dynamic.VariantSelector;
public class SelectorSide {
private VariantSelector[] selectors = new VariantSelector[RegionType.ConnectionType.values().length];
public SelectorSide Case(RegionType.ConnectionType type, VariantSelector selector) {
selectors[type.ordinal()] = selector;
return this;
}
public VariantSelector Select(Tile tile, PathSide side) {
RegionType.ConnectionType type = PathArea.getConnectionType(tile, side, null);
return selectors[type.ordinal()];
}
}
@@ -0,0 +1,113 @@
/*
* 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.special;
import de.steamwar.bausystem.BauSystem;
import de.steamwar.bausystem.region.DynamicRegionSystem;
import de.steamwar.bausystem.region.Point;
import de.steamwar.bausystem.region.Region;
import de.steamwar.bausystem.region.dynamic.DynamicRegion;
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.utils.PasteBuilder;
import lombok.Getter;
import lombok.NonNull;
import org.bukkit.Bukkit;
import org.bukkit.Location;
import org.jetbrains.annotations.Nullable;
import java.io.File;
import java.util.List;
import java.util.UUID;
import java.util.concurrent.atomic.AtomicBoolean;
public class SpecialArea implements Region.Area {
public static final File SPECIAL_PATH_DIR = new File(Bukkit.getWorlds().get(0).getWorldFolder(), "sections/special");
private final DynamicRegion region;
@Getter
private final Tile tile;
private final Point minPoint;
private final Point maxPoint;
private final Point copyPoint;
private final VariantSelector resetFiles;
public SpecialArea(Tile tile, DynamicRegion region, VariantSelector resetFiles) {
this.region = region;
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());
this.resetFiles = resetFiles;
}
@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;
}
private boolean resetting = false;
@Override
public void reset(Location location, PasteBuilder pasteBuilder, boolean extension) {
if (resetting) return;
AtomicBoolean hasResetting = new AtomicBoolean(false);
List<Region> regions = DynamicRegionSystem.INSTANCE.getConnectedRegions(region)
.peek(region -> {
if (((SpecialArea) region.getArea()).resetting) hasResetting.set(true);
})
.toList();
if (hasResetting.get()) return;
regions.forEach(region -> {
((SpecialArea) region.getArea()).resetting = true;
});
for (int i = 0; i < regions.size(); i++) {
final int offset = i / 10;
final Region region = regions.get(i);
Bukkit.getScheduler().runTaskLater(BauSystem.getInstance(), () -> {
region.getArea().place(null, null, false); // TODO: Fix this!
}, offset);
}
}
@Override
public void place(Location location, PasteBuilder pasteBuilder, boolean extension) {
File resetFile = resetFiles.select().orElse(null);
if (resetFile != null) PasteUtils.paste(resetFile, minPoint, 0);
resetting = false;
}
}
@@ -0,0 +1,63 @@
/*
* This file is a part of the SteamWar software.
*
* Copyright (C) 2026 SteamWar.de-Serverteam
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package de.steamwar.bausystem.region.dynamic.special;
import de.steamwar.bausystem.region.DynamicRegionSystem;
import de.steamwar.bausystem.region.Region;
import de.steamwar.bausystem.region.RegionData;
import de.steamwar.bausystem.region.RegionFlagPolicy;
import de.steamwar.bausystem.region.dynamic.DynamicRegion;
import de.steamwar.bausystem.region.flags.Flag;
import de.steamwar.bausystem.region.flags.TNTMode;
import de.steamwar.core.Core;
import lombok.NonNull;
import java.util.stream.Stream;
public class SpecialRegionData extends RegionData {
private final DynamicRegion region;
public SpecialRegionData(DynamicRegion region) {
super(region);
this.region = region;
}
@Override
protected void initialize() {
flagMap.put(Flag.TNT, TNTMode.DENY);
}
@Override
protected Stream<Region> connectedRegions() {
return DynamicRegionSystem.INSTANCE.getConnectedRegions(region);
}
@Override
public @NonNull <T extends Enum<T> & Flag.Value<T>> RegionFlagPolicy has(@NonNull Flag<T> flag) {
if (flag.oneOf(Flag.TNT, Flag.FIRE, Flag.FREEZE, Flag.PROTECT, Flag.NO_GRAVITY, Flag.WATER_DESTROY)) {
return RegionFlagPolicy.WRITABLE;
}
if (flag.oneOf(Flag.ITEMS) && Core.getVersion() >= 20) {
return RegionFlagPolicy.WRITABLE;
}
return RegionFlagPolicy.NOT_APPLICABLE;
}
}
@@ -0,0 +1,121 @@
/*
* 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.special.dry;
import com.google.gson.JsonArray;
import com.google.gson.stream.JsonWriter;
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.*;
import de.steamwar.bausystem.region.dynamic.modes.AreaTile;
import de.steamwar.bausystem.region.dynamic.modes.DisplayRegionData;
import de.steamwar.bausystem.region.dynamic.special.SpecialArea;
import de.steamwar.bausystem.region.dynamic.special.SpecialRegionData;
import de.steamwar.sql.GameModeConfig;
import lombok.NonNull;
import org.bukkit.Material;
import java.io.File;
import java.io.IOException;
import java.util.UUID;
import static de.steamwar.bausystem.region.dynamic.special.SpecialArea.SPECIAL_PATH_DIR;
@RegionConstructorData(
identifier = "special_dry",
name = "Dry",
material = Material.SAND,
widthX = Tile.tileSize,
widthZ = Tile.tileSize
)
public class DryRegion extends DynamicRegion {
private static final VariantSelector DRY = VariantSelector.Get(new File(SPECIAL_PATH_DIR, "dry"));
private final SpecialArea area;
public DryRegion(Tile tile) {
this(UUID.randomUUID(), tile);
finishCreate();
}
public DryRegion(UUID id, JsonArray tileData) {
this(id, readTile(tileData));
finishLoad();
}
private DryRegion(UUID id, Tile tile) {
super(id, null);
area = new SpecialArea(tile, this, DRY);
regionData = new SpecialRegionData(this);
}
@Override
public void writeData(JsonWriter writer) throws IOException {
writeTile(writer, area.getTile());
}
@Override
public @NonNull RegionType getType() {
return RegionType.DRY_SPECIAL;
}
@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<Material, String> 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);
}
}
@@ -0,0 +1,119 @@
/*
* 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.special.wet;
import com.google.gson.JsonArray;
import com.google.gson.stream.JsonWriter;
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.*;
import de.steamwar.bausystem.region.dynamic.special.SpecialArea;
import de.steamwar.bausystem.region.dynamic.special.SpecialRegionData;
import de.steamwar.sql.GameModeConfig;
import lombok.NonNull;
import org.bukkit.Material;
import java.io.File;
import java.io.IOException;
import java.util.UUID;
import static de.steamwar.bausystem.region.dynamic.special.SpecialArea.SPECIAL_PATH_DIR;
@RegionConstructorData(
identifier = "special_wet",
name = "Wet",
material = Material.LIGHT_BLUE_CONCRETE_POWDER,
widthX = Tile.tileSize,
widthZ = Tile.tileSize
)
public class WetRegion extends DynamicRegion {
private static final VariantSelector WET = VariantSelector.Get(new File(SPECIAL_PATH_DIR, "wet"));
private final SpecialArea area;
public WetRegion(Tile tile) {
this(UUID.randomUUID(), tile);
finishCreate();
}
public WetRegion(UUID id, JsonArray tileData) {
this(id, readTile(tileData));
finishLoad();
}
private WetRegion(UUID id, Tile tile) {
super(id, null);
area = new SpecialArea(tile, this, WET);
regionData = new WetRegionData(this);
}
@Override
public void writeData(JsonWriter writer) throws IOException {
writeTile(writer, area.getTile());
}
@Override
public @NonNull RegionType getType() {
return RegionType.WET_SPECIAL;
}
@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<Material, String> 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);
}
}
@@ -0,0 +1,45 @@
/*
* This file is a part of the SteamWar software.
*
* Copyright (C) 2026 SteamWar.de-Serverteam
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package de.steamwar.bausystem.region.dynamic.special.wet;
import de.steamwar.bausystem.region.RegionFlagPolicy;
import de.steamwar.bausystem.region.dynamic.DynamicRegion;
import de.steamwar.bausystem.region.dynamic.special.SpecialRegionData;
import de.steamwar.bausystem.region.flags.Flag;
import de.steamwar.bausystem.region.flags.ProtectMode;
import lombok.NonNull;
public class WetRegionData extends SpecialRegionData {
public WetRegionData(DynamicRegion region) {
super(region);
}
@Override
protected void initialize() {
flagMap.put(Flag.PROTECT, ProtectMode.INACTIVE);
}
@Override
public @NonNull <T extends Enum<T> & Flag.Value<T>> RegionFlagPolicy has(@NonNull Flag<T> flag) {
if (flag.oneOf(Flag.PROTECT)) return RegionFlagPolicy.NOT_APPLICABLE;
return super.has(flag);
}
}
@@ -46,10 +46,6 @@ public class FixedRegionSystem implements RegionSystem {
RegionLoader.load(); RegionLoader.load();
} }
@Override
public void save() {
}
@Override @Override
public @NonNull Location getWorldSpawn() { public @NonNull Location getWorldSpawn() {
Location spawnLocation = Bukkit.getWorlds().get(0).getSpawnLocation(); Location spawnLocation = Bukkit.getWorlds().get(0).getSpawnLocation();
@@ -79,4 +75,9 @@ public class FixedRegionSystem implements RegionSystem {
public Stream<Region> getRegions() { public Stream<Region> getRegions() {
return REGION_MAP.values().stream(); return REGION_MAP.values().stream();
} }
@Override
public @NonNull Stream<Region> getConnectedRegions(Region region) {
return Stream.empty();
}
} }
@@ -21,12 +21,15 @@ package de.steamwar.bausystem.region.fixed;
import com.sk89q.worldedit.extent.clipboard.Clipboard; import com.sk89q.worldedit.extent.clipboard.Clipboard;
import de.steamwar.bausystem.region.*; import de.steamwar.bausystem.region.*;
import de.steamwar.bausystem.region.flags.Flag;
import de.steamwar.bausystem.utils.PasteBuilder; import de.steamwar.bausystem.utils.PasteBuilder;
import de.steamwar.bausystem.worlddata.WorldData;
import de.steamwar.sql.GameModeConfig; import de.steamwar.sql.GameModeConfig;
import lombok.NonNull; import lombok.NonNull;
import lombok.Setter; import lombok.Setter;
import org.bukkit.Location; import org.bukkit.Location;
import org.bukkit.Material; import org.bukkit.Material;
import yapion.hierarchy.types.YAPIONObject;
import javax.annotation.Nullable; import javax.annotation.Nullable;
import java.io.File; import java.io.File;
@@ -43,8 +46,6 @@ public final class FixedGlobalRegion implements Region {
@Setter @Setter
private static RegionData FLAG_STORAGE; private static RegionData FLAG_STORAGE;
private static final UUID GLOBAL_REGION_ID = new UUID(0, 0);
private static final Area GLOBAL_AREA = new Area() { private static final Area GLOBAL_AREA = new Area() {
@Override @Override
public @NonNull Point getMinPoint(boolean extension) { public @NonNull Point getMinPoint(boolean extension) {
@@ -72,14 +73,8 @@ public final class FixedGlobalRegion implements Region {
return null; return null;
} }
@Nullable
@Override @Override
public File getResetFile() { public void place(Location location, PasteBuilder pasteBuilder, boolean extension) {
return null;
}
@Override
public void reset(PasteBuilder pasteBuilder, boolean extension) {
} }
@Override @Override
@@ -102,7 +97,7 @@ public final class FixedGlobalRegion implements Region {
@Override @Override
public @NonNull UUID getID() { public @NonNull UUID getID() {
return GLOBAL_REGION_ID; return RegionSystem.GLOBAL_REGION_ID;
} }
@Override @Override
@@ -139,4 +134,14 @@ public final class FixedGlobalRegion implements Region {
public @NonNull RegionBackups getBackups() { public @NonNull RegionBackups getBackups() {
return RegionBackups.EMPTY; return RegionBackups.EMPTY;
} }
@Override
public void save() {
FixedRegionDataUtils.saveRegionData("global", FLAG_STORAGE);
}
@Override
public void load(RegionData regionData) {
FixedRegionDataUtils.loadRegionData("global", FLAG_STORAGE);
}
} }
@@ -20,6 +20,7 @@
package de.steamwar.bausystem.region.fixed; package de.steamwar.bausystem.region.fixed;
import de.steamwar.bausystem.region.RegionData; import de.steamwar.bausystem.region.RegionData;
import de.steamwar.bausystem.region.RegionDataStore;
import de.steamwar.bausystem.region.RegionFlagPolicy; import de.steamwar.bausystem.region.RegionFlagPolicy;
import de.steamwar.bausystem.region.flags.ColorMode; import de.steamwar.bausystem.region.flags.ColorMode;
import de.steamwar.bausystem.region.flags.Flag; import de.steamwar.bausystem.region.flags.Flag;
@@ -27,12 +28,11 @@ import de.steamwar.bausystem.region.flags.ProtectMode;
import de.steamwar.bausystem.region.flags.TNTMode; import de.steamwar.bausystem.region.flags.TNTMode;
import de.steamwar.core.Core; import de.steamwar.core.Core;
import lombok.NonNull; import lombok.NonNull;
import yapion.hierarchy.types.YAPIONObject;
public class FixedGlobalRegionData extends RegionData { public class FixedGlobalRegionData extends RegionData {
public FixedGlobalRegionData(YAPIONObject data, Runnable onChange) { public FixedGlobalRegionData(RegionDataStore store) {
super(data, onChange); super(store);
} }
@Override @Override
@@ -31,6 +31,7 @@ import de.steamwar.sql.GameModeConfig;
import de.steamwar.sql.SchematicType; import de.steamwar.sql.SchematicType;
import lombok.NonNull; import lombok.NonNull;
import org.bukkit.Bukkit; import org.bukkit.Bukkit;
import org.bukkit.Location;
import org.bukkit.Material; import org.bukkit.Material;
import yapion.hierarchy.types.YAPIONObject; import yapion.hierarchy.types.YAPIONObject;
@@ -45,7 +46,6 @@ import java.util.stream.Collectors;
public class FixedRegion implements Region { public class FixedRegion implements Region {
private static final File backupFolder = new File(Bukkit.getWorlds().get(0).getWorldFolder(), "backup"); private static final File backupFolder = new File(Bukkit.getWorlds().get(0).getWorldFolder(), "backup");
private static final DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy.MM.dd' 'HH:mm:ss");
private final String name; private final String name;
private final UUID uuid; private final UUID uuid;
@@ -75,7 +75,7 @@ public class FixedRegion implements Region {
while (files.size() >= 20) files.remove(0).delete(); while (files.size() >= 20) files.remove(0).delete();
} }
final File backupFile = new File(definedBackupFolder, LocalDateTime.now().format(formatter) + ".schem"); final File backupFile = new File(definedBackupFolder, LocalDateTime.now().format(FORMATTER) + ".schem");
Point minPoint = area.getMinPoint(false); Point minPoint = area.getMinPoint(false);
Point maxPoint = area.getMaxPoint(false); Point maxPoint = area.getMaxPoint(false);
if (!FlatteningWrapper.impl.backup(minPoint, maxPoint, backupFile)) { if (!FlatteningWrapper.impl.backup(minPoint, maxPoint, backupFile)) {
@@ -101,7 +101,7 @@ public class FixedRegion implements Region {
@Nullable @Nullable
@Override @Override
public Backup get(String name) { public Backup get(@NonNull String name) {
final File definedBackupFolder = new File(new File(backupFolder, prototype.getName()), FixedRegion.this.name); final File definedBackupFolder = new File(new File(backupFolder, prototype.getName()), FixedRegion.this.name);
//noinspection ResultOfMethodCallIgnored //noinspection ResultOfMethodCallIgnored
definedBackupFolder.mkdirs(); definedBackupFolder.mkdirs();
@@ -116,14 +116,14 @@ public class FixedRegion implements Region {
private final File file; private final File file;
public BackupImpl(File file) { public BackupImpl(File file) {
super(RegionBackups.BackupType.AUTOMATIC, file.getName().replace(' ', '_').replace(".schem", ""), flagStorage); super(RegionBackups.BackupType.AUTOMATIC, file.getName().replace(' ', '_').replace(".schem", ""), __ -> flagStorage);
this.file = file; this.file = file;
} }
@Override @Override
public boolean load() { public boolean load() {
if (!file.exists()) return false; if (!file.exists()) return false;
EditSession editSession = new PasteBuilder(new PasteBuilder.FileProvider(file)) EditSession editSession = new PasteBuilder(PasteBuilder.ClipboardProvider.file(file))
.pastePoint(area.getMinPoint(false).add(prototype.getSizeX() / 2, 0, prototype.getSizeZ() / 2)) .pastePoint(area.getMinPoint(false).add(prototype.getSizeX() / 2, 0, prototype.getSizeZ() / 2))
.minPoint(area.getMinPoint(false)) .minPoint(area.getMinPoint(false))
.maxPoint(area.getMaxPoint(false)) .maxPoint(area.getMaxPoint(false))
@@ -134,15 +134,20 @@ public class FixedRegion implements Region {
} }
@Override @Override
public void delete() { public long getCreationTime() {
return file.lastModified();
}
@Override
public void delete(Location location) {
file.delete(); file.delete();
} }
} }
public FixedRegion(String name, FixedRegionData flagStorage, Prototype prototype, YAPIONObject regionConfig, YAPIONObject regionData) { public FixedRegion(String name, Prototype prototype, YAPIONObject regionConfig) {
this.name = name; this.name = name;
uuid = UUID.nameUUIDFromBytes(name.getBytes(StandardCharsets.UTF_8)); uuid = UUID.nameUUIDFromBytes(name.getBytes(StandardCharsets.UTF_8));
this.flagStorage = flagStorage; this.flagStorage = new FixedRegionData(this);
this.prototype = prototype; this.prototype = prototype;
this.skin = prototype.getDefaultSkin(); this.skin = prototype.getDefaultSkin();
@@ -170,15 +175,10 @@ public class FixedRegion implements Region {
return minPoint; return minPoint;
} }
@Nullable
@Override @Override
public File getResetFile() { public void place(Location location, PasteBuilder pasteBuilder, boolean extension) {
return prototype.getSkinMap().get(skin).getSchematicFile(); pasteBuilder.with(PasteBuilder.ClipboardProvider.file(prototype.getSkinMap().get(skin).getSchematicFile()))
} .reset(extension)
@Override
public void reset(PasteBuilder pasteBuilder, boolean extension) {
pasteBuilder.reset(extension)
.minPoint(getMinPoint(extension)) .minPoint(getMinPoint(extension))
.maxPoint(getMaxPoint(extension)) .maxPoint(getMaxPoint(extension))
.waterLevel(waterLevel); .waterLevel(waterLevel);
@@ -223,15 +223,10 @@ public class FixedRegion implements Region {
return copyPoint; return copyPoint;
} }
@Nullable
@Override @Override
public File getResetFile() { public void place(Location location, PasteBuilder pasteBuilder, boolean extension) {
return prototype.getSkinMap().get(skin).getBuildSchematicFile(); pasteBuilder.with(PasteBuilder.ClipboardProvider.file(prototype.getSkinMap().get(skin).getBuildSchematicFile()))
} .reset(extension)
@Override
public void reset(PasteBuilder pasteBuilder, boolean extension) {
pasteBuilder.reset(extension)
.minPoint(getMinPoint(extension)) .minPoint(getMinPoint(extension))
.maxPoint(getMaxPoint(extension)) .maxPoint(getMaxPoint(extension))
.waterLevel(waterLevel); .waterLevel(waterLevel);
@@ -277,15 +272,10 @@ public class FixedRegion implements Region {
return copyPoint; return copyPoint;
} }
@Nullable
@Override @Override
public File getResetFile() { public void place(Location location, PasteBuilder pasteBuilder, boolean extension) {
return prototype.getSkinMap().get(skin).getTestblockSchematicFile(); pasteBuilder.with(PasteBuilder.ClipboardProvider.file(prototype.getSkinMap().get(skin).getTestblockSchematicFile()))
} .reset(extension)
@Override
public void reset(PasteBuilder pasteBuilder, boolean extension) {
pasteBuilder.reset(extension)
.minPoint(getMinPoint(extension)) .minPoint(getMinPoint(extension))
.maxPoint(getMaxPoint(extension)) .maxPoint(getMaxPoint(extension))
.waterLevel(waterLevel); .waterLevel(waterLevel);
@@ -387,4 +377,14 @@ public class FixedRegion implements Region {
public @NonNull RegionBackups getBackups() { public @NonNull RegionBackups getBackups() {
return regionBackups; return regionBackups;
} }
@Override
public void save() {
FixedRegionDataUtils.saveRegionData(name, flagStorage);
}
@Override
public void load(RegionData regionData) {
FixedRegionDataUtils.loadRegionData(name, flagStorage);
}
} }
@@ -20,16 +20,16 @@
package de.steamwar.bausystem.region.fixed; package de.steamwar.bausystem.region.fixed;
import de.steamwar.bausystem.region.RegionData; import de.steamwar.bausystem.region.RegionData;
import de.steamwar.bausystem.region.RegionDataStore;
import de.steamwar.bausystem.region.RegionFlagPolicy; import de.steamwar.bausystem.region.RegionFlagPolicy;
import de.steamwar.bausystem.region.flags.Flag; import de.steamwar.bausystem.region.flags.Flag;
import de.steamwar.core.Core; import de.steamwar.core.Core;
import lombok.NonNull; import lombok.NonNull;
import yapion.hierarchy.types.YAPIONObject;
public class FixedRegionData extends RegionData { public class FixedRegionData extends RegionData {
public FixedRegionData(YAPIONObject data, Runnable onChange) { public FixedRegionData(RegionDataStore store) {
super(data, onChange); super(store);
} }
@Override @Override
@@ -0,0 +1,64 @@
/*
* This file is a part of the SteamWar software.
*
* Copyright (C) 2025 SteamWar.de-Serverteam
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package de.steamwar.bausystem.region.fixed;
import de.steamwar.bausystem.region.RegionData;
import de.steamwar.bausystem.region.flags.Flag;
import de.steamwar.bausystem.worlddata.WorldData;
import lombok.NonNull;
import lombok.experimental.UtilityClass;
import yapion.hierarchy.types.YAPIONObject;
@UtilityClass
public class FixedRegionDataUtils {
public void saveRegionData(@NonNull String identifier, @NonNull RegionData regionData) {
YAPIONObject yapionObject = new YAPIONObject();
YAPIONObject flagData = yapionObject.getOrSetDefault("flagStorage", new YAPIONObject());
regionData.getBackedMap().forEach((flag, value) -> {
flagData.put(flag.name(), value.name());
});
regionData.getBackedProperties().forEach(property -> {
yapionObject.put(property.field, property.writer.apply(property.get()));
});
WorldData.getRegionsData().put(identifier, yapionObject);
WorldData.write();
}
public void loadRegionData(@NonNull String identifier, @NonNull RegionData regionData) {
YAPIONObject values = WorldData.getRegionsData().getObject(identifier);
if (values == null) return;
YAPIONObject flagData = values.getObjectOrDefault("flagStorage", new YAPIONObject());
for (final Flag flag : Flag.getFlags()) {
if (!regionData.has(flag).isWritable()) continue;
try {
String s = flagData.getPlainValue(flag.name());
regionData.getBackedMap().put(flag, flag.valueOfValue(s));
} catch (Exception e) {
regionData.getBackedMap().put(flag, (Flag.Value<?>) flag.getDefaultValue());
}
}
regionData.getBackedProperties().forEach(property -> {
Object object = values.getPlainValue(property.field);
property.set(property.loader.apply(object));
});
}
}
@@ -210,14 +210,8 @@ public class Prototype {
} }
} }
public static void generateRegion(String name, YAPIONObject regionConfig, YAPIONObject regionData) { public static void generateRegion(String name, YAPIONObject regionConfig) {
Prototype prototype; Prototype prototype = PROTOTYPE_MAP.get(regionConfig.getPlainValue("prototype"));
if (regionData.containsKey("prototype", String.class)) { FixedRegionSystem.addRegion(new FixedRegion(name, prototype, regionConfig));
prototype = PROTOTYPE_MAP.get(regionData.getPlainValue("prototype"));
} else {
prototype = PROTOTYPE_MAP.get(regionConfig.getPlainValue("prototype"));
}
FixedRegionData flagStorage = new FixedRegionData(regionData, WorldData::write);
FixedRegionSystem.addRegion(new FixedRegion(name, flagStorage, prototype, regionConfig, regionData));
} }
} }
@@ -19,16 +19,14 @@
package de.steamwar.bausystem.region.fixed.loader; package de.steamwar.bausystem.region.fixed.loader;
import de.steamwar.bausystem.region.fixed.FixedGlobalRegionData;
import de.steamwar.bausystem.region.fixed.FixedGlobalRegion; import de.steamwar.bausystem.region.fixed.FixedGlobalRegion;
import de.steamwar.bausystem.region.fixed.FixedGlobalRegionData;
import de.steamwar.bausystem.region.fixed.Prototype; import de.steamwar.bausystem.region.fixed.Prototype;
import de.steamwar.bausystem.worlddata.WorldData;
import lombok.experimental.UtilityClass; import lombok.experimental.UtilityClass;
import org.bukkit.Bukkit; import org.bukkit.Bukkit;
import yapion.hierarchy.diff.DiffChange; import yapion.hierarchy.diff.DiffChange;
import yapion.hierarchy.diff.YAPIONDiff; import yapion.hierarchy.diff.YAPIONDiff;
import yapion.hierarchy.types.YAPIONObject; import yapion.hierarchy.types.YAPIONObject;
import yapion.hierarchy.types.YAPIONType;
import yapion.parser.YAPIONParser; import yapion.parser.YAPIONParser;
import java.io.BufferedInputStream; import java.io.BufferedInputStream;
@@ -55,7 +53,6 @@ public class RegionLoader {
} }
loaded = yapionObject; loaded = yapionObject;
YAPIONObject optionsYapionObject = WorldData.getRegionsData();
yapionObject.forEach((key, yapionAnyType) -> { yapionObject.forEach((key, yapionAnyType) -> {
if (key.equals("global")) { if (key.equals("global")) {
return; return;
@@ -63,23 +60,9 @@ public class RegionLoader {
if (!(yapionAnyType instanceof YAPIONObject)) { if (!(yapionAnyType instanceof YAPIONObject)) {
return; return;
} }
Prototype.generateRegion(key, (YAPIONObject) yapionAnyType);
YAPIONObject regionConfig = (YAPIONObject) yapionAnyType;
YAPIONObject regionData = new YAPIONObject();
if (optionsYapionObject.containsKey(key, YAPIONType.OBJECT)) {
regionData = optionsYapionObject.getObject(key);
} else {
optionsYapionObject.add(key, regionData);
}
Prototype.generateRegion(key, regionConfig, regionData);
}); });
YAPIONObject globalOptions = optionsYapionObject.getObject("global"); FixedGlobalRegion.setFLAG_STORAGE(new FixedGlobalRegionData(FixedGlobalRegion.INSTANCE));
if (globalOptions == null) {
globalOptions = new YAPIONObject();
optionsYapionObject.add("global", globalOptions);
}
FixedGlobalRegion.setFLAG_STORAGE(new FixedGlobalRegionData(globalOptions, WorldData::write));
} }
} }
+11
View File
@@ -28,6 +28,7 @@ tasks.build {
dependencies { dependencies {
implementation(project(":BauSystem:BauSystem_RegionFixed")) implementation(project(":BauSystem:BauSystem_RegionFixed"))
implementation(project(":BauSystem:BauSystem_RegionDynamic"))
implementation(project(":BauSystem:BauSystem_Main")) implementation(project(":BauSystem:BauSystem_Main"))
implementation(project(":BauSystem:BauSystem_15")) implementation(project(":BauSystem:BauSystem_15"))
implementation(project(":BauSystem:BauSystem_18")) implementation(project(":BauSystem:BauSystem_18"))
@@ -53,3 +54,13 @@ tasks.register<DevServer>("DevBau21") {
dependsOn(":SchematicSystem:shadowJar") dependsOn(":SchematicSystem:shadowJar")
template = "Bau21" template = "Bau21"
} }
tasks.register<DevServer>("DevBau21Dynamic") {
group = "run"
description = "Run a 1.21 Dynamic Dev Bau"
dependsOn(":SpigotCore:shadowJar")
dependsOn(":BauSystem:shadowJar")
dependsOn(":SchematicSystem:shadowJar")
template = "Bau21-Dynamic"
jar = "/jars/paper-1.21.6.jar"
}
-1
View File
@@ -24,5 +24,4 @@ plugins {
dependencies { dependencies {
testImplementation(libs.junit) testImplementation(libs.junit)
testImplementation(libs.hamcrest) testImplementation(libs.hamcrest)
compileOnly(project(":CommonCore:SQL"))
} }
@@ -1,71 +0,0 @@
/*
* 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.logger
import de.steamwar.sql.AuditLog
import de.steamwar.sql.AuditLogTable
import de.steamwar.sql.SQLWrapper
import de.steamwar.sql.SteamwarUser
import de.steamwar.sql.internal.useDb
import org.jetbrains.exposed.v1.jdbc.insertIgnore
class LogEntry(val type: AuditLog.Type, val user: SteamwarUser) {
private val start = System.currentTimeMillis()
var text: String = ""
var arguments: String = ""
private var exceptionType: String? = null
private var exceptionText: String? = null
private var exceptionStacktrace: String? = null
private var owner: SteamwarUser? = null
fun addException(e: Throwable) {
exceptionType = e.javaClass.name
exceptionText = e.message
exceptionStacktrace = e.stackTraceToString()
}
fun addServerOwner(owner: SteamwarUser) {
this.owner = owner
}
fun finish() {
val end = System.currentTimeMillis()
val info = SQLWrapper.impl.serverInfo
useDb {
AuditLogTable.insertIgnore {
it[this.action] = type
it[this.actor] = user.getId()
it[this.actionText] = text
it[this.actionArguments] = arguments
it[this.duration] = end - start
it[this.errorType] = exceptionType
it[this.errorText] = exceptionText
it[this.stackTrace] = exceptionStacktrace
it[this.server] = info.name
it[this.checkpointed] = info.checkpointed
it[this.duration] = end - start
it[this.serverOwner] = owner?.getId()
}
}
}
}
+64 -17
View File
@@ -19,47 +19,94 @@
package de.steamwar.sql package de.steamwar.sql
import de.steamwar.sql.internal.useDb
import org.jetbrains.exposed.v1.core.dao.id.EntityID import org.jetbrains.exposed.v1.core.dao.id.EntityID
import org.jetbrains.exposed.v1.core.dao.id.IntIdTable import org.jetbrains.exposed.v1.core.dao.id.IntIdTable
import org.jetbrains.exposed.v1.dao.IntEntity import org.jetbrains.exposed.v1.dao.IntEntity
import org.jetbrains.exposed.v1.dao.IntEntityClass import org.jetbrains.exposed.v1.dao.IntEntityClass
import org.jetbrains.exposed.v1.javatime.timestamp import org.jetbrains.exposed.v1.javatime.timestamp
import java.time.Instant
object AuditLogTable: IntIdTable("AuditLog", "AuditLogId") { object AuditLogTable: IntIdTable("AuditLog", "AuditLogId") {
val time = timestamp("Time") val time = timestamp("Time")
val server = varchar("ServerName", 255) val server = varchar("ServerName", 255)
val serverType = varchar("ServerType", 255) val serverOwner = reference("ServerOwner", SteamwarUserTable).nullable()
val serverOwner = optReference("ServerOwner", SteamwarUserTable)
val checkpointed = bool("Checkpointed")
val actor = reference("Actor", SteamwarUserTable) val actor = reference("Actor", SteamwarUserTable)
val action = enumerationByName("ActionType", 255, AuditLog.Type::class) val action = enumerationByName("ActionType", 255, AuditLog.Type::class)
val actionText = text("ActionText") val actionText = text("ActionText")
val actionArguments = text("ActionArguments")
val duration = long("Duration")
val errorType = text("ErrorType").nullable()
val errorText = text("ErrorText").nullable()
val stackTrace = text("StackTrace").nullable()
} }
class AuditLog(id: EntityID<Int>): IntEntity(id) { class AuditLog(id: EntityID<Int>): IntEntity(id) {
companion object: IntEntityClass<AuditLog>(AuditLogTable) { companion object: IntEntityClass<AuditLog>(AuditLogTable) {
@JvmField const val SERVER_NAME_VELOCITY: String = "Velocity"
val SERVER_NAME_VELOCITY: String = "Velocity"
private fun create(
serverName: String,
serverOwner: SteamwarUser?,
actor: SteamwarUser,
actionType: Type,
text: String = ""
) = useDb {
new {
this.time = Instant.now()
this.server = serverName
this.serverOwner = serverOwner?.id
this.actor = actor.id
this.action = actionType
this.actionText = text
}
}
@JvmStatic
fun createJoin(jointServerName: String, serverOwner: SteamwarUser?, joinedPlayer: SteamwarUser) = create(jointServerName, serverOwner, joinedPlayer, Type.JOIN)
@JvmStatic
fun createLeave(leftServerName: String, serverOwner: SteamwarUser?, joinedPlayer: SteamwarUser) = create(leftServerName, serverOwner, joinedPlayer, Type.LEAVE)
@JvmStatic
fun createCommand(serverName: String, serverOwner: SteamwarUser?, player: SteamwarUser?, command: String) = player?.let { create(serverName, serverOwner, it, Type.COMMAND, command) }
@JvmStatic
fun createSensitiveCommand(
serverName: String,
serverOwner: SteamwarUser?,
player: SteamwarUser?,
command: String
) = player?.let { create(serverName, serverOwner, it, Type.SENSITIVE_COMMAND, command) }
@JvmStatic
fun createChat(serverName: String, serverOwner: SteamwarUser?, chatter: SteamwarUser, chat: String) = create(serverName, serverOwner, chatter, Type.CHAT, chat)
@JvmStatic
fun createGuiOpen(serverName: String, serverOwner: SteamwarUser?, player: SteamwarUser, guiName: String) = create(serverName, serverOwner, player, Type.GUI_OPEN, guiName)
@JvmStatic
fun createGuiClick(
serverName: String,
serverOwner: SteamwarUser?,
player: SteamwarUser,
guiName: String,
clickType: String,
slot: Int,
itemName: String
) = create(
serverName,
serverOwner,
player,
Type.GUI_CLICK,
"Gui: $guiName\nSlot: $slot\nClickType: $clickType\nItemName: $itemName"
)
@JvmStatic
fun createGuiClose(serverName: String, serverOwner: SteamwarUser?, player: SteamwarUser, guiName: String) = create(serverName, serverOwner, player, Type.GUI_CLOSE, guiName)
} }
var time by AuditLogTable.time var time by AuditLogTable.time
var server by AuditLogTable.server var server by AuditLogTable.server
val serverType by AuditLogTable.serverType
var serverOwner by AuditLogTable.serverOwner var serverOwner by AuditLogTable.serverOwner
var actor by AuditLogTable.actor var actor by AuditLogTable.actor
var checkpointed by AuditLogTable.checkpointed
var action by AuditLogTable.action var action by AuditLogTable.action
var actionText by AuditLogTable.actionText var actionText by AuditLogTable.actionText
var actionArguments by AuditLogTable.actionArguments
var duration by AuditLogTable.duration
var errorType by AuditLogTable.errorType
var errorText by AuditLogTable.errorText
var stackTrace by AuditLogTable.stackTrace
enum class Type { enum class Type {
JOIN, JOIN,
@@ -35,7 +35,11 @@ import java.time.Instant
object BannedUserIPsTable: CompositeIdTable("BannedUserIPs") { object BannedUserIPsTable: CompositeIdTable("BannedUserIPs") {
val userId = reference("UserID", SteamwarUserTable) val userId = reference("UserID", SteamwarUserTable)
val timestamp = timestamp("Timestamp") val timestamp = timestamp("Timestamp")
val ip = varchar("IP", 45) val ip = varchar("IP", 45).entityId()
init {
addIdColumn(userId)
}
override val primaryKey = PrimaryKey(userId, ip) override val primaryKey = PrimaryKey(userId, ip)
} }
@@ -402,6 +402,13 @@ public final class GameModeConfig<M, W> {
*/ */
public final int WaterDepth; public final int WaterDepth;
/**
* If TNT should break blocks even underwater
*
* @implSpec {@code true} by default
*/
public final boolean WaterDamage;
/** /**
* The outer border of the arena, measured in blocks around the schematic areas * The outer border of the arena, measured in blocks around the schematic areas
*/ */
@@ -457,6 +464,7 @@ public final class GameModeConfig<M, W> {
private ArenaConfig(YMLWrapper loader, SchematicConfig.SizeConfig Size, List<Integer> EnterStages) { private ArenaConfig(YMLWrapper loader, SchematicConfig.SizeConfig Size, List<Integer> EnterStages) {
loaded = loader.canLoad(); loaded = loader.canLoad();
WaterDepth = loader.getInt("WaterDepth", 0); WaterDepth = loader.getInt("WaterDepth", 0);
WaterDamage = loader.getBoolean("WaterDamage", true);
Schem2Border = new Schem2BorderConfig(loader.with("Schem2Border")); Schem2Border = new Schem2BorderConfig(loader.with("Schem2Border"));
SpawnOffset = new SpawnOffsetConfig(loader.with("SpawnOffset"), Size); SpawnOffset = new SpawnOffsetConfig(loader.with("SpawnOffset"), Size);
BorderFromSchematic = loader.getInt("BorderFromSchematic", 21); BorderFromSchematic = loader.getInt("BorderFromSchematic", 21);
@@ -597,18 +605,18 @@ public final class GameModeConfig<M, W> {
public final boolean IgnorePublicOnly; public final boolean IgnorePublicOnly;
/** /**
* If obsidian and bedrock should be replaced during PRE_RUNNING * Replacements that should be done during PRE_RUNNING with no block updates
* *
* @implSpec {@code false} by default * @implSpec {@code {}} by default
*/ */
public final boolean ReplaceObsidianBedrock; public final Map<M, M> ReplacementsWithoutBlockUpdates;
/** /**
* If the replacement should happen with block updates * Replacements that should be done during PRE_RUNNING with block updates
* *
* @implSpec {@code false} by default * @implSpec {@code {}} by default
*/ */
public final boolean ReplaceWithBlockupdates; public final Map<M, M> ReplacementsWithBlockUpdates;
/** /**
* If the schematic perparation arena mode is time limited * If the schematic perparation arena mode is time limited
@@ -641,7 +649,7 @@ public final class GameModeConfig<M, W> {
/** /**
* Maximal blast resistance for the design blocks * Maximal blast resistance for the design blocks
* *
* @implSpec {@code Double.MAX_VALUE} by default * @implSpec {@link SchematicConfig#MaxBlastResistance} by default
*/ */
public final double MaxDesignBlastResistance; public final double MaxDesignBlastResistance;
@@ -665,13 +673,11 @@ public final class GameModeConfig<M, W> {
PasteAligned = loader.getBoolean("PasteAligned", false); PasteAligned = loader.getBoolean("PasteAligned", false);
OnlyPublicSchematics = loader.getBoolean("OnlyPublicSchematics", false); OnlyPublicSchematics = loader.getBoolean("OnlyPublicSchematics", false);
IgnorePublicOnly = loader.getBoolean("IgnorePublicOnly", false); IgnorePublicOnly = loader.getBoolean("IgnorePublicOnly", false);
ReplaceObsidianBedrock = loader.getBoolean("ReplaceObsidianBedrock", false);
ReplaceWithBlockupdates = loader.getBoolean("ReplaceWithBlockupdates", false);
UnlimitedPrepare = loader.getBoolean("UnlimitedPrepare", false); UnlimitedPrepare = loader.getBoolean("UnlimitedPrepare", false);
MaxBlocks = loader.getInt("MaxBlocks", 0); MaxBlocks = loader.getInt("MaxBlocks", 0);
MaxDispenserItems = loader.getInt("MaxDispenserItems", 128); MaxDispenserItems = loader.getInt("MaxDispenserItems", 128);
MaxBlastResistance = loader.getDouble("MaxBlastResistance", Double.MAX_VALUE); MaxBlastResistance = loader.getDouble("MaxBlastResistance", Double.MAX_VALUE);
MaxDesignBlastResistance = loader.getDouble("MaxDesignBlastResistance", Double.MAX_VALUE); MaxDesignBlastResistance = loader.getDouble("MaxDesignBlastResistance", MaxBlastResistance);
Map<Set<M>, Integer> Limited = new HashMap<>(); Map<Set<M>, Integer> Limited = new HashMap<>();
for (Map<?, ?> entry : loader.getMapList("Limited")) { for (Map<?, ?> entry : loader.getMapList("Limited")) {
@@ -690,6 +696,9 @@ public final class GameModeConfig<M, W> {
Limited.put(Collections.singleton((M) material), 0); Limited.put(Collections.singleton((M) material), 0);
}); });
this.Limited = Collections.unmodifiableMap(Limited); this.Limited = Collections.unmodifiableMap(Limited);
this.ReplacementsWithoutBlockUpdates = loader.getMap("ReplacementsWithoutBlockUpdates", loader.materialMapper, loader.materialMapper);
this.ReplacementsWithBlockUpdates = loader.getMap("ReplacementsWithBlockUpdates", loader.materialMapper, loader.materialMapper);
} }
@ToString @ToString
@@ -20,7 +20,6 @@
package de.steamwar.sql; package de.steamwar.sql;
import de.steamwar.ImplementationProvider; import de.steamwar.ImplementationProvider;
import de.steamwar.ServerInfo;
import java.io.File; import java.io.File;
import java.util.Collections; import java.util.Collections;
@@ -41,6 +40,4 @@ public interface SQLWrapper<M> {
} }
void additionalExceptionMetadata(StringBuilder builder); void additionalExceptionMetadata(StringBuilder builder);
ServerInfo getServerInfo();
} }
@@ -36,8 +36,6 @@ object ExceptionTable: IntIdTable("Exception") {
class SWException { class SWException {
companion object { companion object {
private val exceptionCache = HashSet<String>()
val cwd = System.getProperty("user.dir") val cwd = System.getProperty("user.dir")
val serverName = File(cwd).name val serverName = File(cwd).name
@@ -45,9 +43,7 @@ class SWException {
fun init() = Unit fun init() = Unit
@JvmStatic @JvmStatic
fun log(message: String, stacktrace: String) = if (exceptionCache.contains(stacktrace)) Unit else useDb { fun log(message: String, stacktrace: String) = useDb {
exceptionCache.add(stacktrace)
ExceptionTable.insert { ExceptionTable.insert {
it[ExceptionTable.server] = serverName it[ExceptionTable.server] = serverName
it[ExceptionTable.message] = generateMessage(message) it[ExceptionTable.message] = generateMessage(message)
@@ -186,10 +186,10 @@ class SteamwarUser(id: EntityID<Int>): IntEntity(id) {
val salt = ByteArray(16) val salt = ByteArray(16)
random.nextBytes(salt) random.nextBytes(salt)
val saltString = Base64.getEncoder().encode(salt) val saltString = Base64.getEncoder().encodeToString(salt)
val hash = generateHash(value, salt) val hash = generateHash(value, salt)
val hashString = Base64.getEncoder().encode(hash) val hashString = Base64.getEncoder().encodeToString(hash)
useDb { useDb {
passwordInternal = "$hashString:$saltString" passwordInternal = "$hashString:$saltString"

Some files were not shown because too many files have changed in this diff Show More