Add SteamEdit

This commit is contained in:
2026-09-01 10:32:56 +02:00
parent f35c07ab5b
commit ece12df0bc
16 changed files with 750 additions and 40 deletions
@@ -32,6 +32,7 @@ import de.steamwar.bausystem.features.tracer.TraceManager;
import de.steamwar.bausystem.features.tracer.TraceRecorder;
import de.steamwar.bausystem.features.world.BauScoreboard;
import de.steamwar.bausystem.linkage.BauGuiItem;
import de.steamwar.bausystem.region.Region;
import de.steamwar.bausystem.region.RegionSystem;
import de.steamwar.bausystem.utils.ScoreboardElement;
import de.steamwar.bausystem.utils.TickManager;
@@ -40,8 +41,9 @@ import de.steamwar.command.AbstractValidator;
import de.steamwar.command.SWCommandUtils;
import de.steamwar.core.CRIUSleepEvent;
import de.steamwar.core.Core;
import de.steamwar.core.WorldEditRendererCUIEditor;
import de.steamwar.core.WorldIdentifier;
import de.steamwar.core.worldedit.SteamEdit;
import de.steamwar.core.worldedit.WorldEditRendererCUIEditor;
import de.steamwar.linkage.AbstractLinker;
import de.steamwar.linkage.SpigotLinker;
import de.steamwar.message.Message;
@@ -49,6 +51,7 @@ import de.steamwar.providers.BauServerInfo;
import lombok.Getter;
import org.bukkit.Bukkit;
import org.bukkit.GameRule;
import org.bukkit.Location;
import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
@@ -129,7 +132,19 @@ public class BauSystem extends JavaPlugin implements Listener {
TraceManager.instance.init();
TraceRecorder.instance.init();
new WorldEditRendererCUIEditor();
SteamEdit.init();
SteamEdit.ADJACENT_FILTERS = location -> {
Region region = Region.getRegion(location);
Region.Area area;
if (region.getBuildArea().inRegion(location, true)) {
area = region.getBuildArea();
} else if (region.getTestblockArea().inRegion(location, true)) {
area = region.getTestblockArea();
} else {
return SteamEdit.AdjacentFilter.ALWAYS;
}
return (x, y, z) -> area.inRegion(new Location(null, x, y, z), true);
};
Bukkit.getWorlds().get(0).setGameRule(GameRule.SEND_COMMAND_FEEDBACK, false);
@@ -21,7 +21,8 @@ package de.steamwar.fightsystem;
import com.comphenix.tinyprotocol.TinyProtocol;
import de.steamwar.core.Core;
import de.steamwar.core.WorldEditRendererCUIEditor;
import de.steamwar.core.worldedit.SteamEdit;
import de.steamwar.core.worldedit.WorldEditRendererCUIEditor;
import de.steamwar.fightsystem.fight.Fight;
import de.steamwar.fightsystem.fight.FightTeam;
import de.steamwar.fightsystem.listener.ClickAnalyzer;
@@ -107,7 +108,7 @@ public class FightSystem extends JavaPlugin {
}
});
new OneShotStateDependent(ArenaMode.All, FightState.PreSchemSetup, () -> Fight.playSound(SWSound.BLOCK_NOTE_PLING.getSound(), 100.0f, 2.0f));
new OneShotStateDependent(ArenaMode.Test, FightState.All, WorldEditRendererCUIEditor::new);
new OneShotStateDependent(ArenaMode.Test, FightState.All, SteamEdit::init);
Config.world.setGameRule(GameRule.REDUCED_DEBUG_INFO, ArenaMode.AntiTest.contains(Config.mode));
hullHider = new HullHider();
@@ -20,7 +20,7 @@
package de.steamwar.fightsystem.record;
import com.sk89q.worldedit.extent.clipboard.Clipboard;
import de.steamwar.core.WorldEditWrapper;
import de.steamwar.core.worldedit.WorldEditWrapper;
import de.steamwar.entity.REntity;
import de.steamwar.entity.REntityServer;
import de.steamwar.entity.RPlayer;
@@ -108,19 +108,33 @@ UNNOSCHEMSUBMITTING = §e{0} §7may now submit §e§lschematics§7 again§8.
WORLDEDIT_CUI_TITLE = WorldEdit CUI
WORLDEDIT_CUI_TITLE_SUBMENU = WorldEdit CUI - {0}
WORLDEDIT_CUI_SELECTION = Own Selection
WORLDEDIT_CUI_CLIPBOARD = Own Clipboard
WORLDEDIT_CUI_SELECTION_OTHER = Other Selection
WORLDEDIT_CUI_CLIPBOARD_OTHER = Other Clipboard
WORLDEDIT_CUI_SELECTION = Selection
WORLDEDIT_CUI_CLIPBOARD = Clipboard
WORLDEDIT_CUI_CURSOR = Cursor
WORLDEDIT_CUI_MATERIAL_NAME = §eWorldEdit {0}
WORLDEDIT_CUI_MATERIAL_CLICK = §7Click to edit
WORLDEDIT_CUI_WIDTH_NAME = §eWidth {0}
WORLDEDIT_CUI_WIDTH_NAME_DIST = §eRange {0}
WORLDEDIT_CUI_WIDTH_LORE = §8> §7{0}
WORLDEDIT_CUI_WIDTH_LORE_SELECTED = §8> §e{0}
WORLDEDIT_CUI_WIDTH_CLICK = §7Click to change
WORLDEDIT_CUI_WIDTH_HUGE = 1/ 8 Block
WORLDEDIT_CUI_WIDTH_LARGE = 1/16 Block
WORLDEDIT_CUI_WIDTH_MEDIUM = 1/32 Block
WORLDEDIT_CUI_WIDTH_SLIM = 1/64 Block
WORLDEDIT_CUI_WIDTH_SLIM = 1/64 Block
WORLDEDIT_CUI_WIDTH_HUGE_DIST = 40 Blocks
WORLDEDIT_CUI_WIDTH_LARGE_DIST = 30 Blocks
WORLDEDIT_CUI_WIDTH_MEDIUM_DIST = 20 Blocks
WORLDEDIT_CUI_WIDTH_SLIM_DIST = 10 Blocks
WORLDEDIT_CUI_TOOL_SOON = §7Tool cooming soon!
WORLDEDIT_CUI_TOOL_COPY = §eCopy Tool
WORLDEDIT_CUI_TOOL_COPY_LORE = §eClick§8:§7 copy selection
WORLDEDIT_CUI_TOOL_PASTE = §ePaste Tool
WORLDEDIT_CUI_TOOL_PASTE_LORE_1 = §eClick§8:§7 paste clipboard
WORLDEDIT_CUI_TOOL_PASTE_LORE_2 = §eShift §8+§e Click§8:§7 paste clipboard and select
WORLDEDIT_CUI_TOOL_PASTE_LORE_3 = §eDrop§8:§7 paste clipboard at copy origin
@@ -101,13 +101,20 @@ NOSCHEMSUBMITTING_UNTIL = §7Du bist §ebis zum {0} §7vom §e§lEinsenden von S
UNNOSCHEMSUBMITTING_ERROR = §cDer Spieler ist nicht vom Einsenden von Schematics ausgeschlossen.
UNNOSCHEMSUBMITTING = §e{0} §7darf nun wieder §e§lSchematis§7 einsenden§8.
WORLDEDIT_CUI_SELECTION = Eigene Auswahl
WORLDEDIT_CUI_CLIPBOARD = Eigene Kopie
WORLDEDIT_CUI_SELECTION_OTHER = Auswahl Anderer
WORLDEDIT_CUI_CLIPBOARD_OTHER = Kopie Anderer
WORLDEDIT_CUI_SELECTION = Auswahl
WORLDEDIT_CUI_CLIPBOARD = Kopie
WORLDEDIT_CUI_MATERIAL_NAME = §eWorldEdit {0}
WORLDEDIT_CUI_MATERIAL_CLICK = §7Klicke zum Editieren
WORLDEDIT_CUI_WIDTH_NAME = §eDicke - {0}
WORLDEDIT_CUI_WIDTH_CLICK = §7Klicke zum ändern
WORLDEDIT_CUI_WIDTH_NAME = §eKlicke - {0}
WORLDEDIT_CUI_WIDTH_NAME_DIST = Reichweite§e {0}
WORLDEDIT_CUI_WIDTH_CLICK = §7Klicke zum ändern
WORLDEDIT_CUI_TOOL_SOON = §7Werkzeug kommt in kürze!
WORLDEDIT_CUI_TOOL_COPY = §eCopy Werkzeug
WORLDEDIT_CUI_TOOL_COPY_LORE = §eKlick§8:§7 Auswahl zu kopieren
WORLDEDIT_CUI_TOOL_PASTE = §ePaste Werkzeug
WORLDEDIT_CUI_TOOL_PASTE_LORE_1 = §eKlick§8:§7 Clipboard einzufügen
WORLDEDIT_CUI_TOOL_PASTE_LORE_2 = §eUmschalt §8+§e Klick§8:§7 Clipboard einzufügen und auszuwählen
WORLDEDIT_CUI_TOOL_PASTE_LORE_3 = §eDrop§8:§7 Clipboard am Kopierpunkt einzufügen
@@ -0,0 +1,73 @@
/*
* This file is a part of the SteamWar software.
*
* Copyright (C) 2026 SteamWar.de-Serverteam
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package de.steamwar.core.worldedit;
import de.steamwar.command.SWCommand;
import lombok.experimental.UtilityClass;
import org.bukkit.Location;
import org.bukkit.entity.Player;
import org.bukkit.inventory.ItemStack;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.function.Function;
@UtilityClass
public class SteamEdit {
private static final AtomicBoolean INITIALIZED = new AtomicBoolean();
public static void init() {
INITIALIZED.set(true);
new Command();
new SteamEditWand();
new WorldEditRendererCUIEditor();
}
public boolean isInitialized() {
return INITIALIZED.get();
}
public static Function<Location, AdjacentFilter> ADJACENT_FILTERS = location -> AdjacentFilter.ALWAYS;
public interface AdjacentFilter {
AdjacentFilter ALWAYS = (x, y, z) -> true;
boolean contains(int x, int y, int z);
}
private static final class Command extends SWCommand {
public Command() {
super("steamwand", "steamedit", "steameditwand", "swwand", "sweditwand", "swedit");
}
@Register
public void get(Player player) {
if (player.getInventory().contains(SteamEditWand.ITEM)) {
player.getInventory().remove(SteamEditWand.ITEM);
}
ItemStack handItem = player.getInventory().getItemInMainHand();
player.getInventory().setItemInMainHand(SteamEditWand.ITEM);
if (!handItem.isEmpty()) {
player.getInventory().addItem(handItem);
}
}
}
}
@@ -0,0 +1,116 @@
/*
* 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.core.worldedit;
import com.sk89q.worldedit.math.BlockVector3;
import de.steamwar.core.Core;
import org.bukkit.Location;
import org.bukkit.block.BlockFace;
import org.bukkit.util.Vector;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.stream.Stream;
public class SteamEditSelectAdjacent {
private static final BlockFace[] FACES = {
BlockFace.NORTH,
BlockFace.SOUTH,
BlockFace.EAST,
BlockFace.WEST,
BlockFace.UP,
BlockFace.DOWN,
};
private Location min;
private Location max;
private SteamEdit.AdjacentFilter filter;
private LinkedHashSet<Location> frontier = new LinkedHashSet<>();
public SteamEditSelectAdjacent(Location origin) {
min = origin.clone();
max = origin.clone();
frontier.add(origin);
filter = SteamEdit.ADJACENT_FILTERS.apply(origin);
long time = System.currentTimeMillis();
for (int i = 0; i < 500_000 && !frontier.isEmpty(); i++) {
if (System.currentTimeMillis() - time > 1000) break;
expandNext();
}
Core.getInstance().getLogger().finer(System.currentTimeMillis() - time + "ms " + frontier.size() + " " + min + " to " + max);
}
public BlockVector3 pos1() {
return BlockVector3.at(min.getBlockX(), min.getBlockY(), min.getBlockZ());
}
public BlockVector3 pos2() {
return BlockVector3.at(max.getBlockX(), max.getBlockY(), max.getBlockZ());
}
private boolean inside(Location loc, int offset) {
return loc.getBlockX() >= min.getBlockX() + offset && loc.getBlockX() <= max.getBlockX() - offset &&
loc.getBlockY() >= min.getBlockY() + offset && loc.getBlockY() <= max.getBlockY() - offset &&
loc.getBlockZ() >= min.getBlockZ() + offset && loc.getBlockZ() <= max.getBlockZ() - offset;
}
public void expandNext() {
Location location = frontier.removeFirst();
expand(location);
}
private void expand(Location loc) {
for (BlockFace face : FACES) {
Vector direction = face.getDirection();
Location location = loc.clone().add(direction);
if (!filter.contains(location.getBlockX(), location.getBlockY(), location.getBlockZ())) continue;
if (inside(location, 0)) continue;
List<Location> layer = Stream.concat(selectFrontier(direction.getBlockX() == 0 ? null : loc.getBlockX(), direction.getBlockY() == 0 ? null : loc.getBlockY(), direction.getBlockZ() == 0 ? null : loc.getBlockZ())
.map(Location::clone)
.map(l -> l.add(direction)), Stream.of(location))
.filter(l -> !l.getBlock().isEmpty())
.filter(l -> !l.getBlock().isLiquid())
.toList();
if (layer.isEmpty()) continue;
if (direction.getBlockX() < 0 || direction.getBlockY() < 0 || direction.getBlockZ() < 0) {
min.add(direction);
} else {
max.add(direction);
}
layer.stream().filter(l -> !inside(l, 1))
.filter(l -> filter.contains(l.getBlockX(), l.getBlockY(), l.getBlockZ()))
.forEach(frontier::add);
}
}
private Stream<Location> selectFrontier(Integer x, Integer y, Integer z) {
Stream<Location> stream = frontier.stream();
if (x != null) stream = stream.filter(loc -> loc.getBlockX() == x);
if (y != null) stream = stream.filter(loc -> loc.getBlockY() == y);
if (z != null) stream = stream.filter(loc -> loc.getBlockZ() == z);
return stream;
}
}
@@ -0,0 +1,406 @@
/*
* 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.core.worldedit;
import com.comphenix.tinyprotocol.TinyProtocol;
import com.sk89q.worldedit.LocalSession;
import com.sk89q.worldedit.bukkit.BukkitAdapter;
import com.sk89q.worldedit.bukkit.WorldEditPlugin;
import com.sk89q.worldedit.math.BlockVector3;
import com.sk89q.worldedit.regions.RegionSelector;
import com.sk89q.worldedit.regions.selector.limit.PermissiveSelectorLimits;
import com.sk89q.worldedit.world.World;
import de.steamwar.core.Core;
import de.steamwar.core.SWPlayer;
import de.steamwar.entity.CWireframe;
import de.steamwar.entity.REntityServer;
import de.steamwar.inventory.SWItem;
import net.minecraft.core.BlockPos;
import net.minecraft.network.protocol.Packet;
import net.minecraft.network.protocol.game.*;
import org.bukkit.Bukkit;
import org.bukkit.FluidCollisionMode;
import org.bukkit.Location;
import org.bukkit.Material;
import org.bukkit.block.Block;
import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
import org.bukkit.event.Listener;
import org.bukkit.event.inventory.InventoryType;
import org.bukkit.event.player.PlayerInteractEvent;
import org.bukkit.inventory.ItemStack;
import org.bukkit.scheduler.BukkitTask;
import org.bukkit.util.RayTraceResult;
import java.util.List;
public class SteamEditWand implements Listener {
public static final Material MATERIAL = Material.GOLDEN_AXE;
public static final String NAME = "§eSteam§8Edit §7Wand";
public static final String[] LORE = {
"§eL-Click§8: §7Set Pos1",
"§eR-Click§8: §7Set Pos2",
"§eShift §8+ §eR-Click§8: §7Set Pos2 or Expand Selection",
"§eM-Click§8: §7Select connected",
"§eSwap-Hands§8: §7Open CUI"
};
public static final ItemStack ITEM = new SWItem(MATERIAL, NAME).setLore(LORE).getItemStack();
private boolean noWand(Player player) {
return player.getInventory().getItemInMainHand().getType() != SteamEditWand.MATERIAL;
}
private final WorldEditPlugin we;
SteamEditWand() {
we = WorldEditWrapper.getWorldEditPlugin();
Bukkit.getPluginManager().registerEvents(this, Core.getInstance());
TinyProtocol.instance.addFilter(ServerboundSwingPacket.class, this::leftClick);
TinyProtocol.instance.addFilter(ServerboundInteractPacket.class, (player, packet) -> {
if (packet.isAttack()) {
return leftClick(player, packet);
} else {
return rightClick(player, packet);
}
});
TinyProtocol.instance.addFilter(ServerboundUseItemPacket.class, this::rightClick);
TinyProtocol.instance.addFilter(ServerboundUseItemOnPacket.class, this::rightClick);
TinyProtocol.instance.addFilter(ServerboundPickItemFromBlockPacket.class, this::middleClick);
TinyProtocol.instance.addFilter(ServerboundPlayerActionPacket.class, this::swapHands);
TinyProtocol.instance.addFilter(ServerboundMovePlayerPacket.Pos.class, this::movePlayerPacket);
TinyProtocol.instance.addFilter(ServerboundMovePlayerPacket.Rot.class, this::movePlayerPacket);
TinyProtocol.instance.addFilter(ServerboundMovePlayerPacket.PosRot.class, this::movePlayerPacket);
Bukkit.getScheduler().runTaskTimer(Core.getInstance(), () -> {
for (Player player : Bukkit.getOnlinePlayers()) {
if (noWand(player)) SWPlayer.of(player).removeComponent(SWEditCursor.class);
}
}, 1, 1);
}
@EventHandler
public void onPlayerInteract(PlayerInteractEvent event) {
if (noWand(event.getPlayer())) return;
event.setCancelled(true);
}
public static class SWEditCursor implements SWPlayer.Component {
private Player player;
private REntityServer entityServer = new REntityServer();
private CWireframe wireframe;
@Override
public void onMount(SWPlayer player) {
this.player = player.getPlayer();
entityServer.addPlayer(player.getPlayer());
wireframe = new CWireframe(entityServer);
}
@Override
public void onUnmount(SWPlayer player) {
entityServer.close();
}
public void setLocation(Location location) {
float distance = (float) player.getLocation().distanceSquared(location);
wireframe.setPos1And2(location, location);
float width = distance / 1600;
width = ((int) (width * 16)) / 16F;
width = Math.max(width, 1 / 16F);
width = Math.min(width, 1 / 2F);
wireframe.setWidth(width);
wireframe.setBlock(WorldEditRendererCUIEditor.Type.CURSOR.getMaterial(player).createBlockData());
}
}
private Packet<?> leftClick(Player player, Packet<?> packet) {
if (noWand(player)) return packet;
if (player.getOpenInventory().getType() != InventoryType.CRAFTING) return packet;
double distance = WorldEditRendererCUIEditor.Type.CURSOR.getWidth(player).blocks;
RayTraceResult result = player.rayTraceBlocks(distance, FluidCollisionMode.NEVER);
if (result == null) return packet;
Block block = result.getHitBlock();
if (block == null) return packet;
BlockVector3 pos1 = BlockVector3.at(block.getX(), block.getY(), block.getZ());
setPos1(player, pos1);
return null;
}
private void setPos1(Player player, BlockVector3 pos1) {
LocalSession session = we.getSession(player);
World world = session.getSelectionWorld();
if (world == null) return;
selectPos1(session.getRegionSelector(world), player, session, pos1);
}
private Packet<?> rightClick(Player player, Packet<?> packet) {
if (noWand(player)) return packet;
double distance = WorldEditRendererCUIEditor.Type.CURSOR.getWidth(player).blocks;
RayTraceResult result = player.rayTraceBlocks(distance, FluidCollisionMode.NEVER);
if (result == null) return packet;
Block block = result.getHitBlock();
if (block == null) return packet;
BlockVector3 pos2 = BlockVector3.at(block.getX(), block.getY(), block.getZ());
if (!player.isSneaking()) {
setPos2(player, pos2);
} else {
setPos2OrExpand(player, pos2);
}
return null;
}
private void setPos2(Player player, BlockVector3 pos2) {
LocalSession session = we.getSession(player);
World world = session.getSelectionWorld();
if (world == null) return;
selectPos2(session.getRegionSelector(world), player, session, pos2);
}
private void setPos2OrExpand(Player player, BlockVector3 expand) {
LocalSession session = we.getSession(player);
World world = session.getSelectionWorld();
if (world == null) return;
RegionSelector regionSelector = session.getRegionSelector(world);
// Only primary set for now
if (!regionSelector.isDefined()) {
selectPos2(session.getRegionSelector(world), player, session, expand);
return;
}
List<BlockVector3> vertices = regionSelector.getVertices();
// Probably not a cuboid selection!
if (vertices.size() > 2) {
selectPos2(session.getRegionSelector(world), player, session, expand);
return;
}
BlockVector3 pos1 = vertices.get(0);
BlockVector3 pos2 = vertices.get(1);
BlockVector3 min = pos1.getMinimum(pos2);
BlockVector3 max = pos1.getMaximum(pos2);
if (expand.containedWithin(pos1, pos2)) {
return;
}
Closer x = Closer.UNKNOWN;
Closer y = Closer.UNKNOWN;
Closer z = Closer.UNKNOWN;
boolean insideX = min.x() < expand.x() && max.x() > expand.x();
boolean insideY = min.y() < expand.y() && max.y() > expand.y();
boolean insideZ = min.z() < expand.z() && max.z() > expand.z();
if (insideX) {
// No Change needed
} else if (Math.abs(pos1.x() - expand.x()) > Math.abs(pos2.x() - expand.x())) {
x = Closer.POS2;
} else if (Math.abs(pos1.x() - expand.x()) < Math.abs(pos2.x() - expand.x())) {
x = Closer.POS1;
}
if (insideY) {
// No Change needed
} else if (Math.abs(pos1.y() - expand.y()) > Math.abs(pos2.y() - expand.y())) {
y = Closer.POS2;
} else if (Math.abs(pos1.y() - expand.y()) < Math.abs(pos2.y() - expand.y())) {
y = Closer.POS1;
}
if (insideZ) {
// No Change needed
} else if (Math.abs(pos1.z() - expand.z()) > Math.abs(pos2.z() - expand.z())) {
z = Closer.POS2;
} else if (Math.abs(pos1.z() - expand.z()) < Math.abs(pos2.z() - expand.z())) {
z = Closer.POS1;
}
Closer x1 = Closer.UNKNOWN;
Closer y1 = Closer.UNKNOWN;
Closer z1 = Closer.UNKNOWN;
if (!insideX && pos1.x() != expand.x() && pos2.x() != expand.x() && x == Closer.UNKNOWN) {
x1 = y.and(z).or(Closer.POS1);
}
if (!insideY && pos1.y() != expand.y() && pos2.y() != expand.y() && y == Closer.UNKNOWN) {
y1 = x.and(z).or(Closer.POS1);
}
if (!insideZ && pos1.z() != expand.z() && pos2.z() != expand.z() && z == Closer.UNKNOWN) {
z1 = x.and(y).or(Closer.POS1);
}
if (x1 != Closer.UNKNOWN) x = x1;
if (y1 != Closer.UNKNOWN) y = y1;
if (z1 != Closer.UNKNOWN) z = z1;
boolean p1updated = false;
boolean p2updated = false;
if (x == Closer.POS1) {
pos1 = pos1.withX(expand.x());
p1updated = true;
} else if (x == Closer.POS2) {
pos2 = pos2.withX(expand.x());
p2updated = true;
}
if (y == Closer.POS1) {
pos1 = pos1.withY(expand.y());
p1updated = true;
} else if (y == Closer.POS2) {
pos2 = pos2.withY(expand.y());
p2updated = true;
}
if (z == Closer.POS1) {
pos1 = pos1.withZ(expand.z());
p1updated = true;
} else if (z == Closer.POS2) {
pos2 = pos2.withZ(expand.z());
p2updated = true;
}
if (p1updated) selectPos1(session.getRegionSelector(world), player, session, pos1);
if (p2updated) selectPos2(session.getRegionSelector(world), player, session, pos2);
}
private enum Closer {
POS1,
POS2,
UNKNOWN,
;
public Closer and(Closer closer) {
if (this == closer) {
return this;
} else if (this == UNKNOWN) {
return closer;
} else if (closer == UNKNOWN) {
return this;
} else {
return UNKNOWN;
}
}
public Closer or(Closer closer) {
if (this == UNKNOWN) return closer;
return this;
}
}
private ServerboundPickItemFromBlockPacket middleClick(Player player, ServerboundPickItemFromBlockPacket packet) {
if (noWand(player)) return packet;
LocalSession session = we.getSession(player);
World world = session.getSelectionWorld();
if (world == null) return packet;
RegionSelector regionSelector = session.getRegionSelector(world);
BlockPos pos = packet.pos();
SteamEditSelectAdjacent selector = new SteamEditSelectAdjacent(new Location(player.getWorld(), pos.getX(), pos.getY(), pos.getZ()));
selectPos1(regionSelector, player, session, selector.pos1());
selectPos2(regionSelector, player, session, selector.pos2());
return null;
}
private static class Pos1Message implements SWPlayer.Component {
private BukkitTask task;
public Pos1Message(RegionSelector regionSelector, Player player, LocalSession session, BlockVector3 pos1) {
task = Bukkit.getScheduler().runTaskLater(Core.getInstance(), () -> {
regionSelector.explainPrimarySelection(BukkitAdapter.adapt(player), session, pos1);
SWPlayer.of(player).removeComponent(Pos1Message.class);
}, 1);
}
@Override
public void onUnmount(SWPlayer player) {
task.cancel();
}
}
private void selectPos1(RegionSelector regionSelector, Player player, LocalSession session, BlockVector3 pos1) {
if (!pos1.equals(regionSelector.getPrimaryPosition())) {
SWPlayer.of(player).setComponent(new Pos1Message(regionSelector, player, session, pos1));
}
regionSelector.selectPrimary(pos1, PermissiveSelectorLimits.getInstance());
WorldEditRenderer.renderPlayer(player);
}
private static class Pos2Message implements SWPlayer.Component {
private BukkitTask task;
public Pos2Message(RegionSelector regionSelector, Player player, LocalSession session, BlockVector3 pos2) {
task = Bukkit.getScheduler().runTaskLater(Core.getInstance(), () -> {
regionSelector.explainSecondarySelection(BukkitAdapter.adapt(player), session, pos2);
SWPlayer.of(player).removeComponent(Pos1Message.class);
}, 1);
}
@Override
public void onUnmount(SWPlayer player) {
task.cancel();
}
}
private void selectPos2(RegionSelector regionSelector, Player player, LocalSession session, BlockVector3 pos2) {
if (regionSelector.getVertices().size() == 2 && !pos2.equals(regionSelector.getVertices().get(1))) {
SWPlayer.of(player).setComponent(new Pos2Message(regionSelector, player, session, pos2));
}
regionSelector.selectSecondary(pos2, PermissiveSelectorLimits.getInstance());
WorldEditRenderer.renderPlayer(player);
}
private ServerboundPlayerActionPacket swapHands(Player player, ServerboundPlayerActionPacket packet) {
if (noWand(player)) return packet;
if (packet.getAction() != ServerboundPlayerActionPacket.Action.SWAP_ITEM_WITH_OFFHAND) return packet;
Bukkit.getScheduler().runTask(Core.getInstance(), () -> {
WorldEditRendererCUIEditor.open(player);
});
return null;
}
public ServerboundMovePlayerPacket movePlayerPacket(Player player, ServerboundMovePlayerPacket packet) {
SWPlayer swPlayer = SWPlayer.of(player);
if (noWand(player)) {
swPlayer.removeComponent(SWEditCursor.class);
return packet;
}
double distance = WorldEditRendererCUIEditor.Type.CURSOR.getWidth(player).blocks;
RayTraceResult result = player.rayTraceBlocks(distance, FluidCollisionMode.NEVER);
Block block = result == null ? null : result.getHitBlock();
if (result == null || block == null) {
swPlayer.removeComponent(SWEditCursor.class);
return packet;
}
if (!swPlayer.hasComponent(SWEditCursor.class)) {
swPlayer.setComponent(new SWEditCursor());
}
swPlayer.getComponent(SWEditCursor.class).ifPresent(cursor -> {
cursor.setLocation(block.getLocation());
});
return packet;
}
}
@@ -1,7 +1,7 @@
/*
* This file is a part of the SteamWar software.
*
* Copyright (C) 2025 SteamWar.de-Serverteam
* 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
@@ -17,7 +17,7 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package de.steamwar.core;
package de.steamwar.core.worldedit;
import com.sk89q.worldedit.EmptyClipboardException;
import com.sk89q.worldedit.IncompleteRegionException;
@@ -28,6 +28,7 @@ import com.sk89q.worldedit.math.transform.Transform;
import com.sk89q.worldedit.regions.Region;
import com.sk89q.worldedit.regions.RegionSelector;
import com.sk89q.worldedit.world.World;
import de.steamwar.core.Core;
import de.steamwar.linkage.Linked;
import de.steamwar.linkage.PluginCheck;
import org.bukkit.Bukkit;
@@ -39,13 +40,15 @@ import org.bukkit.event.block.BlockBreakEvent;
import org.bukkit.event.player.*;
import org.bukkit.util.Vector;
import java.util.Set;
@Linked
@PluginCheck("FastAsyncWorldEdit")
public class WorldEditRenderer implements Listener {
private static WorldEditRenderer INSTANCE;
private static final Material WAND = Material.WOODEN_AXE;
private static final Set<Material> WANDS = Set.of(Material.WOODEN_AXE, SteamEditWand.MATERIAL);
private final WorldEditPlugin we;
@@ -102,7 +105,7 @@ public class WorldEditRenderer implements Listener {
private void drawCuboid(Vector min, Vector max, boolean scheduled, boolean clipboard, Player owner) {
//noinspection deprecation
if (owner.getItemInHand().getType() != WAND) {
if (!WANDS.contains(owner.getItemInHand().getType())) {
WorldEditRendererWrapper.impl.hide(owner, true, true);
WorldEditRendererWrapper.impl.hide(owner, false, true);
} else {
@@ -114,7 +117,8 @@ public class WorldEditRenderer implements Listener {
@EventHandler
public void onPlayerMove(PlayerMoveEvent event) {
if (event.getPlayer().getItemInHand().getType() == WAND) {
//noinspection deprecation
if (WANDS.contains(event.getPlayer().getItemInHand().getType())) {
WorldEditRendererWrapper.impl.tick(event.getPlayer());
}
renderClipboard(event.getPlayer(), we.getSession(event.getPlayer()), false);
@@ -1,7 +1,7 @@
/*
* This file is a part of the SteamWar software.
*
* Copyright (C) 2025 SteamWar.de-Serverteam
* 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
@@ -17,9 +17,11 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package de.steamwar.core;
package de.steamwar.core.worldedit;
import de.steamwar.command.SWCommand;
import de.steamwar.core.CRIUSleepEvent;
import de.steamwar.core.Core;
import de.steamwar.inventory.SWInventory;
import de.steamwar.inventory.SWItem;
import de.steamwar.sql.UserConfig;
@@ -30,6 +32,7 @@ import org.bukkit.block.data.type.Light;
import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
import org.bukkit.event.Listener;
import org.bukkit.event.inventory.ClickType;
import org.bukkit.event.player.PlayerQuitEvent;
import org.bukkit.inventory.meta.BlockDataMeta;
import org.bukkit.inventory.meta.ItemMeta;
@@ -59,6 +62,7 @@ public class WorldEditRendererCUIEditor implements Listener {
public enum Type {
SELECTION("cui_selection_material", "cui_selection_width", Material.PURPLE_CONCRETE, Width.MEDIUM),
CLIPBOARD("cui_clipboard_material", "cui_clipboard_width", Material.LIME_CONCRETE, Width.SLIM),
CURSOR("cui_cursor_material", "cui_cursor_range", Material.RED_CONCRETE, Width.SLIM),
;
private final Map<Player, Material> materialCache = new HashMap<>();
@@ -104,19 +108,27 @@ public class WorldEditRendererCUIEditor implements Listener {
@AllArgsConstructor
public enum Width {
HUGE(15, "WORLDEDIT_CUI_WIDTH_HUGE", 2 / 16f),
LARGE(8, "WORLDEDIT_CUI_WIDTH_LARGE", 1 / 16f),
MEDIUM(4, "WORLDEDIT_CUI_WIDTH_MEDIUM", 1 / 32f),
SLIM(0, "WORLDEDIT_CUI_WIDTH_SLIM", 1 / 64f);
HUGE(15, "WORLDEDIT_CUI_WIDTH_HUGE", 1 / 8f, 40),
LARGE(8, "WORLDEDIT_CUI_WIDTH_LARGE", 1 / 16f, 30),
MEDIUM(4, "WORLDEDIT_CUI_WIDTH_MEDIUM", 1 / 32f, 20),
SLIM(0, "WORLDEDIT_CUI_WIDTH_SLIM", 1 / 64f, 10);
public final int lightLevel;
public final String name;
public final float value;
public final float width;
public final int blocks;
}
private static Command command = null;
public WorldEditRendererCUIEditor() {
Bukkit.getPluginManager().registerEvents(this, Core.getInstance());
new Command();
command = new Command();
}
public static void open(Player player) {
if (command == null) return;
command.cuiEditor(player);
}
private static class Command extends SWCommand {
@@ -128,8 +140,30 @@ public class WorldEditRendererCUIEditor implements Listener {
@Register
public void cuiEditor(Player player) {
SWInventory inv = new SWInventory(player, 9 * 2, Core.MESSAGE.parse("WORLDEDIT_CUI_TITLE", player));
setElement(inv, player, 3, "WORLDEDIT_CUI_SELECTION", Type.SELECTION);
setElement(inv, player, 5, "WORLDEDIT_CUI_CLIPBOARD", Type.CLIPBOARD);
if (SteamEdit.isInitialized()) {
setElement(inv, player, 1, "WORLDEDIT_CUI_SELECTION", Type.SELECTION);
setElement(inv, player, 2, "WORLDEDIT_CUI_CLIPBOARD", Type.CLIPBOARD);
setCursor(inv, player, 3, "WORLDEDIT_CUI_CURSOR");
inv.setItem(5, new SWItem(Material.PAPER, "WORLDEDIT_CUI_TOOL_COPY").setLore("WORLDEDIT_CUI_TOOL_COPY_LORE").translate(player, Core.MESSAGE).getItemStack(), click -> {
player.performCommand("/copy");
});
inv.setItem(6, new SWItem(Material.STRUCTURE_VOID, "WORLDEDIT_CUI_TOOL_SOON").translate(player, Core.MESSAGE).getItemStack(), click -> {});
inv.setItem(7, new SWItem(Material.GRASS_BLOCK, "WORLDEDIT_CUI_TOOL_PASTE").setLore("WORLDEDIT_CUI_TOOL_PASTE_LORE_1", "WORLDEDIT_CUI_TOOL_PASTE_LORE_2", "WORLDEDIT_CUI_TOOL_PASTE_LORE_3").translate(player, Core.MESSAGE).getItemStack(), click -> {
if (click == ClickType.DROP) {
player.performCommand("/paste -o");
} else if (click == ClickType.SHIFT_LEFT || click == ClickType.SHIFT_RIGHT) {
player.performCommand("/paste -s");
} else if (click == ClickType.LEFT || click == ClickType.RIGHT) {
player.performCommand("/paste");
}
});
inv.setItem(14, new SWItem(Material.STRUCTURE_VOID, "WORLDEDIT_CUI_TOOL_SOON").translate(player, Core.MESSAGE).getItemStack(), click -> {});
inv.setItem(15, new SWItem(Material.STRUCTURE_VOID, "WORLDEDIT_CUI_TOOL_SOON").translate(player, Core.MESSAGE).getItemStack(), click -> {});
inv.setItem(16, new SWItem(Material.STRUCTURE_VOID, "WORLDEDIT_CUI_TOOL_SOON").translate(player, Core.MESSAGE).getItemStack(), click -> {});
} else {
setElement(inv, player, 3, "WORLDEDIT_CUI_SELECTION", Type.SELECTION);
setElement(inv, player, 5, "WORLDEDIT_CUI_CLIPBOARD", Type.CLIPBOARD);
}
inv.open();
}
@@ -163,6 +197,31 @@ public class WorldEditRendererCUIEditor implements Listener {
inv.setItem(index + 9, lightItem);
}
private void setCursor(SWInventory inv, Player player, int index, String uiName) {
Material material = Type.CURSOR.getMaterial(player);
Width width = Type.CURSOR.getWidth(player);
inv.setItem(index, new SWItem(material, Core.MESSAGE.parse("WORLDEDIT_CUI_MATERIAL_NAME", player, Core.MESSAGE.parse(uiName, player)), Arrays.asList(Core.MESSAGE.parse("WORLDEDIT_CUI_MATERIAL_CLICK", player)), false, click -> {
cuiMaterial(player, uiName, Type.CURSOR, material);
}));
List<String> lore = new ArrayList<>();
lore.add(Core.MESSAGE.parse("WORLDEDIT_CUI_WIDTH_CLICK", player));
lore.add("");
for (Width value : Width.values()) {
if (value == width) {
lore.add(Core.MESSAGE.parse("WORLDEDIT_CUI_WIDTH_LORE_SELECTED", player, Core.MESSAGE.parse(value.name + "_DIST", player)));
} else {
lore.add(Core.MESSAGE.parse("WORLDEDIT_CUI_WIDTH_LORE", player, Core.MESSAGE.parse(value.name + "_DIST", player)));
}
}
SWItem stickItem = new SWItem(Material.STICK, Core.MESSAGE.parse("WORLDEDIT_CUI_WIDTH_NAME_DIST", player, Core.MESSAGE.parse(uiName, player)), lore, false, click -> {
Type.CURSOR.setWidth(player, Width.values()[(width.ordinal() + 1) % Width.values().length]);
setCursor(inv, player, index, uiName);
}).setCustomModelData(1);
inv.setItem(index + 9, stickItem);
}
private final Material[] materials = {Material.WHITE_CONCRETE, Material.LIGHT_GRAY_CONCRETE, Material.GRAY_CONCRETE, Material.BLACK_CONCRETE, Material.BROWN_CONCRETE, Material.RED_CONCRETE, Material.ORANGE_CONCRETE, Material.YELLOW_CONCRETE, Material.LIME_CONCRETE, Material.GREEN_CONCRETE, Material.CYAN_CONCRETE, Material.LIGHT_BLUE_CONCRETE, Material.BLUE_CONCRETE, Material.PURPLE_CONCRETE, Material.MAGENTA_CONCRETE, Material.PINK_CONCRETE, null, Material.BARRIER};
private void cuiMaterial(Player player, String subMenu, Type type, Material currentSelection) {
@@ -17,7 +17,7 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package de.steamwar.core;
package de.steamwar.core.worldedit;
import org.bukkit.Location;
import org.bukkit.Particle;
@@ -1,7 +1,7 @@
/*
* This file is a part of the SteamWar software.
*
* Copyright (C) 2025 SteamWar.de-Serverteam
* 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
@@ -17,8 +17,9 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package de.steamwar.core;
package de.steamwar.core.worldedit;
import de.steamwar.core.PlayerVersion;
import de.steamwar.entity.CWireframe;
import de.steamwar.entity.REntityServer;
import org.bukkit.Material;
@@ -82,7 +83,7 @@ public class WorldEditRendererWrapper {
});
WorldEditRendererCUIEditor.Type type = clipboard ? WorldEditRendererCUIEditor.Type.CLIPBOARD : WorldEditRendererCUIEditor.Type.SELECTION;
float width = type.getWidth(player).value;
float width = type.getWidth(player).width;
Material material = type.getMaterial(player);
if (material == Material.BARRIER) {
hide(player, clipboard, true);
@@ -1,7 +1,7 @@
/*
* This file is a part of the SteamWar software.
*
* Copyright (C) 2025 SteamWar.de-Serverteam
* 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
@@ -17,7 +17,7 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package de.steamwar.core;
package de.steamwar.core.worldedit;
import com.fastasyncworldedit.core.extent.clipboard.io.FastSchematicReaderV2;
import com.fastasyncworldedit.core.extent.clipboard.io.FastSchematicReaderV3;
@@ -34,6 +34,7 @@ import com.sk89q.worldedit.math.Vector3;
import com.sk89q.worldedit.math.transform.Transform;
import com.sk89q.worldedit.regions.Region;
import com.sk89q.worldedit.session.ClipboardHolder;
import de.steamwar.core.Core;
import de.steamwar.sql.NoClipboardException;
import de.steamwar.sql.NodeData;
import org.bukkit.Bukkit;
@@ -22,10 +22,12 @@ package de.steamwar.inventory;
import com.destroystokyo.paper.profile.PlayerProfile;
import com.google.gson.JsonArray;
import com.google.gson.JsonObject;
import de.steamwar.message.Message;
import org.bukkit.Bukkit;
import org.bukkit.Material;
import org.bukkit.OfflinePlayer;
import org.bukkit.enchantments.Enchantment;
import org.bukkit.entity.Player;
import org.bukkit.inventory.ItemFlag;
import org.bukkit.inventory.ItemStack;
import org.bukkit.inventory.meta.ItemMeta;
@@ -225,4 +227,14 @@ public class SWItem {
itemStack.setAmount(count);
return this;
}
public SWItem translate(Player player, Message message) {
setName(message.parse(itemMeta.getDisplayName(), player));
if (itemMeta.hasLore()) {
setLore(itemMeta.getLore().stream()
.map(s -> message.parse(s, player))
.toList());
}
return this;
}
}
@@ -20,7 +20,7 @@
package de.steamwar.sql;
import com.sk89q.worldedit.extent.clipboard.Clipboard;
import de.steamwar.core.WorldEditWrapper;
import de.steamwar.core.worldedit.WorldEditWrapper;
import org.bukkit.entity.Player;
import java.io.ByteArrayInputStream;
@@ -19,7 +19,8 @@
package de.steamwar.teamserver;
import de.steamwar.core.WorldEditRendererCUIEditor;
import de.steamwar.core.worldedit.SteamEdit;
import de.steamwar.core.worldedit.WorldEditRendererCUIEditor;
import de.steamwar.linkage.AbstractLinker;
import de.steamwar.linkage.SpigotLinker;
import de.steamwar.message.Message;
@@ -51,7 +52,7 @@ public final class Builder extends JavaPlugin {
}
Bukkit.getWorlds().get(0).setGameRule(GameRule.REDUCED_DEBUG_INFO, false);
new WorldEditRendererCUIEditor();
SteamEdit.init();
}
@Override