From 1dc78b8eb866ffabdb5bba2c55f4e677b3258b73 Mon Sep 17 00:00:00 2001 From: YoyoNow Date: Sun, 31 May 2026 00:44:56 +0200 Subject: [PATCH 01/36] Add RedstoneEngineCommand --- .../RedstoneEngineCommand.java | 130 ++++++++++++++++++ 1 file changed, 130 insertions(+) create mode 100644 BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/experimental/redstone_engine/RedstoneEngineCommand.java diff --git a/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/experimental/redstone_engine/RedstoneEngineCommand.java b/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/experimental/redstone_engine/RedstoneEngineCommand.java new file mode 100644 index 00000000..40c8ff08 --- /dev/null +++ b/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/experimental/redstone_engine/RedstoneEngineCommand.java @@ -0,0 +1,130 @@ +/* + * 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.features.experimental.redstone_engine; + +import de.steamwar.bausystem.region.Region; +import de.steamwar.bausystem.utils.ScoreboardElement; +import de.steamwar.command.SWCommand; +import de.steamwar.linkage.Linked; +import io.papermc.paper.configuration.WorldConfiguration; +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.format.NamedTextColor; +import net.kyori.adventure.title.TitlePart; +import org.bukkit.Bukkit; +import org.bukkit.craftbukkit.CraftWorld; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.Listener; +import org.bukkit.event.player.PlayerJoinEvent; + +import java.util.Collection; +import java.util.List; + +@Linked +public class RedstoneEngineCommand extends SWCommand implements Listener, ScoreboardElement { + + public RedstoneEngineCommand() { + super("redstoneengine"); + } + + private WorldConfiguration.Misc getConfig() { + return ((CraftWorld) Bukkit.getWorlds().get(0)).getHandle().paperConfig().misc; + } + + @Register + public void setRedstoneEngine(Player player, @StaticValue("alternate_current") String __, WorldConfiguration.Misc.AlternateCurrentUpdateOrder updateOrder) { + WorldConfiguration.Misc misc = getConfig(); + misc.redstoneImplementation = WorldConfiguration.Misc.RedstoneImplementation.ALTERNATE_CURRENT; + misc.alternateCurrentUpdateOrder = updateOrder; + broadcastTitle(Bukkit.getOnlinePlayers()); + } + + @Register + public void setRedstoneEngine(Player player, WorldConfiguration.Misc.RedstoneImplementation implementation) { + getConfig().redstoneImplementation = implementation; + broadcastTitle(Bukkit.getOnlinePlayers()); + } + + @EventHandler + public void onPlayerJoin(PlayerJoinEvent event) { + WorldConfiguration.Misc misc = getConfig(); + if (misc.redstoneImplementation != WorldConfiguration.Misc.RedstoneImplementation.EIGENCRAFT) { + broadcastTitle(List.of(event.getPlayer())); + } + } + + private void broadcastTitle(Collection players) { + WorldConfiguration.Misc misc = getConfig(); + Component title = switch (misc.redstoneImplementation) { + case VANILLA -> Component.text("⚠").color(NamedTextColor.RED).append(Component.text(" Redstone: Vanilla ").color(NamedTextColor.WHITE)).append(Component.text("⚠").color(NamedTextColor.RED)); + case EIGENCRAFT -> Component.text("Redstone: Eigencraft"); + case ALTERNATE_CURRENT -> Component.text("⚠").color(NamedTextColor.RED).append(Component.text(" Redstone: AC ").color(NamedTextColor.WHITE)).append(Component.text("⚠").color(NamedTextColor.RED)); + }; + Component subtitle; + if (misc.redstoneImplementation != WorldConfiguration.Misc.RedstoneImplementation.ALTERNATE_CURRENT) { + subtitle = Component.text().build(); + } else { + subtitle = switch (misc.alternateCurrentUpdateOrder) { + case VERTICAL_FIRST_INWARD -> Component.text("Y first inwards"); // Y before XZ + case VERTICAL_FIRST_OUTWARD -> Component.text("Y first outwards"); // XZ before Y + case HORIZONTAL_FIRST_INWARD -> Component.text("XZ first inwards"); // Y before XZ + case HORIZONTAL_FIRST_OUTWARD -> Component.text("XZ first outwards"); // XZ before Y + }; + } + players.forEach(player -> { + player.sendTitlePart(TitlePart.TITLE, title); + player.sendTitlePart(TitlePart.SUBTITLE, subtitle); + }); + } + + @Override + public ScoreboardGroup getGroup() { + return ScoreboardGroup.FOOTER; + } + + @Override + public int order() { + return Integer.MAX_VALUE; + } + + @Override + public String get(Region region, Player p) { + WorldConfiguration.Misc misc = getConfig(); + switch (misc.redstoneImplementation) { + case ALTERNATE_CURRENT: + switch (misc.alternateCurrentUpdateOrder) { + case VERTICAL_FIRST_INWARD: + return "§eRedstone§8: §cAC §8(§7Y in§8)"; + case VERTICAL_FIRST_OUTWARD: + return "§eRedstone§8: §cAC §8(§7Y out§8)"; + case HORIZONTAL_FIRST_INWARD: + return "§eRedstone§8: §cAC §8(§7XZ in§8)"; + case HORIZONTAL_FIRST_OUTWARD: + return "§eRedstone§8: §cAC §8(§7XZ out§8)"; + } + return "§eRedstone§8: §cAC"; + case EIGENCRAFT: + return null; + case VANILLA: + default: + return "§eRedstone§8: §cVanilla"; + } + } +} From bc0177df43fd4c424647328cd66df33b2c4761bf Mon Sep 17 00:00:00 2001 From: YoyoNow Date: Sun, 31 May 2026 00:56:05 +0200 Subject: [PATCH 02/36] Add ExperimentalCommand --- .../experimental/ExperimentalCommand.java | 31 +++++++++++++++++++ ...EngineCommand.java => RedstoneEngine.java} | 16 +++++----- 2 files changed, 40 insertions(+), 7 deletions(-) create mode 100644 BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/experimental/ExperimentalCommand.java rename BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/experimental/redstone_engine/{RedstoneEngineCommand.java => RedstoneEngine.java} (93%) diff --git a/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/experimental/ExperimentalCommand.java b/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/experimental/ExperimentalCommand.java new file mode 100644 index 00000000..a765a351 --- /dev/null +++ b/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/experimental/ExperimentalCommand.java @@ -0,0 +1,31 @@ +/* + * 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.features.experimental; + +import de.steamwar.command.SWCommand; +import de.steamwar.linkage.Linked; + +@Linked +public class ExperimentalCommand extends SWCommand { + + public ExperimentalCommand() { + super("experimental", "experiment"); + } +} diff --git a/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/experimental/redstone_engine/RedstoneEngineCommand.java b/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/experimental/redstone_engine/RedstoneEngine.java similarity index 93% rename from BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/experimental/redstone_engine/RedstoneEngineCommand.java rename to BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/experimental/redstone_engine/RedstoneEngine.java index 40c8ff08..8dc99349 100644 --- a/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/experimental/redstone_engine/RedstoneEngineCommand.java +++ b/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/experimental/redstone_engine/RedstoneEngine.java @@ -21,8 +21,8 @@ package de.steamwar.bausystem.features.experimental.redstone_engine; import de.steamwar.bausystem.region.Region; import de.steamwar.bausystem.utils.ScoreboardElement; +import de.steamwar.command.AbstractSWCommand; import de.steamwar.command.SWCommand; -import de.steamwar.linkage.Linked; import io.papermc.paper.configuration.WorldConfiguration; import net.kyori.adventure.text.Component; import net.kyori.adventure.text.format.NamedTextColor; @@ -37,18 +37,19 @@ import org.bukkit.event.player.PlayerJoinEvent; import java.util.Collection; import java.util.List; -@Linked -public class RedstoneEngineCommand extends SWCommand implements Listener, ScoreboardElement { +@AbstractSWCommand.PartOf(RedstoneEngine.class) +public class RedstoneEngine extends SWCommand implements Listener, ScoreboardElement { - public RedstoneEngineCommand() { - super("redstoneengine"); + public RedstoneEngine() { + super(""); } private WorldConfiguration.Misc getConfig() { return ((CraftWorld) Bukkit.getWorlds().get(0)).getHandle().paperConfig().misc; } - @Register + @Register("redstone") + @Register("redstoneengine") public void setRedstoneEngine(Player player, @StaticValue("alternate_current") String __, WorldConfiguration.Misc.AlternateCurrentUpdateOrder updateOrder) { WorldConfiguration.Misc misc = getConfig(); misc.redstoneImplementation = WorldConfiguration.Misc.RedstoneImplementation.ALTERNATE_CURRENT; @@ -56,7 +57,8 @@ public class RedstoneEngineCommand extends SWCommand implements Listener, Scoreb broadcastTitle(Bukkit.getOnlinePlayers()); } - @Register + @Register("redstone") + @Register("redstoneengine") public void setRedstoneEngine(Player player, WorldConfiguration.Misc.RedstoneImplementation implementation) { getConfig().redstoneImplementation = implementation; broadcastTitle(Bukkit.getOnlinePlayers()); From eab8826583f39e1bbc43f68e6f481379fe85cdf9 Mon Sep 17 00:00:00 2001 From: YoyoNow Date: Sun, 31 May 2026 11:36:21 +0200 Subject: [PATCH 03/36] Fix RedstoneEngine not registering --- .../features/experimental/redstone_engine/RedstoneEngine.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/experimental/redstone_engine/RedstoneEngine.java b/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/experimental/redstone_engine/RedstoneEngine.java index 8dc99349..f6d7934a 100644 --- a/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/experimental/redstone_engine/RedstoneEngine.java +++ b/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/experimental/redstone_engine/RedstoneEngine.java @@ -23,6 +23,7 @@ import de.steamwar.bausystem.region.Region; import de.steamwar.bausystem.utils.ScoreboardElement; import de.steamwar.command.AbstractSWCommand; import de.steamwar.command.SWCommand; +import de.steamwar.linkage.Linked; import io.papermc.paper.configuration.WorldConfiguration; import net.kyori.adventure.text.Component; import net.kyori.adventure.text.format.NamedTextColor; @@ -38,6 +39,7 @@ import java.util.Collection; import java.util.List; @AbstractSWCommand.PartOf(RedstoneEngine.class) +@Linked public class RedstoneEngine extends SWCommand implements Listener, ScoreboardElement { public RedstoneEngine() { From fd8f942014a308f666d56b181ef52d6b60e6050c Mon Sep 17 00:00:00 2001 From: YoyoNow Date: Sun, 31 May 2026 11:37:43 +0200 Subject: [PATCH 04/36] Fix RedstoneEngine not registering --- .../features/experimental/redstone_engine/RedstoneEngine.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/experimental/redstone_engine/RedstoneEngine.java b/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/experimental/redstone_engine/RedstoneEngine.java index f6d7934a..1247b667 100644 --- a/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/experimental/redstone_engine/RedstoneEngine.java +++ b/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/experimental/redstone_engine/RedstoneEngine.java @@ -19,6 +19,7 @@ package de.steamwar.bausystem.features.experimental.redstone_engine; +import de.steamwar.bausystem.features.experimental.ExperimentalCommand; import de.steamwar.bausystem.region.Region; import de.steamwar.bausystem.utils.ScoreboardElement; import de.steamwar.command.AbstractSWCommand; @@ -38,7 +39,7 @@ import org.bukkit.event.player.PlayerJoinEvent; import java.util.Collection; import java.util.List; -@AbstractSWCommand.PartOf(RedstoneEngine.class) +@AbstractSWCommand.PartOf(ExperimentalCommand.class) @Linked public class RedstoneEngine extends SWCommand implements Listener, ScoreboardElement { From fc2997e011cdbabb6b83beecbbd6eeafd030ceca Mon Sep 17 00:00:00 2001 From: D4rkr34lm Date: Sat, 6 Jun 2026 12:17:23 +0200 Subject: [PATCH 05/36] Rennable replays for serverteam --- .../velocitycore/commands/ReplayCommand.java | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/VelocityCore/src/de/steamwar/velocitycore/commands/ReplayCommand.java b/VelocityCore/src/de/steamwar/velocitycore/commands/ReplayCommand.java index b6bdd7ec..ae276519 100644 --- a/VelocityCore/src/de/steamwar/velocitycore/commands/ReplayCommand.java +++ b/VelocityCore/src/de/steamwar/velocitycore/commands/ReplayCommand.java @@ -42,13 +42,6 @@ public class ReplayCommand extends SWCommand { super("replay"); } - @Register - public void genericCommand(PlayerChatter sender) { - sender.system("REPLAY_UNAVAILABLE"); - return; - } - - /* @Register public void genericCommand(PlayerChatter sender, int replayId, @StaticValue(value = {"", "-a"}, allowISE = true) @OptionalValue("") boolean isAdmin, @OptionalValue("") String optionalMap) { Fight fight = Fight.getById(replayId); @@ -64,6 +57,9 @@ public class ReplayCommand extends SWCommand { @Register public void genericCommand(PlayerChatter sender, @OptionalValue("") String optionalMap) { if (PunishmentCommand.isPunishedWithMessage(sender, Punishment.PunishmentType.NoFightServer)) return; + if (!sender.user().hasPerm(UserPerm.TEAM)) return; + + new SWStreamInv<>(sender, new Message("REPLAY_TITLE"), (click, fight) -> { startReplay(sender, click.isShiftClick(), optionalMap, fight); @@ -72,6 +68,7 @@ public class ReplayCommand extends SWCommand { private void startReplay(PlayerChatter sender, boolean isAdmin, String optionalMap, Fight fight) { if (PunishmentCommand.isPunishedWithMessage(sender, Punishment.PunishmentType.NoFightServer)) return; + if (!sender.user().hasPerm(UserPerm.TEAM)) return; GameModeConfig mode = ArenaMode.getBySchemType(fight.getSchemType()); ServerStarter starter = new ServerStarter().replay(fight.getFightID()).blueLeader(sender.getPlayer()); @@ -116,5 +113,5 @@ public class ReplayCommand extends SWCommand { private Message parseLeader(SteamwarUser leader, int players, boolean winner) { return new Message("REPLAY_" + (players > 1 ? "" : "SOLO_") + (winner ? "WINNER" : "LOSER"), leader.getUserName(), players - 1); } - */ + } From 03c3d496590ca88d426070467c0efef194d44f5b Mon Sep 17 00:00:00 2001 From: YoyoNow Date: Tue, 9 Jun 2026 16:38:22 +0200 Subject: [PATCH 06/36] Add BlastResistanceCommand --- .../BauSystem_Main/src/BauSystem.properties | 4 + .../src/BauSystem_de.properties | 4 + .../BlastResistanceCommand.java | 73 +++++++++++++++++++ 3 files changed, 81 insertions(+) create mode 100644 BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/slaves/blastresistance/BlastResistanceCommand.java diff --git a/BauSystem/BauSystem_Main/src/BauSystem.properties b/BauSystem/BauSystem_Main/src/BauSystem.properties index a045edf4..c5adfb70 100644 --- a/BauSystem/BauSystem_Main/src/BauSystem.properties +++ b/BauSystem/BauSystem_Main/src/BauSystem.properties @@ -834,6 +834,10 @@ SKIN_NO_REGION = §7You are not in a region with a changealbe skin SKIN_ALREADY_EXISTS = §cThis skin already exists like this SKIN_MESSAGE = §7Skin created SKIN_MESSAGE_HOVER = §eClick to copy for YoyoNow and send +# Blast Resistance +BLASTRESISTANCE_HELP = §8/§eblastresistance §8-§7 Calculate min/max and average blast resistance of current clipboard +BLASTRESISTANCE_NO_CLIPBOARD = §cYou currently do not have a clipboard to be used. +BLASTRESISTANCE_RESULT = §7BlastResistance §8>>§7 Min§8: §e{0}§7 Max§8: §e{1}§7 Avg§8: §e{2} # Panzern PANZERN_HELP = §8/§epanzern §8[§7Block§8] §8[§7Slab§8] §8- §7Armor your WorldEdit selection PANZERN_PREPARE1 = §71. Check, if barrels reach until border of armor. diff --git a/BauSystem/BauSystem_Main/src/BauSystem_de.properties b/BauSystem/BauSystem_Main/src/BauSystem_de.properties index dedc0599..9bee037c 100644 --- a/BauSystem/BauSystem_Main/src/BauSystem_de.properties +++ b/BauSystem/BauSystem_Main/src/BauSystem_de.properties @@ -772,6 +772,10 @@ SKIN_NO_REGION = §7Du steht in keiner Region, welche mit einem Skin versehen we SKIN_ALREADY_EXISTS = §cDieser Skin existiert in der Form bereits SKIN_MESSAGE = §7Skin erstellt SKIN_MESSAGE_HOVER = §eKlicken zum kopieren für YoyoNow und an diesen senden +# Blast Resistance +BLASTRESISTANCE_HELP = §8/§eblastresistance §8-§7 Minimal-, Maximal- und durchschnittliche Sprengfestigkeit des aktuellen Inhalts der Zwischenablage berechnen +BLASTRESISTANCE_NO_CLIPBOARD = §cDerzeit steht Ihnen keine Zwischenablage zur Verfügung. +BLASTRESISTANCE_RESULT = §7BlastResistance §8>>§7 Min§8: §e{0}§7 Max§8: §e{1}§7 Avg§8: §e{2} # Panzern PANZERN_HELP = §8/§epanzern §8[§7Block§8] §8[§7Slab§8] §8- §7Panzer deine WorldEdit Auswahl PANZERN_PREPARE1 = §71. Gucke nochmal nach, ob Läufe auch bis zur Panzergrenze führen. diff --git a/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/slaves/blastresistance/BlastResistanceCommand.java b/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/slaves/blastresistance/BlastResistanceCommand.java new file mode 100644 index 00000000..f296f1eb --- /dev/null +++ b/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/slaves/blastresistance/BlastResistanceCommand.java @@ -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 . + */ + +package de.steamwar.bausystem.features.slaves.blastresistance; + +import com.google.common.util.concurrent.AtomicDouble; +import com.sk89q.worldedit.LocalSession; +import com.sk89q.worldedit.WorldEdit; +import com.sk89q.worldedit.WorldEditException; +import com.sk89q.worldedit.bukkit.BukkitAdapter; +import com.sk89q.worldedit.extent.clipboard.Clipboard; +import com.sk89q.worldedit.world.block.BlockState; +import de.steamwar.bausystem.BauSystem; +import de.steamwar.command.SWCommand; +import de.steamwar.linkage.Linked; +import org.bukkit.Material; +import org.bukkit.entity.Player; + +import java.util.*; +import java.util.concurrent.atomic.AtomicInteger; + +@Linked +public class BlastResistanceCommand extends SWCommand { + + public BlastResistanceCommand() { + super("blastresistance"); + } + + @Register(description = "BLASTRESISTANCE_HELP") + public void command(Player player) { + LocalSession localSession = WorldEdit.getInstance().getSessionManager().get(BukkitAdapter.adapt(player)); + Clipboard clipboard; + try { + clipboard = localSession.getClipboard().getClipboards().getFirst(); + } catch (WorldEditException e) { + BauSystem.MESSAGE.send("BLASTRESISTANCE_NO_CLIPBOARD", player); + return; + } + + AtomicDouble min = new AtomicDouble(0); + AtomicDouble max = new AtomicDouble(0); + AtomicDouble sum = new AtomicDouble(0); + AtomicInteger count = new AtomicInteger(0); + clipboard.forEach(blockVector3 -> { + BlockState blockState = clipboard.getBlock(blockVector3); + Material material = BukkitAdapter.adapt(blockState).getMaterial(); + if (material == Material.WATER || material == Material.LAVA) return; + double blastResistance = BukkitAdapter.adapt(blockState).getMaterial().getBlastResistance(); + min.set(Math.min(min.get(), blastResistance)); + max.set(Math.max(max.get(), blastResistance)); + sum.addAndGet(blastResistance); + count.getAndIncrement(); + }); + + BauSystem.MESSAGE.send("BLASTRESISTANCE_RESULT", player, min.get(), max.get(), sum.get() / count.get()); + } +} From ec9b0387c5e738fd919165965147fcc18a4d0700 Mon Sep 17 00:00:00 2001 From: YoyoNow Date: Tue, 9 Jun 2026 16:42:40 +0200 Subject: [PATCH 07/36] Fix permission for BlastResistanceCommand --- .../features/slaves/blastresistance/BlastResistanceCommand.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/slaves/blastresistance/BlastResistanceCommand.java b/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/slaves/blastresistance/BlastResistanceCommand.java index f296f1eb..7c9430a7 100644 --- a/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/slaves/blastresistance/BlastResistanceCommand.java +++ b/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/slaves/blastresistance/BlastResistanceCommand.java @@ -43,7 +43,7 @@ public class BlastResistanceCommand extends SWCommand { } @Register(description = "BLASTRESISTANCE_HELP") - public void command(Player player) { + public void command(@Validator Player player) { LocalSession localSession = WorldEdit.getInstance().getSessionManager().get(BukkitAdapter.adapt(player)); Clipboard clipboard; try { From a938abde3f77e40b198983c12e539c4f09f92f3f Mon Sep 17 00:00:00 2001 From: YoyoNow Date: Tue, 9 Jun 2026 23:00:46 +0200 Subject: [PATCH 08/36] Add 'debug' property to enable/disable debugger --- buildSrc/src/steamwar.devserver.gradle | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/buildSrc/src/steamwar.devserver.gradle b/buildSrc/src/steamwar.devserver.gradle index 20b82def..eea3fdca 100644 --- a/buildSrc/src/steamwar.devserver.gradle +++ b/buildSrc/src/steamwar.devserver.gradle @@ -26,6 +26,10 @@ plugins { class DevServer extends DefaultTask { + @Input + @Optional + boolean debug = false + @Input @Optional String worldName = null @@ -95,7 +99,7 @@ class DevServer extends DefaultTask { doLast { setupTemplate(template) uploadDependencies() - startDebugPort() + if (debug) startDebugPort() startDevServer() } finalizedBy(new Finalizer()) @@ -241,7 +245,7 @@ class DevServer extends DefaultTask { devPy.append(" -D${dParam.key}=${dParam.value}") } devPy.append(" $template") - devPy.append(" -agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=*:$debugPort") + if (debug) devPy.append(" -agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=*:$debugPort") if (jvmArgs != null) devPy.append(" $jvmArgs") println("Starting $template with command ${devPy.toString()}") @@ -286,7 +290,7 @@ class VelocityServer extends DevServer { VelocityServer() { super() doFirst { - if(packetDecodeLogging) dParams.put("velocity.packet-decode-logging", "true") + if (packetDecodeLogging) dParams.put("velocity.packet-decode-logging", "true") } } } From e176b3bca8941aaaf2040474d816ef78eeaae2dc Mon Sep 17 00:00:00 2001 From: YoyoNow Date: Wed, 10 Jun 2026 20:46:13 +0200 Subject: [PATCH 09/36] Fix steamwar.devserver.gradle 'worldName' parameter --- buildSrc/src/steamwar.devserver.gradle | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/buildSrc/src/steamwar.devserver.gradle b/buildSrc/src/steamwar.devserver.gradle index eea3fdca..59e6cfbb 100644 --- a/buildSrc/src/steamwar.devserver.gradle +++ b/buildSrc/src/steamwar.devserver.gradle @@ -30,10 +30,6 @@ class DevServer extends DefaultTask { @Optional boolean debug = false - @Input - @Optional - String worldName = null - @Input String template = null @@ -88,10 +84,13 @@ class DevServer extends DefaultTask { } } - if (worldName == null) worldName = properties.get("worldName") + worldName = properties.get("worldName") host = properties.get("host") debugPort = new Random().nextInt(5001, 10000) + if (worldName == null) { + throw new GradleException("Please supply the 'worldName' in a 'steamwar.properties' files either in this project dir or any parent project!") + } if (host == null) { throw new GradleException("Please supply the 'host' in a 'steamwar.properties' files either in this project dir or any parent project!") } @@ -117,6 +116,9 @@ class DevServer extends DefaultTask { @Internal Boolean running = true + @Internal + String worldName = null + class Finalizer extends DefaultTask { Finalizer() { From 786257ad0e5c0af6253a5caafde7191c04f1f822 Mon Sep 17 00:00:00 2001 From: YoyoNow Date: Thu, 11 Jun 2026 12:22:44 +0200 Subject: [PATCH 10/36] Add initial AccessWidener --- AccessWidener/build.gradle.kts | 45 ++++++ .../java/de/steamwar/AccessWidenerEntry.java | 49 ++++++ .../java/de/steamwar/AccessWidenerParser.java | 104 ++++++++++++ .../de/steamwar/AccessWidenerScanner.java | 145 +++++++++++++++++ .../src/main/java/de/steamwar/Agent.java | 67 ++++++++ .../main/java/de/steamwar/ClassPatcher.java | 63 ++++++++ .../java/de/steamwar/ClassTransformer.java | 98 ++++++++++++ .../src/main/java/de/steamwar/Main.java | 125 +++++++++++++++ .../java/de/steamwar/WideningTransformer.java | 149 ++++++++++++++++++ .../src/bausystem.accesswidener | 3 + .../features/util/NoClipCommand.java | 6 +- BauSystem/build.gradle.kts | 9 ++ .../steamwar/core/AccessWidenerAttacher.java | 117 ++++++++++++++ buildSrc/src/steamwar.devserver.gradle | 1 - settings.gradle.kts | 4 + 15 files changed, 979 insertions(+), 6 deletions(-) create mode 100644 AccessWidener/build.gradle.kts create mode 100644 AccessWidener/src/main/java/de/steamwar/AccessWidenerEntry.java create mode 100644 AccessWidener/src/main/java/de/steamwar/AccessWidenerParser.java create mode 100644 AccessWidener/src/main/java/de/steamwar/AccessWidenerScanner.java create mode 100644 AccessWidener/src/main/java/de/steamwar/Agent.java create mode 100644 AccessWidener/src/main/java/de/steamwar/ClassPatcher.java create mode 100644 AccessWidener/src/main/java/de/steamwar/ClassTransformer.java create mode 100644 AccessWidener/src/main/java/de/steamwar/Main.java create mode 100644 AccessWidener/src/main/java/de/steamwar/WideningTransformer.java create mode 100644 BauSystem/BauSystem_Main/src/bausystem.accesswidener create mode 100644 SpigotCore/SpigotCore_Main/src/de/steamwar/core/AccessWidenerAttacher.java diff --git a/AccessWidener/build.gradle.kts b/AccessWidener/build.gradle.kts new file mode 100644 index 00000000..87a25b4d --- /dev/null +++ b/AccessWidener/build.gradle.kts @@ -0,0 +1,45 @@ +/* + * 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 . + */ + +plugins { + `java-library` + id("com.github.johnrengelman.shadow") version "8.1.1" +} + +dependencies { + implementation("org.ow2.asm:asm:9.7") + implementation("org.ow2.asm:asm-commons:9.7") +} + +tasks.shadowJar { + manifest { + attributes( + "Manifest-Version" to "1.0", + "Build-Jdk-Spec" to "21", + "Main-Class" to "de.steamwar.Main", + "Premain-Class" to "de.steamwar.Agent", + "Can-Retransform-Classes" to "true", + "Can-Redefine-Classes" to "true", + ) + } +} + +tasks.build { + dependsOn(tasks.shadowJar) +} diff --git a/AccessWidener/src/main/java/de/steamwar/AccessWidenerEntry.java b/AccessWidener/src/main/java/de/steamwar/AccessWidenerEntry.java new file mode 100644 index 00000000..5b5ff9a0 --- /dev/null +++ b/AccessWidener/src/main/java/de/steamwar/AccessWidenerEntry.java @@ -0,0 +1,49 @@ +/* + * 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; + +/** + * A single parsed line from a .accesswidener file. + *

+ * Examples: + * accessible class net/minecraft/server/level/ServerPlayer + * accessible method net/minecraft/server/level/ServerPlayer getStats ()V + * mutable field net/minecraft/world/entity/Entity id I + * extendable class net/minecraft/world/level/chunk/LevelChunk + */ +public record AccessWidenerEntry( + /** accessible | mutable | extendable (may have "transitive-" prefix) */ + String directive, + /** class | method | field */ + String memberType, + /** Internal class name, e.g. net/minecraft/server/level/ServerPlayer */ + String target, + /** Method/field name, null for class entries */ + String name, + /** Descriptor, null for class entries */ + String descriptor) { + /** + * Returns true if this entry targets the class with the given internal name. + */ + public boolean targets(String internalName) { + return target.equals(internalName); + } +} + diff --git a/AccessWidener/src/main/java/de/steamwar/AccessWidenerParser.java b/AccessWidener/src/main/java/de/steamwar/AccessWidenerParser.java new file mode 100644 index 00000000..d962e0dc --- /dev/null +++ b/AccessWidener/src/main/java/de/steamwar/AccessWidenerParser.java @@ -0,0 +1,104 @@ +/* + * 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; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.List; + +/** + * Parses Fabric-compatible .accesswidener files. + *

+ * Supported format: + *

+ *   accessWidener v2 named
+ *
+ *   # comments are supported
+ *   accessible class  net/minecraft/Foo
+ *   accessible method net/minecraft/Foo someMethod ()V
+ *   accessible field  net/minecraft/Foo someField I
+ *   mutable    field  net/minecraft/Foo someField I
+ *   extendable class  net/minecraft/Foo
+ *   extendable method net/minecraft/Foo someMethod ()V
+ *
+ *   # transitive variants (expose widening to dependents)
+ *   transitive-accessible class net/minecraft/Foo
+ * 
+ */ +public final class AccessWidenerParser { + + private AccessWidenerParser() { + } + + public static List parse(InputStream in) throws IOException { + List entries = new ArrayList<>(); + + try (BufferedReader reader = new BufferedReader(new InputStreamReader(in, StandardCharsets.UTF_8))) { + + String line; + boolean headerSeen = false; + + while ((line = reader.readLine()) != null) { + // Strip inline comments + int commentIdx = line.indexOf('#'); + if (commentIdx >= 0) line = line.substring(0, commentIdx); + line = line.strip(); + + if (line.isEmpty()) continue; + + if (!headerSeen) { + // First non-blank, non-comment line must be the header + if (!line.startsWith("accessWidener")) { + throw new IOException("Missing accessWidener header, got: " + line); + } + headerSeen = true; + continue; + } + + AccessWidenerEntry entry = parseLine(line); + if (entry != null) entries.add(entry); + } + } + + return entries; + } + + private static AccessWidenerEntry parseLine(String line) { + String[] parts = line.split("\\s+"); + if (parts.length < 3) return null; + + String directive = parts[0]; // accessible / mutable / extendable / transitive-* + String memberType = parts[1]; // class / method / field + String target = parts[2]; // internal class name + + return switch (memberType) { + case "class" -> new AccessWidenerEntry(directive, "class", target, null, null); + case "method", "field" -> { + if (parts.length < 5) yield null; + yield new AccessWidenerEntry(directive, memberType, target, parts[3], parts[4]); + } + default -> null; + }; + } +} \ No newline at end of file diff --git a/AccessWidener/src/main/java/de/steamwar/AccessWidenerScanner.java b/AccessWidener/src/main/java/de/steamwar/AccessWidenerScanner.java new file mode 100644 index 00000000..70d2c0c9 --- /dev/null +++ b/AccessWidener/src/main/java/de/steamwar/AccessWidenerScanner.java @@ -0,0 +1,145 @@ +/* + * 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; + +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.lang.instrument.Instrumentation; +import java.lang.reflect.Field; +import java.net.URL; +import java.net.URLClassLoader; +import java.nio.file.Path; +import java.util.*; +import java.util.logging.Logger; +import java.util.zip.ZipEntry; +import java.util.zip.ZipInputStream; + +/** + * Scans all plugin ClassLoaders for "plugin.accesswidener" resources + * and returns the parsed entries. + */ +public final class AccessWidenerScanner { + + private static final Logger LOG = Logger.getLogger("AccessWidenerScanner"); + + private AccessWidenerScanner() { + } + + /** + * Scans every ClassLoader visible from the already-loaded classes for + */ + public static List scanAll(Instrumentation inst) { + List allEntries = new ArrayList<>(); + Set seen = Collections.newSetFromMap(new IdentityHashMap<>()); + + for (Class clazz : inst.getAllLoadedClasses()) { + ClassLoader cl = clazz.getClassLoader(); + if (cl == null) continue; // bootstrap — skip + if (!seen.add(cl)) continue; // already processed + if (!isPluginClassLoader(cl)) continue; + + allEntries.addAll(scanClassLoader(cl)); + } + + return allEntries; + } + + /** + * Uses getResources() so it finds ALL matching files on the loader's classpath. + */ + public static List scanClassLoader(ClassLoader cl) { + List entries = new ArrayList<>(); + + try { + for (URL url : findAccessWideners(cl)) { + try (InputStream in = url.openStream()) { + List parsed = AccessWidenerParser.parse(in); + LOG.info("[AccessWidener] Loaded " + parsed.size() + " entries from " + url); + entries.addAll(parsed); + } catch (IOException e) { + LOG.warning("[AccessWidener] Failed to read " + url + ": " + e.getMessage()); + } + } + } catch (IOException e) { + LOG.warning("[AccessWidener] Failed to scan " + cl + ": " + e.getMessage()); + } + + return entries; + } + + public static List findAccessWideners(ClassLoader cl) throws IOException { + List results = new ArrayList<>(); + + // Standard path — works on most Paper versions + if (cl instanceof URLClassLoader urlCl) { + return scanUrls(urlCl.getURLs()); + } + + // Newer Paper — PluginClassLoader stores the jar as a field + try { + // Try common field names used across Paper versions + for (String fieldName : List.of("file", "jarFile", "pluginJar", "source")) { + try { + Field f = cl.getClass().getDeclaredField(fieldName); + f.setAccessible(true); + Object value = f.get(cl); + if (value instanceof File file) { + return scanUrls(new URL[]{ file.toURI().toURL() }); + } + if (value instanceof Path path) { + return scanUrls(new URL[]{ path.toUri().toURL() }); + } + } catch (NoSuchFieldException ignored) {} + } + } catch (Exception e) { + LOG.warning("[AccessWidener] Could not extract JAR path from " + cl + ": " + e.getMessage()); + } + + return results; + } + + private static List scanUrls(URL[] urls) throws IOException { + List results = new ArrayList<>(); + for (URL url : urls) { + if (!url.getPath().endsWith(".jar")) continue; + try (ZipInputStream zip = new ZipInputStream(url.openStream())) { + ZipEntry entry; + while ((entry = zip.getNextEntry()) != null) { + if (entry.getName().endsWith(".accesswidener")) { + results.add(new URL("jar:" + url + "!/" + entry.getName())); + } + } + } + } + return results; + } + + /** + * Returns true if the classloader looks like a Paper plugin classloader. + * Checks by name so we don't need a direct dependency on Paper internals. + */ + public static boolean isPluginClassLoader(ClassLoader cl) { + String name = cl.getClass().getName(); + return name.contains("PluginClassLoader") // legacy Paper / Spigot + || name.contains("PaperPluginLoader") // Paper 1.20.5+ + || name.contains("PaperSimplePluginManager"); // just in case + } +} diff --git a/AccessWidener/src/main/java/de/steamwar/Agent.java b/AccessWidener/src/main/java/de/steamwar/Agent.java new file mode 100644 index 00000000..a786fed8 --- /dev/null +++ b/AccessWidener/src/main/java/de/steamwar/Agent.java @@ -0,0 +1,67 @@ +/* + * 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; + +import java.lang.instrument.Instrumentation; +import java.util.logging.Logger; + +/** + * Java agent entry point. + * + * Can be used two ways: + * 1. At JVM startup: java -javaagent:paper-access-widener-agent.jar -jar server.jar + * 2. Late attach: from inside a Paper plugin via the Attach API + * + * On attach the agent: + * 1. Scans all existing plugin ClassLoaders for "plugin.accesswidener" resources + * 2. Registers a ClassFileTransformer that: + * a. Applies widening to every class as it loads + * b. Detects new plugin ClassLoaders and scans them automatically + * 3. Retransforms any Minecraft/server classes that are already loaded + */ +public class Agent { + private Agent() { + /* This utility class should not be instantiated */ + } + + private static final Logger LOG = Logger.getLogger("AccessWidenerAgent"); + + // Exposed so tests or other code can inspect the live transformer + static volatile WideningTransformer transformer; + + // -javaagent: startup + public static void premain(String args, Instrumentation inst) { + init(inst); + } + + private static void init(Instrumentation inst) { + LOG.info("[AccessWidener] Agent initialising."); + + WideningTransformer t = new WideningTransformer(inst); + transformer = t; + + // --- Phase 2: register transformer for future class loads --- + // canRetransform=true so we can call retransformClasses() later + inst.addTransformer(t, true); + + LOG.info("[AccessWidener] Agent ready."); + } +} + diff --git a/AccessWidener/src/main/java/de/steamwar/ClassPatcher.java b/AccessWidener/src/main/java/de/steamwar/ClassPatcher.java new file mode 100644 index 00000000..3e640694 --- /dev/null +++ b/AccessWidener/src/main/java/de/steamwar/ClassPatcher.java @@ -0,0 +1,63 @@ +/* + * This file is a part of the SteamWar software. + * + * Copyright (C) 2026 SteamWar.de-Serverteam + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package de.steamwar; + +import org.objectweb.asm.*; + +import java.util.List; +import java.util.Set; +import java.util.stream.Collectors; + +/** + * Uses ASM to patch class bytecode according to a list of access widener entries. + * + * Returns {@code null} if the class is not targeted by any entry (no-op signal + * to the caller so it can skip the write). + */ +public class ClassPatcher { + + private final List entries; + + /** Pre-computed set of targeted internal names for fast filtering. */ + private final Set targets; + + public ClassPatcher(List entries) { + this.entries = entries; + this.targets = entries.stream() + .map(AccessWidenerEntry::target) + .collect(Collectors.toSet()); + } + + /** + * Patches {@code classBytes} if {@code internalName} is targeted. + * + * @return patched bytes, or {@code null} if no changes were needed + */ + public byte[] patch(String internalName, byte[] classBytes) { + if (!targets.contains(internalName)) return null; + + ClassReader cr = new ClassReader(classBytes); + // COMPUTE_FRAMES would require the full classpath; we only touch flags so 0 is fine + ClassWriter cw = new ClassWriter(cr, 0); + cr.accept(new ClassTransformer(cw, internalName, entries), 0); + return cw.toByteArray(); + } +} + diff --git a/AccessWidener/src/main/java/de/steamwar/ClassTransformer.java b/AccessWidener/src/main/java/de/steamwar/ClassTransformer.java new file mode 100644 index 00000000..c329b966 --- /dev/null +++ b/AccessWidener/src/main/java/de/steamwar/ClassTransformer.java @@ -0,0 +1,98 @@ +/* + * 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; + +import org.objectweb.asm.ClassVisitor; +import org.objectweb.asm.FieldVisitor; +import org.objectweb.asm.MethodVisitor; +import org.objectweb.asm.Opcodes; + +import java.util.List; + +public class ClassTransformer extends ClassVisitor { + + private final String internalName; + private final List entries; + + public ClassTransformer(ClassVisitor cv, String internalName, List entries) { + super(Opcodes.ASM9, cv); + this.internalName = internalName; + this.entries = entries; + } + + @Override + public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) { + int newAccess = access; + for (AccessWidenerEntry e : entries) { + if (!e.targets(internalName) || !"class".equals(e.memberType())) continue; + newAccess = applyDirective(e.directive(), newAccess, false); + } + super.visit(version, newAccess, name, signature, superName, interfaces); + } + + @Override + public MethodVisitor visitMethod(int access, String name, String descriptor, String signature, String[] exceptions) { + int newAccess = access; + for (AccessWidenerEntry e : entries) { + if (!e.targets(internalName) || !"method".equals(e.memberType())) continue; + if (!name.equals(e.name()) || !descriptor.equals(e.descriptor())) continue; + newAccess = applyDirective(e.directive(), newAccess, false); + } + return super.visitMethod(newAccess, name, descriptor, signature, exceptions); + } + + @Override + public FieldVisitor visitField(int access, String name, String descriptor, String signature, Object value) { + int newAccess = access; + for (AccessWidenerEntry e : entries) { + if (!e.targets(internalName) || !"field".equals(e.memberType())) continue; + if (!name.equals(e.name())) continue; + newAccess = applyDirective(e.directive(), newAccess, true); + } + return super.visitField(newAccess, name, descriptor, signature, value); + } + + /** + * Apply a directive to an access bitmask. + * + * @param directive accessible / mutable / extendable (with optional "transitive-" prefix) + * @param access current access flags + * @param isField true when processing a field (mutable removes final) + */ + private static int applyDirective(String directive, int access, boolean isField) { + // Strip transitive- prefix — the widening itself is the same + String effective = directive.startsWith("transitive-") ? directive.substring("transitive-".length()) : directive; + + return switch (effective) { + case "accessible" -> makePublic(access); + case "extendable" -> makePublic(removeFinal(access)); + case "mutable" -> isField ? removeFinal(access) : access; + default -> access; + }; + } + + private static int makePublic(int access) { + return (access & ~(Opcodes.ACC_PRIVATE | Opcodes.ACC_PROTECTED)) | Opcodes.ACC_PUBLIC; + } + + private static int removeFinal(int access) { + return access & ~Opcodes.ACC_FINAL; + } +} diff --git a/AccessWidener/src/main/java/de/steamwar/Main.java b/AccessWidener/src/main/java/de/steamwar/Main.java new file mode 100644 index 00000000..8dc81875 --- /dev/null +++ b/AccessWidener/src/main/java/de/steamwar/Main.java @@ -0,0 +1,125 @@ +/* + * 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; + +import java.io.IOException; +import java.io.InputStream; +import java.nio.file.*; +import java.util.ArrayList; +import java.util.List; + +/** + * Command-line tool that produces a widened copy of a JAR for use as a + * compile-time stub in IntelliJ / Gradle. + * + * Usage: + * java -jar jar-widener.jar [file.accesswidener ...] + * + * The output JAR is identical to the input JAR except that every class + * targeted by the access widener entries has its access flags patched: + * accessible → public + * extendable → public + non-final + * mutable → non-final field + * + * Intended for use as a Gradle task so IntelliJ sees the already-widened + * class when you Ctrl+click NMS code, and javac compiles without complaints. + */ +public class Main { + + public static void main(String[] args) throws Exception { + if (args.length < 2) { + System.err.println("Usage: jar-widener [*.accesswidener ...]"); + System.exit(1); + } + + Path inputJar = Path.of(args[0]); + Path outputJar = Path.of(args[1]); + + if (!Files.exists(inputJar)) { + System.err.println("Input JAR not found: " + inputJar); + System.exit(1); + } + + // --- Collect all access widener entries --- + List entries = new ArrayList<>(); + + if (args.length > 2) { + for (int i = 2; i < args.length; i++) { + Path awFile = Path.of(args[i]); + if (!Files.exists(awFile)) { + System.err.println("Warning: access widener file not found, skipping: " + awFile); + continue; + } + try (InputStream in = Files.newInputStream(awFile)) { + List parsed = AccessWidenerParser.parse(in); + System.out.println("Loaded " + parsed.size() + " entries from " + awFile.getFileName()); + entries.addAll(parsed); + } + } + } + + if (entries.isEmpty()) { + System.out.println("No access widener entries found — copying JAR unchanged."); + Files.createDirectories(outputJar.getParent()); + Files.copy(inputJar, outputJar, StandardCopyOption.REPLACE_EXISTING); + return; + } + + System.out.println("Widening " + inputJar.getFileName() + + " with " + entries.size() + " total entr" + + (entries.size() == 1 ? "y" : "ies") + "..."); + + // --- Copy input → output, transforming .class files in place --- + Files.createDirectories(outputJar.getParent()); + Files.copy(inputJar, outputJar, StandardCopyOption.REPLACE_EXISTING); + + ClassPatcher patcher = new ClassPatcher(entries); + + try (FileSystem fs = FileSystems.newFileSystem(outputJar)) { + // Walk every .class entry in the JAR + try (var stream = Files.walk(fs.getPath("/"))) { + stream.filter(p -> p.toString().endsWith(".class")) + .forEach(classPath -> patchClass(fs, classPath, patcher)); + } + } + + System.out.println("Done. Widened JAR written to " + outputJar); + } + + private static void patchClass(FileSystem fs, Path classPath, ClassPatcher patcher) { + // Derive internal class name from path e.g. /net/minecraft/Foo.class → net/minecraft/Foo + String internalName = classPath.toString() + .replaceFirst("^/", "") + .replace(".class", ""); + + try { + byte[] original = Files.readAllBytes(classPath); + byte[] patched = patcher.patch(internalName, original); + + if (patched != null) { + Files.write(classPath, patched); + System.out.println(" Widened: " + internalName); + } + } catch (IOException e) { + System.err.println(" Warning: failed to patch " + internalName + ": " + e.getMessage()); + } + } +} + diff --git a/AccessWidener/src/main/java/de/steamwar/WideningTransformer.java b/AccessWidener/src/main/java/de/steamwar/WideningTransformer.java new file mode 100644 index 00000000..dd14a9a0 --- /dev/null +++ b/AccessWidener/src/main/java/de/steamwar/WideningTransformer.java @@ -0,0 +1,149 @@ +/* + * 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; + +import org.objectweb.asm.ClassReader; +import org.objectweb.asm.ClassWriter; + +import java.lang.instrument.ClassFileTransformer; +import java.lang.instrument.Instrumentation; +import java.lang.instrument.UnmodifiableClassException; +import java.security.ProtectionDomain; +import java.util.*; +import java.util.concurrent.CopyOnWriteArrayList; +import java.util.logging.Logger; +import java.util.stream.Collectors; + +/** + * Transforms class bytecode to apply access widening rules. + *

+ * Also monitors for new plugin ClassLoaders appearing (when plugins load after + * the agent attaches) and automatically picks up their .accesswidener files. + */ +public class WideningTransformer implements ClassFileTransformer { + + private static final Logger LOG = Logger.getLogger("WidenerTransformer"); + + private final Instrumentation instrumentation; + + /** + * All entries collected across all plugins. Thread-safe for concurrent plugin loads. + */ + private final List entries = new CopyOnWriteArrayList<>(); + + /** + * ClassLoaders we have already scanned, to avoid re-scanning. + */ + private final Set scannedLoaders = Collections.synchronizedSet(Collections.newSetFromMap(new IdentityHashMap<>())); + + public WideningTransformer(Instrumentation instrumentation) { + this.instrumentation = instrumentation; + } + + /** + * Add entries — used during initial scan before the transformer is registered. + */ + public void addEntries(List newEntries) { + entries.addAll(newEntries); + } + + // ------------------------------------------------------------------------- + // ClassFileTransformer + // ------------------------------------------------------------------------- + + @Override + public byte[] transform(ClassLoader loader, String className, Class classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer) { + + // Check for new plugin classloaders we haven't seen yet + if (loader != null && AccessWidenerScanner.isPluginClassLoader(loader) && scannedLoaders.add(loader)) { + onNewPluginClassLoader(loader); + } + + // No entries for this class — skip transformation entirely + if (className == null) return classfileBuffer; + boolean relevant = entries.stream().anyMatch(e -> e.targets(className)); + if (!relevant) return classfileBuffer; + + // Apply widening via ASM + try { + ClassReader cr = new ClassReader(classfileBuffer); + ClassWriter cw = new ClassWriter(cr, 0); + cr.accept(new ClassTransformer(cw, className, entries), 0); + return cw.toByteArray(); + } catch (Exception e) { + LOG.warning("[AccessWidener] Failed to transform " + className + ": " + e.getMessage()); + return classfileBuffer; + } + } + + // ------------------------------------------------------------------------- + // New ClassLoader detection + // ------------------------------------------------------------------------- + + private void onNewPluginClassLoader(ClassLoader loader) { + List newEntries = AccessWidenerScanner.scanClassLoader(loader); + if (newEntries.isEmpty()) return; + + entries.addAll(newEntries); + LOG.info("[AccessWidener] Picked up " + newEntries.size() + " new entr" + (newEntries.size() == 1 ? "y" : "ies") + " from " + loader); + + // Retransform already-loaded classes that are now targeted + scheduleRetransform(newEntries); + } + + /** + * Retransformation cannot be called from within a transform() call, + * so we dispatch it to a short-lived daemon thread. + */ + private void scheduleRetransform(List newEntries) { + Set targets = newEntries.stream().map(e -> e.target().replace('/', '.')).collect(Collectors.toSet()); + + Thread t = new Thread(() -> retransform(targets), "access-widener-retransform"); + t.setDaemon(true); + t.start(); + } + + private void retransform(Set dotNames) { + List toRetransform = Arrays.stream(instrumentation.getAllLoadedClasses()) + .filter(c -> dotNames.contains(c.getName())) + .filter(instrumentation::isModifiableClass) + .collect(Collectors.toList()); + + if (toRetransform.isEmpty()) return; + + LOG.info("[AccessWidener] Retransforming " + toRetransform.size() + " class(es)."); + List failed = new ArrayList<>(); + + for (Class clazz : toRetransform) { + try { + instrumentation.retransformClasses(clazz); + } catch (UnmodifiableClassException | UnsupportedOperationException e) { + failed.add(clazz.getName()); + } + } + + if (!failed.isEmpty()) { + LOG.warning("[AccessWidener] The following classes were already loaded before the agent" + + " and cannot be retransformed. Add -javaagent: to your start script" + + " to fix this:"); + failed.forEach(name -> LOG.warning(" - " + name)); + } + } +} diff --git a/BauSystem/BauSystem_Main/src/bausystem.accesswidener b/BauSystem/BauSystem_Main/src/bausystem.accesswidener new file mode 100644 index 00000000..7a0f195d --- /dev/null +++ b/BauSystem/BauSystem_Main/src/bausystem.accesswidener @@ -0,0 +1,3 @@ +accessWidener v2 named + +accessible field net/minecraft/server/level/ServerPlayerGameMode gameModeForPlayer Lnet/minecraft/world/level/GameType; diff --git a/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/util/NoClipCommand.java b/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/util/NoClipCommand.java index 0b10e163..6ead52d4 100644 --- a/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/util/NoClipCommand.java +++ b/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/util/NoClipCommand.java @@ -21,7 +21,6 @@ package de.steamwar.bausystem.features.util; import com.comphenix.tinyprotocol.TinyProtocol; import com.mojang.authlib.GameProfile; -import de.steamwar.Reflection; import de.steamwar.bausystem.BauSystem; import de.steamwar.bausystem.features.tpslimit.TPSUtils; import de.steamwar.bausystem.utils.BauMemberUpdateEvent; @@ -30,7 +29,6 @@ import de.steamwar.core.ProtocolWrapper; import de.steamwar.core.SWPlayer; import de.steamwar.linkage.Linked; import net.minecraft.network.protocol.game.*; -import net.minecraft.server.level.ServerPlayerGameMode; import net.minecraft.world.entity.player.Abilities; import net.minecraft.world.level.GameType; import org.bukkit.Bukkit; @@ -103,10 +101,8 @@ public class NoClipCommand extends SWCommand implements Listener { TinyProtocol.instance.addFilter(ServerboundSetCreativeModeSlotPacket.class, third); } - private static final Reflection.Field playerGameMode = Reflection.getField(ServerPlayerGameMode.class, GameType.class, 0); - private void setInternalGameMode(Player player, GameMode gameMode) { - playerGameMode.set(((CraftPlayer) player).getHandle().gameMode, GameType.byId(gameMode.getValue())); + // ((CraftPlayer) player).getHandle().gameMode.gameModeForPlayer = GameType.byId(gameMode.getValue()); } @Register(help = true) diff --git a/BauSystem/build.gradle.kts b/BauSystem/build.gradle.kts index de9fe1b8..b6180f3f 100644 --- a/BauSystem/build.gradle.kts +++ b/BauSystem/build.gradle.kts @@ -20,15 +20,22 @@ plugins { `java-library` alias(libs.plugins.shadow) + id("io.papermc.paperweight.userdev") version "1.7.1" } tasks.build { finalizedBy(tasks.shadowJar) } +repositories { + maven("https://repo.papermc.io/repository/maven-public/") +} + dependencies { implementation(project(":BauSystem:BauSystem_RegionFixed")) implementation(project(":BauSystem:BauSystem_Main")) + + paperweight.paperDevBundle("1.21.1-R0.1-SNAPSHOT") } tasks.register("DevBau21") { @@ -37,5 +44,7 @@ tasks.register("DevBau21") { dependsOn(":SpigotCore:shadowJar") dependsOn(":BauSystem:shadowJar") dependsOn(":SchematicSystem:shadowJar") + dependsOn(":AccessWidener:shadowJar") template = "Bau21" + jvmArgs = "-javaagent:/home/yoyonow/Bau21/plugins/AccessWidener.jar=start" } diff --git a/SpigotCore/SpigotCore_Main/src/de/steamwar/core/AccessWidenerAttacher.java b/SpigotCore/SpigotCore_Main/src/de/steamwar/core/AccessWidenerAttacher.java new file mode 100644 index 00000000..ccf61c40 --- /dev/null +++ b/SpigotCore/SpigotCore_Main/src/de/steamwar/core/AccessWidenerAttacher.java @@ -0,0 +1,117 @@ +package de.steamwar.core; + +import java.lang.reflect.Method; +import java.nio.file.Path; +import java.util.logging.Logger; + +/** + * Public API for Paper plugins that want to attach the access widener agent. + * + *

Usage

+ * Call {@link #ensureAttached(Path)} from your plugin's {@code onLoad()} method + * (not {@code onEnable()} — onLoad fires before classes start loading): + * + *
{@code
+ * public class MyPlugin extends JavaPlugin {
+ *     @Override
+ *     public void onLoad() {
+ *         try {
+ *             AccessWidenerAttacher.ensureAttached(getDataFolder());
+ *         } catch (Exception e) {
+ *             getSLF4JLogger().error("Failed to attach access widener agent", e);
+ *         }
+ *     }
+ * }
+ * }
+ * + *

Resource file

+ * Drop a {@code plugin.accesswidener} file in your plugin's resources: + * + *
+ *   accessWidener v2 named
+ *
+ *   accessible method net/minecraft/server/level/ServerPlayer getStats ()V
+ *   mutable    field  net/minecraft/world/entity/Entity id I
+ * 
+ * + * The agent discovers it automatically via your plugin's ClassLoader. + */ +public final class AccessWidenerAttacher { + + private static final Logger LOG = Logger.getLogger("AccessWidenerAttacher"); + private static final Object LOCK = new Object(); + + private static volatile boolean attached = false; + + private AccessWidenerAttacher() {} + + /** + * Attaches the access widener agent to the running JVM if it has not already + * been attached. Safe to call from multiple plugins — the agent is only + * attached once. + * + * @param agentJar the agentJar + * @throws AccessWidenerException if the agent could not be attached + */ + public static void ensureAttached(Path agentJar) throws AccessWidenerException { + if (attached) return; // fast path — no locking needed after first attach + + synchronized (LOCK) { + if (attached) return; // double-checked + + try { + attachAgent(agentJar); + attached = true; + LOG.info("[AccessWidener] Agent attached successfully."); + } catch (Exception e) { + throw new AccessWidenerException("Failed to attach access widener agent", e); + } + } + } + + /** Returns true if the agent has been successfully attached. */ + public static boolean isAttached() { + return attached; + } + + private static void attachAgent(Path agentJar) throws Exception { + // Verify the Attach API is available before trying + try { + Class.forName("com.sun.tools.attach.VirtualMachine"); + } catch (ClassNotFoundException e) { + throw new AccessWidenerException( + "The JDK Attach API is not available. " + + "Make sure you are running on a JDK (not a JRE) " + + "and add '--add-opens jdk.attach/sun.tools.attach=ALL-UNNAMED' " + + "to your JVM flags if on Java 9+.", e); + } + + String pid = String.valueOf(ProcessHandle.current().pid()); + + // Use reflection so the Attach API is not a hard compile-time dependency + Class vmClass = Class.forName("com.sun.tools.attach.VirtualMachine"); + Method attachMethod = vmClass.getMethod("attach", String.class); + Method loadAgentMethod = vmClass.getMethod("loadAgent", String.class); + Method detachMethod = vmClass.getMethod("detach"); + + Object vm = attachMethod.invoke(null, pid); + try { + loadAgentMethod.invoke(vm, agentJar.toAbsolutePath().toString()); + } finally { + detachMethod.invoke(vm); + } + } + + // ------------------------------------------------------------------------- + // Exception type + // ------------------------------------------------------------------------- + + public static class AccessWidenerException extends Exception { + public AccessWidenerException(String message, Throwable cause) { + super(message, cause); + } + public AccessWidenerException(String message) { + super(message); + } + } +} diff --git a/buildSrc/src/steamwar.devserver.gradle b/buildSrc/src/steamwar.devserver.gradle index 59e6cfbb..af6d224e 100644 --- a/buildSrc/src/steamwar.devserver.gradle +++ b/buildSrc/src/steamwar.devserver.gradle @@ -27,7 +27,6 @@ plugins { class DevServer extends DefaultTask { @Input - @Optional boolean debug = false @Input diff --git a/settings.gradle.kts b/settings.gradle.kts index b84535d4..c3da5d8d 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -162,6 +162,10 @@ dependencyResolutionManagement { } } +include( + "AccessWidener" +) + include( "BauSystem", "BauSystem:BauSystem_Main", From 961331f029edfee17c585d83a2b84c7ca50869ec Mon Sep 17 00:00:00 2001 From: YoyoNow Date: Thu, 11 Jun 2026 13:19:04 +0200 Subject: [PATCH 11/36] Fix Agent and WideningTransformer and ClassPatcher --- .../de/steamwar/AccessWidenerScanner.java | 145 ------------------ .../src/main/java/de/steamwar/Agent.java | 30 ++-- .../main/java/de/steamwar/ClassPatcher.java | 26 ++-- .../src/main/java/de/steamwar/Utils.java | 60 ++++++++ .../java/de/steamwar/WideningTransformer.java | 116 +------------- BauSystem/build.gradle.kts | 7 - 6 files changed, 105 insertions(+), 279 deletions(-) delete mode 100644 AccessWidener/src/main/java/de/steamwar/AccessWidenerScanner.java create mode 100644 AccessWidener/src/main/java/de/steamwar/Utils.java diff --git a/AccessWidener/src/main/java/de/steamwar/AccessWidenerScanner.java b/AccessWidener/src/main/java/de/steamwar/AccessWidenerScanner.java deleted file mode 100644 index 70d2c0c9..00000000 --- a/AccessWidener/src/main/java/de/steamwar/AccessWidenerScanner.java +++ /dev/null @@ -1,145 +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 . - */ - -package de.steamwar; - -import java.io.File; -import java.io.IOException; -import java.io.InputStream; -import java.lang.instrument.Instrumentation; -import java.lang.reflect.Field; -import java.net.URL; -import java.net.URLClassLoader; -import java.nio.file.Path; -import java.util.*; -import java.util.logging.Logger; -import java.util.zip.ZipEntry; -import java.util.zip.ZipInputStream; - -/** - * Scans all plugin ClassLoaders for "plugin.accesswidener" resources - * and returns the parsed entries. - */ -public final class AccessWidenerScanner { - - private static final Logger LOG = Logger.getLogger("AccessWidenerScanner"); - - private AccessWidenerScanner() { - } - - /** - * Scans every ClassLoader visible from the already-loaded classes for - */ - public static List scanAll(Instrumentation inst) { - List allEntries = new ArrayList<>(); - Set seen = Collections.newSetFromMap(new IdentityHashMap<>()); - - for (Class clazz : inst.getAllLoadedClasses()) { - ClassLoader cl = clazz.getClassLoader(); - if (cl == null) continue; // bootstrap — skip - if (!seen.add(cl)) continue; // already processed - if (!isPluginClassLoader(cl)) continue; - - allEntries.addAll(scanClassLoader(cl)); - } - - return allEntries; - } - - /** - * Uses getResources() so it finds ALL matching files on the loader's classpath. - */ - public static List scanClassLoader(ClassLoader cl) { - List entries = new ArrayList<>(); - - try { - for (URL url : findAccessWideners(cl)) { - try (InputStream in = url.openStream()) { - List parsed = AccessWidenerParser.parse(in); - LOG.info("[AccessWidener] Loaded " + parsed.size() + " entries from " + url); - entries.addAll(parsed); - } catch (IOException e) { - LOG.warning("[AccessWidener] Failed to read " + url + ": " + e.getMessage()); - } - } - } catch (IOException e) { - LOG.warning("[AccessWidener] Failed to scan " + cl + ": " + e.getMessage()); - } - - return entries; - } - - public static List findAccessWideners(ClassLoader cl) throws IOException { - List results = new ArrayList<>(); - - // Standard path — works on most Paper versions - if (cl instanceof URLClassLoader urlCl) { - return scanUrls(urlCl.getURLs()); - } - - // Newer Paper — PluginClassLoader stores the jar as a field - try { - // Try common field names used across Paper versions - for (String fieldName : List.of("file", "jarFile", "pluginJar", "source")) { - try { - Field f = cl.getClass().getDeclaredField(fieldName); - f.setAccessible(true); - Object value = f.get(cl); - if (value instanceof File file) { - return scanUrls(new URL[]{ file.toURI().toURL() }); - } - if (value instanceof Path path) { - return scanUrls(new URL[]{ path.toUri().toURL() }); - } - } catch (NoSuchFieldException ignored) {} - } - } catch (Exception e) { - LOG.warning("[AccessWidener] Could not extract JAR path from " + cl + ": " + e.getMessage()); - } - - return results; - } - - private static List scanUrls(URL[] urls) throws IOException { - List results = new ArrayList<>(); - for (URL url : urls) { - if (!url.getPath().endsWith(".jar")) continue; - try (ZipInputStream zip = new ZipInputStream(url.openStream())) { - ZipEntry entry; - while ((entry = zip.getNextEntry()) != null) { - if (entry.getName().endsWith(".accesswidener")) { - results.add(new URL("jar:" + url + "!/" + entry.getName())); - } - } - } - } - return results; - } - - /** - * Returns true if the classloader looks like a Paper plugin classloader. - * Checks by name so we don't need a direct dependency on Paper internals. - */ - public static boolean isPluginClassLoader(ClassLoader cl) { - String name = cl.getClass().getName(); - return name.contains("PluginClassLoader") // legacy Paper / Spigot - || name.contains("PaperPluginLoader") // Paper 1.20.5+ - || name.contains("PaperSimplePluginManager"); // just in case - } -} diff --git a/AccessWidener/src/main/java/de/steamwar/Agent.java b/AccessWidener/src/main/java/de/steamwar/Agent.java index a786fed8..6558508c 100644 --- a/AccessWidener/src/main/java/de/steamwar/Agent.java +++ b/AccessWidener/src/main/java/de/steamwar/Agent.java @@ -19,7 +19,11 @@ package de.steamwar; +import java.io.File; +import java.io.IOException; import java.lang.instrument.Instrumentation; +import java.util.ArrayList; +import java.util.List; import java.util.logging.Logger; /** @@ -38,14 +42,11 @@ import java.util.logging.Logger; */ public class Agent { private Agent() { - /* This utility class should not be instantiated */ + throw new IllegalStateException("Utility class"); } private static final Logger LOG = Logger.getLogger("AccessWidenerAgent"); - // Exposed so tests or other code can inspect the live transformer - static volatile WideningTransformer transformer; - // -javaagent: startup public static void premain(String args, Instrumentation inst) { init(inst); @@ -54,13 +55,22 @@ public class Agent { private static void init(Instrumentation inst) { LOG.info("[AccessWidener] Agent initialising."); - WideningTransformer t = new WideningTransformer(inst); - transformer = t; - - // --- Phase 2: register transformer for future class loads --- - // canRetransform=true so we can call retransformClasses() later - inst.addTransformer(t, true); + List entries = new ArrayList<>(); + File file = new File(new File(".").getAbsoluteFile(), "plugins/"); + File[] files = file.listFiles(); + if (files == null) files = new File[0]; + for (File jarFile : files) { + if (!jarFile.isFile()) continue; + if (!jarFile.getName().endsWith(".jar")) continue; + try { + entries.addAll(Utils.findAndParseAccessWideners(jarFile.toPath())); + } catch (IOException e) { + LOG.warning("Failed to parse access wideners from " + jarFile.getAbsolutePath()); + } + } + LOG.info("[AccessWidener] Loaded " + entries.size() + " access wideners."); + inst.addTransformer(new WideningTransformer(entries), false); LOG.info("[AccessWidener] Agent ready."); } } diff --git a/AccessWidener/src/main/java/de/steamwar/ClassPatcher.java b/AccessWidener/src/main/java/de/steamwar/ClassPatcher.java index 3e640694..a5549762 100644 --- a/AccessWidener/src/main/java/de/steamwar/ClassPatcher.java +++ b/AccessWidener/src/main/java/de/steamwar/ClassPatcher.java @@ -19,10 +19,12 @@ package de.steamwar; -import org.objectweb.asm.*; +import org.objectweb.asm.ClassReader; +import org.objectweb.asm.ClassWriter; import java.util.List; import java.util.Set; +import java.util.logging.Logger; import java.util.stream.Collectors; /** @@ -33,6 +35,8 @@ import java.util.stream.Collectors; */ public class ClassPatcher { + private static final Logger LOG = Logger.getLogger("ClassPatcher"); + private final List entries; /** Pre-computed set of targeted internal names for fast filtering. */ @@ -46,18 +50,22 @@ public class ClassPatcher { } /** - * Patches {@code classBytes} if {@code internalName} is targeted. + * Patches {@code classBytes} if {@code className} is targeted. * * @return patched bytes, or {@code null} if no changes were needed */ - public byte[] patch(String internalName, byte[] classBytes) { - if (!targets.contains(internalName)) return null; + public byte[] patch(String className, byte[] classBytes) { + if (!targets.contains(className)) return null; - ClassReader cr = new ClassReader(classBytes); - // COMPUTE_FRAMES would require the full classpath; we only touch flags so 0 is fine - ClassWriter cw = new ClassWriter(cr, 0); - cr.accept(new ClassTransformer(cw, internalName, entries), 0); - return cw.toByteArray(); + try { + ClassReader cr = new ClassReader(classBytes); + ClassWriter cw = new ClassWriter(cr, 0); + cr.accept(new ClassTransformer(cw, className, entries), ClassReader.SKIP_CODE | ClassReader.SKIP_DEBUG | ClassReader.SKIP_FRAMES); + return cw.toByteArray(); + } catch (Exception e) { + LOG.warning("[AccessWidener] Failed to transform " + className + ": " + e.getMessage()); + return null; + } } } diff --git a/AccessWidener/src/main/java/de/steamwar/Utils.java b/AccessWidener/src/main/java/de/steamwar/Utils.java new file mode 100644 index 00000000..1a5bc541 --- /dev/null +++ b/AccessWidener/src/main/java/de/steamwar/Utils.java @@ -0,0 +1,60 @@ +/* + * 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; + +import java.io.IOException; +import java.io.InputStream; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.Enumeration; +import java.util.List; +import java.util.logging.Logger; +import java.util.zip.ZipEntry; +import java.util.zip.ZipFile; + +public class Utils { + + private static final Logger LOG = Logger.getLogger("AccessWidenerAgent"); + + private Utils() { + throw new IllegalStateException("Utility class"); + } + + public static List findAndParseAccessWideners(Path jarPath) throws IOException { + List results = new ArrayList<>(); + + try (ZipFile zip = new ZipFile(jarPath.toFile())) { + Enumeration entries = zip.entries(); + while (entries.hasMoreElements()) { + ZipEntry entry = entries.nextElement(); + if (entry.isDirectory() || !entry.getName().endsWith(".accesswidener")) continue; + + try (InputStream in = zip.getInputStream(entry)) { + results.addAll(AccessWidenerParser.parse(in)); + } catch (IOException e) { + LOG.warning("[AccessWidener] Failed to parse " + entry.getName() + + " in " + jarPath.getFileName() + ": " + e.getMessage()); + } + } + } + + return results; + } +} diff --git a/AccessWidener/src/main/java/de/steamwar/WideningTransformer.java b/AccessWidener/src/main/java/de/steamwar/WideningTransformer.java index dd14a9a0..2215c769 100644 --- a/AccessWidener/src/main/java/de/steamwar/WideningTransformer.java +++ b/AccessWidener/src/main/java/de/steamwar/WideningTransformer.java @@ -19,17 +19,9 @@ package de.steamwar; -import org.objectweb.asm.ClassReader; -import org.objectweb.asm.ClassWriter; - import java.lang.instrument.ClassFileTransformer; -import java.lang.instrument.Instrumentation; -import java.lang.instrument.UnmodifiableClassException; import java.security.ProtectionDomain; -import java.util.*; -import java.util.concurrent.CopyOnWriteArrayList; -import java.util.logging.Logger; -import java.util.stream.Collectors; +import java.util.List; /** * Transforms class bytecode to apply access widening rules. @@ -39,111 +31,19 @@ import java.util.stream.Collectors; */ public class WideningTransformer implements ClassFileTransformer { - private static final Logger LOG = Logger.getLogger("WidenerTransformer"); + private final ClassPatcher patcher; - private final Instrumentation instrumentation; - - /** - * All entries collected across all plugins. Thread-safe for concurrent plugin loads. - */ - private final List entries = new CopyOnWriteArrayList<>(); - - /** - * ClassLoaders we have already scanned, to avoid re-scanning. - */ - private final Set scannedLoaders = Collections.synchronizedSet(Collections.newSetFromMap(new IdentityHashMap<>())); - - public WideningTransformer(Instrumentation instrumentation) { - this.instrumentation = instrumentation; + public WideningTransformer(List entries) { + patcher = new ClassPatcher(entries); } - /** - * Add entries — used during initial scan before the transformer is registered. - */ - public void addEntries(List newEntries) { - entries.addAll(newEntries); - } - - // ------------------------------------------------------------------------- - // ClassFileTransformer - // ------------------------------------------------------------------------- - @Override public byte[] transform(ClassLoader loader, String className, Class classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer) { - - // Check for new plugin classloaders we haven't seen yet - if (loader != null && AccessWidenerScanner.isPluginClassLoader(loader) && scannedLoaders.add(loader)) { - onNewPluginClassLoader(loader); - } - - // No entries for this class — skip transformation entirely - if (className == null) return classfileBuffer; - boolean relevant = entries.stream().anyMatch(e -> e.targets(className)); - if (!relevant) return classfileBuffer; - - // Apply widening via ASM - try { - ClassReader cr = new ClassReader(classfileBuffer); - ClassWriter cw = new ClassWriter(cr, 0); - cr.accept(new ClassTransformer(cw, className, entries), 0); - return cw.toByteArray(); - } catch (Exception e) { - LOG.warning("[AccessWidener] Failed to transform " + className + ": " + e.getMessage()); + byte[] result = patcher.patch(className, classfileBuffer); + if (result == null) { return classfileBuffer; - } - } - - // ------------------------------------------------------------------------- - // New ClassLoader detection - // ------------------------------------------------------------------------- - - private void onNewPluginClassLoader(ClassLoader loader) { - List newEntries = AccessWidenerScanner.scanClassLoader(loader); - if (newEntries.isEmpty()) return; - - entries.addAll(newEntries); - LOG.info("[AccessWidener] Picked up " + newEntries.size() + " new entr" + (newEntries.size() == 1 ? "y" : "ies") + " from " + loader); - - // Retransform already-loaded classes that are now targeted - scheduleRetransform(newEntries); - } - - /** - * Retransformation cannot be called from within a transform() call, - * so we dispatch it to a short-lived daemon thread. - */ - private void scheduleRetransform(List newEntries) { - Set targets = newEntries.stream().map(e -> e.target().replace('/', '.')).collect(Collectors.toSet()); - - Thread t = new Thread(() -> retransform(targets), "access-widener-retransform"); - t.setDaemon(true); - t.start(); - } - - private void retransform(Set dotNames) { - List toRetransform = Arrays.stream(instrumentation.getAllLoadedClasses()) - .filter(c -> dotNames.contains(c.getName())) - .filter(instrumentation::isModifiableClass) - .collect(Collectors.toList()); - - if (toRetransform.isEmpty()) return; - - LOG.info("[AccessWidener] Retransforming " + toRetransform.size() + " class(es)."); - List failed = new ArrayList<>(); - - for (Class clazz : toRetransform) { - try { - instrumentation.retransformClasses(clazz); - } catch (UnmodifiableClassException | UnsupportedOperationException e) { - failed.add(clazz.getName()); - } - } - - if (!failed.isEmpty()) { - LOG.warning("[AccessWidener] The following classes were already loaded before the agent" - + " and cannot be retransformed. Add -javaagent: to your start script" - + " to fix this:"); - failed.forEach(name -> LOG.warning(" - " + name)); + } else { + return result; } } } diff --git a/BauSystem/build.gradle.kts b/BauSystem/build.gradle.kts index b6180f3f..aa03f184 100644 --- a/BauSystem/build.gradle.kts +++ b/BauSystem/build.gradle.kts @@ -20,22 +20,15 @@ plugins { `java-library` alias(libs.plugins.shadow) - id("io.papermc.paperweight.userdev") version "1.7.1" } tasks.build { finalizedBy(tasks.shadowJar) } -repositories { - maven("https://repo.papermc.io/repository/maven-public/") -} - dependencies { implementation(project(":BauSystem:BauSystem_RegionFixed")) implementation(project(":BauSystem:BauSystem_Main")) - - paperweight.paperDevBundle("1.21.1-R0.1-SNAPSHOT") } tasks.register("DevBau21") { From eb866125ea7a8247cc1a434405063927efc846b2 Mon Sep 17 00:00:00 2001 From: YoyoNow Date: Thu, 11 Jun 2026 13:45:49 +0200 Subject: [PATCH 12/36] Remove reflection from BauSystem --- .../src/main/java/de/steamwar/Agent.java | 18 ++++---- BauSystem/BauSystem_Main/build.gradle.kts | 44 +++++++++++++++++++ .../src/bausystem.accesswidener | 5 +++ .../features/util/NoClipCommand.java | 2 +- .../bausystem/utils/PlaceItemUtils.java | 9 ++-- .../steamwar/bausystem/utils/TickManager.java | 4 +- 6 files changed, 62 insertions(+), 20 deletions(-) diff --git a/AccessWidener/src/main/java/de/steamwar/Agent.java b/AccessWidener/src/main/java/de/steamwar/Agent.java index 6558508c..e078d357 100644 --- a/AccessWidener/src/main/java/de/steamwar/Agent.java +++ b/AccessWidener/src/main/java/de/steamwar/Agent.java @@ -28,17 +28,15 @@ import java.util.logging.Logger; /** * Java agent entry point. - * - * Can be used two ways: - * 1. At JVM startup: java -javaagent:paper-access-widener-agent.jar -jar server.jar - * 2. Late attach: from inside a Paper plugin via the Attach API - * + *

+ * At JVM startup: java -javaagent:paper-access-widener-agent.jar -jar server.jar + *

* On attach the agent: - * 1. Scans all existing plugin ClassLoaders for "plugin.accesswidener" resources - * 2. Registers a ClassFileTransformer that: - * a. Applies widening to every class as it loads - * b. Detects new plugin ClassLoaders and scans them automatically - * 3. Retransforms any Minecraft/server classes that are already loaded + *

    + *
  1. Find all .jar files inside the plugins folder
  2. + *
  3. Scan all found jars for *.accesswidener resources
  4. + *
  5. Transform any class during loading
  6. + *
*/ public class Agent { private Agent() { diff --git a/BauSystem/BauSystem_Main/build.gradle.kts b/BauSystem/BauSystem_Main/build.gradle.kts index f51b33cd..f81b7614 100644 --- a/BauSystem/BauSystem_Main/build.gradle.kts +++ b/BauSystem/BauSystem_Main/build.gradle.kts @@ -30,6 +30,45 @@ java { targetCompatibility = JavaVersion.VERSION_21 } +// ─── Collect all .accesswidener files from this plugin's resources ──────────── +val accessWidenerFiles: FileCollection = fileTree("src/") { + include("**/*.accesswidener") +} + +val paperJarProvider: Provider = provider { + val dep = libs.nms.get() + configurations["compileClasspath"].resolvedConfiguration.resolvedArtifacts.first { artifact -> + artifact.moduleVersion.id.module.group == dep.module.group && artifact.moduleVersion.id.module.name == dep.module.name + }.file +} + +// ─── Widen the Paper dev JAR so the IDE / javac see the patched access ──────── +val widenedJar by tasks.registering(JavaExec::class) { + description = "Produces a widened copy of the Paper dev JAR for compile-time use." + group = "widener" + + // Re-run whenever the .accesswidener files change + inputs.file(paperJarProvider) + inputs.files(accessWidenerFiles) + + val output = layout.buildDirectory.file("widened/paper-widened.jar") + outputs.file(output) + + classpath = project(":AccessWidener").tasks.named("shadowJar").get().outputs.files + + mainClass.set("de.steamwar.Main") + + doFirst { + args = buildList { + add(paperJarProvider.get().absolutePath) + add(output.get().asFile.absolutePath) + addAll(accessWidenerFiles.map { it.absolutePath }) + } + } + + dependsOn(":AccessWidener:shadowJar") +} + dependencies { compileOnly(libs.classindex) annotationProcessor(libs.classindex) @@ -40,6 +79,7 @@ dependencies { compileOnly(libs.paperapi) compileOnly(libs.nms) + compileOnly(files(widenedJar)) compileOnly(libs.fawe) compileOnly(libs.netty) @@ -47,3 +87,7 @@ dependencies { implementation(libs.luaj) implementation(files("$projectDir/../libs/YAPION-SNAPSHOT.jar")) } + +tasks.compileJava { + dependsOn(widenedJar) +} diff --git a/BauSystem/BauSystem_Main/src/bausystem.accesswidener b/BauSystem/BauSystem_Main/src/bausystem.accesswidener index 7a0f195d..921ce434 100644 --- a/BauSystem/BauSystem_Main/src/bausystem.accesswidener +++ b/BauSystem/BauSystem_Main/src/bausystem.accesswidener @@ -1,3 +1,8 @@ accessWidener v2 named accessible field net/minecraft/server/level/ServerPlayerGameMode gameModeForPlayer Lnet/minecraft/world/level/GameType; +accessible field org/bukkit/craftbukkit/block/CraftBlockState position Lnet/minecraft/core/BlockPos; +accessible field org/bukkit/craftbukkit/block/CraftBlockState world Lorg/bukkit/craftbukkit/CraftWorld; +mutable field org/bukkit/craftbukkit/block/CraftBlockState position Lnet/minecraft/core/BlockPos; +mutable field org/bukkit/craftbukkit/block/CraftBlockState world Lorg/bukkit/craftbukkit/CraftWorld; +accessible field net/minecraft/server/ServerTickRateManager remainingSprintTicks J \ No newline at end of file diff --git a/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/util/NoClipCommand.java b/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/util/NoClipCommand.java index 6ead52d4..19bffd3f 100644 --- a/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/util/NoClipCommand.java +++ b/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/util/NoClipCommand.java @@ -102,7 +102,7 @@ public class NoClipCommand extends SWCommand implements Listener { } private void setInternalGameMode(Player player, GameMode gameMode) { - // ((CraftPlayer) player).getHandle().gameMode.gameModeForPlayer = GameType.byId(gameMode.getValue()); + ((CraftPlayer) player).getHandle().gameMode.gameModeForPlayer = GameType.byId(gameMode.getValue()); } @Register(help = true) diff --git a/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/utils/PlaceItemUtils.java b/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/utils/PlaceItemUtils.java index c4ab9a54..71f42cbb 100644 --- a/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/utils/PlaceItemUtils.java +++ b/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/utils/PlaceItemUtils.java @@ -19,7 +19,6 @@ package de.steamwar.bausystem.utils; -import de.steamwar.Reflection; import lombok.AllArgsConstructor; import lombok.Getter; import lombok.experimental.UtilityClass; @@ -86,9 +85,6 @@ public class PlaceItemUtils { .collect(Collectors.toSet()); } - private static final Reflection.Field positionAccessor = Reflection.getField(CraftBlockState.class, BlockPos.class, 0); - private static final Reflection.Field worldAccessor = Reflection.getField(CraftBlockState.class, CraftWorld.class, 0); - /** * Attempt to place an {@link ItemStack} the {@link Player} is holding against a {@link Block} inside the World. * This can be easily used inside the {@link org.bukkit.event.player.PlayerInteractEvent} to mimik placing a @@ -288,8 +284,9 @@ public class PlaceItemUtils { } else { // If a BlockState is present set the Position and World to the Block you want to place Location blockLocation = block.getLocation(); - positionAccessor.set(blockState, new BlockPos(blockLocation.getBlockX(), blockLocation.getBlockY(), blockLocation.getBlockZ())); - worldAccessor.set(blockState, blockLocation.getWorld()); + CraftBlockState craftBlockState = (CraftBlockState) blockState; + craftBlockState.position = new BlockPos(blockLocation.getBlockX(), blockLocation.getBlockY(), blockLocation.getBlockZ()); + craftBlockState.world = (CraftWorld) blockLocation.getWorld(); } if (blockData.getMaterial().isSolid()) { diff --git a/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/utils/TickManager.java b/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/utils/TickManager.java index 59d12df2..613b3ea4 100644 --- a/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/utils/TickManager.java +++ b/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/utils/TickManager.java @@ -20,7 +20,6 @@ package de.steamwar.bausystem.utils; import com.comphenix.tinyprotocol.TinyProtocol; -import de.steamwar.Reflection; import de.steamwar.bausystem.BauSystem; import net.minecraft.network.protocol.game.ClientboundTickingStatePacket; import net.minecraft.server.MinecraftServer; @@ -33,7 +32,6 @@ public class TickManager implements Listener { public static final TickManager impl = new TickManager(); private static final ServerTickRateManager manager = MinecraftServer.getServer().tickRateManager(); - private static final Reflection.Field remainingSprintTicks = Reflection.getField(ServerTickRateManager.class, long.class, 0); private boolean blockTpsPacket = true; private int totalSteps; @@ -121,7 +119,7 @@ public class TickManager implements Listener { public long getRemainingTicks() { if (isSprinting()) { - return remainingSprintTicks.get(manager); + return manager.remainingSprintTicks; } else { return manager.frozenTicksToRun(); } From 641cefad01a7897d1f3f8ea9aaf35516e0726933 Mon Sep 17 00:00:00 2001 From: YoyoNow Date: Thu, 11 Jun 2026 13:46:55 +0200 Subject: [PATCH 13/36] Add comments to bausystem.accesswidener --- BauSystem/BauSystem_Main/src/bausystem.accesswidener | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/BauSystem/BauSystem_Main/src/bausystem.accesswidener b/BauSystem/BauSystem_Main/src/bausystem.accesswidener index 921ce434..5dd89fdf 100644 --- a/BauSystem/BauSystem_Main/src/bausystem.accesswidener +++ b/BauSystem/BauSystem_Main/src/bausystem.accesswidener @@ -1,8 +1,13 @@ accessWidener v2 named +# For NoClipCommand accessible field net/minecraft/server/level/ServerPlayerGameMode gameModeForPlayer Lnet/minecraft/world/level/GameType; + +# For PlaceItemUtils accessible field org/bukkit/craftbukkit/block/CraftBlockState position Lnet/minecraft/core/BlockPos; -accessible field org/bukkit/craftbukkit/block/CraftBlockState world Lorg/bukkit/craftbukkit/CraftWorld; mutable field org/bukkit/craftbukkit/block/CraftBlockState position Lnet/minecraft/core/BlockPos; +accessible field org/bukkit/craftbukkit/block/CraftBlockState world Lorg/bukkit/craftbukkit/CraftWorld; mutable field org/bukkit/craftbukkit/block/CraftBlockState world Lorg/bukkit/craftbukkit/CraftWorld; -accessible field net/minecraft/server/ServerTickRateManager remainingSprintTicks J \ No newline at end of file + +# For TickManager +accessible field net/minecraft/server/ServerTickRateManager remainingSprintTicks J From 36b31fac77049d941278cd65a3d6c581b42e6155 Mon Sep 17 00:00:00 2001 From: YoyoNow Date: Thu, 11 Jun 2026 13:49:35 +0200 Subject: [PATCH 14/36] Remove unneeded code Cleanup build.gradle.kts --- AccessWidener/build.gradle.kts | 2 +- .../steamwar/core/AccessWidenerAttacher.java | 117 ------------------ 2 files changed, 1 insertion(+), 118 deletions(-) delete mode 100644 SpigotCore/SpigotCore_Main/src/de/steamwar/core/AccessWidenerAttacher.java diff --git a/AccessWidener/build.gradle.kts b/AccessWidener/build.gradle.kts index 87a25b4d..a6632cf1 100644 --- a/AccessWidener/build.gradle.kts +++ b/AccessWidener/build.gradle.kts @@ -19,7 +19,7 @@ plugins { `java-library` - id("com.github.johnrengelman.shadow") version "8.1.1" + alias(libs.plugins.shadow) } dependencies { diff --git a/SpigotCore/SpigotCore_Main/src/de/steamwar/core/AccessWidenerAttacher.java b/SpigotCore/SpigotCore_Main/src/de/steamwar/core/AccessWidenerAttacher.java deleted file mode 100644 index ccf61c40..00000000 --- a/SpigotCore/SpigotCore_Main/src/de/steamwar/core/AccessWidenerAttacher.java +++ /dev/null @@ -1,117 +0,0 @@ -package de.steamwar.core; - -import java.lang.reflect.Method; -import java.nio.file.Path; -import java.util.logging.Logger; - -/** - * Public API for Paper plugins that want to attach the access widener agent. - * - *

Usage

- * Call {@link #ensureAttached(Path)} from your plugin's {@code onLoad()} method - * (not {@code onEnable()} — onLoad fires before classes start loading): - * - *
{@code
- * public class MyPlugin extends JavaPlugin {
- *     @Override
- *     public void onLoad() {
- *         try {
- *             AccessWidenerAttacher.ensureAttached(getDataFolder());
- *         } catch (Exception e) {
- *             getSLF4JLogger().error("Failed to attach access widener agent", e);
- *         }
- *     }
- * }
- * }
- * - *

Resource file

- * Drop a {@code plugin.accesswidener} file in your plugin's resources: - * - *
- *   accessWidener v2 named
- *
- *   accessible method net/minecraft/server/level/ServerPlayer getStats ()V
- *   mutable    field  net/minecraft/world/entity/Entity id I
- * 
- * - * The agent discovers it automatically via your plugin's ClassLoader. - */ -public final class AccessWidenerAttacher { - - private static final Logger LOG = Logger.getLogger("AccessWidenerAttacher"); - private static final Object LOCK = new Object(); - - private static volatile boolean attached = false; - - private AccessWidenerAttacher() {} - - /** - * Attaches the access widener agent to the running JVM if it has not already - * been attached. Safe to call from multiple plugins — the agent is only - * attached once. - * - * @param agentJar the agentJar - * @throws AccessWidenerException if the agent could not be attached - */ - public static void ensureAttached(Path agentJar) throws AccessWidenerException { - if (attached) return; // fast path — no locking needed after first attach - - synchronized (LOCK) { - if (attached) return; // double-checked - - try { - attachAgent(agentJar); - attached = true; - LOG.info("[AccessWidener] Agent attached successfully."); - } catch (Exception e) { - throw new AccessWidenerException("Failed to attach access widener agent", e); - } - } - } - - /** Returns true if the agent has been successfully attached. */ - public static boolean isAttached() { - return attached; - } - - private static void attachAgent(Path agentJar) throws Exception { - // Verify the Attach API is available before trying - try { - Class.forName("com.sun.tools.attach.VirtualMachine"); - } catch (ClassNotFoundException e) { - throw new AccessWidenerException( - "The JDK Attach API is not available. " - + "Make sure you are running on a JDK (not a JRE) " - + "and add '--add-opens jdk.attach/sun.tools.attach=ALL-UNNAMED' " - + "to your JVM flags if on Java 9+.", e); - } - - String pid = String.valueOf(ProcessHandle.current().pid()); - - // Use reflection so the Attach API is not a hard compile-time dependency - Class vmClass = Class.forName("com.sun.tools.attach.VirtualMachine"); - Method attachMethod = vmClass.getMethod("attach", String.class); - Method loadAgentMethod = vmClass.getMethod("loadAgent", String.class); - Method detachMethod = vmClass.getMethod("detach"); - - Object vm = attachMethod.invoke(null, pid); - try { - loadAgentMethod.invoke(vm, agentJar.toAbsolutePath().toString()); - } finally { - detachMethod.invoke(vm); - } - } - - // ------------------------------------------------------------------------- - // Exception type - // ------------------------------------------------------------------------- - - public static class AccessWidenerException extends Exception { - public AccessWidenerException(String message, Throwable cause) { - super(message, cause); - } - public AccessWidenerException(String message) { - super(message); - } - } -} From f5d9c6e1758fce566876de03c638c128571a4a26 Mon Sep 17 00:00:00 2001 From: YoyoNow Date: Thu, 11 Jun 2026 18:26:15 +0200 Subject: [PATCH 15/36] Unreflect everything --- .../main/java/de/steamwar/ClassPatcher.java | 6 ++ .../java/de/steamwar/ClassTransformer.java | 10 +++ .../fightsystem/listener/Recording.java | 15 ++-- SpigotCore/SpigotCore_Main/build.gradle.kts | 44 ++++++++++++ .../comphenix/tinyprotocol/TinyProtocol.java | 21 ++---- .../de/steamwar/core/BountifulWrapper.java | 49 ------------- .../de/steamwar/core/CheckpointUtilsJ9.java | 14 ++-- .../src/de/steamwar/core/ErrorHandler.java | 5 +- .../src/de/steamwar/core/WorldIdentifier.java | 8 +-- .../SteamwarGameProfileRepository.java | 9 +-- .../src/de/steamwar/entity/REntity.java | 70 ++++--------------- .../src/de/steamwar/techhider/ChunkHider.java | 44 ++++-------- .../de/steamwar/techhider/ProtocolUtils.java | 20 ++---- .../src/de/steamwar/techhider/TechHider.java | 23 ++---- .../steamwar/techhider/legacy/ChunkHider.java | 40 ++++------- .../techhider/legacy/ProtocolWrapper.java | 33 +++------ .../steamwar/techhider/legacy/TechHider.java | 54 ++++++-------- .../src/spigotcore.accesswidener | 57 +++++++++++++++ 18 files changed, 217 insertions(+), 305 deletions(-) create mode 100644 SpigotCore/SpigotCore_Main/src/spigotcore.accesswidener diff --git a/AccessWidener/src/main/java/de/steamwar/ClassPatcher.java b/AccessWidener/src/main/java/de/steamwar/ClassPatcher.java index a5549762..2cce0c77 100644 --- a/AccessWidener/src/main/java/de/steamwar/ClassPatcher.java +++ b/AccessWidener/src/main/java/de/steamwar/ClassPatcher.java @@ -26,6 +26,7 @@ import java.util.List; import java.util.Set; import java.util.logging.Logger; import java.util.stream.Collectors; +import java.util.stream.Stream; /** * Uses ASM to patch class bytecode according to a list of access widener entries. @@ -46,6 +47,11 @@ public class ClassPatcher { this.entries = entries; this.targets = entries.stream() .map(AccessWidenerEntry::target) + .flatMap(s -> { + if (!s.contains("$")) return Stream.of(s); + int index = s.lastIndexOf('$'); + return Stream.of(s, s.substring(0, index)); + }) .collect(Collectors.toSet()); } diff --git a/AccessWidener/src/main/java/de/steamwar/ClassTransformer.java b/AccessWidener/src/main/java/de/steamwar/ClassTransformer.java index c329b966..ccc3df9d 100644 --- a/AccessWidener/src/main/java/de/steamwar/ClassTransformer.java +++ b/AccessWidener/src/main/java/de/steamwar/ClassTransformer.java @@ -47,6 +47,16 @@ public class ClassTransformer extends ClassVisitor { super.visit(version, newAccess, name, signature, superName, interfaces); } + @Override + public void visitInnerClass(String name, String outerName, String innerName, int access) { + int newAccess = access; + for (AccessWidenerEntry e : entries) { + if (!e.target().equals(name) || !"class".equals(e.memberType())) continue; + newAccess = applyDirective(e.directive(), newAccess, false); + } + super.visitInnerClass(name, outerName, innerName, newAccess); + } + @Override public MethodVisitor visitMethod(int access, String name, String descriptor, String signature, String[] exceptions) { int newAccess = access; diff --git a/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/listener/Recording.java b/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/listener/Recording.java index 0187d089..d11c98db 100644 --- a/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/listener/Recording.java +++ b/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/listener/Recording.java @@ -115,18 +115,18 @@ public class Recording implements Listener { }.register(); new StateDependent(ArenaMode.AntiReplay, FightState.Ingame) { private final BiFunction place = Recording.this::blockPlace; - private final BiFunction dig = Recording.this::blockDig; + private final BiFunction dig = Recording.this::blockDig; @Override public void enable() { TinyProtocol.instance.addFilter(ServerboundUseItemPacket.class, place); - TinyProtocol.instance.addFilter(blockDigPacket, dig); + TinyProtocol.instance.addFilter(ServerboundPlayerActionPacket.class, dig); } @Override public void disable() { TinyProtocol.instance.removeFilter(ServerboundUseItemPacket.class, place); - TinyProtocol.instance.removeFilter(blockDigPacket, dig); + TinyProtocol.instance.removeFilter(ServerboundPlayerActionPacket.class, dig); } }.register(); new StateDependentTask(ArenaMode.AntiReplay, FightState.All, () -> { @@ -143,13 +143,8 @@ public class Recording implements Listener { GlobalRecorder.getInstance().entitySpeed(entity); } - private static final Class> blockDigPacket = ServerboundPlayerActionPacket.class; - private static final Class playerDigType = blockDigPacket.getDeclaredClasses()[0]; - private static final Reflection.Field blockDigType = Reflection.getField(blockDigPacket, playerDigType, 0); - private static final Object releaseUseItem = playerDigType.getEnumConstants()[5]; - - private Object blockDig(Player p, Object packet) { - if (!isNotSent(p) && blockDigType.get(packet) == releaseUseItem) { + private Object blockDig(Player p, ServerboundPlayerActionPacket packet) { + if (!isNotSent(p) && packet.getAction() == ServerboundPlayerActionPacket.Action.RELEASE_USE_ITEM) { GlobalRecorder.getInstance().bowSpan(p, false, false); } return packet; diff --git a/SpigotCore/SpigotCore_Main/build.gradle.kts b/SpigotCore/SpigotCore_Main/build.gradle.kts index a5e3227d..34bc1d49 100644 --- a/SpigotCore/SpigotCore_Main/build.gradle.kts +++ b/SpigotCore/SpigotCore_Main/build.gradle.kts @@ -42,6 +42,45 @@ tasks.withType().configureEach { jvmArgs("--add-exports", "java.base/jdk.internal.misc=ALL-UNNAMED") } +// ─── Collect all .accesswidener files from this plugin's resources ──────────── +val accessWidenerFiles: FileCollection = fileTree("src/") { + include("**/*.accesswidener") +} + +val paperJarProvider: Provider = provider { + val dep = libs.nms.get() + configurations["compileClasspath"].resolvedConfiguration.resolvedArtifacts.first { artifact -> + artifact.moduleVersion.id.module.group == dep.module.group && artifact.moduleVersion.id.module.name == dep.module.name + }.file +} + +// ─── Widen the Paper dev JAR so the IDE / javac see the patched access ──────── +val widenedJar by tasks.registering(JavaExec::class) { + description = "Produces a widened copy of the Paper dev JAR for compile-time use." + group = "widener" + + // Re-run whenever the .accesswidener files change + inputs.file(paperJarProvider) + inputs.files(accessWidenerFiles) + + val output = layout.buildDirectory.file("widened/paper-widened.jar") + outputs.file(output) + + classpath = project(":AccessWidener").tasks.named("shadowJar").get().outputs.files + + mainClass.set("de.steamwar.Main") + + doFirst { + args = buildList { + add(paperJarProvider.get().absolutePath) + add(output.get().asFile.absolutePath) + addAll(accessWidenerFiles.map { it.absolutePath }) + } + } + + dependsOn(":AccessWidener:shadowJar") +} + dependencies { compileOnly(libs.classindex) annotationProcessor(libs.classindex) @@ -53,6 +92,7 @@ dependencies { compileOnly(libs.paperapi) compileOnly(libs.nms) + compileOnly(files(widenedJar)) compileOnly(libs.authlib) compileOnly(libs.datafixer) compileOnly(libs.netty) @@ -61,3 +101,7 @@ dependencies { implementation(libs.anvilgui) } + +tasks.compileJava { + dependsOn(widenedJar) +} diff --git a/SpigotCore/SpigotCore_Main/src/com/comphenix/tinyprotocol/TinyProtocol.java b/SpigotCore/SpigotCore_Main/src/com/comphenix/tinyprotocol/TinyProtocol.java index f7df16c1..5391f59b 100644 --- a/SpigotCore/SpigotCore_Main/src/com/comphenix/tinyprotocol/TinyProtocol.java +++ b/SpigotCore/SpigotCore_Main/src/com/comphenix/tinyprotocol/TinyProtocol.java @@ -1,7 +1,6 @@ package com.comphenix.tinyprotocol; import com.google.common.collect.MapMaker; -import de.steamwar.Reflection; import de.steamwar.core.CRIUWakeupEvent; import de.steamwar.core.Core; import io.netty.channel.*; @@ -180,22 +179,12 @@ public class TinyProtocol { networkManagers = serverConnection.getConnections(); // We need to synchronize against this list createServerChannelHandler(); + for (ChannelFuture item : serverConnection.channels) { + // Channel future that contains the server connection + Channel serverChannel = item.channel(); - // Find the correct list, or implicitly throw an exception - boolean looking = true; - for (int i = 0; looking; i++) { - List list = Reflection.getField(serverConnection.getClass(), List.class, i).get(serverConnection); - - for (Object item : list) { - if (!(item instanceof ChannelFuture)) break; - - // Channel future that contains the server connection - Channel serverChannel = ((ChannelFuture) item).channel(); - - serverChannels.add(serverChannel); - serverChannel.pipeline().addFirst(serverChannelHandler); - looking = false; - } + serverChannels.add(serverChannel); + serverChannel.pipeline().addFirst(serverChannelHandler); } } diff --git a/SpigotCore/SpigotCore_Main/src/de/steamwar/core/BountifulWrapper.java b/SpigotCore/SpigotCore_Main/src/de/steamwar/core/BountifulWrapper.java index e5a69d9f..64bef9b5 100644 --- a/SpigotCore/SpigotCore_Main/src/de/steamwar/core/BountifulWrapper.java +++ b/SpigotCore/SpigotCore_Main/src/de/steamwar/core/BountifulWrapper.java @@ -26,13 +26,9 @@ import net.minecraft.network.syncher.EntityDataAccessor; import net.minecraft.network.syncher.EntityDataSerializer; import net.minecraft.network.syncher.EntityDataSerializers; import net.minecraft.network.syncher.SynchedEntityData; -import net.minecraft.world.entity.PositionMoveRotation; -import net.minecraft.world.phys.Vec3; import org.bukkit.Sound; import org.bukkit.entity.Player; -import java.util.UUID; - public class BountifulWrapper { public static final BountifulWrapper impl = new BountifulWrapper(); @@ -55,49 +51,4 @@ public class BountifulWrapper { public Object getDataWatcherItem(Object dwo, Object value) { return new SynchedEntityData.DataItem<>((EntityDataAccessor) dwo, value); } - - public BountifulWrapper.PositionSetter getPositionSetter(Class packetClass, int fieldOffset) { - try { - Reflection.Field field = Reflection.getField(packetClass, PositionMoveRotation.class, 0); - - return (packet, x, y, z, pitch, yaw) -> { - field.set(packet, new PositionMoveRotation(new Vec3(x, y, z), field.get(packet).deltaMovement(), yaw, pitch)); - }; - } catch (IllegalArgumentException e) { - Reflection.Field posX = Reflection.getField(packetClass, double.class, fieldOffset); - Reflection.Field posY = Reflection.getField(packetClass, double.class, fieldOffset + 1); - Reflection.Field posZ = Reflection.getField(packetClass, double.class, fieldOffset + 2); - boolean isByteClass = packetClass.getSimpleName().contains("PacketPlayOutEntityTeleport") || packetClass.getSimpleName().contains("PacketPlayOutNamedEntitySpawn"); - Class pitchYawType = isByteClass ? byte.class : int.class; - Reflection.Field lookYaw = Reflection.getField(packetClass, pitchYawType, isByteClass ? 0 : 1); - Reflection.Field lookPitch = Reflection.getField(packetClass, pitchYawType, isByteClass ? 1 : 2); - - return (packet, x, y, z, pitch, yaw) -> { - posX.set(packet, x); - posY.set(packet, y); - posZ.set(packet, z); - if (isByteClass) { - lookYaw.set(packet, (byte) (yaw * 256 / 360)); - lookPitch.set(packet, (byte) (pitch * 256 / 360)); - } else { - lookYaw.set(packet, (int) (yaw * 256 / 360)); - lookPitch.set(packet, (int) (pitch * 256 / 360)); - } - }; - } - } - - public BountifulWrapper.UUIDSetter getUUIDSetter(Class packetClass) { - Reflection.Field uuidField = Reflection.getField(packetClass, UUID.class, 0); - - return uuidField::set; - } - - public interface PositionSetter { - void set(Object packet, double x, double y, double z, float pitch, float yaw); - } - - public interface UUIDSetter { - void set(Object packet, UUID uuid); - } } diff --git a/SpigotCore/SpigotCore_Main/src/de/steamwar/core/CheckpointUtilsJ9.java b/SpigotCore/SpigotCore_Main/src/de/steamwar/core/CheckpointUtilsJ9.java index 4ccbaaa7..2849b5a4 100644 --- a/SpigotCore/SpigotCore_Main/src/de/steamwar/core/CheckpointUtilsJ9.java +++ b/SpigotCore/SpigotCore_Main/src/de/steamwar/core/CheckpointUtilsJ9.java @@ -20,7 +20,6 @@ package de.steamwar.core; import com.comphenix.tinyprotocol.TinyProtocol; -import de.steamwar.Reflection; import de.steamwar.sql.internal.Statement; import io.netty.channel.ChannelFuture; import net.minecraft.server.MinecraftServer; @@ -96,9 +95,6 @@ class CheckpointUtilsJ9 { } } - - private static final Reflection.Field channelFutures = Reflection.getField(ServerConnectionListener.class, List.class, 0, ChannelFuture.class); - private static void freezeInternal(Path path) throws Exception { Bukkit.getPluginManager().callEvent(new CRIUSleepEvent()); @@ -109,9 +105,9 @@ class CheckpointUtilsJ9 { // Close socket ServerConnectionListener serverConnection = MinecraftServer.getServer().getConnection(); - List channels = channelFutures.get(serverConnection); - for (Object future : channels) { - ((ChannelFuture) future).channel().close().syncUninterruptibly(); + List channels = serverConnection.channels; + for (ChannelFuture future : channels) { + future.channel().close().syncUninterruptibly(); } channels.clear(); @@ -145,8 +141,8 @@ class CheckpointUtilsJ9 { // Reopen socket serverConnection.startTcpServerListener(InetAddress.getLoopbackAddress(), port); - for (Object future : channels) { - ((ChannelFuture) future).channel().config().setAutoRead(true); + for (ChannelFuture future : channels) { + future.channel().config().setAutoRead(true); } Bukkit.getPluginManager().callEvent(new CRIUWakeupEvent()); diff --git a/SpigotCore/SpigotCore_Main/src/de/steamwar/core/ErrorHandler.java b/SpigotCore/SpigotCore_Main/src/de/steamwar/core/ErrorHandler.java index f60eb406..5995a3c1 100644 --- a/SpigotCore/SpigotCore_Main/src/de/steamwar/core/ErrorHandler.java +++ b/SpigotCore/SpigotCore_Main/src/de/steamwar/core/ErrorHandler.java @@ -19,7 +19,6 @@ package de.steamwar.core; -import de.steamwar.Reflection; import de.steamwar.sql.SWException; import org.spigotmc.WatchdogThread; @@ -39,9 +38,7 @@ public class ErrorHandler extends Handler { public ErrorHandler() { Logger.getLogger("").addHandler(this); - - Reflection.Field getInstance = Reflection.getField(WatchdogThread.class, WatchdogThread.class, 0); - watchdogThreadId = getInstance.get(null).getId(); + watchdogThreadId = WatchdogThread.instance.threadId(); } void unregister() { diff --git a/SpigotCore/SpigotCore_Main/src/de/steamwar/core/WorldIdentifier.java b/SpigotCore/SpigotCore_Main/src/de/steamwar/core/WorldIdentifier.java index 2ca9d085..2fa27b1e 100644 --- a/SpigotCore/SpigotCore_Main/src/de/steamwar/core/WorldIdentifier.java +++ b/SpigotCore/SpigotCore_Main/src/de/steamwar/core/WorldIdentifier.java @@ -20,7 +20,6 @@ package de.steamwar.core; import com.comphenix.tinyprotocol.TinyProtocol; -import de.steamwar.Reflection; import de.steamwar.linkage.Linked; import net.minecraft.network.protocol.game.ClientboundLoginPacket; import net.minecraft.network.protocol.game.CommonPlayerSpawnInfo; @@ -33,13 +32,8 @@ public class WorldIdentifier { private static ResourceKey resourceKey = null; - private static final Class resourceKeyClass = ResourceKey.class; - private static final Class minecraftKeyClass = ResourceLocation.class; - private static final Reflection.Constructor resourceKeyConstructor = Reflection.getConstructor(resourceKeyClass, minecraftKeyClass, minecraftKeyClass); - private static final Reflection.Constructor minecraftKeyConstructor = Reflection.getConstructor(minecraftKeyClass, String.class, String.class); - public static void set(String name) { - resourceKey = (ResourceKey) resourceKeyConstructor.invoke(minecraftKeyConstructor.invoke("minecraft", "dimension"), minecraftKeyConstructor.invoke("steamwar", name)); + resourceKey = new ResourceKey<>(new ResourceLocation("minecraft", "dimension"), new ResourceLocation("steamwar", name)); } public WorldIdentifier() { diff --git a/SpigotCore/SpigotCore_Main/src/de/steamwar/core/authlib/SteamwarGameProfileRepository.java b/SpigotCore/SpigotCore_Main/src/de/steamwar/core/authlib/SteamwarGameProfileRepository.java index 32e394e7..dd7540c6 100644 --- a/SpigotCore/SpigotCore_Main/src/de/steamwar/core/authlib/SteamwarGameProfileRepository.java +++ b/SpigotCore/SpigotCore_Main/src/de/steamwar/core/authlib/SteamwarGameProfileRepository.java @@ -22,7 +22,6 @@ package de.steamwar.core.authlib; import com.mojang.authlib.GameProfile; import com.mojang.authlib.GameProfileRepository; import com.mojang.authlib.ProfileLookupCallback; -import de.steamwar.Reflection; import de.steamwar.sql.SteamwarUser; import net.minecraft.server.MinecraftServer; import net.minecraft.server.Services; @@ -35,13 +34,10 @@ public class SteamwarGameProfileRepository implements GameProfileRepository { public static final SteamwarGameProfileRepository impl = new SteamwarGameProfileRepository(); private static final GameProfileRepository fallback; - private static final Reflection.Field field; private static final Services current; static { - Class clazz = MinecraftServer.getServer().getClass(); - field = Reflection.getField(clazz, Services.class, 0); - current = field.get(MinecraftServer.getServer()); + current = MinecraftServer.getServer().services; fallback = current.profileRepository(); } @@ -68,7 +64,6 @@ public class SteamwarGameProfileRepository implements GameProfileRepository { } public void inject() { - Services newServices = new Services(current.sessionService(), current.servicesKeySet(), this, current.profileCache(), current.paperConfigurations()); - field.set(MinecraftServer.getServer(), newServices); + MinecraftServer.getServer().services = new Services(current.sessionService(), current.servicesKeySet(), this, current.profileCache(), current.paperConfigurations()); } } diff --git a/SpigotCore/SpigotCore_Main/src/de/steamwar/entity/REntity.java b/SpigotCore/SpigotCore_Main/src/de/steamwar/entity/REntity.java index 8ac0c01b..2d867cc1 100644 --- a/SpigotCore/SpigotCore_Main/src/de/steamwar/entity/REntity.java +++ b/SpigotCore/SpigotCore_Main/src/de/steamwar/entity/REntity.java @@ -44,7 +44,6 @@ import org.bukkit.inventory.ItemStack; import java.util.*; import java.util.function.Consumer; -import java.util.function.Function; public class REntity { @@ -174,14 +173,10 @@ public class REntity { server.postEntityMove(this, fromX, fromZ); } - private static final Class animationPacket = ClientboundAnimatePacket.class; - private static final Reflection.Field animationEntity = Reflection.getField(animationPacket, int.class, 5); - private static final Reflection.Field animationAnimation = Reflection.getField(animationPacket, int.class, 6); - public void showAnimation(byte animation) { - Object packet = Reflection.newInstance(animationPacket); - animationEntity.set(packet, entityId); - animationAnimation.set(packet, (int) animation); + ClientboundAnimatePacket packet = (ClientboundAnimatePacket) Reflection.newInstance(ClientboundAnimatePacket.class); + packet.id = entityId; + packet.action = animation; server.updateEntity(this, packet); } @@ -189,14 +184,10 @@ public class REntity { server.updateEntity(this, new ClientboundSetEntityMotionPacket(entityId, new Vec3(calcVelocity(dX), calcVelocity(dY), calcVelocity(dZ)))); } - private static final Class statusPacket = ClientboundEntityEventPacket.class; - private static final Reflection.Field statusEntity = Reflection.getField(statusPacket, int.class, 0); - private static final Reflection.Field statusStatus = Reflection.getField(statusPacket, byte.class, 0); - public void showDamage() { - Object packet = Reflection.newInstance(statusPacket); - statusEntity.set(packet, entityId); - statusStatus.set(packet, (byte) 2); + ClientboundEntityEventPacket packet = (ClientboundEntityEventPacket) Reflection.newInstance(ClientboundEntityEventPacket.class); + packet.entityId = entityId; + packet.eventId = (byte) 2; server.updateEntity(this, packet); } @@ -251,14 +242,10 @@ public class REntity { server.updateEntity(this, getDataWatcherPacket(entityStatusWatcher, getEntityStatus())); } - private static final Function spawnPacketGenerator = entitySpawnPacketGenerator(ClientboundAddEntityPacket.class, 2); - - private static final Reflection.Field additionalData = Reflection.getField(ClientboundAddEntityPacket.class, int.class, 4); - - private Object spawnPacketGenerator() { - Object packet = spawnPacketGenerator.apply(this); - additionalData.set(packet, objectData); - return packet; + private ClientboundAddEntityPacket spawnPacketGenerator() { + ResourceLocation key = CraftNamespacedKey.toMinecraft(entityType.getKey()); + net.minecraft.world.entity.EntityType entityType = BuiltInRegistries.ENTITY_TYPE.get(key).get().value(); + return new ClientboundAddEntityPacket(entityId, uuid, x, y, z, pitch, yaw, entityType, objectData, Vec3.ZERO, 0); } void list(Consumer packetSink) { @@ -359,14 +346,10 @@ public class REntity { } } - private static final Class headRotationPacket = ClientboundRotateHeadPacket.class; - private static final Reflection.Field headRotationEntity = Reflection.getField(headRotationPacket, int.class, 0); - private static final Reflection.Field headRotationYaw = Reflection.getField(headRotationPacket, byte.class, 0); - private Object getHeadRotationPacket() { - Object packet = Reflection.newInstance(headRotationPacket); - headRotationEntity.set(packet, entityId); - headRotationYaw.set(packet, headYaw); + ClientboundRotateHeadPacket packet = (ClientboundRotateHeadPacket) Reflection.newInstance(ClientboundRotateHeadPacket.class); + packet.entityId = entityId; + packet.yHeadRot = headYaw; return packet; } @@ -374,33 +357,6 @@ public class REntity { return new ClientboundSetEquipmentPacket(entityId, Collections.singletonList(Pair.of((EquipmentSlot) slot, CraftItemStack.asNMSCopy(stack)))); } - private static final Reflection.Field spawnType = Reflection.getField(ClientboundAddEntityPacket.class, net.minecraft.world.entity.EntityType.class, 0); - - private static Function entitySpawnPacketGenerator(Class spawnPacket, int posOffset) { - BountifulWrapper.UUIDSetter uuid = BountifulWrapper.impl.getUUIDSetter(spawnPacket); - Function packetGenerator = spawnPacketGenerator(spawnPacket, posOffset); - - return entity -> { - Object packet = packetGenerator.apply(entity); - uuid.set(packet, entity.uuid); - ResourceLocation key = CraftNamespacedKey.toMinecraft(entity.entityType.getKey()); - spawnType.set(packet, BuiltInRegistries.ENTITY_TYPE.get(key).get().value()); - return packet; - }; - } - - protected static Function spawnPacketGenerator(Class spawnPacket, int posOffset) { - Reflection.Field entityId = Reflection.getField(spawnPacket, int.class, 0); - BountifulWrapper.PositionSetter position = BountifulWrapper.impl.getPositionSetter(spawnPacket, posOffset); - - return entity -> { - Object packet = Reflection.newInstance(spawnPacket); - entityId.set(packet, entity.entityId); - position.set(packet, entity.x, entity.y, entity.z, entity.pitch, entity.yaw); - return packet; - }; - } - private byte rotToByte(float rot) { return (byte) ((int) (rot * 256.0F / 360.0F)); } diff --git a/SpigotCore/SpigotCore_Main/src/de/steamwar/techhider/ChunkHider.java b/SpigotCore/SpigotCore_Main/src/de/steamwar/techhider/ChunkHider.java index 3d7fa97c..471cdc4a 100644 --- a/SpigotCore/SpigotCore_Main/src/de/steamwar/techhider/ChunkHider.java +++ b/SpigotCore/SpigotCore_Main/src/de/steamwar/techhider/ChunkHider.java @@ -19,10 +19,8 @@ package de.steamwar.techhider; -import de.steamwar.Reflection; import io.netty.buffer.ByteBuf; import io.netty.buffer.Unpooled; -import net.minecraft.core.SectionPos; import net.minecraft.network.protocol.game.ClientboundLevelChunkPacketData; import net.minecraft.network.protocol.game.ClientboundLevelChunkWithLightPacket; import net.minecraft.util.SimpleBitStorage; @@ -35,13 +33,8 @@ import java.util.List; import java.util.function.UnaryOperator; public class ChunkHider { - private static final UnaryOperator chunkPacketShallowCloner = ProtocolUtils.shallowCloneGenerator(ClientboundLevelChunkWithLightPacket.class); - private static final UnaryOperator chunkDataShallowCloner = ProtocolUtils.shallowCloneGenerator(ClientboundLevelChunkPacketData.class); - - private static final Reflection.Field levelChunkPacketDataField = Reflection.getField(ClientboundLevelChunkWithLightPacket.class, ClientboundLevelChunkPacketData.class, 0); - - private static final Reflection.Field chunkBlockDataField = Reflection.getField(ClientboundLevelChunkPacketData.class, byte[].class, 0); - private static final Reflection.Field chunkBlockEntitiesDataField = Reflection.getField(ClientboundLevelChunkPacketData.class, List.class, 0); + private static final UnaryOperator chunkPacketShallowCloner = ProtocolUtils.shallowCloneGenerator(ClientboundLevelChunkWithLightPacket.class); + private static final UnaryOperator chunkDataShallowCloner = ProtocolUtils.shallowCloneGenerator(ClientboundLevelChunkPacketData.class); private final int SECTION_SPAN_SIZE = 16; private final byte BIT_PER_BLOCK_INDIRECTION_LIMIT = 8; @@ -178,8 +171,8 @@ public class ChunkHider { byte[] data = new byte[out.readableBytes()]; out.readBytes(data); - List blockEntities = chunkBlockEntitiesDataField.get(chunkData); - List filteredBlockEntities = filterBlockEntities(player, blockEntities, chunkX, chunkZ); + List blockEntities = chunkData.blockEntitiesData; + List filteredBlockEntities = filterBlockEntities(player, blockEntities, chunkX, chunkZ); return buildNewChunkPacket(packet, data, filteredBlockEntities); @@ -237,33 +230,24 @@ public class ChunkHider { return reEncodedData.getRaw(); } - private ClientboundLevelChunkWithLightPacket buildNewChunkPacket(ClientboundLevelChunkWithLightPacket originalPacket, byte[] newBlockDataBuffer, List newBlockEntities) { - ClientboundLevelChunkWithLightPacket clonedPacket = (ClientboundLevelChunkWithLightPacket) chunkPacketShallowCloner.apply(originalPacket); - ClientboundLevelChunkPacketData clonedPacketChunkData = (ClientboundLevelChunkPacketData) chunkDataShallowCloner.apply(originalPacket.getChunkData()); + private ClientboundLevelChunkWithLightPacket buildNewChunkPacket(ClientboundLevelChunkWithLightPacket originalPacket, byte[] newBlockDataBuffer, List newBlockEntities) { + ClientboundLevelChunkWithLightPacket clonedPacket = chunkPacketShallowCloner.apply(originalPacket); + ClientboundLevelChunkPacketData clonedPacketChunkData = chunkDataShallowCloner.apply(originalPacket.getChunkData()); - chunkBlockDataField.set(clonedPacketChunkData, newBlockDataBuffer); - chunkBlockEntitiesDataField.set(clonedPacketChunkData, newBlockEntities); - - levelChunkPacketDataField.set(clonedPacket, clonedPacketChunkData); + clonedPacketChunkData.buffer = newBlockDataBuffer; + clonedPacketChunkData.blockEntitiesData = newBlockEntities; + clonedPacket.chunkData = clonedPacketChunkData; return clonedPacket; } - - private static final Class blockEntitiyInfoClass = Reflection.getClass("net.minecraft.network.protocol.game.ClientboundLevelChunkPacketData$BlockEntityInfo"); - - private static final Reflection.Field blockEntityInfoTypeField = Reflection.getField(blockEntitiyInfoClass, BlockEntityType.class, 0); - private static final Reflection.Field packedXZField = Reflection.getField(blockEntitiyInfoClass, int.class, 0); - private static final Reflection.Field yField = Reflection.getField(blockEntitiyInfoClass, int.class, 1); - - private List filterBlockEntities(Player player, List blockEntities, int chunkX, int chunkZ) { + private List filterBlockEntities(Player player, List blockEntities, int chunkX, int chunkZ) { int fourBitBitmask = 0b0000_1111; return blockEntities.stream() .filter((blockEntityInfo) -> { - BlockEntityType type = blockEntityInfoTypeField.get(blockEntityInfo); - - int packedXZ = packedXZField.get(blockEntityInfo); + BlockEntityType type = blockEntityInfo.type; + int packedXZ = blockEntityInfo.packedXZ; int localX = (packedXZ >> 4) & fourBitBitmask; int localZ = packedXZ & fourBitBitmask; @@ -271,7 +255,7 @@ public class ChunkHider { int worldX = (chunkX * SECTION_SPAN_SIZE) + localX; int worldZ = (chunkZ * SECTION_SPAN_SIZE) + localZ; - int worldY = yField.get(blockEntityInfo); + int worldY = blockEntityInfo.y; return accessPrivilegeProvider.isPlayerPrivilegedToAccessPosition(player, worldX, worldY, worldZ) && accessPrivilegeProvider.isPlayerPrivilegedToAccessBlockEntity(player, worldX, worldY, worldZ, type); }).toList(); diff --git a/SpigotCore/SpigotCore_Main/src/de/steamwar/techhider/ProtocolUtils.java b/SpigotCore/SpigotCore_Main/src/de/steamwar/techhider/ProtocolUtils.java index e71b522a..b97fe828 100644 --- a/SpigotCore/SpigotCore_Main/src/de/steamwar/techhider/ProtocolUtils.java +++ b/SpigotCore/SpigotCore_Main/src/de/steamwar/techhider/ProtocolUtils.java @@ -50,33 +50,23 @@ public class ProtocolUtils { }; } - public static UnaryOperator shallowCloneGenerator(Class clazz) { - BiConsumer filler = shallowFill(clazz); + public static UnaryOperator shallowCloneGenerator(Class clazz) { + BiConsumer filler = shallowFill(clazz); return source -> { - Object clone = Reflection.newInstance(clazz); + T clone = (T) Reflection.newInstance(clazz); filler.accept(source, clone); return clone; }; } - public static UnaryOperator shallowTypedCloneGenerator(Class clazz) { - BiConsumer filler = shallowFill(clazz); - - return source -> { - Object clone = Reflection.newInstance(clazz); - filler.accept(source, clone); - return (T) clone; - }; - } - - private static BiConsumer shallowFill(Class clazz) { + private static BiConsumer shallowFill(Class clazz) { if (clazz == null) { return (source, clone) -> { }; } - BiConsumer superFiller = shallowFill(clazz.getSuperclass()); + BiConsumer superFiller = shallowFill(clazz.getSuperclass()); Field[] fds = clazz.getDeclaredFields(); List fields = new ArrayList<>(); diff --git a/SpigotCore/SpigotCore_Main/src/de/steamwar/techhider/TechHider.java b/SpigotCore/SpigotCore_Main/src/de/steamwar/techhider/TechHider.java index 1539f16d..0fef6f1e 100644 --- a/SpigotCore/SpigotCore_Main/src/de/steamwar/techhider/TechHider.java +++ b/SpigotCore/SpigotCore_Main/src/de/steamwar/techhider/TechHider.java @@ -20,7 +20,6 @@ package de.steamwar.techhider; import com.comphenix.tinyprotocol.TinyProtocol; -import de.steamwar.Reflection; import it.unimi.dsi.fastutil.ints.IntArrayList; import it.unimi.dsi.fastutil.ints.IntList; import it.unimi.dsi.fastutil.shorts.ShortArraySet; @@ -364,24 +363,16 @@ public class TechHider { }; } - private final Reflection.Field moveEntityPacketEntityIdField = Reflection.getField(ClientboundMoveEntityPacket.class, int.class, 0); - private Packet processMoveEntityPacket(Player player, ClientboundMoveEntityPacket packet) { - int entityId = moveEntityPacketEntityIdField.get(packet); - - if (privilegeProvider.isPlayerPrivilegedToAccessEntity(player, entityId)) { + if (privilegeProvider.isPlayerPrivilegedToAccessEntity(player, packet.entityId)) { return packet; } else { return null; } } - private final Reflection.Field rotateHeadPacketEntityIdField = Reflection.getField(ClientboundRotateHeadPacket.class, int.class, 0); - private Packet processRotateHeadPacket(Player player, ClientboundRotateHeadPacket packet) { - int entityId = rotateHeadPacketEntityIdField.get(packet); - - if (privilegeProvider.isPlayerPrivilegedToAccessEntity(player, entityId)) { + if (privilegeProvider.isPlayerPrivilegedToAccessEntity(player, packet.entityId)) { return packet; } else { return null; @@ -463,14 +454,10 @@ public class TechHider { } } - private final Reflection.Field sectionPosField = Reflection.getField(ClientboundSectionBlocksUpdatePacket.class, SectionPos.class, 0); - private final Reflection.Field oldPosField = Reflection.getField(ClientboundSectionBlocksUpdatePacket.class, short[].class, 0); - private final Reflection.Field oldStatesField = Reflection.getField(ClientboundSectionBlocksUpdatePacket.class, BlockState[].class, 0); - private ClientboundSectionBlocksUpdatePacket processSectionUpdate(Player p, ClientboundSectionBlocksUpdatePacket packet) { - SectionPos sectionPos = sectionPosField.get(packet); - short[] oldPos = oldPosField.get(packet); - BlockState[] oldStates = oldStatesField.get(packet); + SectionPos sectionPos = packet.sectionPos; + short[] oldPos = packet.positions; + BlockState[] oldStates = packet.states; List filteredPos = new ArrayList<>(oldPos.length); List filteredStates = new ArrayList<>(oldStates.length); diff --git a/SpigotCore/SpigotCore_Main/src/de/steamwar/techhider/legacy/ChunkHider.java b/SpigotCore/SpigotCore_Main/src/de/steamwar/techhider/legacy/ChunkHider.java index c3df8e6f..57bce2e2 100644 --- a/SpigotCore/SpigotCore_Main/src/de/steamwar/techhider/legacy/ChunkHider.java +++ b/SpigotCore/SpigotCore_Main/src/de/steamwar/techhider/legacy/ChunkHider.java @@ -34,7 +34,6 @@ import net.minecraft.world.level.block.entity.BlockEntityType; import org.bukkit.entity.Player; import java.util.Collections; -import java.util.List; import java.util.Set; import java.util.function.BiFunction; import java.util.function.UnaryOperator; @@ -44,35 +43,26 @@ import java.util.stream.Collectors; public class ChunkHider { public static final ChunkHider impl = new ChunkHider(); - public Class mapChunkPacket() { - return ClientboundLevelChunkWithLightPacket.class; - } + private static final UnaryOperator chunkPacketCloner = ProtocolUtils.shallowCloneGenerator(ClientboundLevelChunkWithLightPacket.class); + private static final UnaryOperator chunkDataCloner = ProtocolUtils.shallowCloneGenerator(ClientboundLevelChunkPacketData.class); - private static final UnaryOperator chunkPacketCloner = ProtocolUtils.shallowCloneGenerator(ClientboundLevelChunkWithLightPacket.class); - private static final UnaryOperator chunkDataCloner = ProtocolUtils.shallowCloneGenerator(ClientboundLevelChunkPacketData.class); - - private static final Reflection.Field chunkXField = Reflection.getField(ClientboundLevelChunkWithLightPacket.class, int.class, 0); - private static final Reflection.Field chunkZField = Reflection.getField(ClientboundLevelChunkWithLightPacket.class, int.class, 1); - private static final Reflection.Field chunkData = Reflection.getField(ClientboundLevelChunkWithLightPacket.class, ClientboundLevelChunkPacketData.class, 0); - - private static final Reflection.Field dataField = Reflection.getField(ClientboundLevelChunkPacketData.class, byte[].class, 0); - private static final Reflection.Field tileEntities = Reflection.getField(ClientboundLevelChunkPacketData.class, List.class, 0); - - public BiFunction chunkHiderGenerator(TechHider techHider) { + public BiFunction chunkHiderGenerator(TechHider techHider) { return (p, packet) -> { - int chunkX = chunkXField.get(packet); - int chunkZ = chunkZField.get(packet); + int chunkX = packet.getX(); + int chunkZ = packet.getZ(); if (techHider.getLocationEvaluator().skipChunk(p, chunkX, chunkZ)) { return packet; } packet = chunkPacketCloner.apply(packet); - Object dataWrapper = chunkDataCloner.apply(chunkData.get(packet)); + ClientboundLevelChunkPacketData dataWrapper = chunkDataCloner.apply(packet.getChunkData()); Set hiddenBlockEntities = techHider.getHiddenBlockEntities(); - tileEntities.set(dataWrapper, ((List) tileEntities.get(dataWrapper)).stream().filter(te -> tileEntityVisible(hiddenBlockEntities, te)).collect(Collectors.toList())); + dataWrapper.blockEntitiesData = dataWrapper.blockEntitiesData.stream() + .filter(te -> tileEntityVisible(hiddenBlockEntities, te)) + .collect(Collectors.toList()); - ByteBuf in = Unpooled.wrappedBuffer(dataField.get(dataWrapper)); + ByteBuf in = Unpooled.wrappedBuffer(dataWrapper.buffer); ByteBuf out = Unpooled.buffer(in.readableBytes() + 64); for (int yOffset = p.getWorld().getMinHeight(); yOffset < p.getWorld().getMaxHeight(); yOffset += 16) { SectionHider section = new SectionHider(p, techHider, in, out, chunkX, yOffset / 16, chunkZ); @@ -88,20 +78,18 @@ public class ChunkHider { byte[] data = new byte[out.readableBytes()]; out.readBytes(data); - dataField.set(dataWrapper, data); + dataWrapper.buffer = data; - chunkData.set(packet, dataWrapper); + packet.chunkData = dataWrapper; return packet; }; } private static final Registry> registry = Reflection.getField(BuiltInRegistries.class, "BLOCK_ENTITY_TYPE", Registry.class).get(null); private static final Reflection.Method getKey = Reflection.getTypedMethod(Reflection.getClass("net.minecraft.core.Registry"), "getKey", ResourceLocation.class, Object.class); - public static final Class tileEntity = Reflection.getClass("net.minecraft.network.protocol.game.ClientboundLevelChunkPacketData$BlockEntityInfo"); - protected static final Reflection.Field entityType = Reflection.getField(tileEntity, BlockEntityType.class, 0); - protected boolean tileEntityVisible(Set hiddenBlockEntities, Object tile) { - BlockEntityType type = entityType.get(tile); + protected boolean tileEntityVisible(Set hiddenBlockEntities, ClientboundLevelChunkPacketData.BlockEntityInfo tile) { + BlockEntityType type = tile.type; String path = ((ResourceLocation) getKey.invoke(registry, type)).getPath(); return !hiddenBlockEntities.contains(path); } diff --git a/SpigotCore/SpigotCore_Main/src/de/steamwar/techhider/legacy/ProtocolWrapper.java b/SpigotCore/SpigotCore_Main/src/de/steamwar/techhider/legacy/ProtocolWrapper.java index 00e0dc31..cb7b8d05 100644 --- a/SpigotCore/SpigotCore_Main/src/de/steamwar/techhider/legacy/ProtocolWrapper.java +++ b/SpigotCore/SpigotCore_Main/src/de/steamwar/techhider/legacy/ProtocolWrapper.java @@ -19,10 +19,8 @@ package de.steamwar.techhider.legacy; -import de.steamwar.Reflection; import net.minecraft.core.SectionPos; -import net.minecraft.world.level.block.entity.BlockEntityType; -import net.minecraft.world.level.block.entity.SignBlockEntity; +import net.minecraft.network.protocol.game.ClientboundSectionBlocksUpdatePacket; import net.minecraft.world.level.block.state.BlockState; import org.bukkit.entity.Player; @@ -33,24 +31,20 @@ import java.util.function.BiFunction; public class ProtocolWrapper { public static final ProtocolWrapper impl = new ProtocolWrapper(); - private static final Reflection.Field multiBlockChangeChunk = Reflection.getField(TechHider.multiBlockChangePacket, SectionPos.class, 0); - private static final Reflection.Field multiBlockChangePos = Reflection.getField(TechHider.multiBlockChangePacket, short[].class, 0); - private static final Reflection.Field multiBlockChangeBlocks = Reflection.getField(TechHider.multiBlockChangePacket, BlockState[].class, 0); - - public BiFunction multiBlockChangeGenerator(TechHider techHider) { + public BiFunction multiBlockChangeGenerator(TechHider techHider) { return (p, packet) -> { TechHider.LocationEvaluator locationEvaluator = techHider.getLocationEvaluator(); - Object chunkCoords = multiBlockChangeChunk.get(packet); - int chunkX = TechHider.blockPositionX.get(chunkCoords); - int chunkY = TechHider.blockPositionY.get(chunkCoords); - int chunkZ = TechHider.blockPositionZ.get(chunkCoords); + SectionPos chunkCoords = packet.sectionPos; + int chunkX = chunkCoords.getX(); + int chunkY = chunkCoords.getY(); + int chunkZ = chunkCoords.getZ(); if (locationEvaluator.skipChunkSection(p, chunkX, chunkY, chunkZ)) { return packet; } packet = TechHider.multiBlockChangeCloner.apply(packet); - final short[] oldPos = multiBlockChangePos.get(packet); - final BlockState[] oldBlocks = multiBlockChangeBlocks.get(packet); + final short[] oldPos = packet.positions; + final BlockState[] oldBlocks = packet.states; ArrayList poss = new ArrayList<>(oldPos.length); ArrayList blocks = new ArrayList<>(oldPos.length); for (int i = 0; i < oldPos.length; i++) { @@ -77,16 +71,9 @@ public class ProtocolWrapper { newPos[i] = poss.get(i); } - multiBlockChangePos.set(packet, newPos); - multiBlockChangeBlocks.set(packet, blocks.toArray(new BlockState[0])); + packet.positions = newPos; + packet.states = blocks.toArray(BlockState[]::new); return packet; }; } - - private static final Reflection.Field tileEntityType = Reflection.getField(TechHider.tileEntityDataPacket, BlockEntityType.class, 0); - private static final BlockEntityType signType = Reflection.getField(BlockEntityType.class, BlockEntityType.class, 0, SignBlockEntity.class).get(null); - - public boolean unfilteredTileEntityDataAction(Object packet) { - return tileEntityType.get(packet) != signType; - } } diff --git a/SpigotCore/SpigotCore_Main/src/de/steamwar/techhider/legacy/TechHider.java b/SpigotCore/SpigotCore_Main/src/de/steamwar/techhider/legacy/TechHider.java index 25b3f215..9a96771f 100644 --- a/SpigotCore/SpigotCore_Main/src/de/steamwar/techhider/legacy/TechHider.java +++ b/SpigotCore/SpigotCore_Main/src/de/steamwar/techhider/legacy/TechHider.java @@ -28,6 +28,7 @@ import net.minecraft.core.BlockPos; import net.minecraft.core.Vec3i; import net.minecraft.network.protocol.game.*; import net.minecraft.world.level.block.Block; +import net.minecraft.world.level.block.entity.BlockEntityType; import net.minecraft.world.level.block.state.BlockState; import org.bukkit.Material; import org.bukkit.craftbukkit.util.CraftMagicNumbers; @@ -37,7 +38,6 @@ import java.util.HashMap; import java.util.Map; import java.util.Set; import java.util.function.BiFunction; -import java.util.function.Function; import java.util.function.UnaryOperator; import java.util.stream.Collectors; @@ -45,10 +45,6 @@ import java.util.stream.Collectors; public class TechHider { public static final Class blockPosition = BlockPos.class; - private static final Class baseBlockPosition = Vec3i.class; - public static final Reflection.Field blockPositionX = Reflection.getField(baseBlockPosition, int.class, 0); - public static final Reflection.Field blockPositionY = Reflection.getField(baseBlockPosition, int.class, 1); - public static final Reflection.Field blockPositionZ = Reflection.getField(baseBlockPosition, int.class, 2); public static final Class iBlockData = BlockState.class; public static final Class block = Block.class; @@ -79,11 +75,11 @@ public class TechHider { this.obfuscationTarget = CraftMagicNumbers.getBlock(obfuscationTarget).defaultBlockState(); this.obfuscationTargetId = BlockIds.impl.materialToId(obfuscationTarget); - techhiders.put(blockActionPacket, this::blockActionHider); - techhiders.put(blockChangePacket, this::blockChangeHider); - techhiders.put(tileEntityDataPacket, this::tileEntityDataHider); - techhiders.put(multiBlockChangePacket, ProtocolWrapper.impl.multiBlockChangeGenerator(this)); - techhiders.put(ChunkHider.impl.mapChunkPacket(), ChunkHider.impl.chunkHiderGenerator(this)); + techhiders.put(ClientboundBlockEventPacket.class, (player, o) -> this.blockActionHider(player, (ClientboundBlockEventPacket) o)); + techhiders.put(ClientboundBlockUpdatePacket.class, (player, o) -> this.blockChangeHider(player, (ClientboundBlockUpdatePacket) o)); + techhiders.put(ClientboundBlockEntityDataPacket.class, (player, o) -> this.tileEntityDataHider(player, (ClientboundBlockEntityDataPacket) o)); + techhiders.put(ClientboundSectionBlocksUpdatePacket.class, (BiFunction) ProtocolWrapper.impl.multiBlockChangeGenerator(this)); + techhiders.put(ClientboundLevelChunkWithLightPacket.class, (BiFunction) ChunkHider.impl.chunkHiderGenerator(this)); techhiders.put(ServerboundUseItemOnPacket.class, (p, packet) -> locationEvaluator.suppressInteractions(p) ? null : packet); techhiders.put(ServerboundInteractPacket.class, (p, packet) -> locationEvaluator.suppressInteractions(p) ? null : packet); @@ -97,53 +93,43 @@ public class TechHider { techhiders.forEach(TinyProtocol.instance::removeFilter); } - public static final Class multiBlockChangePacket = ClientboundSectionBlocksUpdatePacket.class; - public static final UnaryOperator multiBlockChangeCloner = ProtocolUtils.shallowCloneGenerator(TechHider.multiBlockChangePacket); + public static final UnaryOperator multiBlockChangeCloner = ProtocolUtils.shallowCloneGenerator(ClientboundSectionBlocksUpdatePacket.class); - private static final Class blockChangePacket = ClientboundBlockUpdatePacket.class; - private static final Function blockChangeCloner = ProtocolUtils.shallowCloneGenerator(blockChangePacket); - private static final Reflection.Field blockChangePosition = Reflection.getField(blockChangePacket, blockPosition, 0); - private static final Reflection.Field blockChangeBlockData = Reflection.getField(blockChangePacket, iBlockData, 0); + private static final UnaryOperator blockChangeCloner = ProtocolUtils.shallowCloneGenerator(ClientboundBlockUpdatePacket.class); - private Object blockChangeHider(Player p, Object packet) { - switch (locationEvaluator.checkBlockPos(p, blockChangePosition.get(packet))) { + private ClientboundBlockUpdatePacket blockChangeHider(Player p, ClientboundBlockUpdatePacket packet) { + switch (locationEvaluator.checkBlockPos(p, packet.getPos())) { case SKIP: return packet; case CHECK: - if (!iBlockDataHidden((BlockState) blockChangeBlockData.get(packet))) { + if (!iBlockDataHidden(packet.blockState)) { return packet; } case HIDE: packet = blockChangeCloner.apply(packet); - blockChangeBlockData.set(packet, obfuscationTarget); + packet.blockState = (BlockState) obfuscationTarget; return packet; case HIDE_AIR: default: packet = blockChangeCloner.apply(packet); - blockChangeBlockData.set(packet, AIR); + packet.blockState = (BlockState) AIR; return packet; } } - private static final Class blockActionPacket = ClientboundBlockEventPacket.class; - private static final Reflection.Field blockActionPosition = Reflection.getField(blockActionPacket, blockPosition, 0); - - private Object blockActionHider(Player p, Object packet) { - if (locationEvaluator.checkBlockPos(p, blockActionPosition.get(packet)) == State.SKIP) { + private Object blockActionHider(Player p, ClientboundBlockEventPacket packet) { + if (locationEvaluator.checkBlockPos(p, packet.getPos()) == State.SKIP) { return packet; } return null; } - public static final Class tileEntityDataPacket = ClientboundBlockEntityDataPacket.class; - private static final Reflection.Field tileEntityDataPosition = Reflection.getField(tileEntityDataPacket, blockPosition, 0); - - private Object tileEntityDataHider(Player p, Object packet) { - switch (locationEvaluator.checkBlockPos(p, tileEntityDataPosition.get(packet))) { + private ClientboundBlockEntityDataPacket tileEntityDataHider(Player p, ClientboundBlockEntityDataPacket packet) { + switch (locationEvaluator.checkBlockPos(p, packet.getPos())) { case SKIP: return packet; case CHECK: - if (ProtocolWrapper.impl.unfilteredTileEntityDataAction(packet)) { + if (packet.getType() != BlockEntityType.SIGN) { return packet; } default: @@ -173,8 +159,8 @@ public class TechHider { return skipChunkSection(player, ProtocolUtils.posToChunk(x), ProtocolUtils.posToChunk(y), ProtocolUtils.posToChunk(z)) ? State.SKIP : State.CHECK; } - default State checkBlockPos(Player player, Object pos) { - return check(player, blockPositionX.get(pos), blockPositionY.get(pos), blockPositionZ.get(pos)); + default State checkBlockPos(Player player, Vec3i pos) { + return check(player, pos.getX(), pos.getY(), pos.getZ()); } default boolean blockPrecise(Player player, int x, int y, int z) { diff --git a/SpigotCore/SpigotCore_Main/src/spigotcore.accesswidener b/SpigotCore/SpigotCore_Main/src/spigotcore.accesswidener new file mode 100644 index 00000000..1fcaac48 --- /dev/null +++ b/SpigotCore/SpigotCore_Main/src/spigotcore.accesswidener @@ -0,0 +1,57 @@ +accessWidener v2 named + +# For TinyProtocol and CheckpointUtilsJ9 +accessible field net/minecraft/server/network/ServerConnectionListener channels Ljava/util/List; + +# For ErrorHandler +accessible field org/spigotmc/WatchdogThread instance Lorg/spigotmc/WatchdogThread; + +# For ResourceKey +accessible method net/minecraft/resources/ResourceKey (Lnet/minecraft/resources/ResourceLocation;Lnet/minecraft/resources/ResourceLocation;)V +accessible method net/minecraft/resources/ResourceLocation (Ljava/lang/String;Ljava/lang/String;)V + +# For SteamwarGameProfileRepository +accessible field net/minecraft/server/MinecraftServer services Lnet/minecraft/server/Services; +mutable field net/minecraft/server/MinecraftServer services Lnet/minecraft/server/Services; + +# REntity +accessible field net/minecraft/network/protocol/game/ClientboundAnimatePacket id I +mutable field net/minecraft/network/protocol/game/ClientboundAnimatePacket id I +accessible field net/minecraft/network/protocol/game/ClientboundAnimatePacket action I +mutable field net/minecraft/network/protocol/game/ClientboundAnimatePacket action I + +accessible field net/minecraft/network/protocol/game/ClientboundEntityEventPacket entityId I +mutable field net/minecraft/network/protocol/game/ClientboundEntityEventPacket entityId I +accessible field net/minecraft/network/protocol/game/ClientboundEntityEventPacket eventId B +mutable field net/minecraft/network/protocol/game/ClientboundEntityEventPacket eventId B + +accessible field net/minecraft/network/protocol/game/ClientboundRotateHeadPacket yHeadRot B +mutable field net/minecraft/network/protocol/game/ClientboundRotateHeadPacket yHeadRot B +## + TechHider +accessible field net/minecraft/network/protocol/game/ClientboundRotateHeadPacket entityId I +mutable field net/minecraft/network/protocol/game/ClientboundRotateHeadPacket entityId I + +# For ChunkHider +accessible field net/minecraft/network/protocol/game/ClientboundLevelChunkWithLightPacket chunkData Lnet/minecraft/network/protocol/game/ClientboundLevelChunkPacketData; +mutable field net/minecraft/network/protocol/game/ClientboundLevelChunkWithLightPacket chunkData Lnet/minecraft/network/protocol/game/ClientboundLevelChunkPacketData; + +accessible field net/minecraft/network/protocol/game/ClientboundLevelChunkPacketData buffer [B +mutable field net/minecraft/network/protocol/game/ClientboundLevelChunkPacketData buffer [B +accessible field net/minecraft/network/protocol/game/ClientboundLevelChunkPacketData blockEntitiesData Ljava/util/List; +mutable field net/minecraft/network/protocol/game/ClientboundLevelChunkPacketData blockEntitiesData Ljava/util/List; + +accessible class net/minecraft/network/protocol/game/ClientboundLevelChunkPacketData$BlockEntityInfo +accessible field net/minecraft/network/protocol/game/ClientboundLevelChunkPacketData$BlockEntityInfo type Lnet/minecraft/world/level/block/entity/BlockEntityType; +accessible field net/minecraft/network/protocol/game/ClientboundLevelChunkPacketData$BlockEntityInfo packedXZ I +accessible field net/minecraft/network/protocol/game/ClientboundLevelChunkPacketData$BlockEntityInfo y I + +# For TechHider +accessible field net/minecraft/network/protocol/game/ClientboundMoveEntityPacket entityId I +accessible field net/minecraft/network/protocol/game/ClientboundSectionBlocksUpdatePacket sectionPos Lnet/minecraft/core/SectionPos; +accessible field net/minecraft/network/protocol/game/ClientboundSectionBlocksUpdatePacket positions [S +mutable field net/minecraft/network/protocol/game/ClientboundSectionBlocksUpdatePacket positions [S +accessible field net/minecraft/network/protocol/game/ClientboundSectionBlocksUpdatePacket states [Lnet/minecraft/world/level/block/state/BlockState; +mutable field net/minecraft/network/protocol/game/ClientboundSectionBlocksUpdatePacket states [Lnet/minecraft/world/level/block/state/BlockState; + +# For legacy/TechHider +mutable field net/minecraft/network/protocol/game/ClientboundBlockUpdatePacket blockState Lnet/minecraft/world/level/block/state/BlockState; From aa66b3dc838136559e1e9611b7157e9c1df006e9 Mon Sep 17 00:00:00 2001 From: YoyoNow Date: Thu, 11 Jun 2026 18:29:44 +0200 Subject: [PATCH 16/36] Optimize imports --- .../features/simulator/SimulatorCursor.java | 12 ++---------- .../blastresistance/BlastResistanceCommand.java | 1 - .../features/techhider/TechHiderCommand.java | 2 +- .../de/steamwar/bausystem/features/tracer/Trace.java | 1 - .../bausystem/features/world/BauMemberUpdate.java | 2 -- .../bausystem/features/world/SpectatorListener.java | 2 +- .../bausystem/features/xray/XrayCommand.java | 2 +- .../src/de/steamwar/fightsystem/FightSystem.java | 5 ++++- .../src/de/steamwar/fightsystem/fight/FightTeam.java | 4 +++- .../fightsystem/listener/InFightInventory.java | 1 - .../de/steamwar/fightsystem/listener/Recording.java | 2 -- .../steamwar/fightsystem/record/PacketProcessor.java | 4 +++- .../src/de/steamwar/core/events/AntiNocom.java | 4 ---- .../src/de/steamwar/cursor/CursorListener.java | 2 +- .../src/de/steamwar/entity/RFallingBlockEntity.java | 1 - .../src/de/steamwar/techhider/legacy/TechHider.java | 1 - .../de/steamwar/velocitycore/discord/DiscordBot.java | 7 +++---- 17 files changed, 19 insertions(+), 34 deletions(-) diff --git a/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/simulator/SimulatorCursor.java b/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/simulator/SimulatorCursor.java index 73bbfcd7..72178e23 100644 --- a/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/simulator/SimulatorCursor.java +++ b/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/simulator/SimulatorCursor.java @@ -55,19 +55,11 @@ import org.bukkit.event.Listener; import org.bukkit.event.block.Action; import org.bukkit.event.inventory.InventoryClickEvent; import org.bukkit.event.inventory.InventoryDragEvent; -import org.bukkit.event.player.PlayerDropItemEvent; -import org.bukkit.event.player.PlayerItemHeldEvent; -import org.bukkit.event.player.PlayerJoinEvent; -import org.bukkit.event.player.PlayerQuitEvent; -import org.bukkit.event.player.PlayerToggleSneakEvent; +import org.bukkit.event.player.*; import org.bukkit.inventory.ItemStack; import org.bukkit.util.Vector; -import java.util.Collections; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Optional; +import java.util.*; import java.util.function.Function; import java.util.stream.Collectors; diff --git a/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/slaves/blastresistance/BlastResistanceCommand.java b/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/slaves/blastresistance/BlastResistanceCommand.java index 7c9430a7..7afb0e5f 100644 --- a/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/slaves/blastresistance/BlastResistanceCommand.java +++ b/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/slaves/blastresistance/BlastResistanceCommand.java @@ -32,7 +32,6 @@ import de.steamwar.linkage.Linked; import org.bukkit.Material; import org.bukkit.entity.Player; -import java.util.*; import java.util.concurrent.atomic.AtomicInteger; @Linked diff --git a/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/techhider/TechHiderCommand.java b/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/techhider/TechHiderCommand.java index 4744e41d..2f401a59 100644 --- a/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/techhider/TechHiderCommand.java +++ b/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/techhider/TechHiderCommand.java @@ -31,6 +31,7 @@ import de.steamwar.command.TypeMapper; import de.steamwar.core.CraftbukkitWrapper; import de.steamwar.linkage.Linked; import de.steamwar.linkage.LinkedInstance; +import de.steamwar.techhider.legacy.TechHider; import net.md_5.bungee.api.ChatMessageType; import org.bukkit.Bukkit; import org.bukkit.command.CommandSender; @@ -38,7 +39,6 @@ import org.bukkit.entity.Player; import org.bukkit.event.EventHandler; import org.bukkit.event.Listener; import org.bukkit.event.player.PlayerQuitEvent; -import de.steamwar.techhider.legacy.TechHider; import java.util.*; import java.util.stream.Collectors; diff --git a/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/tracer/Trace.java b/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/tracer/Trace.java index 95107761..8eb49d79 100644 --- a/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/tracer/Trace.java +++ b/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/tracer/Trace.java @@ -25,7 +25,6 @@ import de.steamwar.bausystem.features.tracer.rendering.TraceEntity; import de.steamwar.bausystem.features.tracer.rendering.ViewFlag; import de.steamwar.bausystem.region.Region; import de.steamwar.entity.REntity; -import de.steamwar.entity.REntityAction; import de.steamwar.entity.REntityServer; import lombok.Getter; import lombok.Setter; diff --git a/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/world/BauMemberUpdate.java b/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/world/BauMemberUpdate.java index ed5b068b..d3416e08 100644 --- a/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/world/BauMemberUpdate.java +++ b/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/world/BauMemberUpdate.java @@ -36,8 +36,6 @@ import org.bukkit.entity.Player; import org.bukkit.event.EventHandler; import org.bukkit.event.Listener; import org.bukkit.event.entity.PlayerDeathEvent; -import org.bukkit.event.inventory.ClickType; -import org.bukkit.event.inventory.InventoryClickEvent; import org.bukkit.event.player.PlayerItemConsumeEvent; import org.bukkit.event.player.PlayerJoinEvent; import org.bukkit.event.player.PlayerQuitEvent; diff --git a/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/world/SpectatorListener.java b/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/world/SpectatorListener.java index 92a5c59e..645600cd 100644 --- a/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/world/SpectatorListener.java +++ b/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/world/SpectatorListener.java @@ -25,6 +25,7 @@ import de.steamwar.bausystem.config.BauServer; import de.steamwar.bausystem.utils.BauMemberUpdateEvent; import de.steamwar.linkage.Linked; import de.steamwar.sql.BauweltMember; +import de.steamwar.techhider.legacy.TechHider; import org.bukkit.Bukkit; import org.bukkit.Location; import org.bukkit.Material; @@ -39,7 +40,6 @@ import org.bukkit.event.block.BlockPlaceEvent; import org.bukkit.event.entity.EntityPickupItemEvent; import org.bukkit.event.player.*; import org.bukkit.util.Vector; -import de.steamwar.techhider.legacy.TechHider; import java.util.HashSet; import java.util.Set; diff --git a/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/xray/XrayCommand.java b/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/xray/XrayCommand.java index 55bcbeeb..aa90081c 100644 --- a/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/xray/XrayCommand.java +++ b/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/xray/XrayCommand.java @@ -28,6 +28,7 @@ import de.steamwar.command.SWCommand; import de.steamwar.core.CraftbukkitWrapper; import de.steamwar.linkage.Linked; import de.steamwar.linkage.LinkedInstance; +import de.steamwar.techhider.legacy.TechHider; import net.md_5.bungee.api.ChatMessageType; import net.minecraft.network.protocol.game.ServerboundMovePlayerPacket; import net.minecraft.server.level.ServerPlayer; @@ -40,7 +41,6 @@ import org.bukkit.event.Listener; import org.bukkit.event.block.Action; import org.bukkit.event.player.PlayerInteractEvent; import org.bukkit.event.player.PlayerQuitEvent; -import de.steamwar.techhider.legacy.TechHider; import java.util.*; import java.util.function.BiFunction; diff --git a/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/FightSystem.java b/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/FightSystem.java index d3339975..06ceac41 100644 --- a/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/FightSystem.java +++ b/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/FightSystem.java @@ -32,7 +32,10 @@ import de.steamwar.fightsystem.record.GlobalRecorder; import de.steamwar.fightsystem.states.FightState; import de.steamwar.fightsystem.states.OneShotStateDependent; import de.steamwar.fightsystem.states.StateDependentListener; -import de.steamwar.fightsystem.utils.*; +import de.steamwar.fightsystem.utils.FightUI; +import de.steamwar.fightsystem.utils.HullHider; +import de.steamwar.fightsystem.utils.SWSound; +import de.steamwar.fightsystem.utils.TechHiderWrapper; import de.steamwar.linkage.AbstractLinker; import de.steamwar.linkage.SpigotLinker; import de.steamwar.message.Message; diff --git a/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/fight/FightTeam.java b/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/fight/FightTeam.java index a8cddb3a..0119bf40 100644 --- a/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/fight/FightTeam.java +++ b/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/fight/FightTeam.java @@ -35,7 +35,9 @@ import de.steamwar.fightsystem.listener.TeamArea; import de.steamwar.fightsystem.states.FightState; import de.steamwar.fightsystem.states.OneShotStateDependent; import de.steamwar.fightsystem.states.StateDependent; -import de.steamwar.fightsystem.utils.*; +import de.steamwar.fightsystem.utils.FightUI; +import de.steamwar.fightsystem.utils.ItemBuilder; +import de.steamwar.fightsystem.utils.Region; import de.steamwar.fightsystem.winconditions.Wincondition; import de.steamwar.fightsystem.winconditions.Winconditions; import de.steamwar.inventory.SWItem; diff --git a/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/listener/InFightInventory.java b/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/listener/InFightInventory.java index 8d7d339c..c49b46c6 100644 --- a/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/listener/InFightInventory.java +++ b/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/listener/InFightInventory.java @@ -25,7 +25,6 @@ import de.steamwar.fightsystem.states.FightState; import de.steamwar.fightsystem.states.StateDependentListener; import de.steamwar.linkage.Linked; import org.bukkit.Material; -import org.bukkit.entity.Player; import org.bukkit.event.EventHandler; import org.bukkit.event.Listener; import org.bukkit.event.inventory.InventoryClickEvent; diff --git a/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/listener/Recording.java b/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/listener/Recording.java index d11c98db..ecd333ec 100644 --- a/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/listener/Recording.java +++ b/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/listener/Recording.java @@ -20,7 +20,6 @@ package de.steamwar.fightsystem.listener; import com.comphenix.tinyprotocol.TinyProtocol; -import de.steamwar.Reflection; import de.steamwar.fightsystem.ArenaMode; import de.steamwar.fightsystem.Config; import de.steamwar.fightsystem.FightSystem; @@ -37,7 +36,6 @@ import de.steamwar.fightsystem.states.StateDependentListener; import de.steamwar.fightsystem.states.StateDependentTask; import de.steamwar.fightsystem.utils.SWSound; import de.steamwar.linkage.Linked; -import net.minecraft.network.protocol.Packet; import net.minecraft.network.protocol.game.ServerboundPlayerActionPacket; import net.minecraft.network.protocol.game.ServerboundUseItemPacket; import net.minecraft.world.InteractionHand; diff --git a/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/record/PacketProcessor.java b/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/record/PacketProcessor.java index b62ef662..d7cf16e6 100644 --- a/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/record/PacketProcessor.java +++ b/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/record/PacketProcessor.java @@ -34,7 +34,9 @@ import de.steamwar.fightsystem.fight.FightWorld; import de.steamwar.fightsystem.fight.FreezeWorld; import de.steamwar.fightsystem.listener.FightScoreboard; import de.steamwar.fightsystem.states.FightState; -import de.steamwar.fightsystem.utils.*; +import de.steamwar.fightsystem.utils.FightUI; +import de.steamwar.fightsystem.utils.Message; +import de.steamwar.fightsystem.utils.TechHiderWrapper; import de.steamwar.sql.SchematicNode; import de.steamwar.sql.SteamwarUser; import de.steamwar.sql.Team; diff --git a/SpigotCore/SpigotCore_Main/src/de/steamwar/core/events/AntiNocom.java b/SpigotCore/SpigotCore_Main/src/de/steamwar/core/events/AntiNocom.java index b5276c00..d3625026 100644 --- a/SpigotCore/SpigotCore_Main/src/de/steamwar/core/events/AntiNocom.java +++ b/SpigotCore/SpigotCore_Main/src/de/steamwar/core/events/AntiNocom.java @@ -20,16 +20,13 @@ package de.steamwar.core.events; import com.comphenix.tinyprotocol.TinyProtocol; -import de.steamwar.Reflection; import de.steamwar.core.Core; import de.steamwar.linkage.Linked; import de.steamwar.sql.SWException; import de.steamwar.techhider.ProtocolUtils; -import de.steamwar.techhider.TechHider; import net.minecraft.core.BlockPos; import net.minecraft.network.protocol.game.ServerboundPlayerActionPacket; import net.minecraft.network.protocol.game.ServerboundUseItemOnPacket; -import net.minecraft.world.phys.BlockHitResult; import org.bukkit.Bukkit; import org.bukkit.entity.Player; import org.bukkit.event.EventHandler; @@ -38,7 +35,6 @@ import org.bukkit.event.player.PlayerQuitEvent; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; -import java.util.function.Function; @Linked public class AntiNocom implements Listener { diff --git a/SpigotCore/SpigotCore_Main/src/de/steamwar/cursor/CursorListener.java b/SpigotCore/SpigotCore_Main/src/de/steamwar/cursor/CursorListener.java index 2d7b438f..99e286ca 100644 --- a/SpigotCore/SpigotCore_Main/src/de/steamwar/cursor/CursorListener.java +++ b/SpigotCore/SpigotCore_Main/src/de/steamwar/cursor/CursorListener.java @@ -13,7 +13,7 @@ import org.bukkit.event.EventHandler; import org.bukkit.event.Listener; import org.bukkit.event.player.PlayerInteractEvent; -import java.util.*; +import java.util.Optional; @Linked diff --git a/SpigotCore/SpigotCore_Main/src/de/steamwar/entity/RFallingBlockEntity.java b/SpigotCore/SpigotCore_Main/src/de/steamwar/entity/RFallingBlockEntity.java index e1ab5cba..50226dfa 100644 --- a/SpigotCore/SpigotCore_Main/src/de/steamwar/entity/RFallingBlockEntity.java +++ b/SpigotCore/SpigotCore_Main/src/de/steamwar/entity/RFallingBlockEntity.java @@ -20,7 +20,6 @@ package de.steamwar.entity; import de.steamwar.techhider.BlockIds; -import lombok.AccessLevel; import lombok.Getter; import lombok.Setter; import org.bukkit.Location; diff --git a/SpigotCore/SpigotCore_Main/src/de/steamwar/techhider/legacy/TechHider.java b/SpigotCore/SpigotCore_Main/src/de/steamwar/techhider/legacy/TechHider.java index 9a96771f..f8e027be 100644 --- a/SpigotCore/SpigotCore_Main/src/de/steamwar/techhider/legacy/TechHider.java +++ b/SpigotCore/SpigotCore_Main/src/de/steamwar/techhider/legacy/TechHider.java @@ -20,7 +20,6 @@ package de.steamwar.techhider.legacy; import com.comphenix.tinyprotocol.TinyProtocol; -import de.steamwar.Reflection; import de.steamwar.techhider.BlockIds; import de.steamwar.techhider.ProtocolUtils; import lombok.Getter; diff --git a/VelocityCore/src/de/steamwar/velocitycore/discord/DiscordBot.java b/VelocityCore/src/de/steamwar/velocitycore/discord/DiscordBot.java index 9cbd4061..7e7dba1e 100644 --- a/VelocityCore/src/de/steamwar/velocitycore/discord/DiscordBot.java +++ b/VelocityCore/src/de/steamwar/velocitycore/discord/DiscordBot.java @@ -22,7 +22,6 @@ package de.steamwar.velocitycore.discord; import de.steamwar.command.SWCommand; import de.steamwar.messages.Chatter; import de.steamwar.sql.Event; -import de.steamwar.sql.SteamwarUser; import de.steamwar.velocitycore.VelocityCore; import de.steamwar.velocitycore.discord.channels.*; import de.steamwar.velocitycore.discord.listeners.ChannelListener; @@ -53,12 +52,12 @@ import net.dv8tion.jda.api.utils.MemberCachePolicy; import net.dv8tion.jda.api.utils.messages.MessageCreateBuilder; import java.awt.*; -import java.util.*; +import java.util.ArrayList; +import java.util.HashMap; import java.util.List; +import java.util.Map; import java.util.concurrent.TimeUnit; -import java.util.concurrent.atomic.AtomicInteger; import java.util.function.Consumer; -import java.util.stream.Collectors; public class DiscordBot { public static final String ARGUMENT_NAME = "arguments"; From 2bc164c1b0f8e7cf9455d38230b4ffd662251ec2 Mon Sep 17 00:00:00 2001 From: YoyoNow Date: Thu, 11 Jun 2026 20:38:33 +0200 Subject: [PATCH 17/36] Extract widener definition into a widener.gradle.kts plugin --- BauSystem/BauSystem_Main/build.gradle.kts | 45 +---------- SpigotCore/SpigotCore_Main/build.gradle.kts | 45 +---------- buildSrc/{build.gradle => build.gradle.kts} | 23 +++--- .../groovy}/steamwar.devserver.gradle | 0 .../{ => main/groovy}/steamwar.java.gradle | 0 .../{ => main/groovy}/steamwar.kotlin.gradle | 0 buildSrc/src/main/kotlin/WidenerExtension.kt | 52 +++++++++++++ buildSrc/src/main/kotlin/widener.gradle.kts | 78 +++++++++++++++++++ 8 files changed, 148 insertions(+), 95 deletions(-) rename buildSrc/{build.gradle => build.gradle.kts} (82%) rename buildSrc/src/{ => main/groovy}/steamwar.devserver.gradle (100%) rename buildSrc/src/{ => main/groovy}/steamwar.java.gradle (100%) rename buildSrc/src/{ => main/groovy}/steamwar.kotlin.gradle (100%) create mode 100644 buildSrc/src/main/kotlin/WidenerExtension.kt create mode 100644 buildSrc/src/main/kotlin/widener.gradle.kts diff --git a/BauSystem/BauSystem_Main/build.gradle.kts b/BauSystem/BauSystem_Main/build.gradle.kts index f81b7614..7f5f9b18 100644 --- a/BauSystem/BauSystem_Main/build.gradle.kts +++ b/BauSystem/BauSystem_Main/build.gradle.kts @@ -19,6 +19,7 @@ plugins { steamwar.java + widener } tasks.compileJava { @@ -30,45 +31,6 @@ java { targetCompatibility = JavaVersion.VERSION_21 } -// ─── Collect all .accesswidener files from this plugin's resources ──────────── -val accessWidenerFiles: FileCollection = fileTree("src/") { - include("**/*.accesswidener") -} - -val paperJarProvider: Provider = provider { - val dep = libs.nms.get() - configurations["compileClasspath"].resolvedConfiguration.resolvedArtifacts.first { artifact -> - artifact.moduleVersion.id.module.group == dep.module.group && artifact.moduleVersion.id.module.name == dep.module.name - }.file -} - -// ─── Widen the Paper dev JAR so the IDE / javac see the patched access ──────── -val widenedJar by tasks.registering(JavaExec::class) { - description = "Produces a widened copy of the Paper dev JAR for compile-time use." - group = "widener" - - // Re-run whenever the .accesswidener files change - inputs.file(paperJarProvider) - inputs.files(accessWidenerFiles) - - val output = layout.buildDirectory.file("widened/paper-widened.jar") - outputs.file(output) - - classpath = project(":AccessWidener").tasks.named("shadowJar").get().outputs.files - - mainClass.set("de.steamwar.Main") - - doFirst { - args = buildList { - add(paperJarProvider.get().absolutePath) - add(output.get().asFile.absolutePath) - addAll(accessWidenerFiles.map { it.absolutePath }) - } - } - - dependsOn(":AccessWidener:shadowJar") -} - dependencies { compileOnly(libs.classindex) annotationProcessor(libs.classindex) @@ -79,7 +41,6 @@ dependencies { compileOnly(libs.paperapi) compileOnly(libs.nms) - compileOnly(files(widenedJar)) compileOnly(libs.fawe) compileOnly(libs.netty) @@ -88,6 +49,6 @@ dependencies { implementation(files("$projectDir/../libs/YAPION-SNAPSHOT.jar")) } -tasks.compileJava { - dependsOn(widenedJar) +widener { + fromCatalog(libs.nms) } diff --git a/SpigotCore/SpigotCore_Main/build.gradle.kts b/SpigotCore/SpigotCore_Main/build.gradle.kts index 34bc1d49..322b1046 100644 --- a/SpigotCore/SpigotCore_Main/build.gradle.kts +++ b/SpigotCore/SpigotCore_Main/build.gradle.kts @@ -19,6 +19,7 @@ plugins { steamwar.java + widener } tasks.compileJava { @@ -42,45 +43,6 @@ tasks.withType().configureEach { jvmArgs("--add-exports", "java.base/jdk.internal.misc=ALL-UNNAMED") } -// ─── Collect all .accesswidener files from this plugin's resources ──────────── -val accessWidenerFiles: FileCollection = fileTree("src/") { - include("**/*.accesswidener") -} - -val paperJarProvider: Provider = provider { - val dep = libs.nms.get() - configurations["compileClasspath"].resolvedConfiguration.resolvedArtifacts.first { artifact -> - artifact.moduleVersion.id.module.group == dep.module.group && artifact.moduleVersion.id.module.name == dep.module.name - }.file -} - -// ─── Widen the Paper dev JAR so the IDE / javac see the patched access ──────── -val widenedJar by tasks.registering(JavaExec::class) { - description = "Produces a widened copy of the Paper dev JAR for compile-time use." - group = "widener" - - // Re-run whenever the .accesswidener files change - inputs.file(paperJarProvider) - inputs.files(accessWidenerFiles) - - val output = layout.buildDirectory.file("widened/paper-widened.jar") - outputs.file(output) - - classpath = project(":AccessWidener").tasks.named("shadowJar").get().outputs.files - - mainClass.set("de.steamwar.Main") - - doFirst { - args = buildList { - add(paperJarProvider.get().absolutePath) - add(output.get().asFile.absolutePath) - addAll(accessWidenerFiles.map { it.absolutePath }) - } - } - - dependsOn(":AccessWidener:shadowJar") -} - dependencies { compileOnly(libs.classindex) annotationProcessor(libs.classindex) @@ -92,7 +54,6 @@ dependencies { compileOnly(libs.paperapi) compileOnly(libs.nms) - compileOnly(files(widenedJar)) compileOnly(libs.authlib) compileOnly(libs.datafixer) compileOnly(libs.netty) @@ -102,6 +63,6 @@ dependencies { implementation(libs.anvilgui) } -tasks.compileJava { - dependsOn(widenedJar) +widener { + fromCatalog(libs.nms) } diff --git a/buildSrc/build.gradle b/buildSrc/build.gradle.kts similarity index 82% rename from buildSrc/build.gradle rename to buildSrc/build.gradle.kts index e1ee5bb0..3df3ec61 100644 --- a/buildSrc/build.gradle +++ b/buildSrc/build.gradle.kts @@ -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 @@ -18,21 +18,22 @@ */ plugins { - id 'groovy-gradle-plugin' -} - -sourceSets { - main { - groovy { - srcDirs("src/") - } - } + `kotlin-dsl` + `groovy-gradle-plugin` } repositories { mavenCentral() + gradlePluginPortal() +} + +sourceSets { + main { + groovy.srcDirs("src/main/groovy") + kotlin.srcDirs("src/main/kotlin") + } } dependencies { implementation("org.jetbrains.kotlin:kotlin-gradle-plugin:2.2.21") -} \ No newline at end of file +} diff --git a/buildSrc/src/steamwar.devserver.gradle b/buildSrc/src/main/groovy/steamwar.devserver.gradle similarity index 100% rename from buildSrc/src/steamwar.devserver.gradle rename to buildSrc/src/main/groovy/steamwar.devserver.gradle diff --git a/buildSrc/src/steamwar.java.gradle b/buildSrc/src/main/groovy/steamwar.java.gradle similarity index 100% rename from buildSrc/src/steamwar.java.gradle rename to buildSrc/src/main/groovy/steamwar.java.gradle diff --git a/buildSrc/src/steamwar.kotlin.gradle b/buildSrc/src/main/groovy/steamwar.kotlin.gradle similarity index 100% rename from buildSrc/src/steamwar.kotlin.gradle rename to buildSrc/src/main/groovy/steamwar.kotlin.gradle diff --git a/buildSrc/src/main/kotlin/WidenerExtension.kt b/buildSrc/src/main/kotlin/WidenerExtension.kt new file mode 100644 index 00000000..d0263567 --- /dev/null +++ b/buildSrc/src/main/kotlin/WidenerExtension.kt @@ -0,0 +1,52 @@ +/* + * 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 . + */ + +import org.gradle.api.Project +import org.gradle.api.artifacts.MinimalExternalModuleDependency +import org.gradle.api.file.ConfigurableFileCollection +import org.gradle.api.provider.Provider +import javax.inject.Inject + +abstract class WidenerExtension { + + @get:Inject + abstract val project: Project + + abstract val inputJars: ConfigurableFileCollection + abstract val accessWidenerFiles: ConfigurableFileCollection + + fun fromCatalog(vararg dependencies: Provider) { + val files = dependencies.map { dependency -> + project.provider { + val dep = dependency.get() + + project.configurations.getByName("compileClasspath") + .resolvedConfiguration + .resolvedArtifacts + .first { + it.moduleVersion.id.module.group == dep.module.group && + it.moduleVersion.id.module.name == dep.module.name + } + .file + } + } + + inputJars.from(files) + } +} \ No newline at end of file diff --git a/buildSrc/src/main/kotlin/widener.gradle.kts b/buildSrc/src/main/kotlin/widener.gradle.kts new file mode 100644 index 00000000..71da2e8d --- /dev/null +++ b/buildSrc/src/main/kotlin/widener.gradle.kts @@ -0,0 +1,78 @@ +/* + * 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 . + */ + +val widener = extensions.create("widener") + +widener.accessWidenerFiles.setFrom( + fileTree("src/") { include("**/*.accesswidener") } +) + +// ─── Tasks (unchanged from before) ─────────────────────────────────────────── + +val jarWidenerClasspath = rootProject.project(":AccessWidener") + .tasks.named("shadowJar") + .get().outputs.files + +val allWidenedJars = objects.fileCollection() + +project.gradle.projectsEvaluated { + if (widener.inputJars.isEmpty) { + logger.warn("[widener] No input JARs configured for ${project.name} — widenedJar tasks will not be registered.") + return@projectsEvaluated + } + + widener.inputJars.forEachIndexed { index, inputJar -> + val taskName = if (index == 0) "widenedJar" else "widenedJar$index" + val outputFile = layout.buildDirectory + .file("widened/${inputJar.nameWithoutExtension}-widened.jar") + + val task = tasks.register(taskName) { + description = "Produces a widened copy of ${inputJar.name} for compile-time use." + group = "widener" + + inputs.file(inputJar) + inputs.files(widener.accessWidenerFiles) + outputs.file(outputFile) + + classpath = jarWidenerClasspath + mainClass.set("de.steamwar.Main") + + dependsOn(rootProject.project(":AccessWidener").tasks.named("shadowJar")) + + doFirst { + args = buildList { + add(inputJar.absolutePath) + add(outputFile.get().asFile.absolutePath) + addAll(widener.accessWidenerFiles.map { it.absolutePath }) + } + } + } + + allWidenedJars.from(task) + } + + tasks.named("compileJava") { + // dependsOn(allWidenedJars.buildDependencies) + dependsOn(allWidenedJars) + } +} + +project.dependencies { + add("compileOnly", project.fileTree("build/widened")) +} From 932732737d8c075efb39d77d98c0dafcab25ffc2 Mon Sep 17 00:00:00 2001 From: YoyoNow Date: Thu, 11 Jun 2026 22:35:13 +0200 Subject: [PATCH 18/36] Unreflect more stuff --- .../java/de/steamwar/WideningTransformer.java | 7 +- .../src/bausystem.accesswidener | 3 + .../bausystem/utils/WorldEditUtils.java | 39 ++- BauSystem/BauSystem_Main/src/plugin.yml | 2 +- BauSystem/build.gradle.kts | 2 + .../fightsystem/utils/TechHiderWrapper.java | 16 +- .../src/de/steamwar/Reflection.java | 279 ------------------ .../de/steamwar/core/BountifulWrapper.java | 16 - .../src/de/steamwar/core/Core.java | 13 +- .../de/steamwar/core/FlatteningWrapper.java | 34 --- .../src/de/steamwar/entity/RArmorStand.java | 2 +- .../src/de/steamwar/entity/RBlockDisplay.java | 4 +- .../src/de/steamwar/entity/RDisplay.java | 67 ++--- .../src/de/steamwar/entity/REntity.java | 64 ++-- .../src/de/steamwar/entity/RInteraction.java | 38 +-- .../src/de/steamwar/entity/RItemDisplay.java | 8 +- .../src/de/steamwar/entity/RPlayer.java | 7 +- .../src/de/steamwar/entity/RTextDisplay.java | 20 +- .../steamwar/techhider/legacy/ChunkHider.java | 8 +- buildSrc/src/main/kotlin/WidenerExtension.kt | 6 +- buildSrc/src/main/kotlin/widener.gradle.kts | 3 - 21 files changed, 162 insertions(+), 476 deletions(-) delete mode 100644 SpigotCore/SpigotCore_Main/src/de/steamwar/core/FlatteningWrapper.java diff --git a/AccessWidener/src/main/java/de/steamwar/WideningTransformer.java b/AccessWidener/src/main/java/de/steamwar/WideningTransformer.java index 2215c769..8fcd33e2 100644 --- a/AccessWidener/src/main/java/de/steamwar/WideningTransformer.java +++ b/AccessWidener/src/main/java/de/steamwar/WideningTransformer.java @@ -39,11 +39,6 @@ public class WideningTransformer implements ClassFileTransformer { @Override public byte[] transform(ClassLoader loader, String className, Class classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer) { - byte[] result = patcher.patch(className, classfileBuffer); - if (result == null) { - return classfileBuffer; - } else { - return result; - } + return patcher.patch(className, classfileBuffer); } } diff --git a/BauSystem/BauSystem_Main/src/bausystem.accesswidener b/BauSystem/BauSystem_Main/src/bausystem.accesswidener index 5dd89fdf..51fe2890 100644 --- a/BauSystem/BauSystem_Main/src/bausystem.accesswidener +++ b/BauSystem/BauSystem_Main/src/bausystem.accesswidener @@ -11,3 +11,6 @@ mutable field org/bukkit/craftbukkit/block/CraftBlockState world Lorg/bukkit/cra # For TickManager accessible field net/minecraft/server/ServerTickRateManager remainingSprintTicks J + +# Test +accessible field net/minecraft/core/registries/BuiltInRegistries BLOCK_ENTITY_TYPE Lnet/minecraft/core/Registry; \ No newline at end of file diff --git a/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/utils/WorldEditUtils.java b/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/utils/WorldEditUtils.java index 0bab1548..8c493707 100644 --- a/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/utils/WorldEditUtils.java +++ b/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/utils/WorldEditUtils.java @@ -19,6 +19,7 @@ package de.steamwar.bausystem.utils; +import com.fastasyncworldedit.core.regions.selector.PolyhedralRegionSelector; import com.sk89q.worldedit.EditSession; import com.sk89q.worldedit.IncompleteRegionException; import com.sk89q.worldedit.LocalSession; @@ -32,16 +33,19 @@ import com.sk89q.worldedit.internal.registry.InputParser; import com.sk89q.worldedit.math.BlockVector3; import com.sk89q.worldedit.regions.Region; import com.sk89q.worldedit.regions.RegionSelector; +import com.sk89q.worldedit.regions.selector.*; import com.sk89q.worldedit.regions.selector.limit.SelectorLimits; -import de.steamwar.Reflection; +import com.sk89q.worldedit.world.World; import de.steamwar.bausystem.shared.Pair; import lombok.SneakyThrows; import lombok.experimental.UtilityClass; import org.bukkit.Location; -import org.bukkit.World; import org.bukkit.entity.Player; +import java.util.HashMap; import java.util.List; +import java.util.Map; +import java.util.function.Function; import java.util.stream.Collectors; @UtilityClass @@ -91,17 +95,36 @@ public class WorldEditUtils { .getRegionSelector(BukkitAdapter.adapt(player.getWorld())); return new Pair<>(regionSelector.getClass(), regionSelector.getVertices() .stream() - .map(blockVector3 -> blockVector3 == null ? null : adapt(player.getWorld(), blockVector3)) + .map(blockVector3 -> { + if (blockVector3 == null) { + return null; + } else { + return BukkitAdapter.adapt(player.getWorld(), blockVector3); + } + }) .collect(Collectors.toList())); } + private static final Map, Function> constructors = new HashMap<>(); + static { + constructors.put(CuboidRegionSelector.class, CuboidRegionSelector::new); + constructors.put(ExtendingCuboidRegionSelector.class, ExtendingCuboidRegionSelector::new); + constructors.put(Polygonal2DRegionSelector.class, Polygonal2DRegionSelector::new); + constructors.put(EllipsoidRegionSelector.class, EllipsoidRegionSelector::new); + constructors.put(SphereRegionSelector.class, SphereRegionSelector::new); + constructors.put(CylinderRegionSelector.class, CylinderRegionSelector::new); + constructors.put(ConvexPolyhedralRegionSelector.class, ConvexPolyhedralRegionSelector::new); + constructors.put(PolyhedralRegionSelector.class, PolyhedralRegionSelector::new); + } + public void setVertices(Player player, Class clazz, List vertices) { LocalSession localSession = WorldEdit.getInstance() .getSessionManager() .get(BukkitAdapter.adapt(player)); - Reflection.Constructor constructorInvoker = Reflection.getConstructor(clazz, com.sk89q.worldedit.world.World.class); - RegionSelector regionSelector = (RegionSelector) constructorInvoker.invoke(BukkitAdapter.adapt(player.getWorld())); + Function constructor = constructors.get(clazz); + if (constructor == null) return; + RegionSelector regionSelector = constructor.apply(BukkitAdapter.adapt(player.getWorld())); localSession.setRegionSelector(BukkitAdapter.adapt(player.getWorld()), regionSelector); if (vertices.isEmpty()) return; @@ -127,13 +150,9 @@ public class WorldEditUtils { try { BlockVector3 min = regionSelector.getRegion().getMinimumPoint(); BlockVector3 max = regionSelector.getRegion().getMaximumPoint(); - return new Pair<>(adapt(player.getWorld(), min), adapt(player.getWorld(), max)); + return new Pair<>(BukkitAdapter.adapt(player.getWorld(), min), BukkitAdapter.adapt(player.getWorld(), max)); } catch (IncompleteRegionException e) { return null; } } - - private Location adapt(World world, BlockVector3 blockVector3) { - return new Location(world, blockVector3.getBlockX(), blockVector3.getBlockY(), blockVector3.getBlockZ()); - } } diff --git a/BauSystem/BauSystem_Main/src/plugin.yml b/BauSystem/BauSystem_Main/src/plugin.yml index 71b08d95..bbc550bc 100644 --- a/BauSystem/BauSystem_Main/src/plugin.yml +++ b/BauSystem/BauSystem_Main/src/plugin.yml @@ -4,7 +4,7 @@ version: "2.0" depend: [ WorldEdit, SpigotCore ] load: POSTWORLD main: de.steamwar.bausystem.BauSystem -api-version: "1.13" +api-version: "1.21" website: "https://steamwar.de" description: "So unseriös wie wir sind: BauSystem nur besser." diff --git a/BauSystem/build.gradle.kts b/BauSystem/build.gradle.kts index aa03f184..1d158e37 100644 --- a/BauSystem/build.gradle.kts +++ b/BauSystem/build.gradle.kts @@ -39,5 +39,7 @@ tasks.register("DevBau21") { dependsOn(":SchematicSystem:shadowJar") dependsOn(":AccessWidener:shadowJar") template = "Bau21" + // TODO: Add to every new server start! newer than 1.21 inclusive! jvmArgs = "-javaagent:/home/yoyonow/Bau21/plugins/AccessWidener.jar=start" + setdParams(mapOf("paper.disablePluginRemapping" to "true")) } diff --git a/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/utils/TechHiderWrapper.java b/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/utils/TechHiderWrapper.java index 3fc4a364..7c4c9d77 100644 --- a/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/utils/TechHiderWrapper.java +++ b/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/utils/TechHiderWrapper.java @@ -19,7 +19,6 @@ package de.steamwar.fightsystem.utils; -import de.steamwar.Reflection; import de.steamwar.core.CraftbukkitWrapper; import de.steamwar.fightsystem.Config; import de.steamwar.fightsystem.events.BoardingEvent; @@ -51,7 +50,7 @@ import org.bukkit.event.Listener; import org.bukkit.event.player.PlayerQuitEvent; import java.util.Collection; -import java.util.Optional; +import java.util.Objects; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import java.util.stream.Collectors; @@ -82,18 +81,13 @@ public class TechHiderWrapper extends StateDependent implements Listener { .map(CraftMagicNumbers::getBlock) .collect(Collectors.toUnmodifiableSet()); - Object blockEntityType; - try { - blockEntityType = BuiltInRegistries.class.getDeclaredField("BLOCK_ENTITY_TYPE").get(null); - } catch (Exception e) { - throw new IllegalStateException(e); - } - Reflection.Method method = Reflection.getTypedMethod(Reflection.getClass("net.minecraft.core.Registry"), "get", Optional.class, ResourceLocation.class); Set> blockEntityTypeToObfuscate = Config.GameModeConfig.Techhider.HiddenBlockEntities.stream() - .map((id) -> { + .map(id -> { ResourceLocation loc = ResourceLocation.parse(id); - return ((Optional>>) method.invoke(blockEntityType, loc)).get().value(); + return BuiltInRegistries.BLOCK_ENTITY_TYPE.get(loc).orElse(null); }) + .filter(Objects::nonNull) + .map(Holder.Reference::value) .collect(Collectors.toUnmodifiableSet()); new TechHider(CraftMagicNumbers.getBlock(Config.GameModeConfig.Techhider.ObfuscateWith), new AccessPrivilegeProvider() { diff --git a/SpigotCore/SpigotCore_Main/src/de/steamwar/Reflection.java b/SpigotCore/SpigotCore_Main/src/de/steamwar/Reflection.java index d75fdae7..324d0e44 100644 --- a/SpigotCore/SpigotCore_Main/src/de/steamwar/Reflection.java +++ b/SpigotCore/SpigotCore_Main/src/de/steamwar/Reflection.java @@ -19,292 +19,13 @@ package de.steamwar; -import de.steamwar.core.Core; import jdk.internal.misc.Unsafe; -import lombok.AllArgsConstructor; import lombok.experimental.UtilityClass; -import org.bukkit.Bukkit; - -import java.lang.reflect.ParameterizedType; -import java.lang.reflect.Type; -import java.util.Arrays; -import java.util.HashMap; -import java.util.Map; @UtilityClass public final class Reflection { - public static final int MAJOR_VERSION; - public static final int MINOR_VERSION; - - static { - String[] version = Bukkit.getServer().getBukkitVersion().split("-")[0].split("\\."); - MAJOR_VERSION = Integer.parseInt(version[1]); - MINOR_VERSION = version.length > 2 ? Integer.parseInt(version[2]) : 0; - } - - private static final String ORG_BUKKIT_CRAFTBUKKIT = Bukkit.getServer().getClass().getPackage().getName(); - public static final String LEGACY_NET_MINECRAFT_SERVER = ORG_BUKKIT_CRAFTBUKKIT.replace("org.bukkit.craftbukkit", "net.minecraft.server"); - - private static final Map spigotClassnames = new HashMap<>(); - - static { - // See https://mappings.dev for complete mappings - spigotClassnames.put("net.minecraft.Util", "net.minecraft.SystemUtils"); - - spigotClassnames.put("net.minecraft.core.BlockPos", "net.minecraft.core.BlockPosition"); - spigotClassnames.put("net.minecraft.core.DefaultedRegistry", "net.minecraft.core.RegistryBlocks"); - spigotClassnames.put("net.minecraft.core.IdMapper", "net.minecraft.core.RegistryBlockID"); - spigotClassnames.put("net.minecraft.core.Vec3i", "net.minecraft.core.BaseBlockPosition"); - - spigotClassnames.put("net.minecraft.nbt.CompoundTag", "net.minecraft.nbt.NBTTagCompound"); - - spigotClassnames.put("net.minecraft.network.Connection", "net.minecraft.network.NetworkManager"); - - spigotClassnames.put("net.minecraft.network.chat.Component", "net.minecraft.network.chat.IChatBaseComponent"); - - spigotClassnames.put("net.minecraft.network.protocol.game.ClientboundAddEntityPacket", "net.minecraft.network.protocol.game.PacketPlayOutSpawnEntity"); - spigotClassnames.put("net.minecraft.network.protocol.game.ClientboundAddPlayerPacket", "net.minecraft.network.protocol.game.PacketPlayOutNamedEntitySpawn"); - spigotClassnames.put("net.minecraft.network.protocol.game.ClientboundAnimatePacket", "net.minecraft.network.protocol.game.PacketPlayOutAnimation"); - spigotClassnames.put("net.minecraft.network.protocol.game.ClientboundBlockDestructionPacket", "net.minecraft.network.protocol.game.PacketPlayOutBlockBreak"); - spigotClassnames.put("net.minecraft.network.protocol.game.ClientboundBlockEntityDataPacket", "net.minecraft.network.protocol.game.PacketPlayOutTileEntityData"); - spigotClassnames.put("net.minecraft.network.protocol.game.ClientboundBlockEventPacket", "net.minecraft.network.protocol.game.PacketPlayOutBlockAction"); - spigotClassnames.put("net.minecraft.network.protocol.game.ClientboundBlockUpdatePacket", "net.minecraft.network.protocol.game.PacketPlayOutBlockChange"); - spigotClassnames.put("net.minecraft.network.protocol.game.ClientboundContainerClosePacket", "net.minecraft.network.protocol.game.PacketPlayOutCloseWindow"); - spigotClassnames.put("net.minecraft.network.protocol.game.ClientboundEntityEventPacket", "net.minecraft.network.protocol.game.PacketPlayOutEntityStatus"); - spigotClassnames.put("net.minecraft.network.protocol.game.ClientboundExplodePacket", "net.minecraft.network.protocol.game.PacketPlayOutExplosion"); - spigotClassnames.put("net.minecraft.network.protocol.game.ClientboundGameEventPacket", "net.minecraft.network.protocol.game.PacketPlayOutGameStateChange"); - spigotClassnames.put("net.minecraft.network.protocol.game.ClientboundLevelChunkPacketData$BlockEntityInfo", "net.minecraft.network.protocol.game.ClientboundLevelChunkPacketData$a"); - spigotClassnames.put("net.minecraft.network.protocol.game.ClientboundLevelEventPacket", "net.minecraft.network.protocol.game.PacketPlayOutWorldEvent"); - spigotClassnames.put("net.minecraft.network.protocol.game.ClientboundLevelParticlesPacket", "net.minecraft.network.protocol.game.PacketPlayOutWorldParticles"); - spigotClassnames.put("net.minecraft.network.protocol.game.ClientboundMoveEntityPacket", "net.minecraft.network.protocol.game.PacketPlayOutEntity"); - spigotClassnames.put("net.minecraft.network.protocol.game.ClientboundMoveEntityPacket$Pos", "net.minecraft.network.protocol.game.PacketPlayOutEntity$PacketPlayOutRelEntityMove"); - spigotClassnames.put("net.minecraft.network.protocol.game.ClientboundMoveEntityPacket$PosRot", "net.minecraft.network.protocol.game.PacketPlayOutEntity$PacketPlayOutRelEntityMoveLook"); - spigotClassnames.put("net.minecraft.network.protocol.game.ClientboundMoveEntityPacket$Rot", "net.minecraft.network.protocol.game.PacketPlayOutEntity$PacketPlayOutEntityLook"); - spigotClassnames.put("net.minecraft.network.protocol.game.ClientboundOpenSignEditorPacket", "net.minecraft.network.protocol.game.PacketPlayOutOpenSignEditor"); - spigotClassnames.put("net.minecraft.network.protocol.game.ClientboundRemoveEntitiesPacket", "net.minecraft.network.protocol.game.PacketPlayOutEntityDestroy"); - spigotClassnames.put("net.minecraft.network.protocol.game.ClientboundRotateHeadPacket", "net.minecraft.network.protocol.game.PacketPlayOutEntityHeadRotation"); - spigotClassnames.put("net.minecraft.network.protocol.game.ClientboundSectionBlocksUpdatePacket", "net.minecraft.network.protocol.game.PacketPlayOutMultiBlockChange"); - spigotClassnames.put("net.minecraft.network.protocol.game.ClientboundSetEntityDataPacket", "net.minecraft.network.protocol.game.PacketPlayOutEntityMetadata"); - spigotClassnames.put("net.minecraft.network.protocol.game.ClientboundSetEntityMotionPacket", "net.minecraft.network.protocol.game.PacketPlayOutEntityVelocity"); - spigotClassnames.put("net.minecraft.network.protocol.game.ClientboundSetEquipmentPacket", "net.minecraft.network.protocol.game.PacketPlayOutEntityEquipment"); - spigotClassnames.put("net.minecraft.network.protocol.game.ClientboundSetObjectivePacket", "net.minecraft.network.protocol.game.PacketPlayOutScoreboardObjective"); - spigotClassnames.put("net.minecraft.network.protocol.game.ClientboundSetScorePacket", "net.minecraft.network.protocol.game.PacketPlayOutScoreboardScore"); - spigotClassnames.put("net.minecraft.network.protocol.game.ClientboundSoundPacket", "net.minecraft.network.protocol.game.PacketPlayOutNamedSoundEffect"); - spigotClassnames.put("net.minecraft.network.protocol.game.ClientboundTeleportEntityPacket", "net.minecraft.network.protocol.game.PacketPlayOutEntityTeleport"); - spigotClassnames.put("net.minecraft.network.protocol.game.ServerboundContainerClickPacket", "net.minecraft.network.protocol.game.PacketPlayInWindowClick"); - spigotClassnames.put("net.minecraft.network.protocol.game.ServerboundInteractPacket", "net.minecraft.network.protocol.game.PacketPlayInUseEntity"); - spigotClassnames.put("net.minecraft.network.protocol.game.ServerboundInteractPacket$Action", "net.minecraft.network.protocol.game.PacketPlayInUseEntity$EnumEntityUseAction"); - spigotClassnames.put("net.minecraft.network.protocol.game.ServerboundInteractPacket$ActionType", "net.minecraft.network.protocol.game.PacketPlayInUseEntity$b"); - spigotClassnames.put("net.minecraft.network.protocol.game.ServerboundMovePlayerPacket$Pos", "net.minecraft.network.protocol.game.PacketPlayInFlying$PacketPlayInPosition"); - spigotClassnames.put("net.minecraft.network.protocol.game.ServerboundMovePlayerPacket$PosRot", "net.minecraft.network.protocol.game.PacketPlayInFlying$PacketPlayInPositionLook"); - spigotClassnames.put("net.minecraft.network.protocol.game.ServerboundMovePlayerPacket$Rot", "net.minecraft.network.protocol.game.PacketPlayInFlying$PacketPlayInLook"); - spigotClassnames.put("net.minecraft.network.protocol.game.ServerboundPlayerActionPacket", "net.minecraft.network.protocol.game.PacketPlayInBlockDig"); - spigotClassnames.put("net.minecraft.network.protocol.game.ServerboundSetCreativeModeSlotPacket", "net.minecraft.network.protocol.game.PacketPlayInSetCreativeSlot"); - spigotClassnames.put("net.minecraft.network.protocol.game.ServerboundSignUpdatePacket", "net.minecraft.network.protocol.game.PacketPlayInUpdateSign"); - spigotClassnames.put("net.minecraft.network.protocol.game.ServerboundUseItemPacket", "net.minecraft.network.protocol.game.PacketPlayInBlockPlace"); - spigotClassnames.put("net.minecraft.network.protocol.game.ServerboundUseItemOnPacket", "net.minecraft.network.protocol.game.PacketPlayInUseItem"); - - spigotClassnames.put("net.minecraft.network.syncher.EntityDataAccessor", "net.minecraft.network.syncher.DataWatcherObject"); - spigotClassnames.put("net.minecraft.network.syncher.EntityDataSerializer", "net.minecraft.network.syncher.DataWatcherSerializer"); - spigotClassnames.put("net.minecraft.network.syncher.EntityDataSerializers", "net.minecraft.network.syncher.DataWatcherRegistry"); - spigotClassnames.put("net.minecraft.network.syncher.SynchedEntityData$DataItem", "net.minecraft.network.syncher.DataWatcher$Item"); - - spigotClassnames.put("net.minecraft.server.ServerScoreboard$Method", "net.minecraft.server.ScoreboardServer$Action"); - - spigotClassnames.put("net.minecraft.server.level.ChunkMap", "net.minecraft.server.level.PlayerChunkMap"); - spigotClassnames.put("net.minecraft.server.level.ChunkMap$TrackedEntity", "net.minecraft.server.level.PlayerChunkMap$EntityTracker"); - spigotClassnames.put("net.minecraft.server.level.ServerChunkCache", "net.minecraft.server.level.ChunkProviderServer"); - spigotClassnames.put("net.minecraft.server.level.ServerLevel", "net.minecraft.server.level.WorldServer"); - spigotClassnames.put("net.minecraft.server.level.ServerPlayer", "net.minecraft.server.level.EntityPlayer"); - - spigotClassnames.put("net.minecraft.server.network.ServerConnectionListener", "net.minecraft.server.network.ServerConnection"); - - spigotClassnames.put("net.minecraft.world.InteractionHand", "net.minecraft.world.EnumHand"); - - spigotClassnames.put("net.minecraft.world.entity.EntityType", "net.minecraft.world.entity.EntityTypes"); - spigotClassnames.put("net.minecraft.world.entity.Pose", "net.minecraft.world.entity.EntityPose"); - - spigotClassnames.put("net.minecraft.world.entity.item.PrimedTnt", "net.minecraft.world.entity.item.EntityTNTPrimed"); - - spigotClassnames.put("net.minecraft.world.entity.projectile.AbstractArrow", "net.minecraft.world.entity.projectile.EntityArrow"); - - spigotClassnames.put("net.minecraft.world.level.GameType", "net.minecraft.world.level.EnumGamemode"); - spigotClassnames.put("net.minecraft.world.level.LevelAccessor", "net.minecraft.world.level.GeneratorAccess"); - - spigotClassnames.put("net.minecraft.world.level.block.state.BlockState", "net.minecraft.world.level.block.state.IBlockData"); - spigotClassnames.put("net.minecraft.world.level.block.state.StateDefinition", "net.minecraft.world.level.block.state.BlockStateList"); - - spigotClassnames.put("net.minecraft.world.level.chunk.LevelChunk", "net.minecraft.world.level.chunk.Chunk"); - - spigotClassnames.put("net.minecraft.world.level.material.FlowingFluid", "net.minecraft.world.level.material.FluidTypeFlowing"); - spigotClassnames.put("net.minecraft.world.level.material.Fluids", "net.minecraft.world.level.material.FluidTypes"); - spigotClassnames.put("net.minecraft.world.level.material.FluidState", "net.minecraft.world.level.material.Fluid"); - - spigotClassnames.put("net.minecraft.world.phys.BlockHitResult", "net.minecraft.world.phys.MovingObjectPositionBlock"); - spigotClassnames.put("net.minecraft.world.phys.Vec3", "net.minecraft.world.phys.Vec3D"); - - spigotClassnames.put("net.minecraft.resources.ResourceLocation", "net.minecraft.resources.MinecraftKey"); - - spigotClassnames.put("net.minecraft.util.ProgressListener", "net.minecraft.util.IProgressUpdate"); - } - - public static Class getClass(String name) { - try { - if (name.startsWith("org.bukkit.craftbukkit")) { - return Class.forName(ORG_BUKKIT_CRAFTBUKKIT + name.substring(22)); - } else if (MAJOR_VERSION < 17 && name.startsWith("net.minecraft")) { - return Class.forName(LEGACY_NET_MINECRAFT_SERVER + "." + spigotClassnames.getOrDefault(name, name).split("[.](?=[^.]*$)")[1]); - } else if (MAJOR_VERSION < 21 || MINOR_VERSION < 4) { - return Class.forName(spigotClassnames.getOrDefault(name, name)); - } else { - Class clazz = null; - try { - clazz = Class.forName(name); - } catch (ClassNotFoundException e) { - } - if (clazz != null && clazz.getName().equals(name)) { - return clazz; - } - - try { - return Core.class.getClassLoader().getParent().loadClass(name); - } catch (ClassNotFoundException e) { - if (clazz == null) { - throw e; - } - - return clazz; - } - } - } catch (ClassNotFoundException e) { - throw new IllegalArgumentException("Cannot find " + name, e); - } - } - - @AllArgsConstructor - public static class Field { - private final java.lang.reflect.Field f; - - @SuppressWarnings("unchecked") - public T get(Object target) { - try { - return (T) f.get(target); - } catch (IllegalAccessException e) { - throw new IllegalArgumentException("Cannot read field", e); - } - } - - public void set(Object target, Object value) { - try { - f.set(target, value); - } catch (IllegalAccessException e) { - throw new IllegalArgumentException("Cannot write field", e); - } - } - } - - public static Field getField(Class target, String name, Class fieldType) { - return getField(target, name, fieldType, 0); - } - - public static Field getField(Class target, Class fieldType, int index) { - return getField(target, null, fieldType, index); - } - - public static Field getField(Class target, Class fieldType, int index, Class... parameters) { - return getField(target, null, fieldType, index, parameters); - } - - private static Field getField(Class target, String name, Class fieldType, int index, Class... parameters) { - for (final java.lang.reflect.Field field : target.getDeclaredFields()) { - if (matching(field, name, fieldType, parameters) && index-- <= 0) { - field.setAccessible(true); - return new Field<>(field); - } - } - - // Search in parent classes - if (target.getSuperclass() != null) { - return getField(target.getSuperclass(), name, fieldType, index); - } - - throw new IllegalArgumentException("Cannot find field with type " + fieldType); - } - - private static boolean matching(java.lang.reflect.Field field, String name, Class fieldType, Class... parameters) { - if (name != null && !field.getName().equals(name)) return false; - - if (!fieldType.isAssignableFrom(field.getType())) return false; - - if (parameters.length > 0) { - Type[] arguments = ((ParameterizedType) field.getGenericType()).getActualTypeArguments(); - - for (int i = 0; i < parameters.length; i++) { - if (arguments[i] instanceof ParameterizedType ? ((ParameterizedType) arguments[i]).getRawType() != parameters[i] : arguments[i] != parameters[i]) { - return false; - } - } - } - return true; - } - - @AllArgsConstructor - public static class Method { - private final java.lang.reflect.Method m; - - public Object invoke(Object target, Object... arguments) { - try { - return m.invoke(target, arguments); - } catch (Exception e) { - throw new IllegalArgumentException("Cannot invoke method " + m, e); - } - } - } - - public static Method getTypedMethod(Class clazz, String methodName, Class returnType, Class... params) { - for (final java.lang.reflect.Method method : clazz.getDeclaredMethods()) { - if ((methodName == null || method.getName().equals(methodName)) - && (returnType == null || method.getReturnType().equals(returnType)) - && Arrays.equals(method.getParameterTypes(), params)) { - method.setAccessible(true); - return new Method(method); - } - } - - // Search in every superclass - if (clazz.getSuperclass() != null) { - return getTypedMethod(clazz.getSuperclass(), methodName, returnType, params); - } - - throw new IllegalArgumentException(String.format("Cannot find method %s (%s).", methodName, Arrays.asList(params))); - } - - @AllArgsConstructor - public static class Constructor { - private final java.lang.reflect.Constructor c; - - public Object invoke(Object... arguments) { - try { - return c.newInstance(arguments); - } catch (Exception e) { - throw new IllegalArgumentException("Cannot invoke constructor " + c, e); - } - } - } - - public static Constructor getConstructor(Class clazz, Class... params) { - for (final java.lang.reflect.Constructor constructor : clazz.getDeclaredConstructors()) { - if (Arrays.equals(constructor.getParameterTypes(), params)) { - constructor.setAccessible(true); - return new Constructor(constructor); - } - } - - throw new IllegalStateException(String.format("Unable to find constructor for %s (%s).", clazz, Arrays.asList(params))); - } - public static Object newInstance(Class clazz) { try { return Unsafe.getUnsafe().allocateInstance(clazz); diff --git a/SpigotCore/SpigotCore_Main/src/de/steamwar/core/BountifulWrapper.java b/SpigotCore/SpigotCore_Main/src/de/steamwar/core/BountifulWrapper.java index 64bef9b5..f02a59f7 100644 --- a/SpigotCore/SpigotCore_Main/src/de/steamwar/core/BountifulWrapper.java +++ b/SpigotCore/SpigotCore_Main/src/de/steamwar/core/BountifulWrapper.java @@ -19,13 +19,8 @@ package de.steamwar.core; -import de.steamwar.Reflection; import net.md_5.bungee.api.ChatMessageType; import net.md_5.bungee.api.chat.BaseComponent; -import net.minecraft.network.syncher.EntityDataAccessor; -import net.minecraft.network.syncher.EntityDataSerializer; -import net.minecraft.network.syncher.EntityDataSerializers; -import net.minecraft.network.syncher.SynchedEntityData; import org.bukkit.Sound; import org.bukkit.entity.Player; @@ -40,15 +35,4 @@ public class BountifulWrapper { if (type == ChatMessageType.CHAT) type = ChatMessageType.SYSTEM; player.spigot().sendMessage(type, msg); } - - private static final Class dataWatcherRegistry = EntityDataSerializers.class; - private static final Class dataWatcherSerializer = EntityDataSerializer.class; - - public Object getDataWatcherObject(int index, Class type) { - return new EntityDataAccessor<>(index, (EntityDataSerializer) Reflection.getField(dataWatcherRegistry, dataWatcherSerializer, 0, type).get(null)); - } - - public Object getDataWatcherItem(Object dwo, Object value) { - return new SynchedEntityData.DataItem<>((EntityDataAccessor) dwo, value); - } } diff --git a/SpigotCore/SpigotCore_Main/src/de/steamwar/core/Core.java b/SpigotCore/SpigotCore_Main/src/de/steamwar/core/Core.java index a1aefd55..8f076c0a 100644 --- a/SpigotCore/SpigotCore_Main/src/de/steamwar/core/Core.java +++ b/SpigotCore/SpigotCore_Main/src/de/steamwar/core/Core.java @@ -5,8 +5,8 @@ * * 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. + * the Free Software Foundation, either versionStrings 3 of the License, or + * (at your option) any later versionStrings. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of @@ -20,7 +20,6 @@ package de.steamwar.core; import com.comphenix.tinyprotocol.TinyProtocol; -import de.steamwar.Reflection; import de.steamwar.command.SWCommandUtils; import de.steamwar.command.SWTypeMapperCreator; import de.steamwar.command.TabCompletionCache; @@ -49,9 +48,13 @@ public class Core extends JavaPlugin { public static final Message MESSAGE = new Message("SpigotCore", Core.class.getClassLoader()); + @Getter @Deprecated - public static int getVersion() { - return Reflection.MAJOR_VERSION; + private static final int version; + + static { + String[] versionStrings = Bukkit.getServer().getBukkitVersion().split("-")[0].split("\\."); + version = Integer.parseInt(versionStrings[1]); } @Getter diff --git a/SpigotCore/SpigotCore_Main/src/de/steamwar/core/FlatteningWrapper.java b/SpigotCore/SpigotCore_Main/src/de/steamwar/core/FlatteningWrapper.java deleted file mode 100644 index 6d6d4d1b..00000000 --- a/SpigotCore/SpigotCore_Main/src/de/steamwar/core/FlatteningWrapper.java +++ /dev/null @@ -1,34 +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 . - */ - -package de.steamwar.core; - -import lombok.experimental.UtilityClass; -import net.minecraft.network.chat.MutableComponent; -import net.minecraft.network.chat.contents.PlainTextContents; - -import java.util.Optional; - -@UtilityClass -public class FlatteningWrapper { - - public Object formatDisplayName(String displayName) { - return displayName != null ? Optional.of((Object) MutableComponent.create(PlainTextContents.create(displayName))) : Optional.empty(); - } -} diff --git a/SpigotCore/SpigotCore_Main/src/de/steamwar/entity/RArmorStand.java b/SpigotCore/SpigotCore_Main/src/de/steamwar/entity/RArmorStand.java index 746a080e..be389fcb 100644 --- a/SpigotCore/SpigotCore_Main/src/de/steamwar/entity/RArmorStand.java +++ b/SpigotCore/SpigotCore_Main/src/de/steamwar/entity/RArmorStand.java @@ -49,7 +49,7 @@ public class RArmorStand extends REntity implements RInteractableEntity blockWatcher = new EntityDataAccessor<>(23, EntityDataSerializers.BLOCK_STATE); - private void getBlock(boolean ignoreDefault, BiConsumer packetSink) { + private void getBlock(boolean ignoreDefault, EntityDataPacketBuilder packetSink) { if (ignoreDefault || !block.getAsString(true).equals(DEFAULT_BLOCK.getAsString(true))) { - packetSink.accept(blockWatcher, ((CraftBlockData) block).getState()); + packetSink.add(blockWatcher, ((CraftBlockData) block).getState()); } } } diff --git a/SpigotCore/SpigotCore_Main/src/de/steamwar/entity/RDisplay.java b/SpigotCore/SpigotCore_Main/src/de/steamwar/entity/RDisplay.java index 4ec21947..d96d085c 100644 --- a/SpigotCore/SpigotCore_Main/src/de/steamwar/entity/RDisplay.java +++ b/SpigotCore/SpigotCore_Main/src/de/steamwar/entity/RDisplay.java @@ -89,18 +89,13 @@ public abstract class RDisplay extends REntity { } @SafeVarargs - protected final void sendPacket(Consumer packetSink, BiConsumer>... dataSinkSinks) { - List keyValueData = new ArrayList<>(); + protected final void sendPacket(Consumer packetSink, BiConsumer... dataSinkSinks) { + EntityDataPacketBuilder builder = entityDataPacket(); boolean ignoreDefault = packetSink == updatePacketSink; - for (BiConsumer> dataSinkSink : dataSinkSinks) { - dataSinkSink.accept(ignoreDefault, (dataWatcher, value) -> { - keyValueData.add(dataWatcher); - keyValueData.add(value); - }); - } - if (!keyValueData.isEmpty()) { - packetSink.accept(getDataWatcherPacket(keyValueData.toArray())); + for (BiConsumer dataSinkSink : dataSinkSinks) { + dataSinkSink.accept(ignoreDefault, builder); } + if (!builder.isEmpty()) builder.send(packetSink); } public void setTransform(@NonNull Transformation transform) { @@ -113,12 +108,12 @@ public abstract class RDisplay extends REntity { private static final EntityDataAccessor scaleWatcher = new EntityDataAccessor<>(12, EntityDataSerializers.VECTOR3); private static final EntityDataAccessor rightRotationWatcher = new EntityDataAccessor<>(14, EntityDataSerializers.QUATERNION); - private void getTransformData(boolean ignoreDefault, BiConsumer dataSink) { + private void getTransformData(boolean ignoreDefault, EntityDataPacketBuilder dataSink) { if (ignoreDefault || !transform.equals(DEFAULT_TRANSFORM)) { - dataSink.accept(translationWatcher, transform.getTranslation()); - dataSink.accept(leftRotationWatcher, transform.getLeftRotation()); - dataSink.accept(scaleWatcher, transform.getScale()); - dataSink.accept(rightRotationWatcher, transform.getRightRotation()); + dataSink.add(translationWatcher, transform.getTranslation()); + dataSink.add(leftRotationWatcher, transform.getLeftRotation()); + dataSink.add(scaleWatcher, transform.getScale()); + dataSink.add(rightRotationWatcher, transform.getRightRotation()); } } @@ -130,10 +125,10 @@ public abstract class RDisplay extends REntity { private static final EntityDataAccessor transformationInterpolationDurationWatcher = new EntityDataAccessor<>(9, EntityDataSerializers.INT); private static final EntityDataAccessor positionOrRotationInterpolationDurationWatcher = new EntityDataAccessor<>(10, EntityDataSerializers.INT); - private void getInterpolationDuration(boolean ignoreDefault, BiConsumer packetSink) { + private void getInterpolationDuration(boolean ignoreDefault, EntityDataPacketBuilder packetSink) { if (ignoreDefault || interpolationDelay != 0) { - packetSink.accept(transformationInterpolationDurationWatcher, interpolationDuration); - packetSink.accept(positionOrRotationInterpolationDurationWatcher, interpolationDuration); + packetSink.add(transformationInterpolationDurationWatcher, interpolationDuration); + packetSink.add(positionOrRotationInterpolationDurationWatcher, interpolationDuration); } } @@ -144,9 +139,9 @@ public abstract class RDisplay extends REntity { private static final EntityDataAccessor viewRangeWatcher = new EntityDataAccessor<>(17, EntityDataSerializers.FLOAT); - private void getViewRange(boolean ignoreDefault, BiConsumer packetSink) { + private void getViewRange(boolean ignoreDefault, EntityDataPacketBuilder packetSink) { if (ignoreDefault || viewRange != 1.0F) { - packetSink.accept(viewRangeWatcher, viewRange); + packetSink.add(viewRangeWatcher, viewRange); } } @@ -157,9 +152,9 @@ public abstract class RDisplay extends REntity { private static final EntityDataAccessor shadowRadiusWatcher = new EntityDataAccessor<>(18, EntityDataSerializers.FLOAT); - private void getShadowRadius(boolean ignoreDefault, BiConsumer packetSink) { + private void getShadowRadius(boolean ignoreDefault, EntityDataPacketBuilder packetSink) { if (ignoreDefault || shadowRadius != 0.0F) { - packetSink.accept(shadowRadiusWatcher, shadowRadius); + packetSink.add(shadowRadiusWatcher, shadowRadius); } } @@ -170,9 +165,9 @@ public abstract class RDisplay extends REntity { private static final EntityDataAccessor shadowStrengthWatcher = new EntityDataAccessor<>(19, EntityDataSerializers.FLOAT); - private void getShadowStrength(boolean ignoreDefault, BiConsumer packetSink) { + private void getShadowStrength(boolean ignoreDefault, EntityDataPacketBuilder packetSink) { if (ignoreDefault || shadowStrength != 1.0F) { - packetSink.accept(shadowStrengthWatcher, shadowStrength); + packetSink.add(shadowStrengthWatcher, shadowStrength); } } @@ -183,9 +178,9 @@ public abstract class RDisplay extends REntity { private static final EntityDataAccessor displayWidthWatcher = new EntityDataAccessor<>(20, EntityDataSerializers.FLOAT); - private void getDisplayWidth(boolean ignoreDefault, BiConsumer packetSink) { + private void getDisplayWidth(boolean ignoreDefault, EntityDataPacketBuilder packetSink) { if (ignoreDefault || displayWidth != 0.0F) { - packetSink.accept(displayWidthWatcher, displayWidth); + packetSink.add(displayWidthWatcher, displayWidth); } } @@ -196,9 +191,9 @@ public abstract class RDisplay extends REntity { private static final EntityDataAccessor displayHeightWatcher = new EntityDataAccessor<>(21, EntityDataSerializers.FLOAT); - private void getDisplayHeight(boolean ignoreDefault, BiConsumer packetSink) { + private void getDisplayHeight(boolean ignoreDefault, EntityDataPacketBuilder packetSink) { if (ignoreDefault || displayHeight != 0.0F) { - packetSink.accept(displayHeightWatcher, displayHeight); + packetSink.add(displayHeightWatcher, displayHeight); } } @@ -209,9 +204,9 @@ public abstract class RDisplay extends REntity { private static final EntityDataAccessor interpolationDelayWatcher = new EntityDataAccessor<>(8, EntityDataSerializers.INT); - private void getInterpolationDelay(boolean ignoreDefault, BiConsumer packetSink) { + private void getInterpolationDelay(boolean ignoreDefault, EntityDataPacketBuilder packetSink) { if (ignoreDefault || interpolationDelay != 0) { - packetSink.accept(interpolationDelayWatcher, interpolationDelay); + packetSink.add(interpolationDelayWatcher, interpolationDelay); } } @@ -222,9 +217,9 @@ public abstract class RDisplay extends REntity { private static final EntityDataAccessor billboardWatcher = new EntityDataAccessor<>(15, EntityDataSerializers.BYTE); - private void getBillboard(boolean ignoreDefault, BiConsumer packetSink) { + private void getBillboard(boolean ignoreDefault, EntityDataPacketBuilder packetSink) { if (ignoreDefault || billboard != Display.Billboard.FIXED) { - packetSink.accept(billboardWatcher, (byte) billboard.ordinal()); + packetSink.add(billboardWatcher, (byte) billboard.ordinal()); } } @@ -235,9 +230,9 @@ public abstract class RDisplay extends REntity { private static final EntityDataAccessor glowColorOverrideWatcher = new EntityDataAccessor<>(22, EntityDataSerializers.INT); - private void getGlowColorOverride(boolean ignoreDefault, BiConsumer packetSink) { + private void getGlowColorOverride(boolean ignoreDefault, EntityDataPacketBuilder packetSink) { if (ignoreDefault || glowColorOverride != null) { - packetSink.accept(glowColorOverrideWatcher, glowColorOverride == null ? -1 : glowColorOverride.asARGB()); + packetSink.add(glowColorOverrideWatcher, glowColorOverride == null ? -1 : glowColorOverride.asARGB()); } } @@ -248,9 +243,9 @@ public abstract class RDisplay extends REntity { private static final EntityDataAccessor brightnessWatcher = new EntityDataAccessor<>(16, EntityDataSerializers.INT); - private void getBrightness(boolean ignoreDefault, BiConsumer packetSink) { + private void getBrightness(boolean ignoreDefault, EntityDataPacketBuilder packetSink) { if (ignoreDefault || brightness != null) { - packetSink.accept(brightnessWatcher, brightness == null ? -1 : brightness.getBlockLight() << 4 | brightness.getSkyLight() << 20); + packetSink.add(brightnessWatcher, brightness == null ? -1 : brightness.getBlockLight() << 4 | brightness.getSkyLight() << 20); } } } diff --git a/SpigotCore/SpigotCore_Main/src/de/steamwar/entity/REntity.java b/SpigotCore/SpigotCore_Main/src/de/steamwar/entity/REntity.java index 2d867cc1..63411a0c 100644 --- a/SpigotCore/SpigotCore_Main/src/de/steamwar/entity/REntity.java +++ b/SpigotCore/SpigotCore_Main/src/de/steamwar/entity/REntity.java @@ -21,12 +21,12 @@ package de.steamwar.entity; import com.mojang.datafixers.util.Pair; import de.steamwar.Reflection; -import de.steamwar.core.BountifulWrapper; -import de.steamwar.core.FlatteningWrapper; import it.unimi.dsi.fastutil.ints.IntArrayList; import lombok.Getter; import net.minecraft.core.registries.BuiltInRegistries; import net.minecraft.network.chat.Component; +import net.minecraft.network.chat.MutableComponent; +import net.minecraft.network.chat.contents.PlainTextContents; import net.minecraft.network.protocol.game.*; import net.minecraft.network.syncher.EntityDataAccessor; import net.minecraft.network.syncher.EntityDataSerializers; @@ -193,12 +193,12 @@ public class REntity { public void setPose(Pose pose) { this.pose = pose; - server.updateEntity(this, getDataWatcherPacket(poseDataWatcher, pose)); + server.updateEntity(this, entityDataPacket().add(poseDataWatcher, pose).build()); } public void setOnFire(boolean perma) { fireTick = perma ? -1 : 21; - server.updateEntity(this, getDataWatcherPacket(entityStatusWatcher, getEntityStatus())); + server.updateEntity(this, entityDataPacket().add(entityStatusWatcher, getEntityStatus()).build()); } public boolean isOnFire() { @@ -207,20 +207,18 @@ public class REntity { public void setInvisible(boolean invisible) { this.invisible = invisible; - server.updateEntity(this, getDataWatcherPacket(entityStatusWatcher, getEntityStatus())); + server.updateEntity(this, entityDataPacket().add(entityStatusWatcher, getEntityStatus()).build()); } public void setBowDrawn(boolean drawn, boolean offHand) { bowDrawn = drawn; - server.updateEntity(this, getDataWatcherPacket(poseDataWatcher, Pose.SHOOTING)); + server.updateEntity(this, entityDataPacket().add(poseDataWatcher, Pose.SHOOTING).build()); } public void setDisplayName(String displayName) { this.displayName = displayName; - server.updateEntity(this, getDataWatcherPacket( - nameWatcher, FlatteningWrapper.formatDisplayName(displayName), - nameVisibleWatcher, displayName != null - )); + server.updateEntity(this, entityDataPacket().add(nameWatcher, formatDisplayName(displayName)) + .add(nameVisibleWatcher, displayName != null).build()); } public void setItem(Object slot, ItemStack stack) { @@ -234,12 +232,12 @@ public class REntity { public void setNoGravity(boolean noGravity) { this.noGravity = noGravity; - server.updateEntity(this, getDataWatcherPacket(noGravityDataWatcher, noGravity)); + server.updateEntity(this, entityDataPacket().add(noGravityDataWatcher, noGravity).build()); } public void setGlowing(boolean glowing) { this.isGlowing = glowing; - server.updateEntity(this, getDataWatcherPacket(entityStatusWatcher, getEntityStatus())); + server.updateEntity(this, entityDataPacket().add(entityStatusWatcher, getEntityStatus()).build()); } private ClientboundAddEntityPacket spawnPacketGenerator() { @@ -263,20 +261,22 @@ public class REntity { } if (pose != Pose.STANDING) { - packetSink.accept(getDataWatcherPacket(poseDataWatcher, pose)); + entityDataPacket().add(poseDataWatcher, pose).send(packetSink); } byte status = getEntityStatus(); if (status != 0) { - packetSink.accept(getDataWatcherPacket(entityStatusWatcher, getEntityStatus())); + entityDataPacket().add(entityStatusWatcher, getEntityStatus()).send(packetSink); } if (displayName != null) { - packetSink.accept(getDataWatcherPacket(nameWatcher, FlatteningWrapper.formatDisplayName(displayName), nameVisibleWatcher, true)); + entityDataPacket().add(nameWatcher, formatDisplayName(displayName)) + .add(nameVisibleWatcher, true) + .send(packetSink); } if (noGravity) { - packetSink.accept(getDataWatcherPacket(noGravityDataWatcher, true)); + entityDataPacket().add(noGravityDataWatcher, true).send(packetSink); } } @@ -284,7 +284,7 @@ public class REntity { if (fireTick > 0) { fireTick--; if (fireTick == 0) { - server.updateEntity(this, getDataWatcherPacket(entityStatusWatcher, getEntityStatus())); + server.updateEntity(this, entityDataPacket().add(entityStatusWatcher, getEntityStatus()).build()); } } } @@ -315,13 +315,29 @@ public class REntity { return status; } - protected Object getDataWatcherPacket(Object... dataWatcherKeyValues) { - ArrayList> nativeWatchers = new ArrayList<>(1); - for (int i = 0; i < dataWatcherKeyValues.length; i += 2) { - nativeWatchers.add(((SynchedEntityData.DataItem) BountifulWrapper.impl.getDataWatcherItem(dataWatcherKeyValues[i], dataWatcherKeyValues[i + 1])).value()); + protected EntityDataPacketBuilder entityDataPacket() { + return new EntityDataPacketBuilder(); + } + + public class EntityDataPacketBuilder { + private List> values = new ArrayList<>(); + + public EntityDataPacketBuilder add(EntityDataAccessor accessor, T value) { + values.add(new SynchedEntityData.DataItem<>(accessor, value).value()); + return null; } - return new ClientboundSetEntityDataPacket(entityId, nativeWatchers); + public boolean isEmpty() { + return values.isEmpty(); + } + + public ClientboundSetEntityDataPacket build() { + return new ClientboundSetEntityDataPacket(entityId, values); + } + + public void send(Consumer packetSink) { + packetSink.accept(build()); + } } private Object getTeleportPacket() { @@ -364,4 +380,8 @@ public class REntity { private int calcVelocity(double value) { return (int) (Math.max(-3.9, Math.min(value, 3.9)) * 8000); } + + private static Optional formatDisplayName(String displayName) { + return displayName != null ? Optional.of(MutableComponent.create(PlainTextContents.create(displayName))) : Optional.empty(); + } } diff --git a/SpigotCore/SpigotCore_Main/src/de/steamwar/entity/RInteraction.java b/SpigotCore/SpigotCore_Main/src/de/steamwar/entity/RInteraction.java index 58514280..7d27063e 100644 --- a/SpigotCore/SpigotCore_Main/src/de/steamwar/entity/RInteraction.java +++ b/SpigotCore/SpigotCore_Main/src/de/steamwar/entity/RInteraction.java @@ -19,14 +19,13 @@ package de.steamwar.entity; -import de.steamwar.core.BountifulWrapper; import lombok.Getter; import lombok.Setter; +import net.minecraft.network.syncher.EntityDataAccessor; +import net.minecraft.network.syncher.EntityDataSerializers; import org.bukkit.Location; import org.bukkit.entity.EntityType; -import java.util.ArrayList; -import java.util.List; import java.util.function.BiConsumer; import java.util.function.Consumer; @@ -61,18 +60,13 @@ public class RInteraction extends REntity implements RInteractableEntity packetSink, BiConsumer>... dataSinkSinks) { - List keyValueData = new ArrayList<>(); + protected final void sendPacket(Consumer packetSink, BiConsumer... dataSinkSinks) { + EntityDataPacketBuilder builder = entityDataPacket(); boolean ignoreDefault = packetSink == updatePacketSink; - for (BiConsumer> dataSinkSink : dataSinkSinks) { - dataSinkSink.accept(ignoreDefault, (dataWatcher, value) -> { - keyValueData.add(dataWatcher); - keyValueData.add(value); - }); - } - if (!keyValueData.isEmpty()) { - packetSink.accept(getDataWatcherPacket(keyValueData.toArray())); + for (BiConsumer dataSinkSink : dataSinkSinks) { + dataSinkSink.accept(ignoreDefault, builder); } + if (!builder.isEmpty()) builder.send(packetSink); } public void setInteractionWidth(float interactionWidth) { @@ -80,11 +74,11 @@ public class RInteraction extends REntity implements RInteractableEntity interactionWidthWatcher = new EntityDataAccessor<>(8, EntityDataSerializers.FLOAT); - private void getInteractionWidthData(boolean ignoreDefault, BiConsumer dataSink) { + private void getInteractionWidthData(boolean ignoreDefault, EntityDataPacketBuilder dataSink) { if (ignoreDefault || interactionWidth != 1.0) { - dataSink.accept(interactionWidthWatcher, interactionWidth); + dataSink.add(interactionWidthWatcher, interactionWidth); } } @@ -93,11 +87,11 @@ public class RInteraction extends REntity implements RInteractableEntity interactionHeightWatcher = new EntityDataAccessor<>(9, EntityDataSerializers.FLOAT); - private void getInteractionHeightData(boolean ignoreDefault, BiConsumer dataSink) { + private void getInteractionHeightData(boolean ignoreDefault, EntityDataPacketBuilder dataSink) { if (ignoreDefault || interactionHeight != 1.0) { - dataSink.accept(interactionHeightWatcher, interactionHeight); + dataSink.add(interactionHeightWatcher, interactionHeight); } } @@ -106,11 +100,11 @@ public class RInteraction extends REntity implements RInteractableEntity responsiveWatcher = new EntityDataAccessor<>(10, EntityDataSerializers.BOOLEAN); - private void getResponsiveData(boolean ignoreDefault, BiConsumer dataSink) { + private void getResponsiveData(boolean ignoreDefault, EntityDataPacketBuilder dataSink) { if (ignoreDefault || !responsive) { - dataSink.accept(responsiveWatcher, responsive); + dataSink.add(responsiveWatcher, responsive); } } } \ No newline at end of file diff --git a/SpigotCore/SpigotCore_Main/src/de/steamwar/entity/RItemDisplay.java b/SpigotCore/SpigotCore_Main/src/de/steamwar/entity/RItemDisplay.java index e10d2ec9..21453516 100644 --- a/SpigotCore/SpigotCore_Main/src/de/steamwar/entity/RItemDisplay.java +++ b/SpigotCore/SpigotCore_Main/src/de/steamwar/entity/RItemDisplay.java @@ -63,9 +63,9 @@ public class RItemDisplay extends RDisplay { private static final EntityDataAccessor itemStackWatcher = new EntityDataAccessor<>(23, EntityDataSerializers.ITEM_STACK); - private void getItemStack(boolean ignoreDefault, BiConsumer packetSink) { + private void getItemStack(boolean ignoreDefault, EntityDataPacketBuilder packetSink) { if (ignoreDefault || !itemStack.equals(DEFAULT_ITEM_STACK)) { - packetSink.accept(itemStackWatcher, CraftItemStack.asNMSCopy(itemStack)); + packetSink.add(itemStackWatcher, CraftItemStack.asNMSCopy(itemStack)); } } @@ -76,9 +76,9 @@ public class RItemDisplay extends RDisplay { sendPacket(updatePacketSink, this::getItemDisplayTransform); } - private void getItemDisplayTransform(boolean ignoreDefault, BiConsumer packetSink) { + private void getItemDisplayTransform(boolean ignoreDefault, EntityDataPacketBuilder packetSink) { if (ignoreDefault || itemDisplayTransform != ItemDisplay.ItemDisplayTransform.NONE) { - packetSink.accept(itemDisplayTransformWatcher, (byte) itemDisplayTransform.ordinal()); + packetSink.add(itemDisplayTransformWatcher, (byte) itemDisplayTransform.ordinal()); } } } diff --git a/SpigotCore/SpigotCore_Main/src/de/steamwar/entity/RPlayer.java b/SpigotCore/SpigotCore_Main/src/de/steamwar/entity/RPlayer.java index daa9a648..cfb6304d 100644 --- a/SpigotCore/SpigotCore_Main/src/de/steamwar/entity/RPlayer.java +++ b/SpigotCore/SpigotCore_Main/src/de/steamwar/entity/RPlayer.java @@ -21,7 +21,6 @@ package de.steamwar.entity; import com.mojang.authlib.GameProfile; import com.mojang.authlib.properties.Property; -import de.steamwar.core.BountifulWrapper; import de.steamwar.core.ProtocolWrapper; import de.steamwar.network.CoreNetworkHandler; import de.steamwar.network.NetworkSender; @@ -29,6 +28,8 @@ import de.steamwar.network.packets.common.PlayerSkinRequestPacket; import lombok.Getter; import lombok.Setter; import net.minecraft.network.protocol.game.ClientboundAddEntityPacket; +import net.minecraft.network.syncher.EntityDataAccessor; +import net.minecraft.network.syncher.EntityDataSerializers; import net.minecraft.world.phys.Vec3; import org.bukkit.GameMode; import org.bukkit.Location; @@ -42,7 +43,7 @@ import java.util.function.Consumer; public class RPlayer extends REntity implements RInteractableEntity { - private static final Object skinPartsDataWatcher = BountifulWrapper.impl.getDataWatcherObject(17, Byte.class); + private static final EntityDataAccessor skinPartsDataWatcher = new EntityDataAccessor<>(17, EntityDataSerializers.BYTE); @Getter private final UUID actualUUID; @@ -85,7 +86,7 @@ public class RPlayer extends REntity implements RInteractableEntity { @Override void spawn(Consumer packetSink) { packetSink.accept(getNamedSpawnPacket()); - packetSink.accept(getDataWatcherPacket(skinPartsDataWatcher, (byte) 0x7F)); + entityDataPacket().add(skinPartsDataWatcher, (byte) 0x7F).send(packetSink); for (Map.Entry entry : itemSlots.entrySet()) { packetSink.accept(getEquipmentPacket(entry.getKey(), entry.getValue())); diff --git a/SpigotCore/SpigotCore_Main/src/de/steamwar/entity/RTextDisplay.java b/SpigotCore/SpigotCore_Main/src/de/steamwar/entity/RTextDisplay.java index b6db865f..f30fbfd8 100644 --- a/SpigotCore/SpigotCore_Main/src/de/steamwar/entity/RTextDisplay.java +++ b/SpigotCore/SpigotCore_Main/src/de/steamwar/entity/RTextDisplay.java @@ -77,9 +77,9 @@ public class RTextDisplay extends RDisplay { private static final EntityDataAccessor textWatcher = new EntityDataAccessor<>(23, EntityDataSerializers.COMPONENT); - private void getText(boolean ignoreDefault, BiConsumer packetSink) { + private void getText(boolean ignoreDefault, EntityDataPacketBuilder packetSink) { if (ignoreDefault || !text.isEmpty()) { - packetSink.accept(textWatcher, MutableComponent.create(PlainTextContents.create(text))); + packetSink.add(textWatcher, MutableComponent.create(PlainTextContents.create(text))); } } @@ -90,9 +90,9 @@ public class RTextDisplay extends RDisplay { private static final EntityDataAccessor lineWidthWatcher = new EntityDataAccessor<>(24, EntityDataSerializers.INT); - private void getLineWidth(boolean ignoreDefault, BiConsumer packetSink) { + private void getLineWidth(boolean ignoreDefault, EntityDataPacketBuilder packetSink) { if (ignoreDefault || lineWidth != 200) { - packetSink.accept(lineWidthWatcher, lineWidth); + packetSink.add(lineWidthWatcher, lineWidth); } } @@ -103,9 +103,9 @@ public class RTextDisplay extends RDisplay { private static final EntityDataAccessor textOpacityWatcher = new EntityDataAccessor<>(26, EntityDataSerializers.BYTE); - private void getTextOpacity(boolean ignoreDefault, BiConsumer packetSink) { + private void getTextOpacity(boolean ignoreDefault, EntityDataPacketBuilder packetSink) { if (ignoreDefault || textOpacity != (byte) -1) { - packetSink.accept(textOpacityWatcher, textOpacity); + packetSink.add(textOpacityWatcher, textOpacity); } } @@ -126,9 +126,9 @@ public class RTextDisplay extends RDisplay { private static final EntityDataAccessor backgroundColorWatcher = new EntityDataAccessor<>(25, EntityDataSerializers.INT); - private void getBackgroundColor(boolean ignoreDefault, BiConsumer packetSink) { + private void getBackgroundColor(boolean ignoreDefault, EntityDataPacketBuilder packetSink) { if (ignoreDefault || backgroundColor != null) { - packetSink.accept(backgroundColorWatcher, backgroundColor); + packetSink.add(backgroundColorWatcher, backgroundColor); } } @@ -146,7 +146,7 @@ public class RTextDisplay extends RDisplay { private static final EntityDataAccessor textStatusWatcher = new EntityDataAccessor<>(27, EntityDataSerializers.BYTE); - private void getTextStatus(boolean ignoreDefault, BiConsumer packetSink) { + private void getTextStatus(boolean ignoreDefault, EntityDataPacketBuilder packetSink) { byte status = 0; if (shadowed) { @@ -167,7 +167,7 @@ public class RTextDisplay extends RDisplay { } if (ignoreDefault || status != 0) { - packetSink.accept(textStatusWatcher, status); + packetSink.add(textStatusWatcher, status); } } } diff --git a/SpigotCore/SpigotCore_Main/src/de/steamwar/techhider/legacy/ChunkHider.java b/SpigotCore/SpigotCore_Main/src/de/steamwar/techhider/legacy/ChunkHider.java index 57bce2e2..dc4e3c33 100644 --- a/SpigotCore/SpigotCore_Main/src/de/steamwar/techhider/legacy/ChunkHider.java +++ b/SpigotCore/SpigotCore_Main/src/de/steamwar/techhider/legacy/ChunkHider.java @@ -19,16 +19,13 @@ package de.steamwar.techhider.legacy; -import de.steamwar.Reflection; import de.steamwar.techhider.ProtocolUtils; import io.netty.buffer.ByteBuf; import io.netty.buffer.Unpooled; import lombok.Getter; -import net.minecraft.core.Registry; import net.minecraft.core.registries.BuiltInRegistries; import net.minecraft.network.protocol.game.ClientboundLevelChunkPacketData; import net.minecraft.network.protocol.game.ClientboundLevelChunkWithLightPacket; -import net.minecraft.resources.ResourceLocation; import net.minecraft.util.SimpleBitStorage; import net.minecraft.world.level.block.entity.BlockEntityType; import org.bukkit.entity.Player; @@ -85,12 +82,9 @@ public class ChunkHider { }; } - private static final Registry> registry = Reflection.getField(BuiltInRegistries.class, "BLOCK_ENTITY_TYPE", Registry.class).get(null); - private static final Reflection.Method getKey = Reflection.getTypedMethod(Reflection.getClass("net.minecraft.core.Registry"), "getKey", ResourceLocation.class, Object.class); - protected boolean tileEntityVisible(Set hiddenBlockEntities, ClientboundLevelChunkPacketData.BlockEntityInfo tile) { BlockEntityType type = tile.type; - String path = ((ResourceLocation) getKey.invoke(registry, type)).getPath(); + String path = BuiltInRegistries.BLOCK_ENTITY_TYPE.getKey(type).getPath(); return !hiddenBlockEntities.contains(path); } diff --git a/buildSrc/src/main/kotlin/WidenerExtension.kt b/buildSrc/src/main/kotlin/WidenerExtension.kt index d0263567..73ae4e5b 100644 --- a/buildSrc/src/main/kotlin/WidenerExtension.kt +++ b/buildSrc/src/main/kotlin/WidenerExtension.kt @@ -32,7 +32,7 @@ abstract class WidenerExtension { abstract val accessWidenerFiles: ConfigurableFileCollection fun fromCatalog(vararg dependencies: Provider) { - val files = dependencies.map { dependency -> + dependencies.map { dependency -> project.provider { val dep = dependency.get() @@ -45,8 +45,6 @@ abstract class WidenerExtension { } .file } - } - - inputJars.from(files) + }.let { inputJars.from(it) } } } \ No newline at end of file diff --git a/buildSrc/src/main/kotlin/widener.gradle.kts b/buildSrc/src/main/kotlin/widener.gradle.kts index 71da2e8d..c85ae3d5 100644 --- a/buildSrc/src/main/kotlin/widener.gradle.kts +++ b/buildSrc/src/main/kotlin/widener.gradle.kts @@ -23,8 +23,6 @@ widener.accessWidenerFiles.setFrom( fileTree("src/") { include("**/*.accesswidener") } ) -// ─── Tasks (unchanged from before) ─────────────────────────────────────────── - val jarWidenerClasspath = rootProject.project(":AccessWidener") .tasks.named("shadowJar") .get().outputs.files @@ -68,7 +66,6 @@ project.gradle.projectsEvaluated { } tasks.named("compileJava") { - // dependsOn(allWidenedJars.buildDependencies) dependsOn(allWidenedJars) } } From 1590f8f0ee9c6689483e666325908feb78cfc0f4 Mon Sep 17 00:00:00 2001 From: YoyoNow Date: Thu, 11 Jun 2026 23:18:22 +0200 Subject: [PATCH 19/36] Remove Reflection --- .../main/java/de/steamwar/ClassPatcher.java | 8 ++++- .../java/de/steamwar/ClassTransformer.java | 11 +++++- .../src/bausystem.accesswidener | 3 -- .../bausystem/features/util/BindCommand.java | 16 ++------- .../src/de/steamwar/Reflection.java | 36 ------------------- .../steamwar/command/CommandRegistering.java | 23 +++--------- .../src/de/steamwar/entity/REntity.java | 7 ++-- .../src/de/steamwar/techhider/ChunkHider.java | 4 +-- .../de/steamwar/techhider/ProtocolUtils.java | 22 ++---------- .../steamwar/techhider/legacy/ChunkHider.java | 4 +-- .../steamwar/techhider/legacy/TechHider.java | 4 +-- .../src/spigotcore.accesswidener | 16 ++++++++- 12 files changed, 50 insertions(+), 104 deletions(-) delete mode 100644 SpigotCore/SpigotCore_Main/src/de/steamwar/Reflection.java diff --git a/AccessWidener/src/main/java/de/steamwar/ClassPatcher.java b/AccessWidener/src/main/java/de/steamwar/ClassPatcher.java index 2cce0c77..c54f69dd 100644 --- a/AccessWidener/src/main/java/de/steamwar/ClassPatcher.java +++ b/AccessWidener/src/main/java/de/steamwar/ClassPatcher.java @@ -43,6 +43,8 @@ public class ClassPatcher { /** Pre-computed set of targeted internal names for fast filtering. */ private final Set targets; + private final Set targetsPublicConstructor; + public ClassPatcher(List entries) { this.entries = entries; this.targets = entries.stream() @@ -53,6 +55,10 @@ public class ClassPatcher { return Stream.of(s, s.substring(0, index)); }) .collect(Collectors.toSet()); + this.targetsPublicConstructor = entries.stream() + .filter(entry -> entry.directive().equals("transitive-extendable")) + .map(AccessWidenerEntry::target) + .collect(Collectors.toSet()); } /** @@ -66,7 +72,7 @@ public class ClassPatcher { try { ClassReader cr = new ClassReader(classBytes); ClassWriter cw = new ClassWriter(cr, 0); - cr.accept(new ClassTransformer(cw, className, entries), ClassReader.SKIP_CODE | ClassReader.SKIP_DEBUG | ClassReader.SKIP_FRAMES); + cr.accept(new ClassTransformer(cw, className, entries, targetsPublicConstructor.contains(className)), ClassReader.SKIP_CODE | ClassReader.SKIP_DEBUG | ClassReader.SKIP_FRAMES); return cw.toByteArray(); } catch (Exception e) { LOG.warning("[AccessWidener] Failed to transform " + className + ": " + e.getMessage()); diff --git a/AccessWidener/src/main/java/de/steamwar/ClassTransformer.java b/AccessWidener/src/main/java/de/steamwar/ClassTransformer.java index ccc3df9d..534155b9 100644 --- a/AccessWidener/src/main/java/de/steamwar/ClassTransformer.java +++ b/AccessWidener/src/main/java/de/steamwar/ClassTransformer.java @@ -30,11 +30,13 @@ public class ClassTransformer extends ClassVisitor { private final String internalName; private final List entries; + private final boolean appendPublicConstructor; - public ClassTransformer(ClassVisitor cv, String internalName, List entries) { + public ClassTransformer(ClassVisitor cv, String internalName, List entries, boolean appendPublicConstructor) { super(Opcodes.ASM9, cv); this.internalName = internalName; this.entries = entries; + this.appendPublicConstructor = appendPublicConstructor; } @Override @@ -44,6 +46,13 @@ public class ClassTransformer extends ClassVisitor { if (!e.targets(internalName) || !"class".equals(e.memberType())) continue; newAccess = applyDirective(e.directive(), newAccess, false); } + if (appendPublicConstructor) { + MethodVisitor methodVisitor = visitMethod(Opcodes.ACC_PUBLIC, "", "()V", null, null); + methodVisitor.visitMaxs(0, 0); + methodVisitor.visitCode(); + methodVisitor.visitInsn(Opcodes.RETURN); + methodVisitor.visitEnd(); + } super.visit(version, newAccess, name, signature, superName, interfaces); } diff --git a/BauSystem/BauSystem_Main/src/bausystem.accesswidener b/BauSystem/BauSystem_Main/src/bausystem.accesswidener index 51fe2890..5dd89fdf 100644 --- a/BauSystem/BauSystem_Main/src/bausystem.accesswidener +++ b/BauSystem/BauSystem_Main/src/bausystem.accesswidener @@ -11,6 +11,3 @@ mutable field org/bukkit/craftbukkit/block/CraftBlockState world Lorg/bukkit/cra # For TickManager accessible field net/minecraft/server/ServerTickRateManager remainingSprintTicks J - -# Test -accessible field net/minecraft/core/registries/BuiltInRegistries BLOCK_ENTITY_TYPE Lnet/minecraft/core/Registry; \ No newline at end of file diff --git a/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/util/BindCommand.java b/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/util/BindCommand.java index bde7967e..5db75a81 100644 --- a/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/util/BindCommand.java +++ b/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/util/BindCommand.java @@ -38,6 +38,7 @@ import org.bukkit.Bukkit; import org.bukkit.NamespacedKey; import org.bukkit.command.CommandMap; import org.bukkit.command.CommandSender; +import org.bukkit.craftbukkit.CraftServer; import org.bukkit.entity.Player; import org.bukkit.event.EventHandler; import org.bukkit.event.Listener; @@ -47,7 +48,6 @@ import org.bukkit.inventory.ItemStack; import org.bukkit.inventory.meta.ItemMeta; import org.bukkit.persistence.PersistentDataType; -import java.lang.reflect.Field; import java.util.Arrays; import java.util.Collection; import java.util.List; @@ -73,19 +73,7 @@ public class BindCommand extends SWCommand implements Listener { } } - private static final CommandMap commandMap; - - static { - Field knownCommandsField; - try { - knownCommandsField = Bukkit.getServer().getClass().getDeclaredField("commandMap"); - knownCommandsField.setAccessible(true); - commandMap = (CommandMap) knownCommandsField.get(Bukkit.getServer()); - } catch (IllegalAccessException | NoSuchFieldException var2) { - Bukkit.shutdown(); - throw new SecurityException("Oh shit. Commands cannot be registered.", var2); - } - } + private static final CommandMap commandMap = ((CraftServer) Bukkit.getServer()).getCommandMap(); private static final NamespacedKey KEY = SWUtils.getNamespaceKey("command"); diff --git a/SpigotCore/SpigotCore_Main/src/de/steamwar/Reflection.java b/SpigotCore/SpigotCore_Main/src/de/steamwar/Reflection.java deleted file mode 100644 index 324d0e44..00000000 --- a/SpigotCore/SpigotCore_Main/src/de/steamwar/Reflection.java +++ /dev/null @@ -1,36 +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 . - */ - -package de.steamwar; - -import jdk.internal.misc.Unsafe; -import lombok.experimental.UtilityClass; - - -@UtilityClass -public final class Reflection { - - public static Object newInstance(Class clazz) { - try { - return Unsafe.getUnsafe().allocateInstance(clazz); - } catch (InstantiationException e) { - throw new SecurityException("Could not create object", e); - } - } -} diff --git a/SpigotCore/SpigotCore_Main/src/de/steamwar/command/CommandRegistering.java b/SpigotCore/SpigotCore_Main/src/de/steamwar/command/CommandRegistering.java index 4adffb58..90026343 100644 --- a/SpigotCore/SpigotCore_Main/src/de/steamwar/command/CommandRegistering.java +++ b/SpigotCore/SpigotCore_Main/src/de/steamwar/command/CommandRegistering.java @@ -22,35 +22,20 @@ package de.steamwar.command; import lombok.experimental.UtilityClass; import org.bukkit.Bukkit; import org.bukkit.command.Command; -import org.bukkit.command.CommandMap; import org.bukkit.command.SimpleCommandMap; +import org.bukkit.craftbukkit.CraftServer; -import java.lang.reflect.Field; import java.util.Map; @UtilityClass class CommandRegistering { - private static final CommandMap commandMap; + private static final SimpleCommandMap commandMap; private static final Map knownCommandMap; static { - try { - final Field commandMapField = Bukkit.getServer().getClass().getDeclaredField("commandMap"); - commandMapField.setAccessible(true); - commandMap = (CommandMap) commandMapField.get(Bukkit.getServer()); - } catch (NoSuchFieldException | IllegalAccessException exception) { - Bukkit.shutdown(); - throw new SecurityException("Oh shit. Commands cannot be registered.", exception); - } - try { - final Field knownCommandsField = SimpleCommandMap.class.getDeclaredField("knownCommands"); - knownCommandsField.setAccessible(true); - knownCommandMap = (Map) knownCommandsField.get(commandMap); - } catch (NoSuchFieldException | IllegalAccessException exception) { - Bukkit.shutdown(); - throw new SecurityException("Oh shit. Commands cannot be registered.", exception); - } + commandMap = ((CraftServer) Bukkit.getServer()).getCommandMap(); + knownCommandMap = commandMap.getKnownCommands(); } static void unregister(Command command) { diff --git a/SpigotCore/SpigotCore_Main/src/de/steamwar/entity/REntity.java b/SpigotCore/SpigotCore_Main/src/de/steamwar/entity/REntity.java index 63411a0c..1c7cce4d 100644 --- a/SpigotCore/SpigotCore_Main/src/de/steamwar/entity/REntity.java +++ b/SpigotCore/SpigotCore_Main/src/de/steamwar/entity/REntity.java @@ -20,7 +20,6 @@ package de.steamwar.entity; import com.mojang.datafixers.util.Pair; -import de.steamwar.Reflection; import it.unimi.dsi.fastutil.ints.IntArrayList; import lombok.Getter; import net.minecraft.core.registries.BuiltInRegistries; @@ -174,7 +173,7 @@ public class REntity { } public void showAnimation(byte animation) { - ClientboundAnimatePacket packet = (ClientboundAnimatePacket) Reflection.newInstance(ClientboundAnimatePacket.class); + ClientboundAnimatePacket packet = new ClientboundAnimatePacket(); packet.id = entityId; packet.action = animation; server.updateEntity(this, packet); @@ -185,7 +184,7 @@ public class REntity { } public void showDamage() { - ClientboundEntityEventPacket packet = (ClientboundEntityEventPacket) Reflection.newInstance(ClientboundEntityEventPacket.class); + ClientboundEntityEventPacket packet = new ClientboundEntityEventPacket(); packet.entityId = entityId; packet.eventId = (byte) 2; server.updateEntity(this, packet); @@ -363,7 +362,7 @@ public class REntity { } private Object getHeadRotationPacket() { - ClientboundRotateHeadPacket packet = (ClientboundRotateHeadPacket) Reflection.newInstance(ClientboundRotateHeadPacket.class); + ClientboundRotateHeadPacket packet = new ClientboundRotateHeadPacket(); packet.entityId = entityId; packet.yHeadRot = headYaw; return packet; diff --git a/SpigotCore/SpigotCore_Main/src/de/steamwar/techhider/ChunkHider.java b/SpigotCore/SpigotCore_Main/src/de/steamwar/techhider/ChunkHider.java index 471cdc4a..bd21f357 100644 --- a/SpigotCore/SpigotCore_Main/src/de/steamwar/techhider/ChunkHider.java +++ b/SpigotCore/SpigotCore_Main/src/de/steamwar/techhider/ChunkHider.java @@ -33,8 +33,8 @@ import java.util.List; import java.util.function.UnaryOperator; public class ChunkHider { - private static final UnaryOperator chunkPacketShallowCloner = ProtocolUtils.shallowCloneGenerator(ClientboundLevelChunkWithLightPacket.class); - private static final UnaryOperator chunkDataShallowCloner = ProtocolUtils.shallowCloneGenerator(ClientboundLevelChunkPacketData.class); + private static final UnaryOperator chunkPacketShallowCloner = ProtocolUtils.shallowCloneGenerator(ClientboundLevelChunkWithLightPacket.class, ClientboundLevelChunkWithLightPacket::new); + private static final UnaryOperator chunkDataShallowCloner = ProtocolUtils.shallowCloneGenerator(ClientboundLevelChunkPacketData.class, ClientboundLevelChunkPacketData::new); private final int SECTION_SPAN_SIZE = 16; private final byte BIT_PER_BLOCK_INDIRECTION_LIMIT = 8; diff --git a/SpigotCore/SpigotCore_Main/src/de/steamwar/techhider/ProtocolUtils.java b/SpigotCore/SpigotCore_Main/src/de/steamwar/techhider/ProtocolUtils.java index b97fe828..18997565 100644 --- a/SpigotCore/SpigotCore_Main/src/de/steamwar/techhider/ProtocolUtils.java +++ b/SpigotCore/SpigotCore_Main/src/de/steamwar/techhider/ProtocolUtils.java @@ -20,41 +20,25 @@ package de.steamwar.techhider; import com.google.common.primitives.Bytes; -import de.steamwar.Reflection; import io.netty.buffer.ByteBuf; -import java.lang.reflect.Array; import java.lang.reflect.Field; import java.lang.reflect.Modifier; import java.util.ArrayList; import java.util.List; import java.util.function.BiConsumer; -import java.util.function.BiFunction; +import java.util.function.Supplier; import java.util.function.UnaryOperator; public class ProtocolUtils { private ProtocolUtils() { } - @Deprecated - public static BiFunction, Object> arrayCloneGenerator(Class elementClass) { - return (array, worker) -> { - int length = Array.getLength(array); - Object result = Array.newInstance(elementClass, length); - - for (int i = 0; i < length; i++) { - Array.set(result, i, worker.apply(Array.get(array, i))); - } - - return result; - }; - } - - public static UnaryOperator shallowCloneGenerator(Class clazz) { + public static UnaryOperator shallowCloneGenerator(Class clazz, Supplier supplier) { BiConsumer filler = shallowFill(clazz); return source -> { - T clone = (T) Reflection.newInstance(clazz); + T clone = supplier.get(); filler.accept(source, clone); return clone; }; diff --git a/SpigotCore/SpigotCore_Main/src/de/steamwar/techhider/legacy/ChunkHider.java b/SpigotCore/SpigotCore_Main/src/de/steamwar/techhider/legacy/ChunkHider.java index dc4e3c33..9aaa9b40 100644 --- a/SpigotCore/SpigotCore_Main/src/de/steamwar/techhider/legacy/ChunkHider.java +++ b/SpigotCore/SpigotCore_Main/src/de/steamwar/techhider/legacy/ChunkHider.java @@ -40,8 +40,8 @@ import java.util.stream.Collectors; public class ChunkHider { public static final ChunkHider impl = new ChunkHider(); - private static final UnaryOperator chunkPacketCloner = ProtocolUtils.shallowCloneGenerator(ClientboundLevelChunkWithLightPacket.class); - private static final UnaryOperator chunkDataCloner = ProtocolUtils.shallowCloneGenerator(ClientboundLevelChunkPacketData.class); + private static final UnaryOperator chunkPacketCloner = ProtocolUtils.shallowCloneGenerator(ClientboundLevelChunkWithLightPacket.class, ClientboundLevelChunkWithLightPacket::new); + private static final UnaryOperator chunkDataCloner = ProtocolUtils.shallowCloneGenerator(ClientboundLevelChunkPacketData.class, ClientboundLevelChunkPacketData::new); public BiFunction chunkHiderGenerator(TechHider techHider) { return (p, packet) -> { diff --git a/SpigotCore/SpigotCore_Main/src/de/steamwar/techhider/legacy/TechHider.java b/SpigotCore/SpigotCore_Main/src/de/steamwar/techhider/legacy/TechHider.java index f8e027be..dfa0a5f3 100644 --- a/SpigotCore/SpigotCore_Main/src/de/steamwar/techhider/legacy/TechHider.java +++ b/SpigotCore/SpigotCore_Main/src/de/steamwar/techhider/legacy/TechHider.java @@ -92,9 +92,9 @@ public class TechHider { techhiders.forEach(TinyProtocol.instance::removeFilter); } - public static final UnaryOperator multiBlockChangeCloner = ProtocolUtils.shallowCloneGenerator(ClientboundSectionBlocksUpdatePacket.class); + public static final UnaryOperator multiBlockChangeCloner = ProtocolUtils.shallowCloneGenerator(ClientboundSectionBlocksUpdatePacket.class, ClientboundSectionBlocksUpdatePacket::new); - private static final UnaryOperator blockChangeCloner = ProtocolUtils.shallowCloneGenerator(ClientboundBlockUpdatePacket.class); + private static final UnaryOperator blockChangeCloner = ProtocolUtils.shallowCloneGenerator(ClientboundBlockUpdatePacket.class, ClientboundBlockUpdatePacket::new); private ClientboundBlockUpdatePacket blockChangeHider(Player p, ClientboundBlockUpdatePacket packet) { switch (locationEvaluator.checkBlockPos(p, packet.getPos())) { diff --git a/SpigotCore/SpigotCore_Main/src/spigotcore.accesswidener b/SpigotCore/SpigotCore_Main/src/spigotcore.accesswidener index 1fcaac48..68d0aa82 100644 --- a/SpigotCore/SpigotCore_Main/src/spigotcore.accesswidener +++ b/SpigotCore/SpigotCore_Main/src/spigotcore.accesswidener @@ -15,16 +15,22 @@ accessible field net/minecraft/server/MinecraftServer services Lnet/minecraft/se mutable field net/minecraft/server/MinecraftServer services Lnet/minecraft/server/Services; # REntity +## transitive-extendable means that a public no args constructor is added without any super initialization +transitive-extendable class net/minecraft/network/protocol/game/ClientboundAnimatePacket accessible field net/minecraft/network/protocol/game/ClientboundAnimatePacket id I mutable field net/minecraft/network/protocol/game/ClientboundAnimatePacket id I accessible field net/minecraft/network/protocol/game/ClientboundAnimatePacket action I mutable field net/minecraft/network/protocol/game/ClientboundAnimatePacket action I +## transitive-extendable means that a public no args constructor is added without any super initialization +transitive-extendable class net/minecraft/network/protocol/game/ClientboundEntityEventPacket accessible field net/minecraft/network/protocol/game/ClientboundEntityEventPacket entityId I mutable field net/minecraft/network/protocol/game/ClientboundEntityEventPacket entityId I accessible field net/minecraft/network/protocol/game/ClientboundEntityEventPacket eventId B mutable field net/minecraft/network/protocol/game/ClientboundEntityEventPacket eventId B +## transitive-extendable means that a public no args constructor is added without any super initialization +transitive-extendable class net/minecraft/network/protocol/game/ClientboundRotateHeadPacket accessible field net/minecraft/network/protocol/game/ClientboundRotateHeadPacket yHeadRot B mutable field net/minecraft/network/protocol/game/ClientboundRotateHeadPacket yHeadRot B ## + TechHider @@ -32,9 +38,13 @@ accessible field net/minecraft/network/protocol/game/ClientboundRotateHeadPacket mutable field net/minecraft/network/protocol/game/ClientboundRotateHeadPacket entityId I # For ChunkHider +## transitive-extendable means that a public no args constructor is added without any super initialization +transitive-extendable class net/minecraft/network/protocol/game/ClientboundLevelChunkWithLightPacket accessible field net/minecraft/network/protocol/game/ClientboundLevelChunkWithLightPacket chunkData Lnet/minecraft/network/protocol/game/ClientboundLevelChunkPacketData; mutable field net/minecraft/network/protocol/game/ClientboundLevelChunkWithLightPacket chunkData Lnet/minecraft/network/protocol/game/ClientboundLevelChunkPacketData; +## transitive-extendable means that a public no args constructor is added without any super initialization +transitive-extendable class net/minecraft/network/protocol/game/ClientboundLevelChunkPacketData accessible field net/minecraft/network/protocol/game/ClientboundLevelChunkPacketData buffer [B mutable field net/minecraft/network/protocol/game/ClientboundLevelChunkPacketData buffer [B accessible field net/minecraft/network/protocol/game/ClientboundLevelChunkPacketData blockEntitiesData Ljava/util/List; @@ -47,6 +57,8 @@ accessible field net/minecraft/network/protocol/game/ClientboundLevelChunkPacket # For TechHider accessible field net/minecraft/network/protocol/game/ClientboundMoveEntityPacket entityId I +## transitive-extendable means that a public no args constructor is added without any super initialization +transitive-extendable class net/minecraft/network/protocol/game/ClientboundSectionBlocksUpdatePacket accessible field net/minecraft/network/protocol/game/ClientboundSectionBlocksUpdatePacket sectionPos Lnet/minecraft/core/SectionPos; accessible field net/minecraft/network/protocol/game/ClientboundSectionBlocksUpdatePacket positions [S mutable field net/minecraft/network/protocol/game/ClientboundSectionBlocksUpdatePacket positions [S @@ -54,4 +66,6 @@ accessible field net/minecraft/network/protocol/game/ClientboundSectionBlocksUpd mutable field net/minecraft/network/protocol/game/ClientboundSectionBlocksUpdatePacket states [Lnet/minecraft/world/level/block/state/BlockState; # For legacy/TechHider -mutable field net/minecraft/network/protocol/game/ClientboundBlockUpdatePacket blockState Lnet/minecraft/world/level/block/state/BlockState; +## transitive-extendable means that a public no args constructor is added without any super initialization +transitive-extendable class net/minecraft/network/protocol/game/ClientboundBlockUpdatePacket +mutable field net/minecraft/network/protocol/game/ClientboundBlockUpdatePacket blockState Lnet/minecraft/world/level/block/state/BlockState; \ No newline at end of file From 2b43af885165e4f9836c972108049140d1c81a58 Mon Sep 17 00:00:00 2001 From: YoyoNow Date: Thu, 11 Jun 2026 23:22:05 +0200 Subject: [PATCH 20/36] Remove unneded reflection --- .../misslewars/slowmo/SlowMoRunner.java | 9 ++- .../misslewars/slowmo/SlowMoUtils.java | 74 ------------------- 2 files changed, 6 insertions(+), 77 deletions(-) delete mode 100644 MissileWars/src/de/steamwar/misslewars/slowmo/SlowMoUtils.java diff --git a/MissileWars/src/de/steamwar/misslewars/slowmo/SlowMoRunner.java b/MissileWars/src/de/steamwar/misslewars/slowmo/SlowMoRunner.java index 0d0363f0..84df082c 100644 --- a/MissileWars/src/de/steamwar/misslewars/slowmo/SlowMoRunner.java +++ b/MissileWars/src/de/steamwar/misslewars/slowmo/SlowMoRunner.java @@ -20,9 +20,12 @@ package de.steamwar.misslewars.slowmo; import de.steamwar.misslewars.MissileWars; +import net.minecraft.server.MinecraftServer; +import net.minecraft.world.TickRateManager; import org.bukkit.Bukkit; public class SlowMoRunner { + private static TickRateManager tickRateManager = MinecraftServer.getServer().tickRateManager(); private static long currentTime = 0; private static long current = 0; @@ -40,14 +43,14 @@ public class SlowMoRunner { if (currentTime > 0) { current += 1; if (current % 5 == 0) { - SlowMoUtils.unfreeze(); + tickRateManager.setFrozen(false); current = 0; } else { - SlowMoUtils.freeze(); + tickRateManager.setFrozen(true); } currentTime--; } else { - SlowMoUtils.unfreeze(); + tickRateManager.setFrozen(false); } }, 0, 1); } diff --git a/MissileWars/src/de/steamwar/misslewars/slowmo/SlowMoUtils.java b/MissileWars/src/de/steamwar/misslewars/slowmo/SlowMoUtils.java deleted file mode 100644 index c5e01cc4..00000000 --- a/MissileWars/src/de/steamwar/misslewars/slowmo/SlowMoUtils.java +++ /dev/null @@ -1,74 +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 . - */ - -package de.steamwar.misslewars.slowmo; - -import net.minecraft.server.level.ServerLevel; -import org.bukkit.Bukkit; -import org.bukkit.World; -import org.bukkit.craftbukkit.CraftWorld; - -import java.lang.reflect.Field; - -public class SlowMoUtils { - - private static final Field field; - public static final boolean freezeEnabled; - - private static boolean frozen = false; - - private static final World world; - - static { - Field temp; - try { - temp = ServerLevel.class.getField("freezed"); - } catch (NoSuchFieldException e) { - temp = null; - } - field = temp; - if (field != null) field.setAccessible(true); - freezeEnabled = field != null; - world = Bukkit.getWorlds().get(0); - } - - public static void freeze() { - setFreeze(world, true); - } - - public static void unfreeze() { - setFreeze(world, false); - } - - public static boolean frozen() { - return freezeEnabled && frozen; - } - - private static void setFreeze(World world, boolean state) { - if (freezeEnabled) { - if (frozen == state) return; - try { - field.set(((CraftWorld) world).getHandle(), state); - frozen = state; - } catch (IllegalAccessException e) { - // Ignored; - } - } - } -} From c362a3e1f0dcd6e6f1b07e552cff488f9bea4db4 Mon Sep 17 00:00:00 2001 From: YoyoNow Date: Thu, 11 Jun 2026 23:24:11 +0200 Subject: [PATCH 21/36] Remove unused code --- .../countdown/EnternCountdown.java | 4 -- .../de/steamwar/techhider/ProtocolUtils.java | 69 ------------------- 2 files changed, 73 deletions(-) diff --git a/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/countdown/EnternCountdown.java b/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/countdown/EnternCountdown.java index 87a45b4a..e5a62ac6 100644 --- a/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/countdown/EnternCountdown.java +++ b/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/countdown/EnternCountdown.java @@ -27,12 +27,9 @@ import de.steamwar.fightsystem.fight.FightPlayer; import de.steamwar.fightsystem.utils.Message; import de.steamwar.fightsystem.utils.Region; import de.steamwar.fightsystem.utils.SWSound; -import de.steamwar.techhider.ProtocolUtils; import net.md_5.bungee.api.ChatMessageType; import org.bukkit.Bukkit; -import java.util.List; - public class EnternCountdown extends Countdown { private static int calcTime(FightPlayer fp, Countdown countdown) { @@ -47,7 +44,6 @@ public class EnternCountdown extends Countdown { } private final FightPlayer fightPlayer; - private List chunkPos; public EnternCountdown(FightPlayer fp, Countdown countdown) { super(calcTime(fp, countdown), new Message("ENTERN_COUNTDOWN"), SWSound.BLOCK_NOTE_PLING, false); diff --git a/SpigotCore/SpigotCore_Main/src/de/steamwar/techhider/ProtocolUtils.java b/SpigotCore/SpigotCore_Main/src/de/steamwar/techhider/ProtocolUtils.java index 18997565..70d86008 100644 --- a/SpigotCore/SpigotCore_Main/src/de/steamwar/techhider/ProtocolUtils.java +++ b/SpigotCore/SpigotCore_Main/src/de/steamwar/techhider/ProtocolUtils.java @@ -19,7 +19,6 @@ package de.steamwar.techhider; -import com.google.common.primitives.Bytes; import io.netty.buffer.ByteBuf; import java.lang.reflect.Field; @@ -80,25 +79,6 @@ public class ProtocolUtils { return chunk; } - @Deprecated - public static int readVarInt(byte[] array, int startPos) { - int numRead = 0; - int result = 0; - byte read; - do { - read = array[startPos + numRead]; - int value = (read & 0b01111111); - result |= (value << (7 * numRead)); - - numRead++; - if (numRead > 5) { - break; - } - } while ((read & 0b10000000) != 0); - - return result; - } - public static int readVarInt(ByteBuf buf) { int numRead = 0; int result = 0; @@ -141,53 +121,4 @@ public class ProtocolUtils { writeVarInt(buf, varInt); } } - - @Deprecated - public static int readVarIntLength(byte[] array, int startPos) { - int numRead = 0; - byte read; - do { - read = array[startPos + numRead]; - numRead++; - if (numRead > 5) { - break; - } - } while ((read & 0b10000000) != 0); - - return numRead; - } - - @Deprecated - public static byte[] writeVarInt(int value) { - List buffer = new ArrayList<>(5); - do { - byte temp = (byte) (value & 0b01111111); - // Note: >>> means that the sign bit is shifted with the rest of the number rather than being left alone - value >>>= 7; - if (value != 0) { - temp |= 0b10000000; - } - buffer.add(temp); - } while (value != 0); - return Bytes.toArray(buffer); - } - - @Deprecated - public static class ChunkPos { - final int x; - final int z; - - public ChunkPos(int x, int z) { - this.x = x; - this.z = z; - } - - public final int x() { - return x; - } - - public final int z() { - return z; - } - } } From 414bfbfe3c259df4162d88025bc03427d59ecc23 Mon Sep 17 00:00:00 2001 From: YoyoNow Date: Thu, 11 Jun 2026 23:48:33 +0200 Subject: [PATCH 22/36] Fix ClassTransformer public constructor --- .../src/main/java/de/steamwar/ClassTransformer.java | 10 +++++++++- BauSystem/build.gradle.kts | 3 +-- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/AccessWidener/src/main/java/de/steamwar/ClassTransformer.java b/AccessWidener/src/main/java/de/steamwar/ClassTransformer.java index 534155b9..4467bd48 100644 --- a/AccessWidener/src/main/java/de/steamwar/ClassTransformer.java +++ b/AccessWidener/src/main/java/de/steamwar/ClassTransformer.java @@ -48,9 +48,17 @@ public class ClassTransformer extends ClassVisitor { } if (appendPublicConstructor) { MethodVisitor methodVisitor = visitMethod(Opcodes.ACC_PUBLIC, "", "()V", null, null); - methodVisitor.visitMaxs(0, 0); methodVisitor.visitCode(); + methodVisitor.visitVarInsn(Opcodes.ALOAD, 0); + methodVisitor.visitMethodInsn( + Opcodes.INVOKESPECIAL, + "java/lang/Object", + "", + "()V", + false + ); methodVisitor.visitInsn(Opcodes.RETURN); + methodVisitor.visitMaxs(1, 1); methodVisitor.visitEnd(); } super.visit(version, newAccess, name, signature, superName, interfaces); diff --git a/BauSystem/build.gradle.kts b/BauSystem/build.gradle.kts index 1d158e37..aebbc9bb 100644 --- a/BauSystem/build.gradle.kts +++ b/BauSystem/build.gradle.kts @@ -37,9 +37,8 @@ tasks.register("DevBau21") { dependsOn(":SpigotCore:shadowJar") dependsOn(":BauSystem:shadowJar") dependsOn(":SchematicSystem:shadowJar") - dependsOn(":AccessWidener:shadowJar") template = "Bau21" // TODO: Add to every new server start! newer than 1.21 inclusive! - jvmArgs = "-javaagent:/home/yoyonow/Bau21/plugins/AccessWidener.jar=start" + jvmArgs = "-javaagent:/jars/AccessWidener.jar=start" setdParams(mapOf("paper.disablePluginRemapping" to "true")) } From f5ac006c0cb6bf9e10d7173c74eaa5cd1cb26e33 Mon Sep 17 00:00:00 2001 From: YoyoNow Date: Thu, 11 Jun 2026 23:49:38 +0200 Subject: [PATCH 23/36] Fix REntity.EntityDataPacketBuilder --- SpigotCore/SpigotCore_Main/src/de/steamwar/entity/REntity.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SpigotCore/SpigotCore_Main/src/de/steamwar/entity/REntity.java b/SpigotCore/SpigotCore_Main/src/de/steamwar/entity/REntity.java index 1c7cce4d..a5be3c41 100644 --- a/SpigotCore/SpigotCore_Main/src/de/steamwar/entity/REntity.java +++ b/SpigotCore/SpigotCore_Main/src/de/steamwar/entity/REntity.java @@ -323,7 +323,7 @@ public class REntity { public EntityDataPacketBuilder add(EntityDataAccessor accessor, T value) { values.add(new SynchedEntityData.DataItem<>(accessor, value).value()); - return null; + return this; } public boolean isEmpty() { From b36464e69e3e285b3fd3d7b627fc9eb7a39036ce Mon Sep 17 00:00:00 2001 From: YoyoNow Date: Thu, 11 Jun 2026 23:50:34 +0200 Subject: [PATCH 24/36] Implement Velocity changes --- .../src/de/steamwar/velocitycore/Node.java | 17 +++++++++++------ .../de/steamwar/velocitycore/ServerStarter.java | 2 +- .../de/steamwar/velocitycore/ServerVersion.java | 8 +++++++- 3 files changed, 19 insertions(+), 8 deletions(-) diff --git a/VelocityCore/src/de/steamwar/velocitycore/Node.java b/VelocityCore/src/de/steamwar/velocitycore/Node.java index c7f68c70..82eec3d1 100644 --- a/VelocityCore/src/de/steamwar/velocitycore/Node.java +++ b/VelocityCore/src/de/steamwar/velocitycore/Node.java @@ -63,7 +63,7 @@ public abstract class Node { nodes.forEach(consumer); } - public abstract ProcessBuilder startServer(String serverJar, File directory, String worldDir, String levelName, int port, String... dParams); + public abstract ProcessBuilder startServer(ServerVersion version, File directory, String worldDir, String levelName, int port, String... dParams); protected abstract ProcessBuilder prepareExecution(String... command); @@ -97,7 +97,8 @@ public abstract class Node { return hostname; } - protected void constructServerstart(File directory, List cmd, String serverJar, String worldDir, String levelName, int port, String... dParams) { + protected void constructServerstart(File directory, List cmd, ServerVersion version, String worldDir, String levelName, int port, String... dParams) { + String serverJar = version.getServerJar(); if (JAVA_8.contains(serverJar)) { cmd.add("/usr/lib/jvm/openj9-8/bin/java"); } else { @@ -107,6 +108,10 @@ public abstract class Node { for (String param : dParams) { cmd.add("-D" + param); } + if (version.isExtendedStartup()) { + cmd.add("-Dpaper.disablePluginRemapping=true"); + cmd.add("-javaagent:/jars/AccessWidener.jar=start"); + } cmd.add("-Xshareclasses:nonfatal,name=" + directory.getName()); cmd.add("-Xmx768M"); cmd.addAll(OPENJ9_ARGS); @@ -145,9 +150,9 @@ public abstract class Node { } @Override - public ProcessBuilder startServer(String serverJar, File directory, String worldDir, String levelName, int port, String... dParams) { + public ProcessBuilder startServer(ServerVersion version, File directory, String worldDir, String levelName, int port, String... dParams) { List cmd = new ArrayList<>(); - constructServerstart(directory, cmd, serverJar, worldDir, levelName, port, dParams); + constructServerstart(directory, cmd, version, worldDir, levelName, port, dParams); ProcessBuilder builder = new ProcessBuilder(cmd); builder.directory(directory); return builder; @@ -177,7 +182,7 @@ public abstract class Node { } @Override - public ProcessBuilder startServer(String serverJar, File directory, String worldDir, String levelName, int port, String... dParams) { + public ProcessBuilder startServer(ServerVersion version, File directory, String worldDir, String levelName, int port, String... dParams) { List cmd = new ArrayList<>(); cmd.add("ssh"); cmd.add("-L"); @@ -186,7 +191,7 @@ public abstract class Node { cmd.add("cd"); cmd.add(directory.getPath()); cmd.add(";"); - constructServerstart(directory, cmd, serverJar, worldDir, levelName, port, dParams); + constructServerstart(directory, cmd, version, worldDir, levelName, port, dParams); return new ProcessBuilder(cmd); } diff --git a/VelocityCore/src/de/steamwar/velocitycore/ServerStarter.java b/VelocityCore/src/de/steamwar/velocitycore/ServerStarter.java index 55bf4d36..85a537f8 100644 --- a/VelocityCore/src/de/steamwar/velocitycore/ServerStarter.java +++ b/VelocityCore/src/de/steamwar/velocitycore/ServerStarter.java @@ -325,7 +325,7 @@ public class ServerStarter { private void regularStart(String serverName, int port) { postStart(constructor.construct(serverName, port, node.startServer( - version.getServerJar(), directory, worldDir, worldName, port, arguments.entrySet().stream().map(entry -> entry.getKey() + "=" + entry.getValue()).toArray(String[]::new) + version, directory, worldDir, worldName, port, arguments.entrySet().stream().map(entry -> entry.getKey() + "=" + entry.getValue()).toArray(String[]::new) ), worldCleanup, null)); } diff --git a/VelocityCore/src/de/steamwar/velocitycore/ServerVersion.java b/VelocityCore/src/de/steamwar/velocitycore/ServerVersion.java index 59f52f0f..f7b2dff9 100644 --- a/VelocityCore/src/de/steamwar/velocitycore/ServerVersion.java +++ b/VelocityCore/src/de/steamwar/velocitycore/ServerVersion.java @@ -23,6 +23,7 @@ import com.velocitypowered.api.network.ProtocolVersion; import de.steamwar.sql.GameModeConfig; import lombok.AllArgsConstructor; import lombok.Getter; +import lombok.RequiredArgsConstructor; import java.io.File; import java.util.HashMap; @@ -33,6 +34,7 @@ import java.util.regex.Pattern; @Getter @AllArgsConstructor +@RequiredArgsConstructor public enum ServerVersion { SPIGOT_8("spigot-1.8.8.jar", 8, ProtocolVersion.MINECRAFT_1_8), SPIGOT_9("spigot-1.9.4.jar", 9, ProtocolVersion.MINECRAFT_1_9), @@ -47,7 +49,7 @@ public enum ServerVersion { PAPER_18("paper-1.18.2.jar", 15, ProtocolVersion.MINECRAFT_1_18_2), PAPER_19("paper-1.19.3.jar", 19, ProtocolVersion.MINECRAFT_1_19_3), PAPER_20("paper-1.20.1.jar", 20, ProtocolVersion.MINECRAFT_1_20), - PAPER_21("paper-1.21.6.jar", 21, ProtocolVersion.MINECRAFT_1_21_6); + PAPER_21("paper-1.21.6.jar", 21, ProtocolVersion.MINECRAFT_1_21_6, true); private static final Map chatMap = new HashMap<>(); @@ -104,6 +106,10 @@ public enum ServerVersion { private final String serverJar; private final int versionSuffix; private final ProtocolVersion protocolVersion; + /** + * Adding AccessWidener agent and setting System Property (paper.disablePluginRemapping) to true + */ + private boolean extendedStartup; public String getWorldFolder(String base) { return base + versionSuffix + "/"; From 117dbd08ae898e865c8b7ac83df526e8c0d930d0 Mon Sep 17 00:00:00 2001 From: YoyoNow Date: Thu, 11 Jun 2026 23:54:42 +0200 Subject: [PATCH 25/36] Implement startup arguments into steamwar.devserver.gradle --- BauSystem/build.gradle.kts | 3 --- buildSrc/src/main/groovy/steamwar.devserver.gradle | 4 +++- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/BauSystem/build.gradle.kts b/BauSystem/build.gradle.kts index aebbc9bb..de9fe1b8 100644 --- a/BauSystem/build.gradle.kts +++ b/BauSystem/build.gradle.kts @@ -38,7 +38,4 @@ tasks.register("DevBau21") { dependsOn(":BauSystem:shadowJar") dependsOn(":SchematicSystem:shadowJar") template = "Bau21" - // TODO: Add to every new server start! newer than 1.21 inclusive! - jvmArgs = "-javaagent:/jars/AccessWidener.jar=start" - setdParams(mapOf("paper.disablePluginRemapping" to "true")) } diff --git a/buildSrc/src/main/groovy/steamwar.devserver.gradle b/buildSrc/src/main/groovy/steamwar.devserver.gradle index af6d224e..ce3b8765 100644 --- a/buildSrc/src/main/groovy/steamwar.devserver.gradle +++ b/buildSrc/src/main/groovy/steamwar.devserver.gradle @@ -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 @@ -245,8 +245,10 @@ class DevServer extends DefaultTask { for (Map.Entry dParam : dParams.entrySet()) { devPy.append(" -D${dParam.key}=${dParam.value}") } + devPy.append(" -Dpaper.disablePluginRemapping=true") devPy.append(" $template") if (debug) devPy.append(" -agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=*:$debugPort") + devPy.append(" -javaagent:/jars/AccessWidener.jar=start") if (jvmArgs != null) devPy.append(" $jvmArgs") println("Starting $template with command ${devPy.toString()}") From 711920464aa1820d294fc198de5fb74cd041a5da Mon Sep 17 00:00:00 2001 From: YoyoNow Date: Fri, 12 Jun 2026 09:37:43 +0200 Subject: [PATCH 26/36] Remove some deprecated code in favor of newer code --- .../de/steamwar/fightsystem/fight/Fight.java | 3 ++- .../fightsystem/record/PacketProcessor.java | 12 ++++++++---- .../steamwar/fightsystem/record/Recorder.java | 3 ++- .../schematicsystem/autocheck/AutoChecker.java | 18 +++++++++--------- .../schematiccommand/SchematicCommand.java | 8 ++++---- 5 files changed, 25 insertions(+), 19 deletions(-) diff --git a/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/fight/Fight.java b/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/fight/Fight.java index 2d2c8acb..8a894013 100644 --- a/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/fight/Fight.java +++ b/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/fight/Fight.java @@ -24,6 +24,7 @@ import de.steamwar.fightsystem.Config; import de.steamwar.fightsystem.record.GlobalRecorder; import lombok.Getter; import org.bukkit.Bukkit; +import org.bukkit.Registry; import org.bukkit.Sound; import org.bukkit.entity.LivingEntity; @@ -88,7 +89,7 @@ public class Fight { } public static void playSound(Sound sound, float volume, float pitch) { - GlobalRecorder.getInstance().soundAtPlayer(sound.name(), volume, pitch); + GlobalRecorder.getInstance().soundAtPlayer(Registry.SOUNDS.getKey(sound).getKey(), volume, pitch); //volume: max. 100, pitch: max. 2 Bukkit.getServer().getOnlinePlayers().forEach(player -> player.playSound(player, sound, volume, pitch)); } diff --git a/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/record/PacketProcessor.java b/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/record/PacketProcessor.java index b62ef662..f4f6983a 100644 --- a/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/record/PacketProcessor.java +++ b/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/record/PacketProcessor.java @@ -510,11 +510,13 @@ public class PacketProcessor implements Listener { float volume = source.readFloat(); float pitch = source.readFloat(); - Sound sound = Sound.valueOf(soundName); + Sound sound = Registry.SOUNDS.get(NamespacedKey.minecraft(soundName)); + if (sound == null) sound = Sound.valueOf(soundName); // TODO: Remove in 26.x because of no longer needed backwards compatibility + Sound finalSound = sound; execSync(() -> { Location location = new Location(Config.world, x, y, z); - location.getWorld().playSound(location, sound, SoundCategory.valueOf(soundCategory), volume, pitch); + location.getWorld().playSound(location, finalSound, SoundCategory.valueOf(soundCategory), volume, pitch); }); } @@ -524,9 +526,11 @@ public class PacketProcessor implements Listener { float volume = source.readFloat(); float pitch = source.readFloat(); - Sound sound = Sound.valueOf(soundName); + Sound sound = Registry.SOUNDS.get(NamespacedKey.minecraft(soundName)); + if (sound == null) sound = Sound.valueOf(soundName); // TODO: Remove in 26.x because of no longer needed backwards compatibility + Sound finalSound = sound; - execSync(() -> Fight.playSound(sound, volume, pitch)); + execSync(() -> Fight.playSound(finalSound, volume, pitch)); } private void pasteSchem(FightTeam team) throws IOException { diff --git a/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/record/Recorder.java b/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/record/Recorder.java index 69f2bbcb..2d42c473 100644 --- a/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/record/Recorder.java +++ b/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/record/Recorder.java @@ -33,6 +33,7 @@ import de.steamwar.sql.SchematicNode; import de.steamwar.sql.SteamwarUser; import org.bukkit.Bukkit; import org.bukkit.Location; + import org.bukkit.Registry; import org.bukkit.block.Block; import org.bukkit.craftbukkit.block.CraftBlockState; import org.bukkit.entity.Entity; @@ -238,7 +239,7 @@ public interface Recorder { } default void sound(int x, int y, int z, SWSound soundType, String soundCategory, float volume, float pitch) { - write(0x32, x, y, z, soundType.getSound().name(), soundCategory, volume, pitch); + write(0x32, x, y, z, Registry.SOUNDS.getKey(soundType.getSound()).getKey(), soundCategory, volume, pitch); } default void soundAtPlayer(String soundType, float volume, float pitch) { diff --git a/SchematicSystem/src/de/steamwar/schematicsystem/autocheck/AutoChecker.java b/SchematicSystem/src/de/steamwar/schematicsystem/autocheck/AutoChecker.java index 1c3a5272..91f90f18 100644 --- a/SchematicSystem/src/de/steamwar/schematicsystem/autocheck/AutoChecker.java +++ b/SchematicSystem/src/de/steamwar/schematicsystem/autocheck/AutoChecker.java @@ -37,8 +37,8 @@ public class AutoChecker { public static final AutoChecker impl = new AutoChecker(); public AutoCheckerResult check(Clipboard clipboard, GameModeConfig type) { - return AutoCheckerResult.builder().type(type).height(clipboard.getDimensions().getBlockY()).width(clipboard.getDimensions().getBlockX()) - .depth(clipboard.getDimensions().getBlockZ()).blockScanResult(scan(clipboard, type)) + return AutoCheckerResult.builder().type(type).height(clipboard.getDimensions().x()).width(clipboard.getDimensions().x()) + .depth(clipboard.getDimensions().z()).blockScanResult(scan(clipboard, type)) .entities(clipboard.getEntities().stream().map(Entity::getLocation) .map(blockVector3 -> new BlockPos(blockVector3.getBlockX(), blockVector3.getBlockY(), blockVector3.getBlockZ())) .collect(Collectors.toList())) @@ -46,19 +46,19 @@ public class AutoChecker { } public AutoCheckerResult sizeCheck(Clipboard clipboard, GameModeConfig type) { - return AutoCheckerResult.builder().type(type).height(clipboard.getDimensions().getBlockY()).width(clipboard.getDimensions().getBlockX()) - .depth(clipboard.getDimensions().getBlockZ()).build(); + return AutoCheckerResult.builder().type(type).height(clipboard.getDimensions().y()).width(clipboard.getDimensions().x()) + .depth(clipboard.getDimensions().z()).build(); } public AutoChecker.BlockScanResult scan(Clipboard clipboard, GameModeConfig type) { AutoChecker.BlockScanResult result = new AutoChecker.BlockScanResult(); BlockVector3 min = clipboard.getMinimumPoint(); BlockVector3 max = clipboard.getMaximumPoint(); - for (int x = min.getBlockX(); x <= max.getBlockX(); x++) { - for (int y = min.getBlockY(); y <= max.getBlockY(); y++) { - for (int z = min.getBlockZ(); z <= max.getBlockZ(); z++) { + for (int x = min.x(); x <= max.x(); x++) { + for (int y = min.y(); y <= max.y(); y++) { + for (int z = min.z(); z <= max.z(); z++) { final BaseBlock block = clipboard.getFullBlock(BlockVector3.at(x, y, z)); - final Material material = Material.matchMaterial(block.getBlockType().getId()); + final Material material = Material.matchMaterial(block.getBlockType().id()); if (material == null) { continue; } @@ -69,7 +69,7 @@ public class AutoChecker { checkInventory(result, block, material, new BlockPos(x, y, z), type); } - if (x == min.getBlockX() || x == max.getBlockX() || y == max.getBlockY() || z == min.getBlockZ() || z == max.getBlockZ()) { + if (x == min.x() || x == max.x() || y == max.y() || z == min.z() || z == max.z()) { result.getDesignBlocks().computeIfAbsent(material, m -> new ArrayList<>()).add(new BlockPos(x, y, z)); } } diff --git a/SchematicSystem/src/de/steamwar/schematicsystem/commands/schematiccommand/SchematicCommand.java b/SchematicSystem/src/de/steamwar/schematicsystem/commands/schematiccommand/SchematicCommand.java index 1fb72577..4e5ecdf7 100644 --- a/SchematicSystem/src/de/steamwar/schematicsystem/commands/schematiccommand/SchematicCommand.java +++ b/SchematicSystem/src/de/steamwar/schematicsystem/commands/schematiccommand/SchematicCommand.java @@ -245,12 +245,12 @@ public class SchematicCommand extends SWCommand { BlockState replaceType = Objects.requireNonNull(toReplace.contains(Material.END_STONE) ? BlockTypes.IRON_BLOCK : BlockTypes.END_STONE).getDefaultState(); BlockVector3 min = clipboard.getMinimumPoint(); BlockVector3 max = clipboard.getMaximumPoint(); - for (int i = min.getBlockX(); i <= max.getBlockX(); i++) { - for (int j = min.getBlockY(); j <= max.getBlockY(); j++) { - for (int k = min.getBlockZ(); k <= max.getBlockZ(); k++) { + for (int i = min.x(); i <= max.x(); i++) { + for (int j = min.y(); j <= max.y(); j++) { + for (int k = min.z(); k <= max.z(); k++) { BlockVector3 vector = BlockVector3.at(i, j, k); BaseBlock block = clipboard.getFullBlock(vector); - if (toReplace.contains(Material.matchMaterial(block.getBlockType().getId()))) { + if (toReplace.contains(Material.matchMaterial(block.getBlockType().id()))) { clipboard.setBlock(vector, replaceType.toBaseBlock()); } } From 2d59eb81c77a5886207ae435608a330467d06af0 Mon Sep 17 00:00:00 2001 From: YoyoNow Date: Fri, 12 Jun 2026 09:49:32 +0200 Subject: [PATCH 27/36] Fix ColorInit and CustomMap deprecation warnings --- LobbySystem/build.gradle.kts | 6 +++++ .../src/de/steamwar/lobby/map/ColorInit.java | 24 ++++++++++++++++++- .../src/de/steamwar/lobby/map/CustomMap.java | 3 ++- LobbySystem/src/lobby.accesswidener | 6 +++++ 4 files changed, 37 insertions(+), 2 deletions(-) create mode 100644 LobbySystem/src/lobby.accesswidener diff --git a/LobbySystem/build.gradle.kts b/LobbySystem/build.gradle.kts index a97d8394..7dd7d27f 100644 --- a/LobbySystem/build.gradle.kts +++ b/LobbySystem/build.gradle.kts @@ -19,6 +19,7 @@ plugins { steamwar.java + widener } dependencies { @@ -32,6 +33,11 @@ dependencies { compileOnly(libs.fawe) } +widener { + fromCatalog(libs.nms) + fromCatalog(libs.paperapi) +} + tasks.register("DevLobby20") { group = "run" description = "Run a 1.20 Dev Lobby" diff --git a/LobbySystem/src/de/steamwar/lobby/map/ColorInit.java b/LobbySystem/src/de/steamwar/lobby/map/ColorInit.java index ed5e0457..d5ffca23 100644 --- a/LobbySystem/src/de/steamwar/lobby/map/ColorInit.java +++ b/LobbySystem/src/de/steamwar/lobby/map/ColorInit.java @@ -42,7 +42,7 @@ public class ColorInit { if (inputStream == null) { colors = new byte[256 * 256 * 256]; for (int i = 0; i < colors.length; i++) { - colors[i] = MapPalette.matchColor(new Color(i)); + colors[i] = matchColor(new Color(i)); } } else { try { @@ -57,4 +57,26 @@ public class ColorInit { } System.out.println("[ColorInit] Initialization took " + (System.currentTimeMillis() - time) + "ms"); } + + public static byte matchColor(Color color) { + if (color.getAlpha() < 128) return 0; + + if (MapPalette.mapColorCache != null && MapPalette.mapColorCache.isCached()) { + return MapPalette.mapColorCache.matchColor(color); + } + + int index = 0; + double best = -1; + + for (int i = 4; i < MapPalette.colors.length; i++) { + double distance = MapPalette.getDistance(color, MapPalette.colors[i]); + if (distance < best || best == -1) { + best = distance; + index = i; + } + } + + // Minecraft has 248 colors, some of which have negative byte representations + return (byte) (index < 128 ? index : -129 + (index - 127)); + } } diff --git a/LobbySystem/src/de/steamwar/lobby/map/CustomMap.java b/LobbySystem/src/de/steamwar/lobby/map/CustomMap.java index 5c140ae9..ee0ebb5a 100644 --- a/LobbySystem/src/de/steamwar/lobby/map/CustomMap.java +++ b/LobbySystem/src/de/steamwar/lobby/map/CustomMap.java @@ -254,7 +254,8 @@ public class CustomMap implements Listener { int green = pixels[i2]; int i3 = (y * width + x) * numBands + 2; int blue = pixels[i3]; - Color nearest = MapPalette.getColor(ColorInit.getColorByte(red, green, blue)); + int colorIndex = ColorInit.getColorByte(red, green, blue); + Color nearest = MapPalette.colors[colorIndex >= 0 ? colorIndex : colorIndex + 256]; pixels[(y * width + x) * numBands] = nearest.getRed(); pixels[i2] = nearest.getGreen(); diff --git a/LobbySystem/src/lobby.accesswidener b/LobbySystem/src/lobby.accesswidener new file mode 100644 index 00000000..75a3fca5 --- /dev/null +++ b/LobbySystem/src/lobby.accesswidener @@ -0,0 +1,6 @@ +accessWidener v2 named + +# For CustomMap and ColorInit +accessible field org/bukkit/map/MapPalette colors [Ljava/awt/Color; +accessible method org/bukkit/map/MapPalette getDistance (Ljava/awt/Color;Ljava/awt/Color;)D +accessible field org/bukkit/map/MapPalette mapColorCache Lorg/bukkit/map/MapPalette$MapColorCache; \ No newline at end of file From 8c8da355bbe49bb93b195bb92f1c686fd5af6374 Mon Sep 17 00:00:00 2001 From: YoyoNow Date: Fri, 12 Jun 2026 10:02:34 +0200 Subject: [PATCH 28/36] Fix build.yml --- .gitea/workflows/build.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitea/workflows/build.yml b/.gitea/workflows/build.yml index e9f4d842..6aced9ef 100644 --- a/.gitea/workflows/build.yml +++ b/.gitea/workflows/build.yml @@ -51,6 +51,7 @@ jobs: rm -rf deploy mkdir -p deploy + cp "AccessWidener/build/libs/AccessWidener-all.jar" "deploy/AccessWidener.jar" cp "BauSystem/build/libs/BauSystem-all.jar" "deploy/BauSystem.jar" cp "FightSystem/build/libs/FightSystem-all.jar" "deploy/FightSystem.jar" cp "KotlinCore/build/libs/KotlinCore-all.jar" "deploy/KotlinCore.jar" From 5511a1f08ebbed1a35588ba8effbd9dbef552d31 Mon Sep 17 00:00:00 2001 From: YoyoNow Date: Fri, 12 Jun 2026 10:41:18 +0200 Subject: [PATCH 29/36] Fix steamwar.devserver.gradle --- .../src/main/groovy/steamwar.devserver.gradle | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/buildSrc/src/main/groovy/steamwar.devserver.gradle b/buildSrc/src/main/groovy/steamwar.devserver.gradle index ce3b8765..71b0027d 100644 --- a/buildSrc/src/main/groovy/steamwar.devserver.gradle +++ b/buildSrc/src/main/groovy/steamwar.devserver.gradle @@ -64,6 +64,10 @@ class DevServer extends DefaultTask { @Optional Boolean forceUpgrade = null + @Input + @Optional + String worldName = null + DevServer() { super() doFirst { @@ -83,13 +87,16 @@ class DevServer extends DefaultTask { } } - worldName = properties.get("worldName") + if (template.startsWith("Bau")) { + if (properties.containsKey("worldName")) { + worldName = properties.get("worldName") + } else { + throw new GradleException("Please supply the 'worldName' in a 'steamwar.properties' files either in this project dir or any parent project!") + } + } host = properties.get("host") debugPort = new Random().nextInt(5001, 10000) - if (worldName == null) { - throw new GradleException("Please supply the 'worldName' in a 'steamwar.properties' files either in this project dir or any parent project!") - } if (host == null) { throw new GradleException("Please supply the 'host' in a 'steamwar.properties' files either in this project dir or any parent project!") } @@ -115,9 +122,6 @@ class DevServer extends DefaultTask { @Internal Boolean running = true - @Internal - String worldName = null - class Finalizer extends DefaultTask { Finalizer() { From c0ff12327946251a42e7de806e823ba4f78773e1 Mon Sep 17 00:00:00 2001 From: YoyoNow Date: Fri, 12 Jun 2026 11:09:13 +0200 Subject: [PATCH 30/36] Fix EntityDamage only applying cancel for Player --- .../fightsystem/listener/EntityDamage.java | 3 ++ FightSystem/build.gradle.kts | 30 ------------------- 2 files changed, 3 insertions(+), 30 deletions(-) diff --git a/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/listener/EntityDamage.java b/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/listener/EntityDamage.java index aa836712..dcc29026 100644 --- a/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/listener/EntityDamage.java +++ b/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/listener/EntityDamage.java @@ -24,6 +24,7 @@ import de.steamwar.fightsystem.Config; import de.steamwar.fightsystem.states.FightState; import de.steamwar.fightsystem.states.StateDependentListener; import de.steamwar.linkage.Linked; +import org.bukkit.entity.Player; import org.bukkit.event.EventHandler; import org.bukkit.event.Listener; import org.bukkit.event.entity.EntityDamageByEntityEvent; @@ -38,11 +39,13 @@ public class EntityDamage implements Listener { @EventHandler public void handleEntityDamage(EntityDamageEvent event) { + if (!(event.getEntity() instanceof Player)) return; if (Config.ArenaRegion.in2dRegion(event.getEntity().getLocation())) event.setCancelled(true); } @EventHandler public void handleEntityDamageByEntity(EntityDamageByEntityEvent event) { + if (!(event.getEntity() instanceof Player)) return; if (Config.ArenaRegion.in2dRegion(event.getEntity().getLocation())) event.setCancelled(true); } } diff --git a/FightSystem/build.gradle.kts b/FightSystem/build.gradle.kts index c6b14b8b..7079e0f1 100644 --- a/FightSystem/build.gradle.kts +++ b/FightSystem/build.gradle.kts @@ -30,16 +30,6 @@ dependencies { implementation(project(":FightSystem:FightSystem_Core")) } -tasks.register("WarGear20") { - group = "run" - description = "Run a WarGear 1.20 Fight Server" - dependsOn(":SpigotCore:shadowJar") - dependsOn(":FightSystem:shadowJar") - template = "WarGear20" - worldName = "arenas/Pentraki" - config = "WarGear20.yml" -} - tasks.register("HalloweenWS") { group = "run" description = "Run a Halloween 1.21 Fight Replay Server" @@ -63,23 +53,3 @@ tasks.register("WarGear21") { config = "WarGear21.yml" jar = "/jars/paper-1.21.6.jar" } - -tasks.register("SpaceCraftDev20") { - group = "run" - description = "Run a SpaceCraftDev 1.20 Fight Server" - dependsOn(":SpigotCore:shadowJar") - dependsOn(":FightSystem:shadowJar") - template = "SpaceCraft20" - worldName = "arenas/AS_Horizon" - config = "SpaceCraftDev20.yml" -} - -tasks.register("QuickGear20") { - group = "run" - description = "Run a QuickGear 1.20 Fight Server" - dependsOn(":SpigotCore:shadowJar") - dependsOn(":FightSystem:shadowJar") - template = "QuickGear20" - worldName = "arenas/WarGearPark" - config = "QuickGear20.yml" -} \ No newline at end of file From 701964c2f2bf7e40b930481407b9c41bd95c6ba7 Mon Sep 17 00:00:00 2001 From: YoyoNow Date: Fri, 12 Jun 2026 12:15:07 +0200 Subject: [PATCH 31/36] Fix DevCommand.kt --- CLI/src/commands/dev/DevCommand.kt | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/CLI/src/commands/dev/DevCommand.kt b/CLI/src/commands/dev/DevCommand.kt index 03b1ed2f..f0b14c53 100644 --- a/CLI/src/commands/dev/DevCommand.kt +++ b/CLI/src/commands/dev/DevCommand.kt @@ -152,8 +152,7 @@ class DevCommand : CliktCommand("dev") { ) val additionalVersions = mapOf( - "Tutorial" to 15, - "Lobby" to 20 + "Lobby" to 21 ) fun findVersion(server: String): Int? = From 27877938bc452e46c1d43c70006455950c2a732b Mon Sep 17 00:00:00 2001 From: Chaoscaot Date: Fri, 12 Jun 2026 12:29:56 +0200 Subject: [PATCH 32/36] Add support for GameMode templates and YAML-based configuration handling in DevCommand. Signed-off-by: Chaoscaot --- CLI/build.gradle.kts | 1 + CLI/src/commands/dev/DevCommand.kt | 71 ++++++++++++++++++++++++++---- 2 files changed, 63 insertions(+), 9 deletions(-) diff --git a/CLI/build.gradle.kts b/CLI/build.gradle.kts index f27d16b8..9629fc04 100644 --- a/CLI/build.gradle.kts +++ b/CLI/build.gradle.kts @@ -18,6 +18,7 @@ dependencies { implementation("com.github.ajalt.clikt:clikt:5.0.3") implementation("com.github.ajalt.mordant:mordant:3.0.2") implementation(libs.logback) + implementation("org.yaml:snakeyaml:2.2") implementation("org.mariadb.jdbc:mariadb-java-client:3.3.1") implementation(libs.exposedCore) diff --git a/CLI/src/commands/dev/DevCommand.kt b/CLI/src/commands/dev/DevCommand.kt index f0b14c53..0cc75728 100644 --- a/CLI/src/commands/dev/DevCommand.kt +++ b/CLI/src/commands/dev/DevCommand.kt @@ -14,9 +14,11 @@ import com.github.ajalt.clikt.parameters.types.file import com.github.ajalt.clikt.parameters.types.long import com.github.ajalt.clikt.parameters.types.path import com.sun.security.auth.module.UnixSystem +import org.yaml.snakeyaml.Yaml import java.io.File import kotlin.io.path.absolute import kotlin.io.path.absolutePathString +import kotlin.random.Random const val LOG4J_CONFIG = """ @@ -69,32 +71,42 @@ class DevCommand : CliktCommand("dev") { override fun run() { val args = mutableListOf() - val serverDirectory = File(workingDir, server) - val serverDir = - if (serverDirectory.exists() && serverDirectory.isDirectory) serverDirectory else File(workingDir, server) + var serverDir = resolveServerDirectory(server) if (isVelocity(server)) { runServer( args, jvmArgs, listOf( jar?.absolutePath - ?: File("/jar/Velocity.jar").absolutePath + ?: File("/jars/Velocity.jar").absolutePath ), serverDir ) } else { setLogConfig(args) val version = findVersion(server) ?: throw CliktError("Unknown Server Version") + val gameModeTemplate = if (serverDir.isDirectory) null else loadGameModeTemplate(server) + if (gameModeTemplate != null) { + serverDir = gameModeTemplate.serverDir + args += "-Dconfig=$server.yml" + } val worldFile = world?.absolute()?.toFile() - ?: File(serverDir, "devtempworld") - val jarFile = jar?.absolutePath + ?: File(workingDir, "devtempworld") + var jarFile = jar?.absolutePath ?: additionalVersions[server]?.let { supportedVersionJars[it] } ?: supportedVersionJars[version] ?: throw CliktError("Unknown Server Version") + if (gameModeTemplate != null) { + jarFile = if (gameModeTemplate.spigot) { + jarFile.replace("paper", "spigot") + } else { + jarFile.replace("spigot", "paper") + } + } if (!worldFile.exists()) { - val templateFile = File(serverDir, "Bauwelt") + val templateFile = gameModeTemplate?.worldTemplate ?: File(serverDir, "Bauwelt") if (!templateFile.exists()) { - throw CliktError("World Template not found!") + throw CliktError("Could not find world template: ${templateFile.absolutePath}") } templateFile.copyRecursively(worldFile) } @@ -123,6 +135,12 @@ class DevCommand : CliktCommand("dev") { } } + data class GameModeTemplate( + val serverDir: File, + val worldTemplate: File, + val spigot: Boolean + ) + val jvmDefaultParams = arrayOf( "-Xmx1G", "-Xgc:excessiveGCratio=80", @@ -165,6 +183,41 @@ class DevCommand : CliktCommand("dev") { fun isVelocity(server: String): Boolean = server.endsWith("Velocity") + fun resolveServerDirectory(server: String): File { + val localServer = File(workingDir, server) + if (localServer.isDirectory) { + return localServer + } + return File("/servers", server) + } + + fun loadGameModeTemplate(server: String): GameModeTemplate? { + val configFile = File("/configs/GameModes/$server.yml") + if (!configFile.exists()) { + throw CliktError("Server/GameMode not found") + } + + val document = configFile.reader().use { reader -> + Yaml().load>(reader) + } ?: throw CliktError("GameMode config is empty: ${configFile.absolutePath}") + val serverConfig = document["Server"] as? Map<*, *> + ?: throw CliktError("GameMode config is missing Server section: ${configFile.absolutePath}") + val folder = serverConfig["Folder"] as? String + ?: throw CliktError("GameMode config is missing Server.Folder: ${configFile.absolutePath}") + val maps = (serverConfig["Maps"] as? List<*>) + ?.filterIsInstance() + ?.takeIf { it.isNotEmpty() } + ?: throw CliktError("GameMode config is missing Server.Maps: ${configFile.absolutePath}") + + val serverDir = File("/servers", folder) + val worldTemplate = File(File(serverDir, "arenas"), maps[Random.nextInt(maps.size)]) + return GameModeTemplate( + serverDir = serverDir, + worldTemplate = worldTemplate, + spigot = serverConfig["Spigot"] == true + ) + } + fun setLogConfig(args: MutableList) { args += "-DlogPath=${workingDir.absolutePath}/logs" args += "-Dlog4j.configurationFile=${log4jConfig.absolutePath}" @@ -190,4 +243,4 @@ class DevCommand : CliktCommand("dev") { Runtime.getRuntime().addShutdownHook(Thread { if (process.isAlive) process.destroyForcibly() }) process.waitFor() } -} \ No newline at end of file +} From e0aaacd66a18e406bd3edcf00a311b9673899d8a Mon Sep 17 00:00:00 2001 From: YoyoNow Date: Fri, 12 Jun 2026 14:52:03 +0200 Subject: [PATCH 33/36] Fix AnvilGUI for 1.21 not being mojmapped --- SpigotCore/SpigotCore_Main/build.gradle.kts | 2 - .../src/net/wesjd/anvilgui/AnvilGUI.java | 922 ++++++++++++++++++ .../anvilgui/version/VersionWrapper.java | 175 ++++ 3 files changed, 1097 insertions(+), 2 deletions(-) create mode 100644 SpigotCore/SpigotCore_Main/src/net/wesjd/anvilgui/AnvilGUI.java create mode 100644 SpigotCore/SpigotCore_Main/src/net/wesjd/anvilgui/version/VersionWrapper.java diff --git a/SpigotCore/SpigotCore_Main/build.gradle.kts b/SpigotCore/SpigotCore_Main/build.gradle.kts index 322b1046..13fe0e40 100644 --- a/SpigotCore/SpigotCore_Main/build.gradle.kts +++ b/SpigotCore/SpigotCore_Main/build.gradle.kts @@ -59,8 +59,6 @@ dependencies { compileOnly(libs.netty) compileOnly(libs.brigadier) compileOnly(libs.fastutil) - - implementation(libs.anvilgui) } widener { diff --git a/SpigotCore/SpigotCore_Main/src/net/wesjd/anvilgui/AnvilGUI.java b/SpigotCore/SpigotCore_Main/src/net/wesjd/anvilgui/AnvilGUI.java new file mode 100644 index 00000000..d2d91852 --- /dev/null +++ b/SpigotCore/SpigotCore_Main/src/net/wesjd/anvilgui/AnvilGUI.java @@ -0,0 +1,922 @@ +package net.wesjd.anvilgui; + +import lombok.NonNull; +import net.md_5.bungee.api.chat.BaseComponent; +import net.wesjd.anvilgui.version.VersionWrapper; +import org.bukkit.Bukkit; +import org.bukkit.Material; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.HandlerList; +import org.bukkit.event.Listener; +import org.bukkit.event.inventory.*; +import org.bukkit.inventory.Inventory; +import org.bukkit.inventory.ItemStack; +import org.bukkit.inventory.meta.ItemMeta; +import org.bukkit.plugin.Plugin; + +import java.util.*; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.Executor; +import java.util.function.BiConsumer; +import java.util.function.BiFunction; +import java.util.function.Consumer; +import java.util.function.Function; +import java.util.logging.Level; + +/** + * An anvil gui, used for gathering a user's input + * + * @author Wesley Smith + * @since 1.0 + */ +public class AnvilGUI { + + /** + * The local {@link VersionWrapper} object for the server's version + */ + private static final VersionWrapper WRAPPER = VersionWrapper.INSTANCE; + + /** + * The variable containing an item with air. Used when the item would be null. + * To keep the heap clean, this object only gets iniziaised once + */ + private static final ItemStack AIR = new ItemStack(Material.AIR); + /** + * If the given ItemStack is null, return an air ItemStack, otherwise return the given ItemStack + * + * @param stack The ItemStack to check + * @return air or the given ItemStack + */ + private static ItemStack itemNotNull(ItemStack stack) { + return stack == null ? AIR : stack; + } + + /** + * The {@link Plugin} that this anvil GUI is associated with + */ + private final Plugin plugin; + /** + * The player who has the GUI open + */ + private final Player player; + /** + * An {@link Executor} that executes tasks on the main server thread + */ + private final Executor mainThreadExecutor; + /** + * The title of the anvil inventory + */ + private final Object titleComponent; + /** + * The initial contents of the inventory + */ + private final ItemStack[] initialContents; + /** + * A state that decides where the anvil GUI is able to get closed by the user + */ + private final boolean preventClose; + /** + * A state that decides whether compatibility with Geyser software is enabled + */ + private final boolean geyserCompatibility; + /** + * A set of slot numbers that are permitted to be interacted with by the user. An interactable + * slot is one that is able to be minipulated by the player, i.e. clicking and picking up an item, + * placing in a new one, etc. + */ + private final Set interactableSlots; + + /** An {@link Consumer} that is called when the anvil GUI is close */ + private final Consumer closeListener; + /** A flag that decides whether the async click handler can be run concurrently */ + private final boolean concurrentClickHandlerExecution; + /** An {@link BiFunction} that is called when a slot is clicked */ + private final ClickHandler clickHandler; + + /** + * The container id of the inventory, used for NMS methods + */ + private int containerId; + + /** + * The inventory that is used on the Bukkit side of things + */ + private Inventory inventory; + /** + * The listener holder class + */ + private final ListenUp listener = new ListenUp(); + + /** + * Represents the state of the inventory being open + */ + private boolean open; + + /** + * The actual container backing the Anvil GUI + */ + private VersionWrapper.AnvilContainerWrapper container; + + /** + * Create an AnvilGUI + * + * @param plugin A {@link org.bukkit.plugin.java.JavaPlugin} instance + * @param player The {@link Player} to open the inventory for + * @param mainThreadExecutor An {@link Executor} that executes on the main server thread + * @param titleComponent What to have the text already set to + * @param initialContents The initial contents of the inventory + * @param preventClose Whether to prevent the inventory from closing + * @param geyserCompatibility Whether to enable compatibility with Geyser software + * @param closeListener A {@link Consumer} when the inventory closes + * @param concurrentClickHandlerExecution Flag to allow concurrent execution of the click handler + * @param clickHandler A {@link ClickHandler} that is called when the player clicks a slot + */ + private AnvilGUI( + Plugin plugin, + Player player, + Executor mainThreadExecutor, + Object titleComponent, + ItemStack[] initialContents, + boolean preventClose, + boolean geyserCompatibility, + Set interactableSlots, + Consumer closeListener, + boolean concurrentClickHandlerExecution, + ClickHandler clickHandler) { + this.plugin = plugin; + this.player = player; + this.mainThreadExecutor = mainThreadExecutor; + this.titleComponent = titleComponent; + this.initialContents = initialContents; + this.preventClose = preventClose; + this.geyserCompatibility = geyserCompatibility; + this.interactableSlots = Collections.unmodifiableSet(interactableSlots); + this.closeListener = closeListener; + this.concurrentClickHandlerExecution = concurrentClickHandlerExecution; + this.clickHandler = clickHandler; + } + + /** + * Opens the anvil GUI + */ + private void openInventory() { + Bukkit.getPluginManager().registerEvents(listener, plugin); + + container = WRAPPER.newContainerAnvil(player, titleComponent); + + inventory = container.getBukkitInventory(); + // We need to use setItem instead of setContents because a Minecraft ContainerAnvil + // contains two separate inventories: the result inventory and the ingredients inventory. + // The setContents method only updates the ingredients inventory unfortunately, + // but setItem handles the index going into the result inventory. + for (int i = 0; i < initialContents.length; i++) { + inventory.setItem(i, initialContents[i]); + } + + containerId = WRAPPER.getNextContainerId(player, container); + WRAPPER.handleInventoryCloseEvent(player); + WRAPPER.sendPacketOpenWindow(player, containerId, titleComponent); + WRAPPER.setActiveContainer(player, container); + WRAPPER.setActiveContainerId(container, containerId); + WRAPPER.addActiveContainerSlotListener(container, player); + + // Primitive Geyser Check! + if (player.getName().startsWith(".")) { + WRAPPER.sendPacketExperienceChange(player, 20); + } + + open = true; + } + + /** + * Closes the inventory if it's open. + */ + public void closeInventory() { + closeInventory(true); + } + + /** + * Closes the inventory if it's open, only sending the close inventory packets if the arg is true + * + * @param sendClosePacket Whether to send the close inventory event, packet, etc + */ + private void closeInventory(boolean sendClosePacket) { + if (!open) { + return; + } + + open = false; + + HandlerList.unregisterAll(listener); + + if (sendClosePacket) { + WRAPPER.handleInventoryCloseEvent(player); + WRAPPER.setActiveContainerDefault(player); + WRAPPER.sendPacketCloseWindow(player, containerId); + } + // Primitive Geyser Check! + if (player.getName().startsWith(".")) { + WRAPPER.sendPacketExperienceChange(player, player.getLevel()); + } + + if (closeListener != null) { + closeListener.accept(StateSnapshot.fromAnvilGUI(this)); + } + } + + /** + * Updates the title of the AnvilGUI to the new one. + * + * @param literalTitle The title to use as literal text + * @param preserveRenameText Whether to preserve the entered rename text + * @throws IllegalArgumentException when literalTitle is null + * @see Builder#title(String) + */ + public void setTitle(@NonNull String literalTitle, boolean preserveRenameText) { + setTitle(WRAPPER.literalChatComponent(literalTitle), preserveRenameText); + } + + /** + * Updates the title of the AnvilGUI to the new one. + * + * @param json The json used to parse into a rich chat component + * @param preserveRenameText Whether to preserve the entered rename text + * @throws IllegalArgumentException when json is null + * @see Builder#jsonTitle(String) + */ + public void setJsonTitle(@NonNull String json, boolean preserveRenameText) { + setTitle(WRAPPER.jsonChatComponent(json), preserveRenameText); + } + + /** + * Updates the title of the AnvilGUI to the new one. + * + * @param title The title as a NMS ChatComponent + * @param preserveRenameText Whether to preserve the entered rename text + */ + private void setTitle(Object title, boolean preserveRenameText) { + if (!true) { + return; + } + String renameText = container.getRenameText(); + WRAPPER.sendPacketOpenWindow(player, containerId, title); + if (preserveRenameText) { + // The renameText field is marked as @Nullable in newer versions + container.setRenameText(renameText == null ? "" : renameText); + } + } + + /** + * Returns the Bukkit inventory for this anvil gui + * + * @return the {@link Inventory} for this anvil gui + */ + public Inventory getInventory() { + return inventory; + } + + /** + * Simply holds the listeners for the GUI + */ + private class ListenUp implements Listener { + + /** + * Boolean storing the running status of the latest click handler to prevent double execution. + * All accesses to this boolean will be from the main server thread, except for the rare event + * that the plugin is disabled and the mainThreadExecutor throws an exception + */ + private boolean clickHandlerRunning = false; + + @EventHandler + public void onInventoryClick(InventoryClickEvent event) { + if (!event.getInventory().equals(inventory)) { + return; + } + + final int rawSlot = event.getRawSlot(); + // ignore items dropped outside the window + if (rawSlot == -999) return; + + final Player clicker = (Player) event.getWhoClicked(); + final Inventory clickedInventory = event.getClickedInventory(); + + if (clickedInventory != null) { + if (clickedInventory.equals(clicker.getInventory())) { + // prevent players from merging items from the anvil inventory + if (event.getClick().equals(ClickType.DOUBLE_CLICK)) { + event.setCancelled(true); + return; + } + // prevent shift moving items from players inv to the anvil inventory + if (event.isShiftClick()) { + event.setCancelled(true); + return; + } + } + // prevent players from swapping items in the anvil gui + if ((event.getCursor() != null && event.getCursor().getType() != Material.AIR) + && !interactableSlots.contains(rawSlot) + && event.getClickedInventory().equals(inventory)) { + event.setCancelled(true); + return; + } + } + + if (rawSlot < 3 && rawSlot >= 0 || event.getAction().equals(InventoryAction.MOVE_TO_OTHER_INVENTORY)) { + event.setCancelled(!interactableSlots.contains(rawSlot)); + if (clickHandlerRunning && !concurrentClickHandlerExecution) { + // A click handler is running, don't launch another one + return; + } + + final CompletableFuture> actionsFuture = + clickHandler.apply(rawSlot, StateSnapshot.fromAnvilGUI(AnvilGUI.this)); + + final Consumer> actionsConsumer = actions -> { + for (final ResponseAction action : actions) { + action.accept(AnvilGUI.this, clicker); + } + }; + + if (actionsFuture.isDone()) { + // Fast-path without scheduling if clickHandler is performed in sync + // Because the future is already completed, .join() will not block the server thread + actionsFuture.thenAccept(actionsConsumer).join(); + } else { + clickHandlerRunning = true; + // If the plugin is disabled and the Executor throws an exception, the exception will be passed to + // the .handle method + actionsFuture + .thenAcceptAsync(actionsConsumer, mainThreadExecutor) + .handle((results, exception) -> { + if (exception != null) { + plugin.getLogger() + .log( + Level.SEVERE, + "An exception occurred in the AnvilGUI clickHandler", + exception); + } + // Whether an exception occurred or not, set running to false + clickHandlerRunning = false; + return null; + }); + } + } + } + + @EventHandler + public void onInventoryDrag(InventoryDragEvent event) { + if (event.getInventory().equals(inventory)) { + for (int slot : Slot.values()) { + if (event.getRawSlots().contains(slot)) { + event.setCancelled(!interactableSlots.contains(slot)); + break; + } + } + } + } + + @EventHandler + public void onInventoryClose(InventoryCloseEvent event) { + if (open && event.getInventory().equals(inventory)) { + closeInventory(false); + if (preventClose) { + mainThreadExecutor.execute(AnvilGUI.this::openInventory); + } + } + } + } + + /** A builder class for an {@link AnvilGUI} object */ + public static class Builder { + + /** An {@link Executor} that executes tasks on the main server thread */ + private Executor mainThreadExecutor; + /** An {@link Consumer} that is called when the anvil GUI is close */ + private Consumer closeListener; + /** A flag that decides whether the async click handler can be run concurrently */ + private boolean concurrentClickHandlerExecution = false; + /** An {@link Function} that is called when a slot in the inventory has been clicked */ + private ClickHandler clickHandler; + /** A state that decides where the anvil GUI is able to be closed by the user */ + private boolean preventClose = false; + /** A state that determines whether support for Geyser software is enabled */ + private boolean geyserCompatibility = true; + /** A set of integers containing the slot numbers that should be modifiable by the user. */ + private Set interactableSlots = Collections.emptySet(); + /** The {@link Plugin} that this anvil GUI is associated with */ + private Plugin plugin; + /** The text that will be displayed to the user */ + private Object titleComponent = WRAPPER.literalChatComponent("Repair & Name"); + /** The starting text on the item */ + private String itemText; + /** An {@link ItemStack} to be put in the left input slot */ + private ItemStack itemLeft; + /** An {@link ItemStack} to be put in the right input slot */ + private ItemStack itemRight; + /** An {@link ItemStack} to be placed in the output slot */ + private ItemStack itemOutput; + + /** + * Set a custom main server thread executor. Useful for plugins targeting Folia. + * + * @param executor The executor to run tasks on + * @return The {@link Builder} instance + * @throws IllegalArgumentException when the executor is null + */ + public Builder mainThreadExecutor(@NonNull Executor executor) { + this.mainThreadExecutor = executor; + return this; + } + + /** + * Prevents the closing of the anvil GUI by the user + * + * @return The {@link Builder} instance + */ + public Builder preventClose() { + preventClose = true; + return this; + } + + /** + * Disables compatibility with Geyser software + */ + public Builder disableGeyserCompat() { + geyserCompatibility = false; + return this; + } + + /** + * Permit the user to modify (take items in and out) the slot numbers provided. + * + * @param slots A varags param for the slot numbers. You can avoid relying on magic constants by using + * the {@link AnvilGUI.Slot} class. + * @return The {@link Builder} instance + */ + public Builder interactableSlots(int... slots) { + final Set newValue = new HashSet<>(); + for (int slot : slots) { + newValue.add(slot); + } + interactableSlots = newValue; + return this; + } + + /** + * Listens for when the inventory is closed + * + * @param closeListener An {@link Consumer} that is called when the anvil GUI is closed + * @return The {@link Builder} instance + * @throws IllegalArgumentException when the closeListener is null + */ + public Builder onClose(@NonNull Consumer closeListener) { + this.closeListener = closeListener; + return this; + } + + /** + * Do an action when a slot is clicked in the inventory + *

+ * The ClickHandler is only called when the previous execution of the ClickHandler has finished. + * To alter this behaviour use {@link #allowConcurrentClickHandlerExecution()} + * + * @param clickHandler A {@link ClickHandler} that is called when the user clicks a slot. The + * {@link Integer} is the slot number corresponding to {@link Slot}, the + * {@link StateSnapshot} contains information about the current state of the anvil, + * and the response is a {@link CompletableFuture} that will eventually return a + * list of {@link ResponseAction} to execute in the order that they are supplied. + * @return The {@link Builder} instance + * @throws IllegalArgumentException when the function supplied is null + */ + public Builder onClickAsync(@NonNull ClickHandler clickHandler) { + this.clickHandler = clickHandler; + return this; + } + + /** + * By default, the {@link #onClickAsync(ClickHandler) async click handler} will not run concurrently + * and instead wait for the previous {@link CompletableFuture} to finish before executing it again. + *

+ * If this trait is desired, it can be enabled by calling this method but may lead to inconsistent + * behaviour if not handled properly. + * + * @return The {@link Builder} instance + */ + public Builder allowConcurrentClickHandlerExecution() { + this.concurrentClickHandlerExecution = true; + return this; + } + + /** + * Do an action when a slot is clicked in the inventory + * + * @param clickHandler A {@link BiFunction} that is called when the user clicks a slot. The + * {@link Integer} is the slot number corresponding to {@link Slot}, the + * {@link StateSnapshot} contains information about the current state of the anvil, + * and the response is a list of {@link ResponseAction} to execute in the order + * that they are supplied. + * @return The {@link Builder} instance + * @throws IllegalArgumentException when the function supplied is null + */ + public Builder onClick(@NonNull BiFunction> clickHandler) { + this.clickHandler = + (slot, stateSnapshot) -> CompletableFuture.completedFuture(clickHandler.apply(slot, stateSnapshot)); + return this; + } + + /** + * Sets the plugin for the {@link AnvilGUI} + * + * @param plugin The {@link Plugin} this anvil GUI is associated with + * @return The {@link Builder} instance + * @throws IllegalArgumentException if the plugin is null + */ + public Builder plugin(@NonNull Plugin plugin) { + this.plugin = plugin; + return this; + } + + /** + * Sets the initial item-text that is displayed to the user. + *

+ * If the usage of Adventure Components is desired, you must create an item, set the displayname of it + * and put it into the AnvilGUI via {@link #itemLeft(ItemStack)} manually. + * + * @param text The initial name of the item in the anvil + * @return The {@link Builder} instance + * @throws IllegalArgumentException if the text is null + */ + public Builder text(@NonNull String text) { + this.itemText = text; + return this; + } + + /** + * Sets the AnvilGUI title that is to be displayed to the user. + *
+ * The provided title will be treated as literal text. + * + * @param title The title that is to be displayed to the user + * @return The {@link Builder} instance + * @throws IllegalArgumentException if the title is null + */ + public Builder title(@NonNull String title) { + this.titleComponent = WRAPPER.literalChatComponent(title); + return this; + } + + /** + * Sets the AnvilGUI title that is to be displayed to the user. + *
+ * The provided json will be parsed into rich chat components. + * + * @param json The title that is to be displayed to the user + * @return The {@link Builder} instance + * @throws IllegalArgumentException if the title is null + * @see net.md_5.bungee.chat.ComponentSerializer#toString(BaseComponent) + */ + public Builder jsonTitle(@NonNull String json) { + this.titleComponent = WRAPPER.jsonChatComponent(json); + return this; + } + + /** + * Sets the {@link ItemStack} to be put in the first slot + * + * @param item The {@link ItemStack} to be put in the first slot + * @return The {@link Builder} instance + * @throws IllegalArgumentException if the {@link ItemStack} is null + */ + public Builder itemLeft(@NonNull ItemStack item) { + this.itemLeft = item; + return this; + } + + /** + * Sets the {@link ItemStack} to be put in the second slot + * + * @param item The {@link ItemStack} to be put in the second slot + * @return The {@link Builder} instance + */ + public Builder itemRight(ItemStack item) { + this.itemRight = item; + return this; + } + + /** + * Sets the {@link ItemStack} to be put in the output slot + * + * @param item The {@link ItemStack} to be put in the output slot + * @return The {@link Builder} instance + */ + public Builder itemOutput(ItemStack item) { + this.itemOutput = item; + return this; + } + + /** + * Creates the anvil GUI and opens it for the player + * + * @param player The {@link Player} the anvil GUI should open for + * @return The {@link AnvilGUI} instance from this builder + * @throws IllegalArgumentException when the onClick function, plugin, or player is null + */ + public AnvilGUI open(@NonNull Player player) { + if (plugin == null) { + throw new IllegalStateException("An AnvilGUI plugin has not been set!"); + } + if (clickHandler == null) { + throw new IllegalStateException("An AnvilGUI clickHandler has not been set!"); + } + + if (itemText != null) { + if (itemLeft == null) { + itemLeft = new ItemStack(Material.PAPER); + } + + ItemMeta paperMeta = itemLeft.getItemMeta(); + paperMeta.setDisplayName(itemText); + itemLeft.setItemMeta(paperMeta); + } + + // If no executor is specified, execute all tasks with the BukkitScheduler + if (mainThreadExecutor == null) { + mainThreadExecutor = task -> Bukkit.getScheduler().runTask(plugin, task); + } + + final AnvilGUI anvilGUI = new AnvilGUI( + plugin, + player, + mainThreadExecutor, + titleComponent, + new ItemStack[] {itemLeft, itemRight, itemOutput}, + preventClose, + geyserCompatibility, + interactableSlots, + closeListener, + concurrentClickHandlerExecution, + clickHandler); + anvilGUI.openInventory(); + return anvilGUI; + } + } + + /** + * A handler that is called when the user clicks a slot. The + * {@link Integer} is the slot number corresponding to {@link Slot}, the + * {@link StateSnapshot} contains information about the current state of the anvil, + * and the response is a {@link CompletableFuture} that will eventually return a + * list of {@link ResponseAction} to execute in the order that they are supplied. + */ + @FunctionalInterface + public interface ClickHandler extends BiFunction>> {} + + /** An action to run in response to a player clicking the output slot in the GUI. This interface is public + * and permits you, the developer, to add additional response features easily to your custom AnvilGUIs. */ + @FunctionalInterface + public interface ResponseAction extends BiConsumer { + + /** + * Replace the input text box value with the provided text value. + * + * Before using this method, it must be verified by the caller that items are either in + * {@link Slot#INPUT_LEFT} or {@link Slot#OUTPUT} present. + * + * @param text The text to write in the input box + * @return The {@link ResponseAction} to achieve the text replacement + * @throws IllegalArgumentException when the text is null + * @throws IllegalStateException when the slots {@link Slot#INPUT_LEFT} and {@link Slot#OUTPUT} are null + */ + static ResponseAction replaceInputText(@NonNull String text) { + return (anvilgui, player) -> { + ItemStack item = anvilgui.getInventory().getItem(Slot.OUTPUT); + if (item == null) { + // Fallback on left input slot if player hasn't typed anything yet + item = anvilgui.getInventory().getItem(Slot.INPUT_LEFT); + } + if (item == null) { + throw new IllegalStateException( + "replaceInputText can only be used if slots OUTPUT or INPUT_LEFT are not empty"); + } + + final ItemStack cloned = item.clone(); + final ItemMeta meta = cloned.getItemMeta(); + meta.setDisplayName(text); + cloned.setItemMeta(meta); + anvilgui.getInventory().setItem(Slot.INPUT_LEFT, cloned); + }; + } + + /** + * Updates the title of the AnvilGUI to the new one. + * + * @param literalTitle The title to use as literal text + * @param preserveRenameText Whether to preserve the entered rename text + * @throws IllegalArgumentException when literalTitle is null + * @see Builder#title(String) + */ + static ResponseAction updateTitle(@NonNull String literalTitle, boolean preserveRenameText) { + return (anvilGUI, player) -> anvilGUI.setTitle(literalTitle, preserveRenameText); + } + + /** + * Updates the title of the AnvilGUI to the new one. + * + * @param json The json used to parse into a rich chat component + * @param preserveRenameText Whether to preserve the entered rename text + * @throws IllegalArgumentException when json is null + * @see Builder#jsonTitle(String) + */ + static ResponseAction updateJsonTitle(@NonNull String json, boolean preserveRenameText) { + return (anvilGUI, player) -> anvilGUI.setJsonTitle(json, preserveRenameText); + } + + /** + * Open another inventory + * @param otherInventory The inventory to open + * @return The {@link ResponseAction} to achieve the inventory open + * @throws IllegalArgumentException when the otherInventory is null + */ + static ResponseAction openInventory(@NonNull Inventory otherInventory) { + return (anvilgui, player) -> player.openInventory(otherInventory); + } + + /** + * Close the AnvilGUI + * @return The {@link ResponseAction} to achieve closing the AnvilGUI + */ + static ResponseAction close() { + return (anvilgui, player) -> anvilgui.closeInventory(); + } + + /** + * Run the provided runnable + * @param runnable The runnable to run + * @return The {@link ResponseAction} to achieve running the runnable + * @throws IllegalArgumentException when the runnable is null + */ + static ResponseAction run(@NonNull Runnable runnable) { + return (anvilgui, player) -> runnable.run(); + } + } + + /** + * Represents a response when the player clicks the output item in the anvil GUI + * @deprecated Since 1.6.2, use {@link ResponseAction} + */ + @Deprecated + public static class Response { + /** + * Returns an {@link Response} object for when the anvil GUI is to close + * @return An {@link Response} object for when the anvil GUI is to display text to the user + * @deprecated Since 1.6.2, use {@link ResponseAction#close()} + */ + public static List close() { + return Arrays.asList(ResponseAction.close()); + } + + /** + * Returns an {@link Response} object for when the anvil GUI is to display text to the user + * + * @param text The text that is to be displayed to the user + * @return A list containing the {@link ResponseAction} for legacy compat + * @deprecated Since 1.6.2, use {@link ResponseAction#replaceInputText(String)} + */ + public static List text(String text) { + return Arrays.asList(ResponseAction.replaceInputText(text)); + } + + /** + * Returns an {@link Response} object for when the GUI should open the provided inventory + * + * @param inventory The inventory to open + * @return A list containing the {@link ResponseAction} for legacy compat + * @deprecated Since 1.6.2, use {@link ResponseAction#openInventory(Inventory)} + */ + public static List openInventory(Inventory inventory) { + return Arrays.asList(ResponseAction.openInventory(inventory)); + } + } + + /** + * Class wrapping the magic constants of slot numbers in an anvil GUI + */ + public static class Slot { + + private static final int[] values = new int[] {Slot.INPUT_LEFT, Slot.INPUT_RIGHT, Slot.OUTPUT}; + + /** + * The slot on the far left, where the first input is inserted. An {@link ItemStack} is always inserted + * here to be renamed + */ + public static final int INPUT_LEFT = 0; + /** + * Not used, but in a real anvil you are able to put the second item you want to combine here + */ + public static final int INPUT_RIGHT = 1; + /** + * The output slot, where an item is put when two items are combined from {@link #INPUT_LEFT} and + * {@link #INPUT_RIGHT} or {@link #INPUT_LEFT} is renamed + */ + public static final int OUTPUT = 2; + + /** + * Get all anvil slot values + * + * @return The array containing all possible anvil slots + */ + public static int[] values() { + return values; + } + } + + /** Represents a snapshot of the state of an AnvilGUI */ + public static final class StateSnapshot { + + /** + * Create an {@link StateSnapshot} from the current state of an {@link AnvilGUI} + * @param anvilGUI The instance to take the snapshot of + * @return The snapshot + */ + private static StateSnapshot fromAnvilGUI(AnvilGUI anvilGUI) { + final Inventory inventory = anvilGUI.getInventory(); + return new StateSnapshot( + itemNotNull(inventory.getItem(Slot.INPUT_LEFT)).clone(), + itemNotNull(inventory.getItem(Slot.INPUT_RIGHT)).clone(), + itemNotNull(inventory.getItem(Slot.OUTPUT)).clone(), + anvilGUI.player); + } + + /** + * The {@link ItemStack} in the anvilGui slots + */ + private final ItemStack leftItem, rightItem, outputItem; + + /** + * The {@link Player} that clicked the output slot + */ + private final Player player; + + /** + * The event parameter constructor + * @param leftItem The left item in the combine slot of the anvilGUI + * @param rightItem The right item in the combine slot of the anvilGUI + * @param outputItem The item that would have been outputted, when the items would have been combined + * @param player The player that clicked the output slot + */ + public StateSnapshot(ItemStack leftItem, ItemStack rightItem, ItemStack outputItem, Player player) { + this.leftItem = leftItem; + this.rightItem = rightItem; + this.outputItem = outputItem; + this.player = player; + } + + /** + * It returns the item in the left combine slot of the gui + * + * @return The leftItem + */ + public ItemStack getLeftItem() { + return leftItem; + } + + /** + * It returns the item in the right combine slot of the gui + * + * @return The rightItem + */ + public ItemStack getRightItem() { + return rightItem; + } + + /** + * It returns the output item that would have been the result + * by combining the left and right one + * + * @return The outputItem + */ + public ItemStack getOutputItem() { + return outputItem; + } + + /** + * It returns the player that clicked onto the output slot + * + * @return The player + */ + public Player getPlayer() { + return player; + } + + /** + * It returns the text the player typed into the rename field + * + * @return The text of the rename field + */ + public String getText() { + return outputItem.hasItemMeta() ? outputItem.getItemMeta().getDisplayName() : ""; + } + } +} \ No newline at end of file diff --git a/SpigotCore/SpigotCore_Main/src/net/wesjd/anvilgui/version/VersionWrapper.java b/SpigotCore/SpigotCore_Main/src/net/wesjd/anvilgui/version/VersionWrapper.java new file mode 100644 index 00000000..6422fb48 --- /dev/null +++ b/SpigotCore/SpigotCore_Main/src/net/wesjd/anvilgui/version/VersionWrapper.java @@ -0,0 +1,175 @@ +package net.wesjd.anvilgui.version; + +import net.minecraft.core.BlockPos; +import net.minecraft.core.component.DataComponents; +import net.minecraft.network.chat.Component; +import net.minecraft.network.protocol.game.ClientboundContainerClosePacket; +import net.minecraft.network.protocol.game.ClientboundOpenScreenPacket; +import net.minecraft.network.protocol.game.ClientboundSetExperiencePacket; +import net.minecraft.server.level.ServerPlayer; +import net.minecraft.world.Container; +import net.minecraft.world.entity.LivingEntity; +import net.minecraft.world.inventory.AnvilMenu; +import net.minecraft.world.inventory.ContainerLevelAccess; +import net.minecraft.world.inventory.MenuType; +import net.minecraft.world.inventory.Slot; +import org.bukkit.craftbukkit.CraftWorld; +import org.bukkit.craftbukkit.entity.CraftPlayer; +import org.bukkit.craftbukkit.event.CraftEventFactory; +import org.bukkit.craftbukkit.util.CraftChatMessage; +import org.bukkit.entity.Player; +import org.bukkit.event.inventory.InventoryCloseEvent; +import org.bukkit.inventory.Inventory; + +import java.lang.reflect.Method; + +public final class VersionWrapper { + public static final VersionWrapper INSTANCE = new VersionWrapper(); + + /** + * Interface implemented by the custom NMS AnvilContainer used to interact with it directly + */ + public interface AnvilContainerWrapper { + + /** + * Retrieves the raw text that has been entered into the Anvil at the moment + *

+ * This field is marked as public in the Minecraft AnvilContainer only from Minecraft 1.11 and upwards + * + * @return The raw text in the rename field + */ + default String getRenameText() { + return null; + } + + /** + * Sets the provided text as the literal hovername of the item in the left input slot + * + * @param text The text to set + */ + default void setRenameText(String text) { + } + + /** + * Gets the {@link Inventory} wrapper of the NMS container + * + * @return The inventory of the NMS container + */ + Inventory getBukkitInventory(); + } + + private static class AnvilContainer extends AnvilMenu implements AnvilContainerWrapper { + public AnvilContainer(Player player, int containerId, Component guiTitle) { + super( + containerId, + ((CraftPlayer) player).getHandle().getInventory(), + ContainerLevelAccess.create(((CraftWorld) player.getWorld()).getHandle(), new BlockPos(0, 0, 0)) + ); + this.checkReachable = false; + setTitle(guiTitle); + } + + public void l() { + // If the output is empty copy the left input into the output + Slot output = this.getSlot(2); + if (!output.hasItem()) { + output.set(this.getSlot(0).getItem().copy()); + } + + this.cost.set(0); + + // Sync to the client + this.sendAllDataToRemote(); + this.broadcastChanges(); + } + + public void a(LivingEntity player) { + } + + protected void a(LivingEntity player, Container container) { + } + + public int getContainerId() { + return this.containerId; + } + + public String getRenameText() { + return this.itemName; + } + + public void setRenameText(String text) { + // If an item is present in the left input slot change its hover name to the literal text. + Slot inputLeft = getSlot(0); + if (inputLeft.hasItem()) { + inputLeft.getItem() + .set(DataComponents.CUSTOM_NAME, Component.literal(text)); + } + } + + public Inventory getBukkitInventory() { + return this.getBukkitView().getTopInventory(); + } + } + + private int getRealNextContainerId(Player player) { + return toNMS(player).nextContainerCounter(); + } + + /** + * Turns a {@link Player} into an NMS one + * + * @param player The player to be converted + * @return the NMS EntityPlayer + */ + private ServerPlayer toNMS(Player player) { + return ((CraftPlayer) player).getHandle(); + } + + public int getNextContainerId(Player player, AnvilContainerWrapper container) { + return ((AnvilContainer) container).getContainerId(); + } + + public void handleInventoryCloseEvent(Player player) { + CraftEventFactory.handleInventoryCloseEvent(toNMS(player), InventoryCloseEvent.Reason.UNKNOWN); + toNMS(player).doCloseContainer(); + } + + public void sendPacketOpenWindow(Player player, int containerId, Object inventoryTitle) { + toNMS(player).connection.send(new ClientboundOpenScreenPacket(containerId, MenuType.ANVIL, (Component) inventoryTitle)); + } + + public void sendPacketCloseWindow(Player player, int containerId) { + toNMS(player).connection.send(new ClientboundContainerClosePacket(containerId)); + } + + public void sendPacketExperienceChange(Player player, int experienceLevel) { + toNMS(player).connection.send(new ClientboundSetExperiencePacket(0f, 0, experienceLevel)); + } + + public void setActiveContainerDefault(Player player) { + toNMS(player).containerMenu = toNMS(player).inventoryMenu; + } + + public void setActiveContainer(Player player, AnvilContainerWrapper container) { + toNMS(player).containerMenu = (AnvilMenu) container; + } + + public void setActiveContainerId(AnvilContainerWrapper container, int containerId) { + } + + public void addActiveContainerSlotListener(AnvilContainerWrapper container, Player player) { + toNMS(player).initMenu((AnvilMenu) container); + } + + public AnvilContainerWrapper newContainerAnvil(Player player, Object title) { + return new AnvilContainer(player, getRealNextContainerId(player), (Component) title); + } + + public Object literalChatComponent(String content) { + return Component.literal(content); + } + + public Object jsonChatComponent(String json) { + return CraftChatMessage.fromJSON(json); + } +} \ No newline at end of file From 9efe35fe0daf9c6e5c45fc4822181f363228e762 Mon Sep 17 00:00:00 2001 From: YoyoNow Date: Fri, 12 Jun 2026 14:52:46 +0200 Subject: [PATCH 34/36] Update LobbySystem run task --- LobbySystem/build.gradle.kts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/LobbySystem/build.gradle.kts b/LobbySystem/build.gradle.kts index 7dd7d27f..b2517c75 100644 --- a/LobbySystem/build.gradle.kts +++ b/LobbySystem/build.gradle.kts @@ -38,11 +38,11 @@ widener { fromCatalog(libs.paperapi) } -tasks.register("DevLobby20") { +tasks.register("DevLobby") { group = "run" - description = "Run a 1.20 Dev Lobby" + description = "Run a Dev Lobby" dependsOn(":SpigotCore:shadowJar") dependsOn(":LobbySystem:jar") - template = "Lobby20" + template = "Lobby21" worldName = "Lobby" } From 796e154c1c98edba6c7650596759613d7ab4edb8 Mon Sep 17 00:00:00 2001 From: YoyoNow Date: Fri, 12 Jun 2026 15:07:46 +0200 Subject: [PATCH 35/36] Fix paper in inventory --- .../src/net/wesjd/anvilgui/AnvilGUI.java | 60 +++++-------------- .../anvilgui/version/VersionWrapper.java | 24 -------- 2 files changed, 14 insertions(+), 70 deletions(-) diff --git a/SpigotCore/SpigotCore_Main/src/net/wesjd/anvilgui/AnvilGUI.java b/SpigotCore/SpigotCore_Main/src/net/wesjd/anvilgui/AnvilGUI.java index d2d91852..d388dc36 100644 --- a/SpigotCore/SpigotCore_Main/src/net/wesjd/anvilgui/AnvilGUI.java +++ b/SpigotCore/SpigotCore_Main/src/net/wesjd/anvilgui/AnvilGUI.java @@ -1,5 +1,6 @@ package net.wesjd.anvilgui; +import lombok.Getter; import lombok.NonNull; import net.md_5.bungee.api.chat.BaseComponent; import net.wesjd.anvilgui.version.VersionWrapper; @@ -76,10 +77,7 @@ public class AnvilGUI { * A state that decides where the anvil GUI is able to get closed by the user */ private final boolean preventClose; - /** - * A state that decides whether compatibility with Geyser software is enabled - */ - private final boolean geyserCompatibility; + /** * A set of slot numbers that are permitted to be interacted with by the user. An interactable * slot is one that is able to be minipulated by the player, i.e. clicking and picking up an item, @@ -102,6 +100,7 @@ public class AnvilGUI { /** * The inventory that is used on the Bukkit side of things */ + @Getter private Inventory inventory; /** * The listener holder class @@ -127,7 +126,6 @@ public class AnvilGUI { * @param titleComponent What to have the text already set to * @param initialContents The initial contents of the inventory * @param preventClose Whether to prevent the inventory from closing - * @param geyserCompatibility Whether to enable compatibility with Geyser software * @param closeListener A {@link Consumer} when the inventory closes * @param concurrentClickHandlerExecution Flag to allow concurrent execution of the click handler * @param clickHandler A {@link ClickHandler} that is called when the player clicks a slot @@ -139,7 +137,6 @@ public class AnvilGUI { Object titleComponent, ItemStack[] initialContents, boolean preventClose, - boolean geyserCompatibility, Set interactableSlots, Consumer closeListener, boolean concurrentClickHandlerExecution, @@ -150,7 +147,6 @@ public class AnvilGUI { this.titleComponent = titleComponent; this.initialContents = initialContents; this.preventClose = preventClose; - this.geyserCompatibility = geyserCompatibility; this.interactableSlots = Collections.unmodifiableSet(interactableSlots); this.closeListener = closeListener; this.concurrentClickHandlerExecution = concurrentClickHandlerExecution; @@ -181,11 +177,6 @@ public class AnvilGUI { WRAPPER.setActiveContainerId(container, containerId); WRAPPER.addActiveContainerSlotListener(container, player); - // Primitive Geyser Check! - if (player.getName().startsWith(".")) { - WRAPPER.sendPacketExperienceChange(player, 20); - } - open = true; } @@ -210,15 +201,20 @@ public class AnvilGUI { HandlerList.unregisterAll(listener); + inventory = container.getBukkitInventory(); + // We need to use setItem instead of setContents because a Minecraft ContainerAnvil + // contains two separate inventories: the result inventory and the ingredients inventory. + // The setContents method only updates the ingredients inventory unfortunately, + // but setItem handles the index going into the result inventory. + for (int i = 0; i < initialContents.length; i++) { + inventory.setItem(i, new ItemStack(Material.AIR)); + } + if (sendClosePacket) { WRAPPER.handleInventoryCloseEvent(player); WRAPPER.setActiveContainerDefault(player); WRAPPER.sendPacketCloseWindow(player, containerId); } - // Primitive Geyser Check! - if (player.getName().startsWith(".")) { - WRAPPER.sendPacketExperienceChange(player, player.getLevel()); - } if (closeListener != null) { closeListener.accept(StateSnapshot.fromAnvilGUI(this)); @@ -256,9 +252,6 @@ public class AnvilGUI { * @param preserveRenameText Whether to preserve the entered rename text */ private void setTitle(Object title, boolean preserveRenameText) { - if (!true) { - return; - } String renameText = container.getRenameText(); WRAPPER.sendPacketOpenWindow(player, containerId, title); if (preserveRenameText) { @@ -267,15 +260,6 @@ public class AnvilGUI { } } - /** - * Returns the Bukkit inventory for this anvil gui - * - * @return the {@link Inventory} for this anvil gui - */ - public Inventory getInventory() { - return inventory; - } - /** * Simply holds the listeners for the GUI */ @@ -324,7 +308,7 @@ public class AnvilGUI { } if (rawSlot < 3 && rawSlot >= 0 || event.getAction().equals(InventoryAction.MOVE_TO_OTHER_INVENTORY)) { - event.setCancelled(!interactableSlots.contains(rawSlot)); + event.setCancelled(true); if (clickHandlerRunning && !concurrentClickHandlerExecution) { // A click handler is running, don't launch another one return; @@ -368,12 +352,7 @@ public class AnvilGUI { @EventHandler public void onInventoryDrag(InventoryDragEvent event) { if (event.getInventory().equals(inventory)) { - for (int slot : Slot.values()) { - if (event.getRawSlots().contains(slot)) { - event.setCancelled(!interactableSlots.contains(slot)); - break; - } - } + event.setCancelled(true); } } @@ -401,8 +380,6 @@ public class AnvilGUI { private ClickHandler clickHandler; /** A state that decides where the anvil GUI is able to be closed by the user */ private boolean preventClose = false; - /** A state that determines whether support for Geyser software is enabled */ - private boolean geyserCompatibility = true; /** A set of integers containing the slot numbers that should be modifiable by the user. */ private Set interactableSlots = Collections.emptySet(); /** The {@link Plugin} that this anvil GUI is associated with */ @@ -440,14 +417,6 @@ public class AnvilGUI { return this; } - /** - * Disables compatibility with Geyser software - */ - public Builder disableGeyserCompat() { - geyserCompatibility = false; - return this; - } - /** * Permit the user to modify (take items in and out) the slot numbers provided. * @@ -653,7 +622,6 @@ public class AnvilGUI { titleComponent, new ItemStack[] {itemLeft, itemRight, itemOutput}, preventClose, - geyserCompatibility, interactableSlots, closeListener, concurrentClickHandlerExecution, diff --git a/SpigotCore/SpigotCore_Main/src/net/wesjd/anvilgui/version/VersionWrapper.java b/SpigotCore/SpigotCore_Main/src/net/wesjd/anvilgui/version/VersionWrapper.java index 6422fb48..24d07d2a 100644 --- a/SpigotCore/SpigotCore_Main/src/net/wesjd/anvilgui/version/VersionWrapper.java +++ b/SpigotCore/SpigotCore_Main/src/net/wesjd/anvilgui/version/VersionWrapper.java @@ -7,8 +7,6 @@ import net.minecraft.network.protocol.game.ClientboundContainerClosePacket; import net.minecraft.network.protocol.game.ClientboundOpenScreenPacket; import net.minecraft.network.protocol.game.ClientboundSetExperiencePacket; import net.minecraft.server.level.ServerPlayer; -import net.minecraft.world.Container; -import net.minecraft.world.entity.LivingEntity; import net.minecraft.world.inventory.AnvilMenu; import net.minecraft.world.inventory.ContainerLevelAccess; import net.minecraft.world.inventory.MenuType; @@ -21,8 +19,6 @@ import org.bukkit.entity.Player; import org.bukkit.event.inventory.InventoryCloseEvent; import org.bukkit.inventory.Inventory; -import java.lang.reflect.Method; - public final class VersionWrapper { public static final VersionWrapper INSTANCE = new VersionWrapper(); @@ -69,26 +65,6 @@ public final class VersionWrapper { setTitle(guiTitle); } - public void l() { - // If the output is empty copy the left input into the output - Slot output = this.getSlot(2); - if (!output.hasItem()) { - output.set(this.getSlot(0).getItem().copy()); - } - - this.cost.set(0); - - // Sync to the client - this.sendAllDataToRemote(); - this.broadcastChanges(); - } - - public void a(LivingEntity player) { - } - - protected void a(LivingEntity player, Container container) { - } - public int getContainerId() { return this.containerId; } From 669235709ed94ffc05d9a851c90f5a349122025b Mon Sep 17 00:00:00 2001 From: YoyoNow Date: Fri, 12 Jun 2026 15:14:09 +0200 Subject: [PATCH 36/36] Fix WorldEditListener --- .../de/steamwar/bausystem/features/world/WorldEditListener.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/world/WorldEditListener.java b/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/world/WorldEditListener.java index 26943331..b0373595 100644 --- a/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/world/WorldEditListener.java +++ b/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/world/WorldEditListener.java @@ -57,7 +57,7 @@ public class WorldEditListener implements Listener { private static final Set commands = new HashSet<>(); private static final Set commandExclusions = new HashSet<>(); - private static final String[] shortcutCommands = {"//1", "//2", "//90", "//-90", "//180", "//p", "//c", "//flopy", "//floppy", "//flopyp", "//floppyp", "//u", "//r"}; + private static final String[] shortcutCommands = {"//1", "//2", "//90", "//-90", "//180", "//p", "//c", "//flopy", "//floppy", "//flopyp", "//floppyp", "//u", "//r", "//download", "/download"}; public static boolean isWorldEditCommand(String command) { for (String shortcut : shortcutCommands) {