Compare commits

..
Author SHA1 Message Date
D4rkr34lm 24a7dae6d8 Apply temporary fix for rendering of the cursor 2026-06-17 22:29:44 +02:00
D4rkr34lm 99f7610d3d . 2026-06-12 19:48:16 +02:00
D4rkr34lm 2c05f06e2b Merge branch 'main' into BauSystem/add-cannon-automation-tool
# Conflicts:
#	BauSystem/BauSystem_Main/build.gradle.kts
2026-06-12 17:21:08 +02:00
D4rkr34lm 9606c6bc0d . 2026-06-12 16:50:26 +02:00
D4rkr34lm 34e7c62768 First draft of initial user flow 2026-06-12 13:08:35 +02:00
186 changed files with 1286 additions and 9932 deletions
-5
View File
@@ -15,11 +15,6 @@ bin/
.vscode .vscode
.settings .settings
# Language Server
**/.project
**/.factorypath
**/.classpath
# Other # Other
lib lib
/WebsiteBackend/data /WebsiteBackend/data
+1 -2
View File
@@ -18,7 +18,7 @@
*/ */
plugins { plugins {
steamwar.java steamwar.kotlin
widener widener
} }
@@ -35,7 +35,6 @@ dependencies {
compileOnly(libs.classindex) compileOnly(libs.classindex)
annotationProcessor(libs.classindex) annotationProcessor(libs.classindex)
compileOnly(project(":SpigotCore", "default")) compileOnly(project(":SpigotCore", "default"))
compileOnly(project(":KotlinCore", "default"))
compileOnly(libs.axiom) compileOnly(libs.axiom)
compileOnly(libs.authlib) compileOnly(libs.authlib)
@@ -22,6 +22,7 @@ package de.steamwar.bausystem;
import de.steamwar.bausystem.config.BauServer; import de.steamwar.bausystem.config.BauServer;
import de.steamwar.bausystem.configplayer.Config; import de.steamwar.bausystem.configplayer.Config;
import de.steamwar.bausystem.configplayer.ConfigConverter; import de.steamwar.bausystem.configplayer.ConfigConverter;
import de.steamwar.bausystem.features.cannonCore.CannonCoreRegistrationKt;
import de.steamwar.bausystem.features.gui.BauGUI; import de.steamwar.bausystem.features.gui.BauGUI;
import de.steamwar.bausystem.features.script.lua.SteamWarLuaPlugin; import de.steamwar.bausystem.features.script.lua.SteamWarLuaPlugin;
import de.steamwar.bausystem.features.script.lua.libs.LuaLib; import de.steamwar.bausystem.features.script.lua.libs.LuaLib;
@@ -38,6 +39,7 @@ 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;
@@ -50,6 +52,8 @@ 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;
@@ -62,7 +66,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 { public class BauSystem extends JavaPlugin implements Listener {
// This should be treated as final! // This should be treated as final!
public static Message MESSAGE; public static Message MESSAGE;
@@ -132,12 +136,20 @@ public class BauSystem extends JavaPlugin {
String identifier = BauServerInfo.getOwnerUser().getUUID().toString().replace("-", ""); String identifier = BauServerInfo.getOwnerUser().getUUID().toString().replace("-", "");
WorldIdentifier.set("bau/" + Core.getVersion() + "/" + identifier); WorldIdentifier.set("bau/" + Core.getVersion() + "/" + identifier);
CannonCoreRegistrationKt.register(this);
}
@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,7 +56,6 @@ 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()) {
@@ -17,10 +17,14 @@
* 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.entity; package de.steamwar.bausystem.features.cannonCore
import org.bukkit.entity.Player; import org.bukkit.Location
public interface RInteractionCallback { class CannonCore(val location: Location) {
void call(Player player, RInteraction interaction, REntityServer.EntityAction entityAction);
companion object Manager {
var activeCores: Observable<List<CannonCore>> = Observable(ArrayList())
}
} }
@@ -17,18 +17,18 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
import org.gradle.api.tasks.Input package de.steamwar.bausystem.features.cannonCore
import org.gradle.api.tasks.Optional
open class VelocityServer : DevServer() { import de.steamwar.bausystem.SWUtils
import de.steamwar.command.SWCommand
import org.bukkit.entity.Player
@get:Input object CannonCoreCommand : SWCommand("cannoncore") {
@get:Optional
var packetDecodeLogging: Boolean? = false
init { @Register
doFirst { fun giveCannonCoreWand(player: Player) {
if (packetDecodeLogging == true) dParams.put("velocity.packet-decode-logging", "true") val wandItem = CannonCoreWand.getWandItem()
}
SWUtils.giveItemToPlayer(player, wandItem)
} }
} }
@@ -17,30 +17,30 @@
* 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.kotlin.ui package de.steamwar.bausystem.features.cannonCore
import de.steamwar.kotlin.ui.components.item import de.steamwar.entity.RBlockDisplay
import de.steamwar.kotlin.util.count import de.steamwar.entity.REntityAction
import kotlinx.coroutines.flow.MutableStateFlow import de.steamwar.entity.REntityServer
import de.steamwar.entity.RInteraction
import org.bukkit.Location
import org.bukkit.Material import org.bukkit.Material
import org.bukkit.entity.Player import org.bukkit.entity.Player
import org.bukkit.inventory.ItemStack
val Counter = MutableStateFlow(0) class CannonCoreEntity: RBlockDisplay {
val entityMaterial = Material.COMMAND_BLOCK
class TestInv(player: Player): UIInventory(player) { val hitbox: RInteraction
override fun view() {
inventory(3, "") { constructor(server: REntityServer, location: Location, onClick: (player: Player, clickAction: REntityAction) -> Unit) : super(server, location) {
item { setBlock(entityMaterial.createBlockData())
val amount by Counter.map { it + 1 }.listen()
item = ItemStack.of(Material.STONE) hitbox = RInteraction(server, location)
.count(amount) hitbox.setCallback(onClick)
x = 0
y = 0
onClick {
Counter.value += 1
}
}
}
} }
}
override fun die() {
super.die()
hitbox.die()
}
}
@@ -17,10 +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.kotlin.ui package de.steamwar.bausystem.features.cannonCore
interface RenderObject { import org.bukkit.plugin.Plugin
fun destroy()
fun render() fun register(plugin: Plugin) {
} CannonCoreCommand.register()
val pluginManager = plugin.server.pluginManager
pluginManager.registerEvents(CannonCoreWand, plugin)
}
@@ -0,0 +1,76 @@
/*
* 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.cannonCore
import de.steamwar.bausystem.BauSystem
import de.steamwar.bausystem.utils.ItemUtils
import de.steamwar.core.SWPlayer
import de.steamwar.inventory.SWItem
import org.bukkit.Material
import org.bukkit.entity.Player
import org.bukkit.event.EventHandler
import org.bukkit.event.Listener
import org.bukkit.event.player.PlayerItemHeldEvent
import org.bukkit.inventory.ItemStack
import org.bukkit.plugin.java.JavaPlugin
object CannonCoreWand : Listener {
val wandId = "CANNON_CORE_WAND"
val wandMaterial = Material.BREEZE_ROD
fun getWandItem(): ItemStack {
val title = "§eCannon Core Wand"
val lore = listOf(
"§eRight Click §8- §7Create a new cannon core"
)
val wand = SWItem(wandMaterial, title, lore, false, null)
val item = wand.itemStack
return ItemUtils.setItem(item, wandId)
}
fun isWandItem(itemStack: ItemStack?): Boolean {
return ItemUtils.isItem(itemStack, wandId)
}
@EventHandler
fun onPlayerEquip(event: PlayerItemHeldEvent) {
val player = event.player
val swPlayer = SWPlayer.of(event.player)
val item = player.inventory.getItem(event.newSlot)
if (isWandItem(item)) {
scheduleDisplayUpdate(player)
} else {
swPlayer.removeComponent(EmptyCannonCoreWandDisplay::class.java)
}
}
private fun scheduleDisplayUpdate(player: Player) {
BauSystem.runTaskLater(JavaPlugin.getPlugin(BauSystem::class.java), Runnable { updateDisplay(player) }, 2)
}
private fun updateDisplay(player: Player) {
if (isWandItem(player.inventory.itemInMainHand)) {
EmptyCannonCoreWandDisplay(player)
}
}
}
@@ -17,23 +17,29 @@
* 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.bausystem.region.dynamic.path; package de.steamwar.bausystem.features.cannonCore
import de.steamwar.bausystem.region.RegionType; import de.steamwar.core.SWPlayer
import de.steamwar.bausystem.region.dynamic.Tile; import de.steamwar.entity.REntityServer
import de.steamwar.bausystem.region.dynamic.VariantSelector; import org.bukkit.entity.Player
class SelectorSide { class CannonCoresDisplay: SWPlayer.Component {
val displayServer = REntityServer()
val unlisten: () -> Unit
private VariantSelector[] selectors = new VariantSelector[RegionType.ConnectionType.values().length]; constructor(owner: Player) {
unlisten = CannonCore.activeCores.observe( { cores ->
protected SelectorSide Case(RegionType.ConnectionType type, VariantSelector selector) { displayServer.entities.forEach { it.die() }
selectors[type.ordinal()] = selector; cores.forEach { CannonCoreEntity(displayServer, it.location) { _, _ -> println("Handler clicked at ${it.location}") } }
return this; })
} }
public VariantSelector Select(Tile tile, PathSide side) { override fun onMount(player: SWPlayer) {
RegionType.ConnectionType type = PathArea.getConnectionType(tile, side, null); displayServer.addPlayer(player.player)
return selectors[type.ordinal()]; }
override fun onUnmount(player: SWPlayer?) {
unlisten()
displayServer.close()
} }
} }
@@ -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.features.cannonCore
import de.steamwar.core.SWPlayer
import de.steamwar.cursor.Cursor
import org.bukkit.Material
import org.bukkit.entity.Player
class EmptyCannonCoreWandDisplay : SWPlayer.Component {
val coresDisplay: CannonCoresDisplay
var cursor: Cursor
constructor(owner: Player) {
coresDisplay = CannonCoresDisplay(owner)
SWPlayer.of(owner).setComponent(this)
cursor = Cursor(
coresDisplay.displayServer, owner, Material.GLASS, Material.COMMAND_BLOCK,
listOf(
Cursor.CursorMode.BLOCK_ALIGNED
),
) { location, hitEntity, action ->
print("Hello")
}
}
override fun onMount(player: SWPlayer) {
coresDisplay.onMount(player)
}
override fun onUnmount(player: SWPlayer) {
player.getComponent(Cursor::class.java)
.filter { it === cursor }
.ifPresent { player.removeComponent(Cursor::class.java) }
coresDisplay.onUnmount(player)
}
}
@@ -17,25 +17,31 @@
* 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.kotlin.ui package de.steamwar.bausystem.features.cannonCore
import kotlinx.coroutines.Job typealias Observer<T> = (currentValue: T) -> Unit
abstract class StateFlowListener { class Observable<T>(var value: T) {
private val jobs = mutableMapOf<Any, Job>() private val observers = ArrayList<Observer<T>>()
internal fun track(key: Any, createJob: () -> Job) { fun set(value: T) {
if (key in jobs) return val oldValue = this.value
this.value = value
val job = createJob() observers.forEach { it.invoke(value) }
jobs[key] = job
job.invokeOnCompletion { jobs.remove(key) }
} }
abstract fun update() fun get(): T {
return value
open fun destroy() {
jobs.values.toList().forEach { it.cancel() }
jobs.clear()
} }
}
fun observe(observer: Observer<T>): () -> Unit {
observers.add(observer)
observer(value)
return {removeObserver(observer)}
}
fun removeObserver(observer: Observer<T>) {
observers.remove(observer)
}
}
@@ -290,7 +290,7 @@ public class KillcheckerVisualizer {
} }
rEntities.get(point).die(); rEntities.get(point).die();
} }
RBlockDisplay entity = new RBlockDisplay(outlinePoints.contains(point) ? outline : inner, point.toLocation(WORLD, 0, 0, 0)); RBlockDisplay entity = new RBlockDisplay(outlinePoints.contains(point) ? outline : inner, point.toLocation(WORLD, 0.5, 0, 0.5));
entity.setBlock(MATERIALS[Math.min(count - 1, MATERIALS.length) - 1].createBlockData()); entity.setBlock(MATERIALS[Math.min(count - 1, MATERIALS.length) - 1].createBlockData());
rEntities.put(point, entity); rEntities.put(point, entity);
if (outlinePoints.contains(point)) outlinePointsCache.add(point); if (outlinePoints.contains(point)) outlinePointsCache.add(point);
@@ -63,7 +63,7 @@ public class ColorCommand extends SWCommand {
} }
region.getRegionData().set(Flag.COLOR, color); region.getRegionData().set(Flag.COLOR, color);
try { try {
PasteBuilder pasteBuilder = new PasteBuilder() PasteBuilder pasteBuilder = new PasteBuilder(new PasteBuilder.FileProvider(region.getArea().getResetFile()))
.ignoreAir(true) .ignoreAir(true)
.onlyColors(true) .onlyColors(true)
.color(color); .color(color);
@@ -60,7 +60,6 @@ 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);
} }
@@ -249,7 +249,6 @@ 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);
} }
@@ -52,7 +52,6 @@ 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,7 +54,6 @@ 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,7 +70,6 @@ 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,7 +30,6 @@ 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;
@@ -72,10 +71,7 @@ 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 (region.getHistory() == RegionHistory.EMPTY) { if (checkGlobalRegion(region, p)) return;
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");
@@ -87,8 +83,7 @@ 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 (region.getHistory() == RegionHistory.EMPTY) { if (checkGlobalRegion(region, p)) {
BauSystem.MESSAGE.send("REGION_REGION_NO_REGION", p);
return; return;
} }
@@ -105,7 +100,7 @@ public class RegionCommand extends SWCommand {
if (checkGlobalRegion(region, p)) return; if (checkGlobalRegion(region, p)) return;
try { try {
PasteBuilder pasteBuilder = new PasteBuilder() PasteBuilder pasteBuilder = new PasteBuilder(new PasteBuilder.FileProvider(region.getArea().getResetFile()))
.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(pasteBuilder, false);
@@ -127,7 +122,7 @@ public class RegionCommand extends SWCommand {
} }
try { try {
PasteBuilder pasteBuilder = new PasteBuilder(PasteBuilder.ClipboardProvider.schematic(node)) PasteBuilder pasteBuilder = new PasteBuilder(new PasteBuilder.SchematicProvider(node))
.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(pasteBuilder, false);
@@ -148,7 +143,7 @@ public class RegionCommand extends SWCommand {
BauSystem.MESSAGE.send("REGION_REGION_TP_UNKNOWN", p); BauSystem.MESSAGE.send("REGION_REGION_TP_UNKNOWN", p);
return; return;
} }
p.teleport(region.getBuildArea().getCopyPoint(false).toLocation(p, 0.5, 0, 0.5), PlayerTeleportEvent.TeleportCause.COMMAND); p.teleport(region.getBuildArea().getCopyPoint().toLocation(p, 0.5, 0, 0.5), PlayerTeleportEvent.TeleportCause.COMMAND);
BauSystem.MESSAGE.send("REGION_REGION_TP_COPY", p); BauSystem.MESSAGE.send("REGION_REGION_TP_COPY", p);
} }
@@ -163,7 +158,7 @@ public class RegionCommand extends SWCommand {
BauSystem.MESSAGE.send("REGION_REGION_TP_UNKNOWN", p); BauSystem.MESSAGE.send("REGION_REGION_TP_UNKNOWN", p);
return; return;
} }
p.teleport(region.getTestblockArea().getCopyPoint(false).toLocation(p, 0.5, 0, 0.5), PlayerTeleportEvent.TeleportCause.COMMAND); p.teleport(region.getTestblockArea().getCopyPoint().toLocation(p, 0.5, 0, 0.5), PlayerTeleportEvent.TeleportCause.COMMAND);
BauSystem.MESSAGE.send("REGION_REGION_TP_TEST_BLOCK", p); BauSystem.MESSAGE.send("REGION_REGION_TP_TEST_BLOCK", p);
} }
@@ -196,7 +191,7 @@ public class RegionCommand extends SWCommand {
break; break;
} }
Clipboard clipboard = FlatteningWrapper.impl.copy(minPoint, maxPoint, region.getBuildArea().getCopyPoint(false)); Clipboard clipboard = FlatteningWrapper.impl.copy(minPoint, maxPoint, region.getBuildArea().getCopyPoint());
WorldEdit.getInstance() WorldEdit.getInstance()
.getSessionManager() .getSessionManager()
.get(BukkitAdapter.adapt(p)) .get(BukkitAdapter.adapt(p))
@@ -241,13 +236,13 @@ public class RegionCommand extends SWCommand {
} }
try (EditSession e = WorldEditUtils.getEditSession(p)) { try (EditSession e = WorldEditUtils.getEditSession(p)) {
Operations.completeBlindly(clipboardHolder.createPaste(e).ignoreAirBlocks(ignoreAir).to(toBlockVector3(region.getBuildArea().getCopyPoint(false))).build()); Operations.completeBlindly(clipboardHolder.createPaste(e).ignoreAirBlocks(ignoreAir).to(toBlockVector3(region.getBuildArea().getCopyPoint())).build());
WorldEditUtils.addToPlayer(p, e); WorldEditUtils.addToPlayer(p, e);
if (selectPasted) { if (selectPasted) {
Clipboard clipboard = clipboardHolder.getClipboards().get(0); Clipboard clipboard = clipboardHolder.getClipboards().get(0);
BlockVector3 minPointSelection = clipboard.getRegion().getMinimumPoint().subtract(clipboard.getOrigin()).add(toBlockVector3(region.getBuildArea().getCopyPoint(false))); BlockVector3 minPointSelection = clipboard.getRegion().getMinimumPoint().subtract(clipboard.getOrigin()).add(toBlockVector3(region.getBuildArea().getCopyPoint()));
BlockVector3 maxPointSelection = clipboard.getRegion().getMaximumPoint().subtract(clipboard.getOrigin()).add(toBlockVector3(region.getBuildArea().getCopyPoint(false))); BlockVector3 maxPointSelection = clipboard.getRegion().getMaximumPoint().subtract(clipboard.getOrigin()).add(toBlockVector3(region.getBuildArea().getCopyPoint()));
FlatteningWrapper.impl.setSelection(p, Point.fromBlockVector3(minPointSelection), Point.fromBlockVector3(maxPointSelection)); FlatteningWrapper.impl.setSelection(p, Point.fromBlockVector3(minPointSelection), Point.fromBlockVector3(maxPointSelection));
} }
BauSystem.MESSAGE.send("REGION_REGION_PASTE_DONE", p); BauSystem.MESSAGE.send("REGION_REGION_PASTE_DONE", p);
@@ -29,6 +29,9 @@ 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;
@@ -49,7 +52,7 @@ 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() PasteBuilder pasteBuilder = new PasteBuilder(new PasteBuilder.FileProvider(region.getArea().getResetFile()))
.color(region.getRegionData().get(Flag.COLOR).getWithDefault()); .color(region.getRegionData().get(Flag.COLOR).getWithDefault());
region.getArea().reset(pasteBuilder, false); region.getArea().reset(pasteBuilder, false);
region.getRegionData().clear(); region.getRegionData().clear();
@@ -60,6 +63,35 @@ 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()) {
@@ -82,7 +82,6 @@ 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,11 +21,9 @@ 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;
@@ -57,12 +55,12 @@ public class TestblockCommand extends SWCommand {
super("testblock", "tb", "dummy"); super("testblock", "tb", "dummy");
} }
@Register(description = {"REGION_TB_HELP_RESET", "REGION_TB_HELP_RESET_EXTENSION"}) @Register
public void schematicTestblockCommand(@Validator Player p, TestblockParameterType... testblockParameterTypes) { public void schematicTestblockCommand(@Validator Player p, TestblockParameterType... testblockParameterTypes) {
schematicTestblockCommand(p, null, testblockParameterTypes); schematicTestblockCommand(p, null, testblockParameterTypes);
} }
@Register(description = {"REGION_TB_HELP_SCHEMATIC", "REGION_TB_HELP_SCHEMATIC_EXTENSION"}) @Register
public void schematicTestblockCommand(@Validator Player p, @Mapper("withPublic") SchematicNode node, TestblockParameterType... testblockParameterTypes) { public void schematicTestblockCommand(@Validator Player p, @Mapper("withPublic") SchematicNode node, TestblockParameterType... testblockParameterTypes) {
Set<TestblockParameterType> testblockParameterTypesSet = new HashSet<>(Arrays.asList(testblockParameterTypes)); Set<TestblockParameterType> testblockParameterTypesSet = new HashSet<>(Arrays.asList(testblockParameterTypes));
boolean isExtension = testblockParameterTypesSet.contains(TestblockParameterType.EXTENSION); boolean isExtension = testblockParameterTypesSet.contains(TestblockParameterType.EXTENSION);
@@ -108,7 +106,13 @@ public class TestblockCommand extends SWCommand {
region.getRegionData().setTestblockSchematic(node); region.getRegionData().setTestblockSchematic(node);
} }
PasteBuilder.ClipboardProvider clipboardProvider = node == null ? PasteBuilder.ClipboardProvider.EMPTY : PasteBuilder.ClipboardProvider.schematic(node); PasteBuilder.ClipboardProvider clipboardProvider;
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)
@@ -169,17 +173,6 @@ 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;
@@ -192,16 +185,17 @@ public class TestblockCommand extends SWCommand {
return new TypeMapper<SchematicNode>() { return new TypeMapper<SchematicNode>() {
@Override @Override
public List<String> tabCompletes(CommandSender commandSender, PreviousArguments previousArguments, String s) { public List<String> tabCompletes(CommandSender commandSender, PreviousArguments previousArguments, String s) {
List<String> stringList = new ArrayList<>(); List<String> stringList = new ArrayList<>(SchematicNode.getNodeTabcomplete(SteamwarUser.get(((Player) commandSender).getUniqueId()), s));
stringList.addAll(SchematicNode.getNodeTabcomplete(SteamwarUser.byId(0), s)); stringList.addAll(SchematicNode.getNodeTabcomplete(SteamwarUser.byId(0), s));
stringList.addAll(SchematicNode.getNodeTabcomplete(SteamwarUser.get(((Player) commandSender).getUniqueId()), s));
return stringList; return stringList;
} }
@Override @Override
public SchematicNode map(CommandSender commandSender, PreviousArguments previousArguments, String s) { public SchematicNode map(CommandSender commandSender, PreviousArguments previousArguments, String s) {
SchematicNode node = SchematicNode.getNodeFromPath(SteamwarUser.byId(0), s); SchematicNode node = SchematicNode.getNodeFromPath(SteamwarUser.get(((Player) commandSender).getUniqueId()), s);
if (node == null) node = SchematicNode.getNodeFromPath(SteamwarUser.get(((Player) commandSender).getUniqueId()), s); if (node == null) {
node = SchematicNode.getNodeFromPath(SteamwarUser.byId(0), s);
}
return node; return node;
} }
}; };
@@ -1,49 +0,0 @@
/*
* 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);
}
}
@@ -53,7 +53,6 @@ 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,8 +50,16 @@ public class ResetBauGuiItem extends BauGuiItem {
@Override @Override
public boolean click(ClickType click, Player p) { public boolean click(ClickType click, Player p) {
p.closeInventory(); if (click == ClickType.LEFT) {
resetCommand.genericResetCommand(p); p.closeInventory();
resetCommand.genericResetCommand(p);
} else {
SchematicSelector selector = new SchematicSelector(p, SchematicSelector.selectSchematic(), node -> {
p.closeInventory();
resetCommand.schematicResetCommand(p, node);
});
selector.open();
}
return false; return false;
} }
@@ -79,12 +79,12 @@ public class RegionLib implements LuaLib {
Loader loader = Loader.getLoader(player); Loader loader = Loader.getLoader(player);
table.set("loader", getter(() -> loader == null ? "OFF" : loader.getStage().name())); table.set("loader", getter(() -> loader == null ? "OFF" : loader.getStage().name()));
table.set("copyPoint", getter(() -> toPos(region.get().getBuildArea().getCopyPoint(false)))); table.set("copyPoint", getter(() -> toPos(region.get().getBuildArea().getCopyPoint())));
table.set("minPointBuild", getter(() -> toPos(region.get().getBuildArea().getMinPoint(false)))); table.set("minPointBuild", getter(() -> toPos(region.get().getBuildArea().getMinPoint(false))));
table.set("maxPointBuild", getter(() -> toPos(region.get().getBuildArea().getMinPoint(false)))); table.set("maxPointBuild", getter(() -> toPos(region.get().getBuildArea().getMinPoint(false))));
table.set("minPointBuildExtension", getter(() -> toPos(region.get().getBuildArea().getMinPoint(true)))); table.set("minPointBuildExtension", getter(() -> toPos(region.get().getBuildArea().getMinPoint(true))));
table.set("maxPointBuildExtension", getter(() -> toPos(region.get().getBuildArea().getMinPoint(true)))); table.set("maxPointBuildExtension", getter(() -> toPos(region.get().getBuildArea().getMinPoint(true))));
table.set("testblockPoint", getter(() -> toPos(region.get().getTestblockArea().getCopyPoint(false)))); table.set("testblockPoint", getter(() -> toPos(region.get().getTestblockArea().getCopyPoint())));
table.set("minPointTestblock", getter(() -> toPos(region.get().getTestblockArea().getMinPoint(false)))); table.set("minPointTestblock", getter(() -> toPos(region.get().getTestblockArea().getMinPoint(false))));
table.set("maxPointTestblock", getter(() -> toPos(region.get().getTestblockArea().getMinPoint(false)))); table.set("maxPointTestblock", getter(() -> toPos(region.get().getTestblockArea().getMinPoint(false))));
table.set("minPointTestblockExtension", getter(() -> toPos(region.get().getTestblockArea().getMinPoint(true)))); table.set("minPointTestblockExtension", getter(() -> toPos(region.get().getTestblockArea().getMinPoint(true))));
@@ -19,7 +19,6 @@
package de.steamwar.bausystem.features.simulator; package de.steamwar.bausystem.features.simulator;
import com.destroystokyo.paper.event.server.ServerTickEndEvent;
import de.steamwar.bausystem.BauSystem; import de.steamwar.bausystem.BauSystem;
import de.steamwar.bausystem.Permission; import de.steamwar.bausystem.Permission;
import de.steamwar.bausystem.SWUtils; import de.steamwar.bausystem.SWUtils;
@@ -78,16 +77,8 @@ public class SimulatorCursor implements Listener {
return isSimulatorItem(player.getInventory().getItemInMainHand()) || isSimulatorItem(player.getInventory().getItemInOffHand()); return isSimulatorItem(player.getInventory().getItemInMainHand()) || isSimulatorItem(player.getInventory().getItemInOffHand());
} }
private static Set<Player> scheduledUpdates = new HashSet<>();
private static void scheduleCursorUpdate(Player player) { private static void scheduleCursorUpdate(Player player) {
scheduledUpdates.add(player); BauSystem.runTaskLater(BauSystem.getInstance(), () -> calcCursor(player), 1);
}
@EventHandler
public void onServerTickEnd(ServerTickEndEvent event) {
scheduledUpdates.forEach(SimulatorCursor::calcCursor);
scheduledUpdates.clear();
} }
@EventHandler @EventHandler
@@ -131,7 +122,6 @@ public class SimulatorCursor implements Listener {
public void onPlayerQuit(PlayerQuitEvent event) { public void onPlayerQuit(PlayerQuitEvent event) {
cursorType.remove(event.getPlayer()); cursorType.remove(event.getPlayer());
removeCursor(event.getPlayer()); removeCursor(event.getPlayer());
scheduledUpdates.remove(event.getPlayer());
} }
private static final Map<Player, Long> LAST_SNEAKS = new HashMap<>(); private static final Map<Player, Long> LAST_SNEAKS = new HashMap<>();
@@ -33,7 +33,7 @@ public class StabFinalizer extends StabStep {
@Override @Override
protected void start() { protected void start() {
try { try {
PasteBuilder.ClipboardProvider clipboardProvider = PasteBuilder.ClipboardProvider.clipboard(data.clipboard); PasteBuilder.ClipboardProvider clipboardProvider = new PasteBuilder.ClipboardProviderImpl(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());
@@ -72,7 +72,7 @@ public class StabGenerator extends StabStep implements Listener {
@Override @Override
protected void start() { protected void start() {
try { try {
PasteBuilder.ClipboardProvider clipboardProvider = PasteBuilder.ClipboardProvider.clipboard(data.clipboard); PasteBuilder.ClipboardProvider clipboardProvider = new PasteBuilder.ClipboardProviderImpl(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());
@@ -72,7 +72,7 @@ public class StabSetup extends StabStep {
if (TraceRecorder.instance.isAutoTraceEnabledInRegion(data.region)) { if (TraceRecorder.instance.isAutoTraceEnabledInRegion(data.region)) {
TraceRecorder.instance.removeAutoTraceRegion(data.region); TraceRecorder.instance.removeAutoTraceRegion(data.region);
} }
data.clipboard = FlatteningWrapper.impl.copy(data.region.getTestblockArea().getMinPoint(true), data.region.getTestblockArea().getMaxPoint(true), data.region.getTestblockArea().getCopyPoint(false)); data.clipboard = FlatteningWrapper.impl.copy(data.region.getTestblockArea().getMinPoint(true), data.region.getTestblockArea().getMaxPoint(true), data.region.getTestblockArea().getCopyPoint());
new StabDirection(data); new StabDirection(data);
} }
@@ -165,7 +165,6 @@ public class TPSSystem implements Listener {
} }
@Register(value = "default", description = "TPSLIMIT_DEFAULT_HELP") @Register(value = "default", description = "TPSLIMIT_DEFAULT_HELP")
@Register(value = "rate")
public void reset(@Validator Player player) { public void reset(@Validator Player player) {
TickManager.impl.setTickRate(20.0F); TickManager.impl.setTickRate(20.0F);
sendTickRateChange(); sendTickRateChange();
@@ -188,18 +187,15 @@ public class TPSSystem implements Listener {
} }
@Register(value = {"rate", "0"}, description = "TICK_FREEZE_HELP") @Register(value = {"rate", "0"}, description = "TICK_FREEZE_HELP")
public void rate(@Validator Player player) { @Register(value = "freeze", description = "TICK_FREEZE_HELP_2")
public void freeze(@Validator Player player) {
TickManager.impl.setFreeze(true); TickManager.impl.setFreeze(true);
sendTickRateChange(); sendTickRateChange();
} }
@Register(value = "freeze", description = "TICK_FREEZE_HELP_2") @Register(value = "unfreeze", description = "TICK_UNFREEZE_HELP")
public void freezeToggle(@Validator Player player) { public void unfreeze(@Validator Player player) {
if (TickManager.impl.isFrozen()) { TickManager.impl.setTickRate(20.0F);
TickManager.impl.setTickRate(20.0F);
} else {
TickManager.impl.setFreeze(true);
}
sendTickRateChange(); sendTickRateChange();
} }
} }
@@ -153,11 +153,7 @@ public class TraceCommand extends SWCommand {
@Register(value = "isolate", description = "TRACE_COMMAND_HELP_ISOLATE") @Register(value = "isolate", description = "TRACE_COMMAND_HELP_ISOLATE")
public void isolate(@Validator Player player, Trace trace, @ErrorMessage("TRACE_RECORD_ID_INVALID") TNTPoint... records) { public void isolate(@Validator Player player, Trace trace, @ErrorMessage("TRACE_RECORD_ID_INVALID") TNTPoint... records) {
if (records.length == 0) { TraceManager.instance.isolate(player, records);
TraceManager.instance.isolate(player, trace, trace.getRecords().toArray(TNTPoint[]::new));
} else {
TraceManager.instance.isolate(player, trace, records);
}
BauSystem.MESSAGE.send("TRACE_MESSAGE_ISOLATE", player); BauSystem.MESSAGE.send("TRACE_MESSAGE_ISOLATE", player);
} }
@@ -347,10 +347,9 @@ public class TraceManager implements Listener {
* Toggles the isolated render for the given records and player * Toggles the isolated render for the given records and player
* *
* @param player the player the trace is shown to * @param player the player the trace is shown to
* @param ptrace the trace for whitch isolation is toggled * @param records the record for which isolation is toggled
* @param records the records of the trace for which isolation is toggled
*/ */
public void isolate(Player player, Trace ptrace, TNTPoint... records) { public void isolate(Player player, TNTPoint... records) {
unfollow(player); unfollow(player);
Region region = Region.getRegion(player.getLocation()); Region region = Region.getRegion(player.getLocation());
@@ -375,20 +374,12 @@ public class TraceManager implements Listener {
isolateFlag.toggleId(record.getTntId()); isolateFlag.toggleId(record.getTntId());
} }
if (isolateFlag.isEmpty() && playerTraceShowData.hasViewFlagOnly(IsolateFlag.class) && records.length != 0) {
playerTraceShowData.removeViewFlag(IsolateFlag.class);
}
PlayerTraceShowData finalPlayerTraceShowData = playerTraceShowData; PlayerTraceShowData finalPlayerTraceShowData = playerTraceShowData;
tracesByRegion.getOrDefault(region, Collections.emptyMap()).forEach((integer, trace) -> { tracesByRegion.getOrDefault(region, Collections.emptyMap()).forEach((integer, trace) -> {
if (trace.getUuid() == ptrace.getUuid() || finalPlayerTraceShowData.hasNoViewFlags()) { trace.render(player, finalPlayerTraceShowData);
trace.render(player, finalPlayerTraceShowData); followerMap.getOrDefault(player, Collections.emptySet()).forEach(follower -> {
followerMap.getOrDefault(player, Collections.emptySet()).forEach(follower -> { trace.render(follower, finalPlayerTraceShowData);
trace.render(follower, finalPlayerTraceShowData); });
});
} else {
trace.hide(player);
}
}); });
} }
@@ -135,13 +135,6 @@ public class TraceRecorder implements Listener {
Iterator<TNTPrimed> iter = trackedTNT.getOrDefault(region, Collections.emptyList()).iterator(); Iterator<TNTPrimed> iter = trackedTNT.getOrDefault(region, Collections.emptyList()).iterator();
while (iter.hasNext()) { while (iter.hasNext()) {
TNTPrimed tnt = iter.next(); TNTPrimed tnt = iter.next();
if (tnt.isDead()) {
iter.remove();
tntSpawnRegion.remove(tnt);
historyMap.remove(tnt);
tntSpawnRegion.remove(tnt);
continue;
}
if (tnt.getFuseTicks() == 80) continue; if (tnt.getFuseTicks() == 80) continue;
TNTPoint record = record(tnt, wrappedTrace, Collections.emptyList()); TNTPoint record = record(tnt, wrappedTrace, Collections.emptyList());
if (record == null) { if (record == null) {
@@ -95,8 +95,4 @@ public class PlayerTraceShowData {
public void addViewFlag(ViewFlag viewFlag) { public void addViewFlag(ViewFlag viewFlag) {
viewFlags.put(viewFlag.getClass(), viewFlag); viewFlags.put(viewFlag.getClass(), viewFlag);
} }
public <T extends ViewFlag> void removeViewFlag(Class<T> clazz) {
viewFlags.remove(clazz);
}
} }
@@ -48,9 +48,9 @@ import static de.steamwar.bausystem.features.util.TNTClickListener.TNT_CLICK_DET
*/ */
public class TraceEntity extends RBlockDisplay { public class TraceEntity extends RBlockDisplay {
public static final float TNT_VISUAL_SCALE = 0.98F; private static final float TNT_VISUAL_SCALE = 0.98F;
public static final float TNT_VISUAL_OFFSET = -TNT_VISUAL_SCALE / 2.0F; private static final float TNT_VISUAL_OFFSET = -TNT_VISUAL_SCALE / 2.0F;
public static final Transformation TNT_VISUAL_TRANSFORM = new Transformation( private static final Transformation TNT_VISUAL_TRANSFORM = new Transformation(
new Vector3f(TNT_VISUAL_OFFSET, 0.0F, TNT_VISUAL_OFFSET), new Vector3f(TNT_VISUAL_OFFSET, 0.0F, TNT_VISUAL_OFFSET),
new Quaternionf(0, 0, 0, 1), new Quaternionf(0, 0, 0, 1),
new Vector3f(TNT_VISUAL_SCALE, TNT_VISUAL_SCALE, TNT_VISUAL_SCALE), new Vector3f(TNT_VISUAL_SCALE, TNT_VISUAL_SCALE, TNT_VISUAL_SCALE),
@@ -131,7 +131,6 @@ public abstract class ViewFlag {
if (yLocation.distanceSquared(representative.getLocation()) >= 1.0 / 256.0 && yLocation.distanceSquared(previous.getLocation()) >= 1.0 / 256.0) { if (yLocation.distanceSquared(representative.getLocation()) >= 1.0 / 256.0 && yLocation.distanceSquared(previous.getLocation()) >= 1.0 / 256.0) {
RBlockDisplay y = new RBlockDisplay(server, yLocation); RBlockDisplay y = new RBlockDisplay(server, yLocation);
y.setBlock(Material.WHITE_STAINED_GLASS.createBlockData()); y.setBlock(Material.WHITE_STAINED_GLASS.createBlockData());
y.setTransform(TraceEntity.TNT_VISUAL_TRANSFORM);
} }
Location secoundLocation; Location secoundLocation;
@@ -144,7 +143,6 @@ public abstract class ViewFlag {
if (secoundLocation.distanceSquared(representative.getLocation()) >= 1.0 / 256.0 && secoundLocation.distanceSquared(previous.getLocation()) >= 1.0 / 256.0) { if (secoundLocation.distanceSquared(representative.getLocation()) >= 1.0 / 256.0 && secoundLocation.distanceSquared(previous.getLocation()) >= 1.0 / 256.0) {
RBlockDisplay second = new RBlockDisplay(server, secoundLocation); RBlockDisplay second = new RBlockDisplay(server, secoundLocation);
second.setBlock(Material.WHITE_STAINED_GLASS.createBlockData()); second.setBlock(Material.WHITE_STAINED_GLASS.createBlockData());
second.setTransform(TraceEntity.TNT_VISUAL_TRANSFORM);
} }
} }
} }
@@ -51,10 +51,6 @@ public class IsolateFlag extends ViewFlag {
} }
} }
public boolean isEmpty() {
return tntToIsolate.isEmpty();
}
@Override @Override
public Stream<TNTPoint> filter(Stream<TNTPoint> records) { public Stream<TNTPoint> filter(Stream<TNTPoint> records) {
if (tntToIsolate.isEmpty()) return records; if (tntToIsolate.isEmpty()) return records;
@@ -139,7 +139,6 @@ public class NoClipCommand extends SWCommand implements Listener {
@EventHandler(ignoreCancelled = true) @EventHandler(ignoreCancelled = true)
public void onBlock(BlockCanBuildEvent event) { public void onBlock(BlockCanBuildEvent event) {
if (event.getPlayer() == null) return;
if (SWPlayer.of(event.getPlayer()).hasComponent(NoClipData.class)) { if (SWPlayer.of(event.getPlayer()).hasComponent(NoClipData.class)) {
event.setBuildable(true); event.setBuildable(true);
} }
@@ -22,7 +22,6 @@ 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;
@@ -36,20 +35,14 @@ 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");
@Override worldSpawn.setLocation(RegionSystem.INSTANCE.getWorldSpawn());
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) {
@@ -98,13 +91,21 @@ 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(getLocation(), PlayerTeleportEvent.TeleportCause.PLUGIN); player.teleport(location, PlayerTeleportEvent.TeleportCause.PLUGIN);
player.playSound(getLocation(), Sound.ENTITY_ENDERMAN_TELEPORT, SoundCategory.PLAYERS, 1, 1); player.playSound(location, Sound.ENTITY_ENDERMAN_TELEPORT, SoundCategory.PLAYERS, 1, 1);
} }
} }
@@ -99,10 +99,10 @@ public class WarpListener implements Listener {
}); });
Region region = Region.getRegion(p.getLocation()); Region region = Region.getRegion(p.getLocation());
if (!region.getBuildArea().isEmpty()) { if (!region.getBuildArea().isEmpty()) {
locations.put("Copy", region.getBuildArea().getCopyPoint(false).toLocation(p).add(0.5, 0, 0.5)); locations.put("Copy", region.getBuildArea().getCopyPoint().toLocation(p).add(0.5, 0, 0.5));
} }
if (!region.getTestblockArea().isEmpty()) { if (!region.getTestblockArea().isEmpty()) {
locations.put("TestBlock", region.getTestblockArea().getCopyPoint(false).toLocation(p).add(0.5, 0, 0.5)); locations.put("TestBlock", region.getTestblockArea().getCopyPoint().toLocation(p).add(0.5, 0, 0.5));
} }
} }
@@ -1,41 +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.bausystem.features.world;
import de.steamwar.linkage.Linked;
import org.bukkit.event.EventHandler;
import org.bukkit.event.EventPriority;
import org.bukkit.event.Listener;
import org.bukkit.event.player.PlayerJoinEvent;
import org.bukkit.scoreboard.Scoreboard;
import org.bukkit.scoreboard.Team;
@Linked
public class AntiPlayerCollision implements Listener {
@EventHandler(priority = EventPriority.MONITOR)
public void onPlayerJoin(PlayerJoinEvent event) {
Scoreboard board = event.getPlayer().getScoreboard();
Team team = board.getTeam("players");
if (team == null) team = board.registerNewTeam("players");
team.setOption(Team.Option.COLLISION_RULE, Team.OptionStatus.NEVER);
team.addPlayer(event.getPlayer());
}
}
@@ -119,8 +119,11 @@ 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.getRegionData().has(Flag.COLOR).notVisibleInScoreboard()) return "§eSteam§8War"; if (region.getType().isGlobal()) return "§eSteam§8War";
String colorCode = "§" + region.getRegionData().get(Flag.COLOR).getWithDefault().getColorCode(); String colorCode = "§e";
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,7 +28,6 @@ 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 {
@@ -44,7 +43,6 @@ 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,18 +48,10 @@ public class Point {
return new Point(blockVector3.getBlockX(), blockVector3.getBlockY(), blockVector3.getBlockZ()); return new Point(blockVector3.getBlockX(), blockVector3.getBlockY(), blockVector3.getBlockZ());
} }
public Point add(Point point) {
return add(point.x, point.y, point.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 subtract(Point point) {
return subtract(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);
} }
@@ -68,18 +60,6 @@ public class Point {
return new Point(x / factor, y / factor, z / factor); return new Point(x / factor, y / factor, z / factor);
} }
public Point setX(int x) {
return new Point(x, this.y, this.z);
}
public Point setY(int y) {
return new Point(this.x, y, this.z);
}
public Point setZ(int z) {
return new Point(this.x, this.y, z);
}
public Location toLocation(World world) { public Location toLocation(World world) {
return new Location(world, x, y, z); return new Location(world, x, y, z);
} }
@@ -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 extends RegionDataStore { public interface Region {
static Stream<Region> getRegions() { static Stream<Region> getRegions() {
return RegionSystem.INSTANCE.getRegions(); return RegionSystem.INSTANCE.getRegions();
@@ -74,14 +74,11 @@ public interface Region extends RegionDataStore {
@NonNull @NonNull
RegionBackups getBackups(); RegionBackups getBackups();
abstract class Area { interface Area {
public static final int WORLD_MIN_Y = Bukkit.getWorlds().get(0).getMinHeight(); Area EMPTY = new Area() {
public static final int WORLD_MAX_Y = Bukkit.getWorlds().get(0).getMaxHeight();
public static final Area EMPTY = new Area() {
@Override @Override
protected boolean isEmptyInternal() { public boolean isEmpty() {
return true; return true;
} }
@@ -96,7 +93,7 @@ public interface Region extends RegionDataStore {
} }
@Override @Override
public Point getCopyPoint(boolean extension) { public Point getCopyPoint() {
return Point.ZERO; return Point.ZERO;
} }
@@ -105,18 +102,18 @@ public interface Region extends RegionDataStore {
return false; return false;
} }
@Override
public boolean inRegion(int x, int z, boolean extension) {
return false;
}
@Override @Override
public Clipboard copy(boolean extension) { public Clipboard copy(boolean extension) {
return null; return null;
} }
@Override @Override
public void place(PasteBuilder pasteBuilder, boolean extension) { public File getResetFile() {
return null;
}
@Override
public void reset(PasteBuilder pasteBuilder, boolean extension) {
} }
@Override @Override
@@ -129,24 +126,20 @@ public interface Region extends RegionDataStore {
} }
}; };
public final boolean isEmpty() { default boolean isEmpty() {
return isEmptyInternal();
}
boolean isEmptyInternal() {
return false; return false;
} }
@NonNull @NonNull
public abstract Point getMinPoint(boolean extension); Point getMinPoint(boolean extension);
@NonNull @NonNull
public abstract Point getMaxPoint(boolean extension); Point getMaxPoint(boolean extension);
@NonNull @NonNull
public abstract Point getCopyPoint(boolean extension); Point getCopyPoint();
public boolean inRegion(Location location, boolean extension) { default boolean inRegion(Location location, boolean extension) {
Point minPoint = getMinPoint(extension); Point minPoint = getMinPoint(extension);
Point maxPoint = getMaxPoint(extension); Point maxPoint = getMaxPoint(extension);
if (location.getBlockX() < minPoint.getX() || location.getBlockX() > maxPoint.getX()) return false; if (location.getBlockX() < minPoint.getX() || location.getBlockX() > maxPoint.getX()) return false;
@@ -155,26 +148,17 @@ public interface Region extends RegionDataStore {
return true; return true;
} }
public boolean inRegion(int x, int z, boolean extension) { @Nullable
Point minPoint = getMinPoint(extension); default Clipboard copy(boolean extension) {
Point maxPoint = getMaxPoint(extension); return FlatteningWrapper.impl.copy(getMinPoint(extension), getMaxPoint(extension), getCopyPoint());
if (x < minPoint.getX() || x > maxPoint.getX()) return false;
if (z < minPoint.getZ() || z > maxPoint.getZ()) return false;
return true;
} }
@Nullable @Nullable
public Clipboard copy(boolean extension) { File getResetFile();
return FlatteningWrapper.impl.copy(getMinPoint(extension), getMaxPoint(extension), getCopyPoint(extension));
}
public void reset(PasteBuilder pasteBuilder, boolean extension) { void reset(PasteBuilder pasteBuilder, boolean extension);
place(pasteBuilder, extension);
}
public abstract void place(PasteBuilder pasteBuilder, boolean extension); default void forEachChunk(BiConsumer<Integer, Integer> executor) {
public void forEachChunk(BiConsumer<Integer, Integer> executor) {
Point minPoint = getMinPoint(false); Point minPoint = getMinPoint(false);
Point maxPoint = getMaxPoint(false); Point maxPoint = getMaxPoint(false);
for (int x = (int) Math.floor(minPoint.getX() / 16.0); x <= (int) Math.ceil(maxPoint.getX() / 16.0); x++) { for (int x = (int) Math.floor(minPoint.getX() / 16.0); x <= (int) Math.ceil(maxPoint.getX() / 16.0); x++) {
@@ -184,7 +168,7 @@ public interface Region extends RegionDataStore {
} }
} }
public boolean isChunkOutside(int chunkX, int chunkZ) { default boolean isChunkOutside(int chunkX, int chunkZ) {
Point minPoint = getMinPoint(true); Point minPoint = getMinPoint(true);
Point maxPoint = getMaxPoint(true); Point maxPoint = getMaxPoint(true);
return Math.floor(minPoint.getX() / 16.0) > chunkX || chunkX >= Math.ceil(maxPoint.getX() / 16.0) || return Math.floor(minPoint.getX() / 16.0) > chunkX || chunkX >= Math.ceil(maxPoint.getX() / 16.0) ||
@@ -25,15 +25,11 @@ import lombok.RequiredArgsConstructor;
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),
@@ -43,43 +39,21 @@ public interface RegionBackups {
public final int maxBackups; public final int maxBackups;
} }
@RequiredArgsConstructor
@Getter @Getter
abstract class Backup implements RegionDataStore, Comparable<Backup> { abstract class Backup {
@NonNull @NonNull
protected final BackupType type; private final BackupType type;
@NonNull @NonNull
protected final String name; private final String name;
@NonNull @NonNull
protected final RegionData regionData; private final RegionData data;
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();
@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 'deleteRegion' for Backups!
@Override
public abstract void delete(); public abstract void delete();
} }
@@ -90,7 +64,7 @@ public interface RegionBackups {
List<Backup> list(); List<Backup> list();
@Nullable @Nullable
Backup get(@NonNull String name); Backup get(String name);
RegionBackups EMPTY = new RegionBackups() { RegionBackups EMPTY = new RegionBackups() {
@Override @Override
@@ -105,7 +79,7 @@ public interface RegionBackups {
@Nullable @Nullable
@Override @Override
public Backup get(@NonNull String name) { public Backup get(String name) {
return null; return null;
} }
}; };
@@ -22,42 +22,81 @@ 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.*; import java.util.ArrayList;
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);
protected RegionData(RegionDataStore store) { private final class Property<T, K> {
this.store = store; private final String field;
initialize(); private final Function<K, T> loader;
store.load(this); 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));
}
}
} }
public final void setStore(RegionDataStore store) { private Property<SchematicNode, Integer> testblockSchematic = new Property<>("testblockSchematic", SchematicNode::byId, SchematicNode::getId);
this.store = store;
store.save(); protected RegionData(YAPIONObject data, Runnable onChange) {
this.data = data;
this.flagData = data.getObjectOrSetDefault("flagStorage", new YAPIONObject());
this.onChange = onChange;
initialize();
for (final Flag flag : Flag.getFlags()) {
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);
} }
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);
@@ -66,15 +105,11 @@ 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()) {
boolean needsSave = flagMap.put(flag, value) != value; if (flagMap.put(flag, value) != value) {
if (needsSave) store.save(); flagData.put(flag.name(), value.name());
onChange.run();
connectedRegions().forEach(region -> { return true;
if (region.getRegionData().flagMap.put(flag, value) != value) { }
region.save();
}
});
return needsSave;
} }
return false; return false;
} }
@@ -85,94 +120,33 @@ 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);
remove.add(flag); flagData.remove(flag.name());
} }
} }
initialize();
properties.forEach(property -> property.set(null)); properties.forEach(property -> property.set(null));
store.save(); onChange.run();
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 final List<Property<Object, Object>> getBackedProperties() { public SchematicNode getTestblockSchematic() {
return (List) properties;
}
public final SchematicNode getTestblockSchematic() {
return testblockSchematic.get(); return testblockSchematic.get();
} }
public final void setTestblockSchematic(SchematicNode schematic) { public void setTestblockSchematic(SchematicNode schematic) {
testblockSchematic.set(schematic); testblockSchematic.set(schematic);
store.save(); onChange.run();
} }
@Override @Override
public final String toString() { public final String toString() {
StringBuilder st = new StringBuilder(); return getClass().getSimpleName() + "{" +
st.append(getClass().getSimpleName()).append("{"); "flagMap=" + flagMap +
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,27 +0,0 @@
/*
* 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;
public interface RegionDataStore {
void save();
void load(RegionData regionData);
default void delete() {
}
}
@@ -35,8 +35,4 @@ public enum RegionFlagPolicy {
public boolean isApplicable() { public boolean isApplicable() {
return readable || writable; return readable || writable;
} }
public boolean notVisibleInScoreboard() {
return !writable;
}
} }
@@ -19,8 +19,6 @@
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;
@@ -28,15 +26,10 @@ 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();
/** /**
@@ -44,6 +37,11 @@ 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"
*/ */
@@ -76,63 +74,47 @@ 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(@NonNull 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() {
@Override
public void load() {
throw new UnsupportedOperationException();
}
@Override
public void save() {
throw new UnsupportedOperationException();
}
@Override
public @NonNull Location getWorldSpawn() {
throw new UnsupportedOperationException();
}
@Override
public Region getGlobalRegion() {
throw new UnsupportedOperationException();
}
@Override
public Region get(Location location) {
throw new UnsupportedOperationException();
}
@Override
public Optional<Region> getRegion(UUID id) {
throw new UnsupportedOperationException();
}
@Override
public Stream<Region> getRegions() {
throw new UnsupportedOperationException();
}
};
} }
return new RegionSystem() {
@Override
public void load() {
throw new UnsupportedOperationException();
}
@Override
public @NonNull Location getWorldSpawn() {
throw new UnsupportedOperationException();
}
@Override
public Region getGlobalRegion() {
throw new UnsupportedOperationException();
}
@Override
public Region get(Location location) {
throw new UnsupportedOperationException();
}
@Override
public Optional<Region> getRegion(UUID id) {
throw new UnsupportedOperationException();
}
@Override
public Stream<Region> getRegions() {
throw new UnsupportedOperationException();
}
@Override
public @NonNull Stream<Region> getConnectedRegions(@NonNull Region region) {
throw new UnsupportedOperationException();
}
};
} }
} }
@@ -26,50 +26,9 @@ import lombok.RequiredArgsConstructor;
@Getter @Getter
public enum RegionType { public enum RegionType {
GLOBAL(ConnectionType.Global), GLOBAL(true),
/** 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 ConnectionType connectionType; private final boolean global;
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,7 +28,6 @@ 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 {
@@ -59,12 +58,10 @@ public class RegionUtils {
} }
public void forEachInRegion(Region region, Consumer<Player> consumer) { public void forEachInRegion(Region region, Consumer<Player> consumer) {
Stream<? extends Player> players = Bukkit.getOnlinePlayers().stream(); Bukkit.getOnlinePlayers()
if (region.getType().isGlobal()) { .stream()
players = players.filter(player -> Region.getRegion(player.getLocation()).getType().isGlobal()); .filter(player -> region.getArea().inRegion(player.getLocation(), false))
} else { .filter(player -> !region.getType().isGlobal() || Region.getRegion(player.getLocation()).getType().isGlobal())
players = players.filter(player -> region.getArea().inRegion(player.getLocation(), false)); .forEach(consumer);
}
players.forEach(consumer);
} }
} }
@@ -20,28 +20,30 @@
package de.steamwar.bausystem.utils; package de.steamwar.bausystem.utils;
import de.steamwar.bausystem.SWUtils; import de.steamwar.bausystem.SWUtils;
import lombok.experimental.UtilityClass;
import org.bukkit.NamespacedKey; import org.bukkit.NamespacedKey;
import org.bukkit.inventory.ItemStack; import org.bukkit.inventory.ItemStack;
import org.bukkit.inventory.meta.ItemMeta; import org.bukkit.inventory.meta.ItemMeta;
import org.bukkit.persistence.PersistentDataContainer; import org.bukkit.persistence.PersistentDataContainer;
import org.bukkit.persistence.PersistentDataType; import org.bukkit.persistence.PersistentDataType;
@UtilityClass public final class ItemUtils {
public class ItemUtils {
private final NamespacedKey ITEM_KEY = SWUtils.getNamespaceKey("bau_item"); private static final NamespacedKey ITEM_KEY = SWUtils.getNamespaceKey("bau_item");
public boolean isItem(ItemStack itemStack, String tag) { private ItemUtils() {
}
public static boolean isItem(ItemStack itemStack, String tag) {
String value = getTag(itemStack, ITEM_KEY); String value = getTag(itemStack, ITEM_KEY);
return value != null && value.equals(tag); return value != null && value.equals(tag);
} }
public void setItem(ItemStack itemStack, String tag) { public static ItemStack setItem(ItemStack itemStack, String tag) {
setTag(itemStack, ITEM_KEY, tag); setTag(itemStack, ITEM_KEY, tag);
return itemStack;
} }
public String getTag(ItemStack itemStack, NamespacedKey key) { public static String getTag(ItemStack itemStack, NamespacedKey key) {
if (itemStack == null) { if (itemStack == null) {
return null; return null;
} }
@@ -56,7 +58,7 @@ public class ItemUtils {
return container.get(key, PersistentDataType.STRING); return container.get(key, PersistentDataType.STRING);
} }
public void setTag(ItemStack itemStack, NamespacedKey key, String value) { public static void setTag(ItemStack itemStack, NamespacedKey key, String value) {
if (itemStack == null) { if (itemStack == null) {
return; return;
} }
@@ -42,7 +42,7 @@ import java.util.function.BiPredicate;
@Getter @Getter
public class PasteBuilder { public class PasteBuilder {
private ClipboardProvider clipboardProvider; private final ClipboardProvider clipboardProvider;
private Point pastPoint; private Point pastPoint;
private boolean rotate; private boolean rotate;
private boolean ignoreAir; private boolean ignoreAir;
@@ -53,20 +53,10 @@ 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;
@@ -102,16 +92,6 @@ 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;
@@ -202,27 +182,13 @@ public class PasteBuilder {
} }
public EditSession run() { public EditSession run() {
if (pastPoint != null || minPoint != null) { if (pastPoint == null) {
return FlatteningWrapper.impl.paste(this); throw new IllegalStateException("pastePoint is null");
} }
throw new IllegalStateException("pastePoint is null"); return FlatteningWrapper.impl.paste(this);
} }
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) {
@@ -234,22 +200,12 @@ 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;
private FileProvider(File file) { public FileProvider(File file) {
this.file = file; this.file = file;
this.clipboard = FlatteningWrapper.impl.loadSchematic(file); this.clipboard = FlatteningWrapper.impl.loadSchematic(file);
} }
@@ -265,7 +221,7 @@ public class PasteBuilder {
private final SchematicNode schematic; private final SchematicNode schematic;
private final Clipboard clipboard; private final Clipboard clipboard;
private SchematicProvider(SchematicNode schematic) { public SchematicProvider(SchematicNode schematic) {
this.schematic = schematic; this.schematic = schematic;
try { try {
this.clipboard = new SchematicData(schematic).load(); this.clipboard = new SchematicData(schematic).load();
@@ -284,7 +240,7 @@ public class PasteBuilder {
public static class ClipboardProviderImpl implements ClipboardProvider { public static class ClipboardProviderImpl implements ClipboardProvider {
private final Clipboard clipboard; private final Clipboard clipboard;
private ClipboardProviderImpl(Clipboard clipboard) { public ClipboardProviderImpl(Clipboard clipboard) {
this.clipboard = clipboard; this.clipboard = clipboard;
} }
@@ -1,44 +0,0 @@
/*
* 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
}
dependencies {
compileOnly(libs.classindex)
annotationProcessor(libs.classindex)
compileOnly(project(":BauSystem:BauSystem_Main", "default"))
compileOnly(project(":SpigotCore", "default"))
compileOnly(libs.paperapi)
compileOnly(libs.axiom)
compileOnly(libs.authlib)
compileOnly(libs.viaapi)
compileOnly(libs.nms)
compileOnly(libs.fawe)
implementation(libs.luaj)
}
@@ -1,77 +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.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(description = "REGION_WIREFRAME_HELP_RESET")
public void wireframeCommand(@Validator Player p) {
Region region = regionCheck(p);
if (region == null) return;
try {
PasteBuilder pasteBuilder = new PasteBuilder()
.ignoreAir(true)
.color(region.getRegionData().get(Flag.COLOR).getWithDefault());
region.getBuildArea().reset(pasteBuilder, false);
RegionUtils.message(region, "REGION_WIREFRAME_DONE");
} catch (SecurityException e) {
BauSystem.MESSAGE.send("REGION_WIREFRAME_ERROR", p);
Bukkit.getLogger().log(Level.WARNING, "Failed wireframe", 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_WIREFRAME_NO_REGION", player);
return null;
}
return region;
}
}
@@ -1,9 +0,0 @@
{
"flags": {
"TNT": "DENY",
"FREEZE": "INACTIVE"
},
"properties": {
"testblockSchematic": 0
}
}
@@ -1,9 +0,0 @@
{
"region_identifier": "SpawnRegion",
"tiles": [
{
"tile_x": 0,
"tile_z": 0
}
]
}
@@ -1,50 +0,0 @@
/*
* 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) {
SWPlayer swPlayer = SWPlayer.of(player);
if (swPlayer.hasComponent(DynamicRegionVisualizer.class)) {
swPlayer.removeComponent(DynamicRegionVisualizer.class);
swPlayer.removeComponent(DynamicRegionEditor.class);
} else {
swPlayer.setComponent(DynamicRegionVisualizer.INSTANCE);
if (Permission.SUPERVISOR.hasPermission(player)) {
swPlayer.setComponent(new DynamicRegionEditor(player.getPlayer()));
}
}
}
}
@@ -1,522 +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.bausystem.region;
import com.comphenix.tinyprotocol.TinyProtocol;
import de.steamwar.Reflection;
import de.steamwar.bausystem.BauSystem;
import de.steamwar.bausystem.Permission;
import de.steamwar.bausystem.region.dynamic.DynamicRegion;
import de.steamwar.bausystem.region.dynamic.DynamicRegionRepository;
import de.steamwar.bausystem.region.dynamic.RegionConstructorData;
import de.steamwar.bausystem.region.dynamic.Tile;
import de.steamwar.bausystem.region.dynamic.spawn.SpawnRegion;
import de.steamwar.bausystem.utils.PasteBuilder;
import de.steamwar.core.SWPlayer;
import de.steamwar.entity.CArea;
import de.steamwar.entity.REntityServer;
import de.steamwar.entity.RInteraction;
import de.steamwar.inventory.SWInventory;
import de.steamwar.inventory.SWItem;
import de.steamwar.inventory.SWListInv;
import lombok.NonNull;
import net.md_5.bungee.api.ChatMessageType;
import net.md_5.bungee.api.chat.TextComponent;
import org.bukkit.Bukkit;
import org.bukkit.Location;
import org.bukkit.Material;
import org.bukkit.block.data.BlockData;
import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
import org.bukkit.event.EventPriority;
import org.bukkit.event.Listener;
import org.bukkit.event.player.PlayerInteractEvent;
import org.bukkit.event.player.PlayerMoveEvent;
import org.bukkit.event.player.PlayerSwapHandItemsEvent;
import org.bukkit.scheduler.BukkitTask;
import org.bukkit.util.Vector;
import org.joml.RayAabIntersection;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
import java.util.function.BiFunction;
public class DynamicRegionEditor implements SWPlayer.Component, Listener {
private static final Class<?> position = Reflection.getClass("net.minecraft.network.protocol.game.ServerboundMovePlayerPacket$Pos");
private static final Class<?> look = Reflection.getClass("net.minecraft.network.protocol.game.ServerboundMovePlayerPacket$Rot");
private static final Class<?> positionLook = Reflection.getClass("net.minecraft.network.protocol.game.ServerboundMovePlayerPacket$PosRot");
static {
BiFunction<Player, Object, Object> function = (player, object) -> {
SWPlayer.of(player).getComponent(DynamicRegionEditor.class).ifPresent(DynamicRegionEditor::calcCursor);
return object;
};
TinyProtocol.instance.addFilter(position, function);
TinyProtocol.instance.addFilter(look, function);
TinyProtocol.instance.addFilter(positionLook, function);
}
private final REntityServer entityServer = new REntityServer();
private final CArea area = new CArea(entityServer);
private static final BlockData DELETE = Material.RED_CONCRETE.createBlockData();
private static final BlockData INVALID = Material.YELLOW_CONCRETE.createBlockData();
private static final BlockData PLACE = Material.LIME_CONCRETE.createBlockData();
{
area.hide(true);
}
private final Player player;
private final BukkitTask task;
private Type type = new Empty();
public DynamicRegionEditor(Player player) {
this.player = player;
entityServer.addPlayer(player);
Bukkit.getPluginManager().registerEvents(this, BauSystem.getInstance());
for (int i = 0; i < 121; i++) {
new RInteraction(entityServer, player.getLocation());
}
moveInteractionEntities(Point.fromLocation(player.getLocation()));
CArea area = new CArea(entityServer);
area.setPos1And2(new Location(null, -Tile.maxTile, 1, -Tile.maxTile), new Location(null, Tile.maxTile, 1, Tile.maxTile));
area.setBlock(Material.BEDROCK.createBlockData());
task = Bukkit.getScheduler().runTaskTimer(BauSystem.getInstance(), this::showHotbar, 0, 20);
}
private void showHotbar() {
entityServer.tick();
StringBuilder st = new StringBuilder();
st.append(type.title());
st.append(" §8(§7Change with §eSwap Hands§8)");
player.spigot().sendMessage(ChatMessageType.ACTION_BAR, TextComponent.fromLegacyText(st.toString()));
}
@Override
public void onUnmount(SWPlayer player) {
entityServer.close();
task.cancel();
PlayerMoveEvent.getHandlerList().unregister(this);
PlayerSwapHandItemsEvent.getHandlerList().unregister(this);
PlayerInteractEvent.getHandlerList().unregister(this);
}
@EventHandler
public void onPlayerMove(PlayerMoveEvent event) {
if (event.getPlayer() != player) return;
if (!Permission.SUPERVISOR.hasPermission(player)) {
SWPlayer.of(player).removeComponent(DynamicRegionEditor.class);
return;
}
if (event.getTo() == null) return;
if (event.getFrom().getBlockX() == event.getTo().getBlockX() && event.getFrom().getBlockZ() == event.getTo().getBlockZ()) {
return;
}
moveInteractionEntities(Point.fromLocation(event.getTo()));
}
private void moveInteractionEntities(Point position) {
List<RInteraction> interactionList = entityServer.getEntitiesByType(RInteraction.class);
for (int x = -5; x <= 5; x++) {
for (int z = -5; z <= 5; z++) {
interactionList.removeFirst().move(position.getX() + x + 0.5, 0, position.getZ() + z + 0.5, 0, 0, (byte) 0);
}
}
}
@EventHandler(priority = EventPriority.LOWEST)
public void onPlayerSwapHandItems(PlayerSwapHandItemsEvent event) {
if (event.getPlayer() != player) return;
event.setCancelled(true);
List<SWListInv.SWListEntry<Type>> list = new ArrayList<>();
list.add(new SWListInv.SWListEntry<>(new SWItem(Material.STRUCTURE_VOID, "§6Deselect"), new Empty()));
list.add(new SWListInv.SWListEntry<>(new SWItem(Material.BARRIER, "§cDelete"), new Delete(false)));
list.add(new SWListInv.SWListEntry<>(new SWItem(Material.COMMAND_BLOCK, "§4§lForce Delete", List.of("§7Force deletes enables §cdeleting", "§7of §6Special§7 regions without", "§7confirmation§8."), false, null), new Delete(true)));
list.add(new SWListInv.SWListEntry<>(new SWItem(Material.LODESTONE, "§fBig Spawn"), new Spawn()));
DynamicRegionSystem.constructorDataMap.values()
.stream()
.filter(RegionConstructorData::placeable)
.sorted(Comparator.comparing(RegionConstructorData::name))
.forEach(data -> {
list.add(new SWListInv.SWListEntry<>(new SWItem(data.material(), "§f" + data.name()), new Place(data)));
});
SWListInv<Type> inventory = new SWListInv<>(player, "Select Region to place", list, (click, type) -> {
if (type instanceof Delete delete) {
delete.confirmForceDelete(player, () -> {
player.closeInventory();
this.type = delete;
area.hide(true);
showHotbar();
});
} else {
player.closeInventory();
this.type = type;
area.hide(true);
showHotbar();
}
});
inventory.open();
}
private void calcCursor() {
Location startPos = player.getLocation().clone().add(0.0, player.getEyeHeight(), 0.0);
Vector direction = player.getLocation().getDirection();
RayAabIntersection intersection = new RayAabIntersection((float) startPos.getX(), (float) startPos.getY(), (float) startPos.getZ(), (float) direction.getX(), (float) direction.getY(), (float) direction.getZ());
for (RInteraction interaction : entityServer.getEntitiesByType(RInteraction.class)) {
float x = (float) (interaction.getX() - 0.5);
float z = (float) (interaction.getZ() - 0.5);
if (intersection.test(x, (float) 0.95, z, x + 1, (float) 1.05, z + 1)) {
int tileX = (int) x;
int tileZ = (int) z;
if (type.visualize(area, tileX, tileZ)) {
area.hide(false);
return;
} else {
break;
}
}
}
area.hide(true);
}
@EventHandler
public void onPlayerInteract(PlayerInteractEvent event) {
if (event.getPlayer() != player) return;
Location startPos = player.getLocation().clone().add(0.0, player.getEyeHeight(), 0.0);
Vector direction = player.getLocation().getDirection();
if (direction.getY() > 0) return;
RayAabIntersection intersection = new RayAabIntersection((float) startPos.getX(), (float) startPos.getY(), (float) startPos.getZ(), (float) direction.getX(), (float) direction.getY(), (float) direction.getZ());
for (RInteraction interaction : entityServer.getEntitiesByType(RInteraction.class)) {
float x = (float) (interaction.getX() - 0.5);
float z = (float) (interaction.getZ() - 0.5);
if (intersection.test(x, (float) 0.95, z, x + 1, (float) 1.05, z + 1)) {
int tileX = (int) x;
int tileZ = (int) z;
type.run(event.getPlayer(), tileX, tileZ);
break;
}
}
}
private interface Type {
String title();
boolean visualize(CArea area, int tileX, int tileZ);
void run(Player player, int tileX, int tileZ);
}
private static class Empty implements Type {
private Empty() {
}
@Override
public String title() {
return "§6---";
}
@Override
public boolean visualize(CArea area, int tileX, int tileZ) {
return false;
}
@Override
public void run(Player player, int tileX, int tileZ) {
}
}
private static class Delete implements Type {
protected static final String CONFIRM_DELETE = "Confirm delete: ";
protected static final String ACCEPT_DELETE = "§cDelete: ";
private final boolean force;
private boolean valid = false;
private Delete(boolean force) {
this.force = force;
}
public void confirmForceDelete(Player player, Runnable callback) {
if (force) {
confirmInventory(player, "Enable Force Delete", "§cEnable", "", callback);
} else {
callback.run();
}
}
@Override
public String title() {
if (force) {
return "§4§lForce Delete";
} else {
return "§cDelete";
}
}
private boolean isBigSpawn() {
if (!DynamicRegionSystem.INSTANCE.get(Tile.ZERO).getType().isSpawn()) return false;
return Tile.ZERO.neighboursRing()
.map(DynamicRegionSystem.INSTANCE::get)
.map(Region::getType)
.allMatch(RegionType::isSpawn);
}
@Override
public boolean visualize(CArea area, int tileX, int tileZ) {
// TODO: Special case for big Spawn!
Tile tile = Tile.fromTile(tileX, tileZ).orElse(null);
if (tile == null) {
valid = false;
return false;
}
Region region = DynamicRegionSystem.INSTANCE.get(tile);
if (region.getType().isGlobal()) {
valid = false;
return false;
}
if (region.getType().isSpawn()) {
if (!isBigSpawn()) {
valid = false;
return false;
}
return selectSpawn(area);
} else {
return selectRegion(area, region);
}
}
private boolean selectSpawn(CArea area) {
Location minLoc = new Location(null, -1, 1, -1);
Location maxLoc = new Location(null, 1, 1, 1);
area.setBlock(DELETE);
area.setPos1And2(minLoc, maxLoc);
valid = true;
return true;
}
private boolean selectRegion(CArea area, Region region) {
Tile minTile = Tile.fromPoint(region.getArea().getMinPoint(false)).orElse(null);
Tile maxTile = Tile.fromPoint(region.getArea().getMaxPoint(false)).orElse(null);
if (minTile == null || maxTile == null) {
valid = false;
return false;
}
Location minLoc = new Location(null, minTile.getTileX(), 1, minTile.getTileZ());
Location maxLoc = new Location(null, maxTile.getTileX(), 1, maxTile.getTileZ());
area.setBlock(DELETE);
area.setPos1And2(minLoc, maxLoc);
valid = true;
return true;
}
@Override
public void run(Player player, int tileX, int tileZ) {
if (!valid) return;
Tile tile = Tile.fromTile(tileX, tileZ).orElse(null);
if (tile == null) return;
Region region = DynamicRegionSystem.INSTANCE.get(tile);
if (region.getType().isGlobal()) return;
if (region.getType().isSpawn()) {
if (isBigSpawn()) deleteSpawn(player);
return;
}
if (region.getType().isPath() || (force && region.getType().isSpecial())) {
region.delete();
} else {
deleteRegion(player, tile, region);
}
}
private void deleteSpawn(Player player) {
confirmInventory(player, CONFIRM_DELETE, ACCEPT_DELETE, "Big Spawn", () -> {
checkSpawnRegionAndDelete(Tile.TILE_NN);
checkSpawnRegionAndDelete(Tile.TILE_NP);
checkSpawnRegionAndDelete(Tile.TILE_PN);
checkSpawnRegionAndDelete(Tile.TILE_PP);
});
}
private void checkSpawnRegionAndDelete(Tile tile) {
Region region = DynamicRegionSystem.INSTANCE.get(tile);
if (region.getType().isSpawn()) region.delete();
}
private void deleteRegion(Player player, Tile tile, Region region) {
RegionConstructorData data = DynamicRegionSystem.constructorDataMap.get(region.getClass());
confirmInventory(player, CONFIRM_DELETE, ACCEPT_DELETE, data.name(), () -> {
Region checkRegion = DynamicRegionSystem.INSTANCE.get(tile);
if (region == checkRegion) region.delete();
});
}
private void confirmInventory(Player player, String title, String accept, String name, Runnable deleteCallback) {
SWInventory inventory = new SWInventory(player, 9, title + name);
inventory.setItem(0, new SWItem(Material.GRAY_CONCRETE, "§7Cancel", click -> {
player.closeInventory();
}));
inventory.setItem(8, new SWItem(Material.RED_CONCRETE, accept + name, click -> {
player.closeInventory();
deleteCallback.run();
}));
inventory.open();
}
}
private static class Spawn extends Place {
private Spawn() {
super(3, 3);
}
@Override
public String title() {
return "§ePlace§8: §aBig Spawn";
}
@Override
public boolean validPlacement(int tileX, int tileZ) {
if (tileX != -1 || tileZ != -1) return false;
if (!DynamicRegionSystem.INSTANCE.get(Tile.TILE_NN).getType().isGlobal()) return false;
if (!DynamicRegionSystem.INSTANCE.get(Tile.TILE_NP).getType().isGlobal()) return false;
if (!DynamicRegionSystem.INSTANCE.get(Tile.TILE_PN).getType().isGlobal()) return false;
if (!DynamicRegionSystem.INSTANCE.get(Tile.TILE_PP).getType().isGlobal()) return false;
return true;
}
@Override
public void run(Player player, int tileX, int tileZ) {
if (!valid) return;
// Center Tile placement!
tileX -= widthX / 2;
tileZ -= widthZ / 2;
if (!validPlacement(tileX, tileZ)) return;
constructSpawnRegion(Tile.TILE_NN);
constructSpawnRegion(Tile.TILE_NP);
constructSpawnRegion(Tile.TILE_PN);
constructSpawnRegion(Tile.TILE_PP);
}
private void constructSpawnRegion(Tile tile) {
DynamicRegion region = DynamicRegionRepository.constructRegion(SpawnRegion.class, tile);
if (region == null) return;
region.getArea().place(new PasteBuilder(), false);
region.updateNeighbours();
}
}
private static class Place implements Type {
private final RegionConstructorData data;
protected final int widthX;
protected final int widthZ;
protected boolean valid = false;
private Place(@NonNull RegionConstructorData data) {
this(data, data.widthX(), data.widthZ());
}
private Place(int widthX, int widthZ) {
this(null, widthX, widthZ);
}
private Place(RegionConstructorData data, int widthX, int widthZ) {
this.data = data;
this.widthX = widthX - 1;
this.widthZ = widthZ - 1;
}
@Override
public String title() {
return "§ePlace§8: §a" + data.name();
}
public boolean validPlacement(int tileX, int tileZ) {
for (int x = 0; x <= widthX; x++) {
for (int z = 0; z <= widthZ; z++) {
Tile tile = Tile.fromTile(tileX + x, tileZ + z).orElse(null);
if (tile == null) return false;
Region region = DynamicRegionSystem.INSTANCE.get(tile);
if (!region.getType().isGlobal()) return false;
}
}
return true;
}
@Override
public boolean visualize(CArea area, int tileX, int tileZ) {
Tile tile = Tile.fromTile(tileX, tileZ).orElse(null);
if (tile == null) {
valid = false;
return false;
}
// Center Tile placement!
tileX -= widthX / 2;
tileZ -= widthZ / 2;
// Validate placements!
if (validPlacement(tileX, tileZ)) {
area.setBlock(PLACE);
valid = true;
} else {
area.setBlock(INVALID);
valid = false;
}
area.setPos1And2(new Location(null, tileX, 1, tileZ), new Location(null, tileX + widthX, 1, tileZ + widthZ));
return true;
}
@Override
public void run(Player player, int tileX, int tileZ) {
if (!valid) return;
// Center Tile placement!
tileX -= widthX / 2;
tileZ -= widthZ / 2;
if (!validPlacement(tileX, tileZ)) return;
Tile tile = Tile.fromTile(tileX, tileZ).orElse(null);
if (tile == null) return;
DynamicRegion region = DynamicRegionRepository.constructRegion(DynamicRegionSystem.identifierDataMap.get(data.identifier()), tile);
if (region == null) return;
region.getArea().place(new PasteBuilder(), false);
region.updateNeighbours();
}
}
}
@@ -1,250 +0,0 @@
/*
* 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 lombok.NonNull;
import org.bukkit.Bukkit;
import org.bukkit.Location;
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 {
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);
DynamicRegionVisualizer.INSTANCE.addRegion(region);
}
public void remove(DynamicRegion region) {
regionCache.clear();
regionMap.remove(region.getID());
regionTypeMap.getOrDefault(region.getType(), Collections.emptySet()).remove(region);
DynamicRegionVisualizer.INSTANCE.removeRegion(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) {
return get(tile, true, regionMap.values());
}
private Region get(@Nullable Tile tile, boolean fastCache, Collection<Region> regions) {
if (tile == null) return getGlobalRegion();
if (regionCache.containsKey(tile.getId())) {
Region region = regionCache.get(tile.getId());
if (fastCache || regions.contains(region)) return region;
}
Location location = tile.getCenterLocation();
Region region = regions.stream()
.filter(rg -> rg.getArea().inRegion(location, 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(Tile.fromLocation(location).orElse(null), 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();
}
public record Neighbour<T extends Region>(T region, Tile tile, 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>> getNeighbours(Region region, boolean noCorners, boolean fastCache, Collection<Region> regions) {
Tile minTile = Tile.fromPoint(region.getArea().getMinPoint(false)).orElse(null);
Tile maxTile = Tile.fromPoint(region.getArea().getMaxPoint(false)).orElse(null);
if (minTile == null || maxTile == null) return Stream.empty();
Set<Neighbour<Region>> neighbours = new HashSet<>();
if (!noCorners) {
neighbours.add(new Neighbour<>(get(minTile.add(-1, -1).orElse(null), fastCache, regions), minTile, NeighbourDirection.NorthWest));
Tile.fromTile(minTile.getTileX(), maxTile.getTileZ()).ifPresent(cornerMinMaxSelf -> {
neighbours.add(new Neighbour<>(get(cornerMinMaxSelf.add(-1, 1).orElse(null), fastCache, regions), cornerMinMaxSelf, NeighbourDirection.SouthWest));
});
Tile.fromTile(maxTile.getTileX(), minTile.getTileZ()).ifPresent(cornerMaxMinSelf -> {
neighbours.add(new Neighbour<>(get(cornerMaxMinSelf.add(1, -1).orElse(null), fastCache, regions), cornerMaxMinSelf, NeighbourDirection.NorthEast));
});
neighbours.add(new Neighbour<>(get(maxTile.add(1, 1).orElse(null), fastCache, regions), maxTile, NeighbourDirection.SouthEast));
}
for (int x = minTile.getTileX(); x <= maxTile.getTileX(); x++) {
Tile.fromTile(x, minTile.getTileZ()).ifPresent(tileMinZSelf -> {
neighbours.add(new Neighbour<>(get(tileMinZSelf.add(0, -1).orElse(null), fastCache, regions), tileMinZSelf, NeighbourDirection.North));
});
Tile.fromTile(x, maxTile.getTileZ()).ifPresent(tileMaxZSelf -> {
neighbours.add(new Neighbour<>(get(tileMaxZSelf.add(0, 1).orElse(null), fastCache, regions), tileMaxZSelf, NeighbourDirection.South));
});
}
for (int z = minTile.getTileZ(); z <= maxTile.getTileZ(); z++) {
Tile.fromTile(minTile.getTileX(), z).ifPresent(tileMinXSelf -> {
neighbours.add(new Neighbour<>(get(tileMinXSelf.add(-1, 0).orElse(null), fastCache, regions), tileMinXSelf, NeighbourDirection.West));
});
Tile.fromTile(maxTile.getTileX(), z).ifPresent(tileMaxXSelf -> {
neighbours.add(new Neighbour<>(get(tileMaxXSelf.add(1, 0).orElse(null), fastCache, regions), tileMaxXSelf, NeighbourDirection.East));
});
}
neighbours.removeIf(neighbour -> neighbour.region.getType().isGlobal());
return neighbours.stream();
}
public Stream<Neighbour<DynamicRegion>> getNeighbours(Region region) {
return getNeighbours(region, false, true, regionMap.values())
.map(neighbour -> neighbour.as(DynamicRegion.class))
.filter(Objects::nonNull);
}
@Override
@NotNull
public Stream<Region> getConnectedRegions(@NonNull 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(neighbour -> neighbour.region)
.forEach(current::add);
}
return connected.stream();
}
}
@@ -1,114 +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.bausystem.region;
import de.steamwar.bausystem.region.dynamic.DynamicRegion;
import de.steamwar.bausystem.region.dynamic.RegionConstructorData;
import de.steamwar.bausystem.region.dynamic.Tile;
import de.steamwar.core.SWPlayer;
import de.steamwar.entity.*;
import org.bukkit.Location;
import org.bukkit.Material;
import org.bukkit.World;
import org.bukkit.entity.Display;
import org.bukkit.util.Transformation;
import org.joml.Quaternionf;
import org.joml.Vector3f;
public class DynamicRegionVisualizer implements SWPlayer.Component {
public static final DynamicRegionVisualizer INSTANCE = new DynamicRegionVisualizer();
private final REntityServer entityServer = new REntityServer();
private DynamicRegionVisualizer() {
RTextDisplay text = new RTextDisplay(entityServer, new Location(null, 0.5, 1.1, 0.5));
text.setText("Spawn");
text.setBillboard(Display.Billboard.VERTICAL);
text.setBackgroundColor(0);
text.setShadowed(false);
text.setTransform(new Transformation(new Vector3f(0, 0, 0), new Quaternionf().rotationX((float) Math.toRadians(270)), new Vector3f(1, 1, 1), new Quaternionf()));
}
public void addRegion(DynamicRegion region) {
new CRegion(entityServer, region);
}
public void removeRegion(DynamicRegion region) {
entityServer.getEntitiesByType(CRegion.class)
.stream()
.filter(cRegion -> cRegion.region == region)
.forEach(CRegion::die);
}
@Override
public void onMount(SWPlayer player) {
entityServer.addPlayer(player.getPlayer());
}
@Override
public void onUnmount(SWPlayer player) {
entityServer.removePlayer(player.getPlayer());
}
private static final Vector3f VEC_ZERO = new Vector3f(0, 0, 0);
private static final Quaternionf QUT_ZERO = new Quaternionf(0, 0, 0, 1);
public static Point toVisualization(Location worldLocation) {
Tile tile = Tile.fromLocation(worldLocation).orElseThrow();
return new Point(tile.getTileX(), 0, tile.getTileZ());
}
private static class CRegion extends CEntity {
private final DynamicRegion region;
private CRegion(REntityServer server, DynamicRegion region) {
super(server);
this.region = region;
RegionConstructorData data = DynamicRegionSystem.constructorDataMap.get(region.getClass());
int widthX = data.widthX();
int widthZ = data.widthZ();
Point point = toVisualization(region.getArea().getMinPoint(false).toLocation((World) null));
if (widthX != 1 || widthZ != 1) {
CArea area = new CArea(server);
area.setBlock(Material.WHITE_CONCRETE.createBlockData());
area.setPos1And2(point.toLocation((World) null).add(0, 1 - CArea.DEFAULT_WIDTH, 0), point.toLocation((World) null).add(widthX - 1, 1 - CArea.DEFAULT_WIDTH, widthZ - 1));
entities.add(area);
RTextDisplay text = new RTextDisplay(server, point.toLocation((World) null).add(widthX / 2.0, 1.1, widthZ / 2.0));
text.setText(data.name());
text.setBillboard(Display.Billboard.VERTICAL);
text.setBackgroundColor(0);
text.setShadowed(false);
text.setTransform(new Transformation(new Vector3f(0, 0, 0), new Quaternionf().rotationX((float) Math.toRadians(270)), new Vector3f(1, 1, 1), new Quaternionf()));
entities.add(text);
}
RBlockDisplay display = new RBlockDisplay(server, point.toLocation((World) null));
display.setTransform(new Transformation(VEC_ZERO, QUT_ZERO, new Vector3f(widthX, 1, widthZ), QUT_ZERO));
display.setBlock(data.material().createBlockData());
entities.add(display);
}
}
}
@@ -1,147 +0,0 @@
/*
* 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.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.PathRegion;
import de.steamwar.bausystem.utils.PasteBuilder;
import lombok.Getter;
import lombok.NonNull;
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;
}
/**
* 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 writeTileData(JsonWriter writer) throws IOException;
public final void updateNeighbours() {
List<DynamicRegionSystem.Neighbour<PathRegion>> list = DynamicRegionSystem.INSTANCE.getNeighbours(this)
.map(neighbour -> neighbour.as(PathRegion.class))
.filter(Objects::nonNull)
.toList();
// Calculate Garden State for all neighbouring PathRegions
Set<UUID> needsFullReset = new HashSet<>();
list.forEach(data -> {
boolean previousGardenState = data.region().isGarden();
data.region().calculateGardenState();
if (data.region().isGarden() != previousGardenState) {
needsFullReset.add(data.region().getID());
}
});
// Updating world state for all neighbouring PathRegions
list.forEach(data -> {
if (needsFullReset.contains(data.region().getID())) {
data.region().getArea().reset(new PasteBuilder(), false);
} else {
data.region().update(this, data.direction().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)
.map(neighbour -> neighbour.as(PathRegion.class))
.filter(Objects::nonNull)
.forEach(data -> {
if (data.region().isGarden()) return;
data.region().update(dynamicRegion, data.direction().opposite());
});
});
}
public void setRegionData(@NonNull RegionData regionData) {
this.regionData = regionData;
regionData.setStore(this);
}
@Override
public void delete() {
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 final @NonNull UUID getID() {
return id;
}
@Override
public final void save() {
DynamicRegionRepository.saveRegion(this);
}
@Override
public final void load(RegionData regionData) {
DynamicRegionRepository.loadRegionData(this, regionData);
}
}
@@ -1,361 +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.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.dynamic.path.PathRegionData;
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";
@Deprecated
private static final String META_FILE_TILE_X = "tile_x";
@Deprecated
private static final String META_FILE_TILE_Z = "tile_z";
public static final String META_FILES_TILES = "tiles";
public static final String FLAG_FILE_NAME = "flags.json";
public static final String BACKUPS_DIR_NAME = "backups";
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) || regionUUID.equals(PathRegionData.PATH_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);
}
// Calculate Garden State for all PathRegions
DynamicRegionSystem.INSTANCE.getRegionsByType(RegionType.PATH)
.forEach(region -> {
((PathRegion) region).calculateGardenState();
});
}
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) {
e.printStackTrace();
RegionSystem.LOGGER.log(Level.SEVERE, "Failed to read region metadata file (invalid data)");
return null;
}
}
private static File getRegionDirectory(UUID uuid) {
return new File(REGION_DATA_FOLDER, uuid.toString());
}
public static File getRegionDirectory(Region region) {
return getRegionDirectory(region.getID());
}
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(UUID uuid, RegionData regionData) {
File regionDirectory = getRegionDirectory(uuid);
if (!regionDirectory.exists()) return;
loadRegionData(regionDirectory, regionData);
}
public static void loadRegionData(Region region, RegionData regionData) {
if (region.getRegionData() instanceof PathRegionData) {
return;
}
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 = getRegionDirectory(region);
if (!regionDirectory.exists()) regionDirectory.mkdir();
if (region instanceof DynamicRegion dynamicRegion) {
RegionConstructorData constructorData = DynamicRegionSystem.constructorDataMap.get(region.getClass());
writeMetaFile(regionDirectory, constructorData, dynamicRegion);
}
if (region.getRegionData() instanceof PathRegionData) {
return;
}
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());
}
public static void saveData(UUID uuid, RegionData regionData) {
File regionDirectory = getRegionDirectory(uuid);
writeFlagsFile(regionDirectory, regionData);
}
@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.writeTileData(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;
}
});
}
}
@@ -1,59 +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.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;
};
}
}
@@ -1,62 +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.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;
}
}
}
@@ -1,40 +0,0 @@
/*
* 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;
}
@@ -1,192 +0,0 @@
/*
* 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 Tile TILE_PZ = new Tile(1, 0);
public static final Tile TILE_NZ = new Tile(-1, 0);
public static final Tile TILE_ZP = new Tile(0, 1);
public static final Tile TILE_ZN = new Tile(0, -1);
public static final Tile TILE_PP = new Tile(1, 1);
public static final Tile TILE_PN = new Tile(1, -1);
public static final Tile TILE_NP = new Tile(-1, 1);
public static final Tile TILE_NN = new Tile(-1, -1);
public static final int tileSize = 21;
public static final int tileOffset = tileSize / 2;
public static final int maxTile = 127;
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();
}
}
@@ -1,62 +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.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 lombok.experimental.UtilityClass;
import java.io.IOException;
@UtilityClass
public class TileUtils {
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);
}
public static Tile readTile(JsonArray tileData) {
if (tileData.size() != 1) return null;
try {
return readTile(tileData.get(0).getAsJsonObject(), "tile");
} catch (IllegalStateException e) {
return null;
}
}
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());
}
public static void writeTile(JsonWriter writer, Tile tile) throws IOException {
writer.beginObject();
writeTile(writer, tile, "tile");
writer.endObject();
}
}
@@ -1,96 +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.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)]);
}
};
}
}
@@ -1,139 +0,0 @@
/*
* 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.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(boolean extension) {
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(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);
}
}
@@ -1,55 +0,0 @@
/*
* 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;
}
}
@@ -1,170 +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.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;
@AllArgsConstructor(access = AccessLevel.PRIVATE)
public class AreaBlock extends 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(boolean extension) {
return extension ? minPointExtension : copyPoint;
}
@Override
public void place(PasteBuilder pasteBuilder, boolean extension) {
EditSession editSession = pasteBuilder
.with(PasteBuilder.ClipboardProvider.file(selector.select().orElse(null)))
.pastePoint(copyPoint)
.run();
region.getHistory()
.remember(editSession);
}
}
@@ -1,72 +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.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 java.io.File;
public class AreaTile extends 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(boolean extension) {
return copyPoint;
}
@Override
public void place(PasteBuilder pasteBuilder, boolean extension) {
File resetFile = selector.select().orElse(null);
if (resetFile != null) PasteUtils.paste(resetFile, minPoint, 0);
}
}
@@ -1,54 +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.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;
}
}
@@ -1,155 +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.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.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();
}
// 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 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, DynamicRegionRepository.BACKUP_FILE_NAME)))) {
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), DynamicRegionRepository.BACKUP_FILE_NAME);
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() {
backups.getOrDefault(type, Collections.emptyList())
.remove(this);
DynamicRegionRepository.deleteBackup(region, this);
}
}
}
@@ -1,53 +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.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;
}
}
@@ -1,112 +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.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.GRAY_CONCRETE,
widthX = MiWG7DisplayRegion.TILE_X,
widthZ = MiWG7DisplayRegion.TILE_Z
)
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, TileUtils.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 writeTileData(JsonWriter writer) throws IOException {
TileUtils.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;
}
}
@@ -1,134 +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.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.GRAY_CONCRETE,
widthX = MiWG7PlotRegion.TILE_X,
widthZ = 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, TileUtils.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 writeTileData(JsonWriter writer) throws IOException {
TileUtils.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;
}
}
@@ -1,35 +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.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();
}
}
@@ -1,112 +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.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.YELLOW_CONCRETE,
widthX = MWGDisplayRegion.TILE_X,
widthZ = MWGDisplayRegion.TILE_Z
)
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, TileUtils.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 writeTileData(JsonWriter writer) throws IOException {
TileUtils.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;
}
}
@@ -1,134 +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.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.YELLOW_CONCRETE,
widthX = MWGPlotRegion.TILE_X,
widthZ = 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, TileUtils.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 writeTileData(JsonWriter writer) throws IOException {
TileUtils.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;
}
}
@@ -1,35 +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.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();
}
}
@@ -1,112 +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.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.YELLOW_CONCRETE,
widthX = WG45DisplayRegion.TILE_X,
widthZ = WG45DisplayRegion.TILE_Z
)
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, TileUtils.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 writeTileData(JsonWriter writer) throws IOException {
TileUtils.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;
}
}
@@ -1,134 +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.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.YELLOW_CONCRETE,
widthX = WG45PlotRegion.TILE_X,
widthZ = 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, TileUtils.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 writeTileData(JsonWriter writer) throws IOException {
TileUtils.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;
}
}
@@ -1,35 +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.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();
}
}
@@ -1,112 +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.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.BLUE_CONCRETE,
widthX = WS175DisplayRegion.TILE_X,
widthZ = WS175DisplayRegion.TILE_Z
)
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, TileUtils.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 writeTileData(JsonWriter writer) throws IOException {
TileUtils.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;
}
}
@@ -1,134 +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.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.BLUE_CONCRETE,
widthX = WS175PlotRegion.TILE_X,
widthZ = 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, TileUtils.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 writeTileData(JsonWriter writer) throws IOException {
TileUtils.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;
}
}
@@ -1,35 +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.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();
}
}
@@ -1,112 +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.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.BLUE_CONCRETE,
widthX = WS230DisplayRegion.TILE_X,
widthZ = WS230DisplayRegion.TILE_Z
)
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, TileUtils.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 writeTileData(JsonWriter writer) throws IOException {
TileUtils.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;
}
}
@@ -1,134 +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.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.BLUE_CONCRETE,
widthX = WS230PlotRegion.TILE_X,
widthZ = 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, TileUtils.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 writeTileData(JsonWriter writer) throws IOException {
TileUtils.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;
}
}
@@ -1,35 +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.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();
}
}
@@ -1,250 +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.bausystem.region.dynamic.path;
import com.sk89q.worldedit.extent.clipboard.Clipboard;
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.PasteUtils;
import de.steamwar.bausystem.region.dynamic.Tile;
import de.steamwar.bausystem.region.dynamic.VariantSelector;
import de.steamwar.bausystem.region.dynamic.spawn.SpawnRegion;
import de.steamwar.bausystem.shared.Pair;
import de.steamwar.bausystem.utils.PasteBuilder;
import lombok.NonNull;
import org.bukkit.Bukkit;
import org.jetbrains.annotations.Nullable;
import java.io.File;
import java.util.Optional;
import java.util.function.BiConsumer;
import static de.steamwar.bausystem.region.RegionType.ConnectionType.*;
public class PathArea extends Region.Area {
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)
;
protected enum RotationCorrection {
Unchanged,
WithCorrection,
UsingOrdinal,
}
private final PathRegion region;
private final Tile tile;
private final Point minPoint;
private final Point maxPoint;
public PathArea(Tile tile, PathRegion region) {
this.region = region;
this.tile = tile;
minPoint = Point.fromLocation(tile.getMinLocation()).setY(WORLD_MIN_Y);
maxPoint = Point.fromLocation(tile.getMaxLocation()).setY(WORLD_MAX_Y);
}
@Override
public @NonNull Point getMinPoint(boolean extension) {
return minPoint;
}
@Override
public @NonNull Point getMaxPoint(boolean extension) {
return maxPoint;
}
@Override
public @NonNull Point getCopyPoint(boolean extension) {
return Point.ZERO;
}
@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;
}
public void reset(PathSide side) {
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;
}
PasteUtils.paste(resetFile, minPoint.add(side.pasteOffsetX, 0, side.pasteOffsetZ), side.rotate);
}
public void reset(PathCorner corner) {
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;
}
PasteUtils.paste(resetFile, minPoint.add(corner.pasteOffsetX, 0, corner.pasteOffsetZ), rotate);
}
@Override
public void place(PasteBuilder pasteBuilder, boolean extension) {
if (region.isGarden()) {
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(side);
}
for (PathCorner corner : PathCorner.values()) {
reset(corner);
}
}
protected static RegionType.ConnectionType getConnectionType(Tile tile, PathSide side, PathSide optionalSide, RegionType.ConnectionType spawnPathConnection) {
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 Global;
}
tile = optionalTile.get();
Region region = DynamicRegionSystem.INSTANCE.get(tile);
if (region instanceof PathRegion pathRegion) {
return pathRegion.isGarden() ? Garden : Path;
}
if (region instanceof SpawnRegion spawnRegion) {
if (optionalSide == null && side.opposite().equals(spawnRegion.getPathSide())) {
return spawnPathConnection;
} else {
return Closed;
}
}
return region.getType().getConnectionType();
}
protected static RegionType.ConnectionType getConnectionType(Tile tile, PathSide side, PathSide optionalSide) {
return getConnectionType(tile, side, optionalSide, Path);
}
}
@@ -1,38 +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.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;
}
@@ -1,134 +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.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.Getter;
import lombok.NonNull;
import org.bukkit.Material;
import java.io.IOException;
import java.util.Set;
import java.util.UUID;
import java.util.stream.Collectors;
@RegionConstructorData(
identifier = "path",
name = "Path",
material = Material.DIRT_PATH,
widthX = PathRegion.TILE_X,
widthZ = PathRegion.TILE_Z
)
public class PathRegion extends DynamicRegion {
protected static final int TILE_X = 1;
protected static final int TILE_Z = 1;
private final PathArea area;
@Getter
private final Tile tile;
public PathRegion(Tile tile) {
this(UUID.randomUUID(), tile);
finishCreate();
}
public PathRegion(UUID id, JsonArray tileData) {
this(id, TileUtils.readTile(tileData));
finishLoad();
}
private PathRegion(UUID id, Tile tile) {
super(id, null);
this.tile = tile;
area = new PathArea(tile, this);
regionData = PathRegionData.INSTANCE;
calculateGardenState();
}
@Override
public void writeTileData(JsonWriter writer) throws IOException {
TileUtils.writeTile(writer, tile);
}
public void update(DynamicRegion updateFrom, NeighbourDirection direction) {
for (PathSide side : direction.getSideUpdates()) {
area.reset(side);
}
for (PathCorner corner : direction.getCornerUpdates()) {
area.reset(corner);
}
}
@Getter
private boolean garden = false;
public void calculateGardenState() {
garden = false;
for (PathSide pathSide : PathSide.values()) {
RegionType.ConnectionType connectionType = PathArea.getConnectionType(tile, pathSide, null, null);
if (connectionType != RegionType.ConnectionType.Path && connectionType != RegionType.ConnectionType.Garden) return;
}
for (PathCorner pathCorner : PathCorner.values()) {
RegionType.ConnectionType connectionType = PathArea.getConnectionType(tile, pathCorner.side1, pathCorner.side2, null);
if (connectionType != RegionType.ConnectionType.Path && connectionType != RegionType.ConnectionType.Garden) return;
}
garden = true;
}
@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;
}
}
@@ -1,75 +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.bausystem.region.dynamic.path;
import de.steamwar.bausystem.region.RegionData;
import de.steamwar.bausystem.region.RegionDataStore;
import de.steamwar.bausystem.region.RegionFlagPolicy;
import de.steamwar.bausystem.region.dynamic.DynamicRegionRepository;
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;
import java.util.UUID;
public class PathRegionData extends RegionData {
public static final UUID PATH_REGION_ID = new UUID(Long.MAX_VALUE, Long.MAX_VALUE);
public static final PathRegionData INSTANCE = new PathRegionData();
private PathRegionData() {
super(new RegionDataStore() {
@Override
public void save() {
DynamicRegionRepository.saveData(PATH_REGION_ID, INSTANCE);
}
@Override
public void load(RegionData regionData) {
DynamicRegionRepository.loadRegionData(PATH_REGION_ID, regionData);
}
});
}
@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;
}
}
@@ -1,46 +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.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;
public PathSide opposite() {
return switch (this) {
case North -> South;
case South -> North;
case East -> West;
case West -> East;
};
}
}
@@ -1,68 +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.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;
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 PathArea.RotationCorrection[] rotationCorrections = new PathArea.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] = PathArea.RotationCorrection.Unchanged;
return this;
}
public SelectorCorner Case(RegionType.ConnectionType left, RegionType.ConnectionType right, RegionType.ConnectionType diagonal, VariantSelector selector, PathArea.RotationCorrection rotationCorrection) {
int index = getIndex(left, right, diagonal);
selectors[index] = selector;
rotationCorrections[index] = rotationCorrection;
return this;
}
public Pair<VariantSelector, PathArea.RotationCorrection> Select(Tile tile, PathCorner corner) {
RegionType.ConnectionType left = PathArea.getConnectionType(tile, corner.side1, null, RegionType.ConnectionType.Closed);
RegionType.ConnectionType right = PathArea.getConnectionType(tile, corner.side2, null, RegionType.ConnectionType.Closed);
RegionType.ConnectionType diagonal = PathArea.getConnectionType(tile, corner.side1, corner.side2);
int index = getIndex(left, right, diagonal);
return new Pair<>(selectors[index], rotationCorrections[index]);
}
}

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