/* * This file is a part of the SteamWar software. * * Copyright (C) 2026 SteamWar.de-Serverteam * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . */ package de.steamwar.bausystem.region; import de.steamwar.bausystem.BauSystem; import de.steamwar.bausystem.region.dynamic.DynamicRegion; import de.steamwar.bausystem.region.dynamic.DynamicRegionRepository; import de.steamwar.bausystem.region.dynamic.RegionConstructorData; import de.steamwar.bausystem.region.dynamic.Tile; import de.steamwar.bausystem.utils.PasteBuilder; import de.steamwar.core.SWPlayer; import de.steamwar.entity.*; import de.steamwar.inventory.SWInventory; import de.steamwar.inventory.SWItem; import de.steamwar.inventory.SWListInv; import org.bukkit.Bukkit; import org.bukkit.Location; import org.bukkit.Material; import org.bukkit.entity.Player; import org.bukkit.event.EventHandler; import org.bukkit.event.Listener; import org.bukkit.event.player.PlayerMoveEvent; import java.util.*; import java.util.stream.Collectors; public class DynamicRegionVisualizer implements SWPlayer.Component, Listener { private final REntityServer entityServer; private Player player; private Location sourceLocation; private Tile sourceTile; private Placement placement = null; public DynamicRegionVisualizer() { this.entityServer = new REntityServer(); } @Override public void onMount(SWPlayer player) { this.player = player.getPlayer(); sourceTile = Tile.fromLocation(player.getLocation()).orElse(null); if (sourceTile == null) { player.removeComponent(DynamicRegionVisualizer.class); return; } sourceLocation = player.getLocation().add(0, -5, 0).getBlock().getLocation(); entityServer.addPlayer(player.getPlayer()); Bukkit.getPluginManager().registerEvents(this, BauSystem.getInstance()); renderTiles(0, 0); } @Override public void onUnmount(SWPlayer player) { Bukkit.getPluginManager().registerEvents(this, BauSystem.getInstance()); entityServer.close(); } @EventHandler public void onPlayerMove(PlayerMoveEvent event) { if (event.getPlayer() != player) return; Location position = event.getTo().getBlock().getLocation(); int dx = position.getBlockX() - sourceLocation.getBlockX(); int dz = position.getBlockZ() - sourceLocation.getBlockZ(); renderTiles(dx, dz); } private void renderTiles(int dx, int dz) { Set tileSet = entityServer.getEntitiesByType(CTile.class) .stream() .filter(cTile -> { if (Math.abs(cTile.tile.getTileX() - dx) > 20 || Math.abs(cTile.tile.getTileZ() - dz) > 20) { cTile.die(); return false; } else { return true; } }) .map(cTile -> cTile.tile) .collect(Collectors.toSet()); for (int x = dx - 10; x <= dx + 10; x++) { for (int z = dz - 10; z <= dz + 10; z++) { Tile tile = sourceTile.add(x, z).orElse(null); if (tile == null || tileSet.contains(tile)) continue; new CTile(entityServer, tile); } } } private void resetTiles(Set tiles) { entityServer.getEntitiesByType(CTile.class) .stream() .filter(cTile -> tiles.contains(cTile.tile)) .peek(CTile::die) .map(cTile -> cTile.tile) .forEach(tile -> { new CTile(entityServer, tile); }); if (placement != null) { placement.check(); } } private class CTile extends CEntity { private final Tile tile; public CTile(REntityServer server, Tile tile) { super(server); this.tile = tile; RegionType regionType = DynamicRegionSystem.INSTANCE.get(tile).getType(); Material material = switch (regionType) { case SPAWN, SPAWN_EXTENSION -> Material.LODESTONE; case SPAWN_PATH, PATH -> Material.DIRT_PATH; case DRY, DRY_SPECIAL -> Material.IRON_BLOCK; case WET, WET_SPECIAL -> Material.LAPIS_BLOCK; default -> Material.WHITE_CARPET; }; Location location = sourceLocation.clone().add(tile.getTileX() - sourceTile.getTileX(), 0, tile.getTileZ() - sourceTile.getTileZ()); if (tile.equals(Tile.ZERO)) { CCubedTextDisplay spawn = new CCubedTextDisplay(entityServer, location); spawn.setText("§eSPAWN"); spawn.setBackgroundColor(0); spawn.setShadowed(false); entities.add(spawn); } else if (tile.equals(sourceTile)) { CCubedTextDisplay origin = new CCubedTextDisplay(entityServer, location); origin.setText("§eORIGIN"); origin.setBackgroundColor(0); origin.setShadowed(false); entities.add(origin); } RBlockDisplay blockDisplay = new RBlockDisplay(entityServer, location); blockDisplay.setBlock(material.createBlockData()); entities.add(blockDisplay); RInteraction interaction = new RInteraction(entityServer, location.clone().add(0.5, 0, 0.5)); interaction.setInteraction((player, entityAction) -> { if (placement != null) { placement.click(tile, regionType.isGlobal()); return; } if (!regionType.isGlobal()) { SWInventory inv = new SWInventory(player, 9, "Delete Region: " + tile.display()); inv.setItem(0, new SWItem(SWItem.getDye(10), "§8Cancel", click -> { player.closeInventory(); })); inv.setItem(8, new SWItem(SWItem.getDye(1), "§cDelete", click -> { player.closeInventory(); Region region = DynamicRegionSystem.INSTANCE.get(tile); Set tiles = DynamicRegionSystem.INSTANCE.getTilesOfRegion(region); region.delete(); SWPlayer.allWithSingleComponent(DynamicRegionVisualizer.class) .forEach(pair -> { pair.getComponent().resetTiles(tiles); }); })); inv.open(); } else { List, RegionConstructorData>>> entries = new ArrayList<>(); DynamicRegionSystem.constructorDataMap.entrySet() .stream() .filter(entry -> entry.getValue().placeable()) .sorted(Comparator.comparing(entry -> entry.getValue().name())) .forEach(entry -> { entries.add(new SWListInv.SWListEntry<>(new SWItem(entry.getValue().material(), entry.getValue().name()), entry)); }); SWListInv, RegionConstructorData>> listInv = new SWListInv<>(player, "Select Region for: " + tile.display(), entries, (click, entry) -> { new Placement(entry.getKey(), entry.getValue(), tile); player.closeInventory(); }); listInv.open(); } }); entities.add(interaction); } } private class Placement { private final Class regionType; private final RegionConstructorData constructorData; private CWireframe wireframe; private Tile sourceTile; private int dx; private int dz; private boolean valid = false; private Location getMinLocation() { Tile tile = sourceTile.add(-DynamicRegionVisualizer.this.sourceTile.getTileX(), -DynamicRegionVisualizer.this.sourceTile.getTileZ()).orElseThrow(); return sourceLocation.clone().add(tile.getTileX(), 0, tile.getTileZ()); } private Location getMaxLocation() { Tile tile = sourceTile.add(-DynamicRegionVisualizer.this.sourceTile.getTileX(), -DynamicRegionVisualizer.this.sourceTile.getTileZ()).orElseThrow(); return sourceLocation.clone().add(tile.getTileX(), 0, tile.getTileZ()).add(dx, 0, dz); } public Placement(Class regionType, RegionConstructorData constructorData, Tile sourceTile) { this.regionType = regionType; this.constructorData = constructorData; this.sourceTile = sourceTile; dx = constructorData.widthX() / Tile.tileSize - 1; dz = constructorData.widthZ() / Tile.tileSize - 1; if (dx == 0 && dz == 0) { place(); return; } placement = this; wireframe = new CWireframe(entityServer); check(); } private void check() { wireframe.setPos1And2(getMinLocation(), getMaxLocation()); for (int x = 0; x <= dx; x++) { for (int z = 0; z <= dz; z++) { if (!DynamicRegionSystem.INSTANCE.get(sourceTile.add(x, z).orElseThrow()).getType().isGlobal()) { wireframe.setBlock(Material.RED_CONCRETE.createBlockData()); valid = false; return; } } } wireframe.setBlock(Material.LIME_CONCRETE.createBlockData()); valid = true; } public void click(Tile tile, boolean global) { if (tile.getTileX() >= sourceTile.getTileX() && tile.getTileX() <= sourceTile.getTileX() + dx && tile.getTileZ() >= sourceTile.getTileZ() && tile.getTileZ() <= sourceTile.getTileZ() + dz) { SWInventory inv = new SWInventory(player, 9, "Place Region: " + constructorData.name()); inv.setItem(0, new SWItem(SWItem.getDye(1), "§cDeselect", click -> { placement = null; wireframe.die(); player.closeInventory(); })); if (valid) { inv.setItem(8, new SWItem(SWItem.getDye(10), "§aPlace", click -> { player.closeInventory(); place(); })); } else { inv.setItem(8, new SWItem(SWItem.getDye(8), "§8Place", click -> { })); } inv.open(); return; } if (!global) { return; } Set tiles = new HashSet<>(); for (int x = 0; x <= dx; x++) { for (int z = 0; z <= dz; z++) { tiles.add(Tile.fromTile(x + sourceTile.getTileX(), z + sourceTile.getTileZ()).orElseThrow()); } } Tile selected = tiles.stream().min(Comparator.comparing(current -> { int dx = current.getTileX() - tile.getTileX(); int dz = current.getTileZ() - tile.getTileZ(); return dx * dx + dz * dz; })) .orElseThrow(); int dx = tile.getTileX() - selected.getTileX(); int dz = tile.getTileZ() - selected.getTileZ(); sourceTile = sourceTile.add(dx, dz).orElseThrow(); check(); } private void place() { DynamicRegion dynamicRegion = DynamicRegionRepository.constructRegion(regionType, UUID.randomUUID(), sourceTile.getMinX(), sourceTile.getMinZ()); if (dynamicRegion == null) { // TODO: Give error to user return; } dynamicRegion.getArea().place(new PasteBuilder(new PasteBuilder.FileProvider(dynamicRegion.getArea().getResetFile())), false); dynamicRegion.updateNeighbours(); placement = null; if (wireframe != null) wireframe.die(); Set tiles = new HashSet<>(); for (int x = 0; x <= dx; x++) { for (int z = 0; z <= dz; z++) { tiles.add(Tile.fromTile(x + sourceTile.getTileX(), z + sourceTile.getTileZ()).orElseThrow()); } } SWPlayer.allWithSingleComponent(DynamicRegionVisualizer.class) .forEach(pair -> pair.getComponent().resetTiles(tiles)); } } }