From 6b8f791497eed3b4d05666669ea2640f5b78b7a0 Mon Sep 17 00:00:00 2001 From: YoyoNow Date: Thu, 6 Nov 2025 21:38:24 +0100 Subject: [PATCH] Add SWPlayer for per player storage and without Memory Leaks --- .../features/countingwand/Countingwand.java | 99 ++++------ .../countingwand/CountingwandListener.java | 6 - .../src/de/steamwar/core/SWPlayer.java | 183 ++++++++++++++++++ 3 files changed, 222 insertions(+), 66 deletions(-) create mode 100644 SpigotCore/SpigotCore_Main/src/de/steamwar/core/SWPlayer.java diff --git a/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/countingwand/Countingwand.java b/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/countingwand/Countingwand.java index bcd13a7f..0e222021 100644 --- a/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/countingwand/Countingwand.java +++ b/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/countingwand/Countingwand.java @@ -21,9 +21,10 @@ package de.steamwar.bausystem.features.countingwand; import de.steamwar.bausystem.BauSystem; import de.steamwar.bausystem.region.Point; -import de.steamwar.bausystem.shared.Pair; import de.steamwar.bausystem.utils.ItemUtils; +import de.steamwar.core.SWPlayer; import de.steamwar.inventory.SWItem; +import lombok.Data; import lombok.experimental.UtilityClass; import org.bukkit.Material; import org.bukkit.entity.Player; @@ -31,13 +32,32 @@ import org.bukkit.inventory.ItemStack; import org.bukkit.inventory.meta.ItemMeta; import java.util.Arrays; -import java.util.HashMap; -import java.util.Map; - @UtilityClass public class Countingwand { + @Data + public class CountingWandComponent implements SWPlayer.Component { + private Point pos1; + private Point pos2; + + public boolean set(boolean pos1, Point point) { + if (this.pos1 == null || this.pos2 == null) { + this.pos1 = point; + this.pos2 = point; + return true; + } + Point current = pos1 ? this.pos1 : this.pos2; + if (current.equals(point)) return false; + if (pos1) { + this.pos1 = point; + } else { + this.pos2 = point; + } + return true; + } + } + public static ItemStack getWandItem(Player player) { ItemStack itemStack = new SWItem(Material.STICK, BauSystem.MESSAGE.parse("COUNTINGWAND_ITEM_NAME", player), Arrays.asList(BauSystem.MESSAGE.parse("COUNTINGWAND_ITEM_LORE1", player), BauSystem.MESSAGE.parse("COUNTINGWAND_ITEM_LORE2", player)), false, null).getItemStack(); ItemUtils.setItem(itemStack, "countingwand"); @@ -47,61 +67,20 @@ public class Countingwand { return itemStack; } - private final Map> selections = new HashMap<>(); + public boolean isCountingwand(ItemStack itemStack) { + return ItemUtils.isItem(itemStack, "countingwand"); + } - public boolean isCountingwand(ItemStack itemStack) { - return ItemUtils.isItem(itemStack, "countingwand"); - } - - public void checkSelection(final Point point, final boolean pos1, final Player p) { - Pair selection = selections.get(p.getUniqueId().toString()); - final boolean newPos; - if (selection != null) { - if (pos1) { - newPos = !point.equals(selection.setKey(point)); - } else { - newPos = !point.equals(selection.setValue(point)); - } - } else { - if (pos1) { - selection = new Pair<>(point, null); - } else { - selection = new Pair<>(null, point); - } - selections.put(p.getUniqueId().toString(), selection); - newPos = true; - } - - if (newPos) { - String dimension = getDimensions(p, selection.getKey(), selection.getValue()); - String volume = getVolume(p, selection.getKey(), selection.getValue()); - if (pos1) { - BauSystem.MESSAGE.send("COUNTINGWAND_MESSAGE_RCLICK", p, point.getX(), point.getY(), point.getZ(), dimension, volume); - } else { - BauSystem.MESSAGE.send("COUNTINGWAND_MESSAGE_LCLICK", p, point.getX(), point.getY(), point.getZ(), dimension, volume); - } - } - } - - public void removePlayer(Player p) { - selections.remove(p.getUniqueId().toString()); - } - - public String getVolume(Player player, final Point point1, final Point point2) { - if (point1 == null || point2 == null) { - return BauSystem.MESSAGE.parse("COUNTINGWAND_MESSAGE_VOLUME", player, 0); - } - return BauSystem.MESSAGE.parse("COUNTINGWAND_MESSAGE_VOLUME", player, getAmount(point1, point2)); - } - - public String getDimensions(Player player, final Point point1, final Point point2) { - if (point1 == null || point2 == null) { - return BauSystem.MESSAGE.parse("COUNTINGWAND_MESSAGE_DIMENSION", player, 0, 0, 0); - } - return BauSystem.MESSAGE.parse("COUNTINGWAND_MESSAGE_DIMENSION", player, Math.abs(point1.getX() - point2.getX()) + 1, Math.abs(point1.getY() - point2.getY()) + 1, Math.abs(point1.getZ() - point2.getZ()) + 1); - } - - public int getAmount(final Point point1, final Point point2) { - return (Math.abs(point1.getX() - point2.getX()) + 1) * (Math.abs(point1.getY() - point2.getY()) + 1) * (Math.abs(point1.getZ() - point2.getZ()) + 1); - } + public void checkSelection(final Point point, final boolean pos1, final Player p) { + SWPlayer player = SWPlayer.of(p); + CountingWandComponent countingWandComponent = player.getComponentOrDefault(CountingWandComponent.class, CountingWandComponent::new); + if (countingWandComponent.set(pos1, point)) { + Point point1 = countingWandComponent.pos1; + Point point2 = countingWandComponent.pos2; + int amount = (Math.abs(point1.getX() - point2.getX()) + 1) * (Math.abs(point1.getY() - point2.getY()) + 1) * (Math.abs(point1.getZ() - point2.getZ()) + 1); + String dimension = player.using(BauSystem.MESSAGE).parse("COUNTINGWAND_MESSAGE_VOLUME", amount); + String volume = player.parse("COUNTINGWAND_MESSAGE_DIMENSION", Math.abs(point1.getX() - point2.getX()) + 1, Math.abs(point1.getY() - point2.getY()) + 1, Math.abs(point1.getZ() - point2.getZ()) + 1); + player.sendMessage(pos1 ? "COUNTINGWAND_MESSAGE_RCLICK" : "COUNTINGWAND_MESSAGE_LCLICK", point.getX(), point.getY(), point.getZ(), dimension, volume); + } + } } \ No newline at end of file diff --git a/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/countingwand/CountingwandListener.java b/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/countingwand/CountingwandListener.java index 127bb964..9daab3f0 100644 --- a/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/countingwand/CountingwandListener.java +++ b/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/countingwand/CountingwandListener.java @@ -27,7 +27,6 @@ import org.bukkit.event.Listener; import org.bukkit.event.block.Action; import org.bukkit.event.block.BlockBreakEvent; import org.bukkit.event.player.PlayerInteractEvent; -import org.bukkit.event.player.PlayerQuitEvent; import org.bukkit.util.RayTraceResult; import java.util.Objects; @@ -67,9 +66,4 @@ public class CountingwandListener implements Listener { event.setCancelled(true); Countingwand.checkSelection(Point.fromLocation(Objects.requireNonNull(event.getClickedBlock()).getLocation()), false, event.getPlayer()); } - - @EventHandler - public void onLeave(PlayerQuitEvent event) { - Countingwand.removePlayer(event.getPlayer()); - } } \ No newline at end of file diff --git a/SpigotCore/SpigotCore_Main/src/de/steamwar/core/SWPlayer.java b/SpigotCore/SpigotCore_Main/src/de/steamwar/core/SWPlayer.java new file mode 100644 index 00000000..12a2aa0c --- /dev/null +++ b/SpigotCore/SpigotCore_Main/src/de/steamwar/core/SWPlayer.java @@ -0,0 +1,183 @@ +/* + * 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 . + */ + +package de.steamwar.core; + +import de.steamwar.message.Message; +import lombok.Getter; +import lombok.NonNull; +import lombok.experimental.Delegate; +import net.md_5.bungee.api.ChatMessageType; +import net.md_5.bungee.api.chat.ClickEvent; +import org.bukkit.Bukkit; +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.PlayerJoinEvent; +import org.bukkit.event.player.PlayerQuitEvent; + +import java.util.HashMap; +import java.util.Map; +import java.util.Optional; +import java.util.function.Supplier; +import java.util.stream.Stream; + +public class SWPlayer { + + private static final Map players = new HashMap<>(); + + private static class SWPlayerListener implements Listener { + + @EventHandler(priority = EventPriority.LOWEST, ignoreCancelled = true) + public void onPlayerJoin(PlayerJoinEvent event) { + SWPlayer.of(event.getPlayer()); + } + + @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true) + public void onPlayerQuit(PlayerQuitEvent event) { + SWPlayer swPlayer = players.remove(event.getPlayer()); + swPlayer.components.forEach((aClass, component) -> { + component.onUnmount(swPlayer); + }); + } + + @EventHandler + public void onCRIUSleep(CRIUSleepEvent event) { + for (SWPlayer value : players.values()) { + value.components.forEach((aClass, component) -> { + component.onUnmount(value); + }); + } + players.clear(); + } + } + + public interface Component { + + default void onMount(SWPlayer player) { + } + + default void onUnmount(SWPlayer player) { + } + } + + static { + Bukkit.getPluginManager().registerEvents(new SWPlayerListener(), Core.getInstance()); + } + + public static @NonNull SWPlayer of(@NonNull Player player) { + return players.computeIfAbsent(player, SWPlayer::new); + } + + public static @NonNull Stream all() { + return players.values().stream(); + } + + @SafeVarargs + public static @NonNull Stream allWithComponent(Class component, Class... components) { + Stream stream = players.values().stream() + .filter(player -> player.components.containsKey(component)); + if (components != null) { + for (Class comp : components) { + stream = stream.filter(player -> player.components.containsKey(comp)); + } + } + return stream; + } + + @Delegate + @Getter + private final Player player; + private final Map, Component> components = new HashMap<>(); + + private SWPlayer(@NonNull Player player) { + this.player = player; + } + + public boolean hasComponent(@NonNull Class component) { + return components.containsKey(component); + } + + public @NonNull Optional getComponent(@NonNull Class clazz) { + return Optional.ofNullable((T) components.get(clazz)); + } + + public @NonNull T getComponentOrDefault(@NonNull Class clazz, @NonNull Supplier defaultValue) { + T value = (T) components.get(clazz); + if (value != null) return value; + value = defaultValue.get(); + setComponent(value); + return value; + } + + public SWPlayer setComponent(@NonNull T value) { + Component component = components.put(value.getClass(), value); + if (component != null) component.onUnmount(this); + value.onMount(this); + return this; + } + + public SWPlayer removeComponent(@NonNull Class clazz) { + Component component = components.remove(clazz); + if (component != null) component.onUnmount(this); + return this; + } + + private ThreadLocal messageThreadLocal = ThreadLocal.withInitial(() -> null); + + public SWPlayer using(Message message) { + this.messageThreadLocal.set(message); + return this; + } + + private Message getMessage() { + Message message = this.messageThreadLocal.get(); + if (message == null) throw new IllegalStateException("Use #using(Message) before sending or parsing a message!"); + return message; + } + + public void sendMessage(String message, Object... params) { + getMessage().send(message, player, ChatMessageType.SYSTEM, params); + } + + public void sendMessagePrefixless(String message, Object... params) { + getMessage().sendPrefixless(message, player, ChatMessageType.SYSTEM, params); + } + + public void sendActionBar(String message, Object... params) { + getMessage().sendPrefixless(message, player, ChatMessageType.ACTION_BAR, params); + } + + public void sendMessage(String message, String onHover, ClickEvent onClick, Object... params) { + getMessage().send(message, true, player, ChatMessageType.SYSTEM, onHover, onClick, params); + } + + public void sendMessagePrefixless(String message, String onHover, ClickEvent onClick, Object... params) { + getMessage().send(message, false, player, ChatMessageType.SYSTEM, onHover, onClick, params); + } + + public String parsePrefixed(String message, Object... params) { + return getMessage().parsePrefixed(message, player, params); + } + + public String parse(String message, Object... params) { + return getMessage().parse(message, player, params); + } +}