Compare commits

..

126 Commits

Author SHA1 Message Date
Chaoscaot 1cb2e70ede Revert "Remove deprecated world management commands and associated SQL logic"
This reverts commit a7adfe37

Signed-off-by: Chaoscaot <max@maxsp.de>
2026-05-31 20:21:30 +02:00
Chaoscaot 8ce25ec19f Merge branch 'main' into world-system 2026-05-31 20:16:17 +02:00
YoyoNow a9fb982143 Merge pull request 'Fix WorldDir of Event servers' (#405) from VelocityCore/FixEventWorldDir into main
Reviewed-on: SteamWar/SteamWar#405
Reviewed-by: D4rkr34lm <dark@steamwar.de>
2026-05-30 18:33:17 +02:00
YoyoNow 5b8d881e01 Fix WorldDir of Event servers 2026-05-30 18:30:51 +02:00
YoyoNow eb55f4b395 Merge pull request 'Remove global Entity Interact callback' (#404) from SpigotCore/RemoveGlobalEntityServerCallback into main
Reviewed-on: SteamWar/SteamWar#404
Reviewed-by: D4rkr34lm <dark@steamwar.de>
2026-05-30 15:27:45 +02:00
YoyoNow 273db91d06 Fix compile 2026-05-30 15:24:20 +02:00
YoyoNow 983ad544c1 Remove global Entity Interact callback 2026-05-30 15:18:37 +02:00
YoyoNow a6a34b2221 Fix Schem add on WGS Schems 2026-05-30 14:13:08 +02:00
YoyoNow 6c6bd19038 Merge pull request 'fix(BauSystem): interaction with trace entity not showing details text' (#398) from BauSystem/fix-interaction-with-trace-entity into main
Reviewed-on: SteamWar/SteamWar#398
Reviewed-by: YoyoNow <4+yoyonow@noreply.localhost>
2026-05-30 13:35:35 +02:00
D4rkr34lm 89e05cd109 Remove remnatn of old impl 2026-05-30 13:30:47 +02:00
D4rkr34lm ea9d7ac584 Refactor to use better impl of interaction entity in REntity system 2026-05-30 13:29:50 +02:00
D4rkr34lm 17e1cf53b0 Merge branch 'main' into BauSystem/fix-interaction-with-trace-entity 2026-05-30 13:24:43 +02:00
YoyoNow f64f337f17 Merge pull request 'Add RInteraction Entity' (#403) from SpigotCore/RInteraction into main
Reviewed-on: SteamWar/SteamWar#403
Reviewed-by: D4rkr34lm <dark@steamwar.de>
2026-05-30 13:22:35 +02:00
YoyoNow c5f5be7d58 Add RInteraction Entity 2026-05-30 13:17:49 +02:00
Chaoscaot 9f18644763 Merge pull request 'fix(FightSystem): arrow stopper removing entites not shot by player (#399)' (#402) from wip/399-fix-error-stopper-for-dispensers into main
Reviewed-on: SteamWar/SteamWar#402
Reviewed-by: Chaoscaot <max@chaoscaot.de>
2026-05-30 12:11:12 +02:00
D4rkr34lm 1de17d27f4 Fix arrow stopper 2026-05-30 11:55:59 +02:00
D4rkr34lm 3cd0db9bdf Merge branch 'main' into BauSystem/fix-interaction-with-trace-entity 2026-05-25 21:32:12 +02:00
D4rkr34lm 9711b48f2f Fix interaction with trace entity not showing detail text 2026-05-25 18:20:42 +02:00
Chaoscaot 9934b8bbb2 Fix TNTLeague InviteCommand.kt
Signed-off-by: Chaoscaot <max@maxsp.de>
2026-05-25 01:55:20 +02:00
Chaoscaot 6447265b90 Merge pull request 'feat(BauSystem): use display entity to render trace' (#397) from BauSystem/use-display-entities-for-trace-render into main
Reviewed-on: SteamWar/SteamWar#397
Reviewed-by: Chaoscaot <max@chaoscaot.de>
2026-05-25 01:40:30 +02:00
D4rkr34lm 71258318db Replace trace rendering by fallingblockentity with display entity for better accuracy 2026-05-24 23:39:50 +02:00
Chaoscaot 52e95e2649 Merge pull request 'fix(VelocityCore): disable replays' (#395) from disable-replays into main
Reviewed-on: SteamWar/SteamWar#395
Reviewed-by: Chaoscaot <max@chaoscaot.de>
2026-05-24 13:45:32 +02:00
D4rkr34lm de6ac2cf20 fix 2026-05-24 13:44:57 +02:00
D4rkr34lm 628001e693 Disable Replays 2026-05-24 13:42:08 +02:00
Chaoscaot 47c148d64a Merge pull request 'fix(SpigotCore): techhider not hiding waterlogged blocks if neccecary' (#393) from fix-techhider-not-hidding-waterlogged-blocks into main
Reviewed-on: SteamWar/SteamWar#393
Reviewed-by: Chaoscaot <max@chaoscaot.de>
2026-05-23 16:33:11 +02:00
D4rkr34lm 78617d5a98 another fix 2026-05-23 16:15:31 +02:00
D4rkr34lm 69b924ded6 fix techhider not hiding waterlogged blocks if neccecary 2026-05-23 12:29:19 +02:00
YoyoNow 002f7e5542 Merge pull request 'fix(FightSystem): techhider being active in check and test state' (#392) from fix-techhider-being-active-in-check-n-test-arena into main
Reviewed-on: SteamWar/SteamWar#392
Reviewed-by: YoyoNow <4+yoyonow@noreply.localhost>
2026-05-23 06:23:16 +02:00
D4rkr34lm 5d70f75ac9 Move techhider init into enable to prevent always active even in check and test state 2026-05-22 23:12:39 +02:00
YoyoNow 7f21a31ec9 Merge pull request 'refactor(SpigotCore): Techhider to ensure safety and improve reviewablity' (#338) from FightSystem/fix-tech-and-hull-hider into main
Reviewed-on: SteamWar/SteamWar#338
2026-05-22 21:37:03 +02:00
YoyoNow 23b5ab3e82 Fix build 2026-05-22 21:35:01 +02:00
YoyoNow 4f9fe07951 Fix final formatting stuff 2026-05-22 21:29:58 +02:00
YoyoNow 3bcff4c5ce Merge pull request 'perf(FightSystem): increase chunk hiding performance by skipping irrelavant chunks' (#385) from FightSystem/Optimize-new-techhider into FightSystem/fix-tech-and-hull-hider
Reviewed-on: SteamWar/SteamWar#385
Reviewed-by: YoyoNow <4+yoyonow@noreply.localhost>
2026-05-22 20:52:11 +02:00
D4rkr34lm 1810cb7546 Fix inlineing 2026-05-22 20:51:24 +02:00
D4rkr34lm e55ca911c4 Inline get all regions 2026-05-22 20:41:05 +02:00
D4rkr34lm 793f2de6c3 Sync with base branch 2026-05-22 19:42:36 +02:00
D4rkr34lm 54fa47bd99 Ensure parity with old techhider by suppressing select packets 2026-05-22 19:39:57 +02:00
YoyoNow 14a770b207 Merge pull request 'fix(BauSystem): tick step bossbar remaining visable' (#391) from fix-390-fix-tickstep-bosbar-not-disapearing into main
Reviewed-on: SteamWar/SteamWar#391
2026-05-22 18:56:30 +02:00
D4rkr34lm 7b3a04f4eb Remove unused custom event 2026-05-22 18:49:51 +02:00
D4rkr34lm f2a06057a8 Fix tick step bossbar remaining visable 2026-05-22 18:46:36 +02:00
D4rkr34lm e11f3f7cbc Fix preschem state and techider not being always active 2026-05-22 18:27:53 +02:00
D4rkr34lm ebc10c1ce4 Fix constructor visiblity 2026-05-22 17:57:15 +02:00
D4rkr34lm d682e35159 Resolve open comments 2026-05-22 17:55:12 +02:00
D4rkr34lm 7d74eb0c09 add old techhider to skip supporting bausystem for now 2026-05-22 17:40:33 +02:00
D4rkr34lm 7e18207b87 fix entities not despawning 2026-05-22 16:32:49 +02:00
YoyoNow 72b23ad116 Add filling off Hull for reset and paste with either visible blocks or occluding blocks 2026-05-22 16:24:34 +02:00
YoyoNow c508627d92 Merge pull request 'fix(BauSystem): techhider bypass on bau by middleclick pick' (#389) from fix-387-bau-techhider-bypass-by-middleclick into main
Reviewed-on: SteamWar/SteamWar#389
Reviewed-by: Chaoscaot <max@chaoscaot.de>
2026-05-22 10:17:19 +02:00
D4rkr34lm 93ff982649 fix techhider bypass on bau by middleclick pick 2026-05-21 23:57:38 +02:00
D4rkr34lm 21f6394359 Fix section packet being unnececarily reconstructed 2026-05-21 22:20:40 +02:00
D4rkr34lm 29d0c8978c Rmeove old player join quickfix 2026-05-21 22:00:15 +02:00
D4rkr34lm 7366f83b15 Merge branch 'FightSystem/fix-tech-and-hull-hider' into FightSystem/Optimize-new-techhider 2026-05-21 21:57:41 +02:00
YoyoNow 9240bcfd5f Add blockUsedForObfuscation 2026-05-21 20:57:59 +02:00
D4rkr34lm bede8caa82 Add chunk skipping optimisation 2026-05-21 20:20:18 +02:00
D4rkr34lm 108aa2af9a remove unused reflection class 2026-05-21 18:46:00 +02:00
D4rkr34lm d07a86c13f remove container hiding as it has no lacalaity mapping posibility 2026-05-21 18:41:55 +02:00
D4rkr34lm 29106d5bb5 Add bundle packet handling 2026-05-21 18:20:15 +02:00
YoyoNow 32fd4b4630 Add Sound handling 2026-05-21 18:11:42 +02:00
YoyoNow c241aef932 Add BlockEntityType handling 2026-05-21 17:53:51 +02:00
D4rkr34lm 2db24ef2eb Merge remote-tracking branch 'origin/FightSystem/fix-tech-and-hull-hider' into FightSystem/fix-tech-and-hull-hider 2026-05-21 15:08:41 +02:00
D4rkr34lm a2caa79f99 Erste bugs ausgemerzt 2026-05-21 15:06:33 +02:00
YoyoNow e49cfa9495 Fix duplicates in CouncilChannel 2026-05-21 13:19:27 +02:00
YoyoNow 1f44b3169e Remove unused stuff 2026-05-21 13:05:16 +02:00
YoyoNow 49fafa5955 Fix build 2026-05-21 13:00:53 +02:00
YoyoNow c81e6b15e5 Merge branch 'main' into FightSystem/fix-tech-and-hull-hider
# Conflicts:
#	FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/utils/HullHider.java
#	SpigotCore/SpigotCore_Main/src/de/steamwar/core/events/AntiNocom.java
2026-05-21 12:59:22 +02:00
D4rkr34lm dc0365834a fixes 2026-05-21 12:13:41 +02:00
YoyoNow 13334e03a4 Add Debugger for remote JVM 2026-05-21 12:11:40 +02:00
YoyoNow b35b2c0138 Add jvmArgs to steamwar.devserver.gradle 2026-05-21 11:31:39 +02:00
YoyoNow 6ef311ce68 Merge pull request 'Fix CouncilChannel' (#384) from VelocityCore/FixDiscordBot into main
Reviewed-on: SteamWar/SteamWar#384
2026-05-21 11:23:06 +02:00
YoyoNow 68d9c55525 Downgrade JDA to 5.5.1 2026-05-21 11:21:35 +02:00
YoyoNow dcd70c5ced Fix CouncilChannel
Update JDA to 6.4.1
2026-05-21 11:20:37 +02:00
D4rkr34lm a1d22b7d77 twast 2026-05-21 10:26:46 +02:00
YoyoNow 3b6fdbeec3 Fix CouncilChannel 2026-05-21 10:16:17 +02:00
YoyoNow 714f86a55b Improve Tablist Header colors 2026-05-21 10:02:34 +02:00
YoyoNow a177cc4faf Fix Tablist
Add UpdateTeamsPacketImpl back
2026-05-21 09:54:48 +02:00
YoyoNow 09aa2ee22c Fix compile of VelocityCore 2026-05-21 08:43:28 +02:00
YoyoNow b8499a7a8a Fix Tablist 2026-05-21 08:36:24 +02:00
YoyoNow e50bff3d38 Merge pull request 'Unwrap the wrappers' (#381) from UnwrapWrapper into main
Reviewed-on: SteamWar/SteamWar#381
2026-05-21 07:56:16 +02:00
D4rkr34lm 8424f842c3 resolve last compile issue in techhider 2026-05-21 00:18:12 +02:00
YoyoNow 8f23f57415 Fix TeamCommand 2026-05-20 23:01:50 +02:00
YoyoNow 3b65b93e73 Merge pull request 'SW Prefix Implementation' (#383) from Jaki/SteamWar:main into main
Reviewed-on: SteamWar/SteamWar#383
Reviewed-by: YoyoNow <yoyonow@noreply.localhost>
2026-05-20 22:47:46 +02:00
Jakob Schulz 4a458f0e92 fix: update team name checks to use constants for better readability 2026-05-20 22:38:23 +02:00
Jakob Schulz 5f5a98bcd6 refactor team prefix handling and improve configuration parsing 2026-05-20 22:35:19 +02:00
D4rkr34lm 72df919312 cut down old protocol wrapper 2026-05-20 21:47:17 +02:00
D4rkr34lm 7ed36fe56e removed reflections form anti nocom guard 2026-05-20 21:44:28 +02:00
D4rkr34lm a7a8c4d051 Fix imports in tinyProtocol 2026-05-20 21:33:01 +02:00
D4rkr34lm 5da3767378 Started embedding new techhider into fight system 2026-05-20 21:30:44 +02:00
Jakob Schulz 53f98d4cd7 evaluate the config preference once per message 2026-05-20 19:30:06 +02:00
Jakob Schulz 48fb6b5ed7 add parsing for chat prefix mode configuration 2026-05-20 19:06:05 +02:00
D4rkr34lm ba29e5cf23 Add tinyProtocol Handler for techhider 2026-05-20 18:28:39 +02:00
Chaoscaot e94eac9b9d Fix Cauldrons in Schematic GUI
Signed-off-by: Chaoscaot <max@maxsp.de>
2026-05-20 16:50:04 +02:00
Chaoscaot a7adfe378f Remove deprecated world management commands and associated SQL logic
Signed-off-by: Chaoscaot <max@maxsp.de>
2026-05-20 16:44:32 +02:00
YoyoNow a000beee20 Remove TinyProtocol.addTypedFilter 2026-05-20 13:52:13 +02:00
YoyoNow 2fd6c40b15 Remove some more wrappers 2026-05-20 13:27:49 +02:00
D4rkr34lm 32a2cbb4dd Finish initial implementation of logic 2026-05-19 22:47:22 +02:00
D4rkr34lm cf7a1ee086 Implement logic for hiding chunk data (not finished) 2026-05-19 22:17:41 +02:00
YoyoNow 8da7956523 Fix build 2026-05-19 21:03:38 +02:00
Chaoscaot 9aa363de6f Add world-based permissions and migration commands
- Switch bau logic from owner-scoped to world-scoped data
- Add CLI commands for world migration, templating, and archiving
- Extend world storage with team worlds and lock state support
2026-05-19 20:53:32 +02:00
YoyoNow 9b821ff08f Unwrap the wrappers 2026-05-19 19:41:04 +02:00
Chaoscaot 5a5090c74d Remove unused import and fix incorrect import path in BuilderCloudCommand and World classes.
Signed-off-by: Chaoscaot <max@maxsp.de>
2026-05-19 19:36:43 +02:00
Chaoscaot 99ca7205fb Merge branch 'main' into world-system
# Conflicts:
#	VelocityCore/src/de/steamwar/velocitycore/ServerStarter.java
#	VelocityCore/src/de/steamwar/velocitycore/commands/BauCommand.java
#	VelocityCore/src/de/steamwar/velocitycore/commands/BuilderCloudCommand.java
2026-05-19 19:30:30 +02:00
YoyoNow ae6f279dcf Merge pull request 'add setting to disable block generators' (#375) from Jaki/SteamWar:main into main
Reviewed-on: SteamWar/SteamWar#375
Reviewed-by: YoyoNow <yoyonow@noreply.localhost>
2026-05-19 19:25:25 +02:00
YoyoNow aead25650e Add debug output to TinyProtocol 2026-05-19 18:55:46 +02:00
YoyoNow 53ada2de83 Add debug output to TinyProtocol 2026-05-19 18:50:17 +02:00
YoyoNow 496cc3121c Add debug output to TinyProtocol 2026-05-19 18:41:15 +02:00
D4rkr34lm 270d82eb71 Merge branch 'main' into FightSystem/fix-tech-and-hull-hider 2026-05-17 19:58:16 +02:00
D4rkr34lm 9f319128cc Update multiblock update packet filter 2026-05-17 13:49:46 +02:00
D4rkr34lm 3bd1cf7167 Even more handlers 2026-05-16 22:12:25 +02:00
D4rkr34lm 723a7dc0ca Add handling for container 2026-05-16 20:14:09 +02:00
D4rkr34lm 88de28ed68 Add new handler 2026-05-16 19:25:32 +02:00
D4rkr34lm 6f1a3fe70c Update a lot of handlers and prepare for merge with main 2026-05-16 19:19:49 +02:00
Chaoscaot e85604fdc4 Merge branch 'main' into world-system 2026-05-16 14:49:18 +02:00
D4rkr34lm 4a43e09a8b Add more handlers 2026-05-16 14:45:36 +02:00
D4rkr34lm 0452a9faa5 Add processors for a sed of id only entity packets 2026-05-15 16:23:23 +02:00
D4rkr34lm 9a75f38226 Continue 2026-05-15 16:14:05 +02:00
Manuel Frohn 42808f40c0 preclassify packets as safe 2026-05-15 00:48:45 +02:00
Manuel Frohn 57c21122e6 Classify packets by scope 2026-05-15 00:32:09 +02:00
Manuel Frohn 42faba70d9 Add remaining packets with toss handler for now 2026-05-14 22:08:17 +02:00
Manuel Frohn 99e1f01869 Update bypass list with harmless play-client bound packets 2026-05-14 21:38:37 +02:00
Manuel Frohn 13bddbe359 Update bypass list with full set of configuration and login packets 2026-05-14 20:21:36 +02:00
D4rkr34lm 51ff0e1a9f Fix remaining local issues 2026-05-14 11:57:26 +02:00
D4rkr34lm 6766862a00 Refactored over all functionality 2026-05-13 22:48:44 +02:00
Manuel Frohn c30aad179b Continue refactor 2026-05-13 17:20:32 +02:00
Manuel Frohn d3aad6a198 Move over signle block event package processing 2026-05-13 16:24:45 +02:00
Manuel Frohn 744dc67ca9 Move over identified packages from old impl 2026-05-13 15:33:08 +02:00
Manuel Frohn 164a7f5826 Replace tinyProtocolLib with newest version 2026-05-13 14:04:38 +02:00
Chaoscaot 936ee38503 Migrate builder and bau worlds to shared world records
- add SQL-backed world entities with archive and rename support
- route builder, bau, deploy, and GDPR flows through world storage
- keep legacy folder support for existing worlds
2026-05-09 16:38:13 +02:00
126 changed files with 4090 additions and 2088 deletions
+15
View File
@@ -0,0 +1,15 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="Remote JVM Debugger" type="Remote">
<option name="USE_SOCKET_TRANSPORT" value="true" />
<option name="SERVER_MODE" value="false" />
<option name="SHMEM_ADDRESS" />
<option name="HOST" value="localhost" />
<option name="PORT" value="5005" />
<option name="AUTO_RESTART" value="false" />
<RunnerSettings RunnerId="Debug">
<option name="DEBUG_PORT" value="5005" />
<option name="LOCAL" value="false" />
</RunnerSettings>
<method v="2" />
</configuration>
</component>
@@ -133,7 +133,12 @@ public class BauSystem extends JavaPlugin implements Listener {
Bukkit.getWorlds().get(0).setGameRule(GameRule.SEND_COMMAND_FEEDBACK, false); Bukkit.getWorlds().get(0).setGameRule(GameRule.SEND_COMMAND_FEEDBACK, false);
String identifier = BauServerInfo.getOwnerUser().getUUID().toString().replace("-", ""); String identifier;
if (BauServerInfo.getWorldId() != null) {
identifier = BauServerInfo.getWorldId().toString().replace("-", "");
} else {
identifier = BauServerInfo.getOwnerUser().getUUID().toString().replace("-", "");
}
WorldIdentifier.set("bau/" + Core.getVersion() + "/" + identifier); WorldIdentifier.set("bau/" + Core.getVersion() + "/" + identifier);
} }
@@ -224,4 +229,4 @@ public class BauSystem extends JavaPlugin implements Listener {
AtomicReference<BukkitTask> task = new AtomicReference<>(); AtomicReference<BukkitTask> task = new AtomicReference<>();
task.set(runTaskTimer(plugin, () -> consumer.accept(task.get()), delay, period)); task.set(runTaskTimer(plugin, () -> consumer.accept(task.get()), delay, period));
} }
} }
@@ -60,11 +60,16 @@ public enum Permission {
} }
public boolean hasPermission(Player member) { public boolean hasPermission(Player member) {
if (SteamwarUser.get(member.getUniqueId()).getId() == BauServer.getInstance().getOwnerID()) { SteamwarUser steamwarUser = SteamwarUser.get(member.getUniqueId());
BauServer server = BauServer.getInstance();
if (!server.isTeamWorld() && steamwarUser.getId() == server.getOwnerID()) {
return this != SPECTATOR; return this != SPECTATOR;
} }
BauweltMember bauweltMember = BauweltMember.getBauMember(BauServer.getInstance().getOwner(), member.getUniqueId()); if (server.isTeamWorld() && steamwarUser.getTeam() == server.getTeamID()) {
return this != SPECTATOR;
}
BauweltMember bauweltMember = BauweltMember.getBauMember(server.getWorldID(), steamwarUser.getId());
if (bauweltMember == null) return this == SPECTATOR; if (bauweltMember == null) return this == SPECTATOR;
return permissionPredicate.test(bauweltMember); return permissionPredicate.test(bauweltMember);
} }
} }
@@ -34,16 +34,41 @@ public class BauServer {
} }
private Integer owner; private Integer owner;
private UUID world;
private Integer team;
public UUID getOwner() { public UUID getOwner() {
return SteamwarUser.byId(getOwnerID()).getUUID(); Integer ownerId = getOwnerID();
return ownerId == null ? null : SteamwarUser.byId(ownerId).getUUID();
} }
public int getOwnerID() { public Integer getOwnerID() {
//Lazy loading to improve startup time of the server in 1.15 //Lazy loading to improve startup time of the server in 1.15
if (owner == null) { if (owner == null) {
owner = BauServerInfo.getOwnerId(); owner = BauServerInfo.getOwnerId();
} }
return owner; return owner;
} }
}
public boolean hasOwner() {
return getOwnerID() != null;
}
public UUID getWorldID() {
if (world == null) {
world = BauServerInfo.getWorldId();
}
return world;
}
public Integer getTeamID() {
if (team == null) {
team = BauServerInfo.getTeamId();
}
return team;
}
public boolean isTeamWorld() {
return getTeamID() != null;
}
}
@@ -47,7 +47,7 @@ public class BauInfoBauGuiItem extends BauGuiItem {
@Override @Override
public ItemStack getItem(Player player) { public ItemStack getItem(Player player) {
SWItem itemStack; SWItem itemStack;
if (!player.getName().endsWith("")) { if (!player.getName().endsWith("") && !BauServer.getInstance().isTeamWorld()) {
itemStack = SWItem.getPlayerSkull(SteamwarUser.get(BauServer.getInstance().getOwner()).getUserName()); itemStack = SWItem.getPlayerSkull(SteamwarUser.get(BauServer.getInstance().getOwner()).getUserName());
} else { } else {
itemStack = new SWItem(Material.PLAYER_HEAD, ""); itemStack = new SWItem(Material.PLAYER_HEAD, "");
@@ -47,7 +47,9 @@ public class InfoCommand extends SWCommand {
@Register(description = "BAU_INFO_COMMAND_HELP") @Register(description = "BAU_INFO_COMMAND_HELP")
public void genericCommand(Player p) { public void genericCommand(Player p) {
BauSystem.MESSAGE.send("BAU_INFO_COMMAND_OWNER", p, SteamwarUser.byId(bauServer.getOwnerID()).getUserName()); if (!bauServer.isTeamWorld()) {
BauSystem.MESSAGE.send("BAU_INFO_COMMAND_OWNER", p, SteamwarUser.byId(bauServer.getOwnerID()).getUserName());
}
Region region = Region.getRegion(p.getLocation()); Region region = Region.getRegion(p.getLocation());
for (Flag flag : Flag.getFlags()) { for (Flag flag : Flag.getFlags()) {
if (!region.getRegionData().has(flag).isApplicable()) continue; if (!region.getRegionData().has(flag).isApplicable()) continue;
@@ -58,7 +60,7 @@ public class InfoCommand extends SWCommand {
} }
if (Permission.BUILD.hasPermission(p)) { if (Permission.BUILD.hasPermission(p)) {
List<BauweltMember> members = BauweltMember.getMembers(bauServer.getOwnerID()); List<BauweltMember> members = BauweltMember.getWorldMembers(bauServer.getWorldID());
Map<Permission, List<BauweltMember>> memberByPermission = new HashMap<>(); Map<Permission, List<BauweltMember>> memberByPermission = new HashMap<>();
members.forEach(member -> { members.forEach(member -> {
if (Permission.SUPERVISOR.hasPermission(member)) { if (Permission.SUPERVISOR.hasPermission(member)) {
@@ -21,9 +21,7 @@ package de.steamwar.bausystem.features.design.endstone;
import de.steamwar.bausystem.BauSystem; import de.steamwar.bausystem.BauSystem;
import de.steamwar.bausystem.region.Region; import de.steamwar.bausystem.region.Region;
import de.steamwar.entity.REntity; import de.steamwar.entity.*;
import de.steamwar.entity.REntityServer;
import de.steamwar.entity.RFallingBlockEntity;
import net.md_5.bungee.api.ChatMessageType; import net.md_5.bungee.api.ChatMessageType;
import org.bukkit.Bukkit; import org.bukkit.Bukkit;
import org.bukkit.Location; import org.bukkit.Location;
@@ -59,15 +57,15 @@ public class DesignEndStone {
.filter(material -> material.getBlastResistance() > region.getGameModeConfig().Schematic.MaxDesignBlastResistance) .filter(material -> material.getBlastResistance() > region.getGameModeConfig().Schematic.MaxDesignBlastResistance)
.collect(Collectors.toSet()); .collect(Collectors.toSet());
calculateFromBottom = region.getGameModeConfig().Arena.NoFloor; calculateFromBottom = region.getGameModeConfig().Arena.NoFloor;
}
entityServer.setCallback((player, rEntity, entityAction) -> { private void interact(Player player, RInteraction entity, REntityAction action) {
if (entityAction != REntityServer.EntityAction.ATTACK) return; if (action != REntityAction.ATTACK) return;
Location location = new Location(WORLD, rEntity.getX(), rEntity.getY(), rEntity.getZ()); Location location = new Location(WORLD, entity.getX(), entity.getY(), entity.getZ());
Bukkit.getScheduler().runTaskLater(BauSystem.getInstance(), () -> { Bukkit.getScheduler().runTaskLater(BauSystem.getInstance(), () -> {
location.getBlock().breakNaturally(); location.getBlock().breakNaturally();
calc(); calc();
}, 1); }, 1);
});
} }
public void calc() { public void calc() {
@@ -110,12 +108,15 @@ public class DesignEndStone {
Material material = WORLD.getBlockAt(cx, cy, cz).getType(); Material material = WORLD.getBlockAt(cx, cy, cz).getType();
if (material != Material.WATER && material != Material.LAVA && limited.contains(material)) { if (material != Material.WATER && material != Material.LAVA && limited.contains(material)) {
Location location = new Location(WORLD, cx + 0.5, cy, cz + 0.5); Location location = new Location(WORLD, cx, cy, cz);
if (!locations.add(location)) break; if (!locations.add(location)) break;
RFallingBlockEntity entity = new RFallingBlockEntity(entityServer, location, Material.RED_STAINED_GLASS); RBlockDisplay entity = new RBlockDisplay(entityServer, location);
entity.setNoGravity(true); entity.setBlock(Material.RED_STAINED_GLASS.createBlockData());
entity.setGlowing(true); entity.setGlowing(true);
entities.add(entity); entities.add(entity);
RInteraction interaction = new RInteraction(entityServer, location);
interaction.setCallback(this::interact);
entities.add(interaction);
break; break;
} else if (!material.isAir() && material != Material.WATER && material != Material.LAVA) { } else if (!material.isAir() && material != Material.WATER && material != Material.LAVA) {
break; break;
@@ -26,8 +26,9 @@ import de.steamwar.bausystem.features.autostart.AutostartListener;
import de.steamwar.bausystem.features.detonator.storage.DetonatorStorage; import de.steamwar.bausystem.features.detonator.storage.DetonatorStorage;
import de.steamwar.bausystem.features.detonator.storage.ItemStorage; import de.steamwar.bausystem.features.detonator.storage.ItemStorage;
import de.steamwar.core.SWPlayer; import de.steamwar.core.SWPlayer;
import de.steamwar.entity.RBlockDisplay;
import de.steamwar.entity.REntityServer; import de.steamwar.entity.REntityServer;
import de.steamwar.entity.RFallingBlockEntity; import de.steamwar.entity.RInteraction;
import lombok.Getter; import lombok.Getter;
import lombok.Setter; import lombok.Setter;
import lombok.experimental.UtilityClass; import lombok.experimental.UtilityClass;
@@ -58,10 +59,6 @@ public class Detonator {
@Override @Override
public void onMount(SWPlayer player) { public void onMount(SWPlayer player) {
entities.addPlayer(player.getPlayer()); entities.addPlayer(player.getPlayer());
entities.setCallback((player1, entity, action) -> {
Vector vector = new Vector(entity.getX(), entity.getY(), entity.getZ());
DetonatorListener.addLocationToDetonator(vector.toLocation(player.getWorld()).getBlock().getLocation(), player1);
});
} }
@Override @Override
@@ -70,8 +67,6 @@ public class Detonator {
} }
} }
private static final Vector HALF = new Vector(0.5, 0, 0.5);
public static boolean isDetonator(ItemStack itemStack) { public static boolean isDetonator(ItemStack itemStack) {
return ItemStorage.isDetonator(itemStack); return ItemStorage.isDetonator(itemStack);
} }
@@ -79,8 +74,14 @@ public class Detonator {
public static void showDetonator(Player p, List<Location> locs) { public static void showDetonator(Player p, List<Location> locs) {
DetonatorComponent detonatorComponent = SWPlayer.of(p).getComponentOrDefault(DetonatorComponent.class, DetonatorComponent::new); DetonatorComponent detonatorComponent = SWPlayer.of(p).getComponentOrDefault(DetonatorComponent.class, DetonatorComponent::new);
locs.forEach(location -> { locs.forEach(location -> {
RFallingBlockEntity entity = new RFallingBlockEntity(detonatorComponent.entities, location.clone().add(HALF), Material.RED_STAINED_GLASS); RBlockDisplay blockDisplay = new RBlockDisplay(detonatorComponent.entities, location);
entity.setNoGravity(true); blockDisplay.setBlock(Material.RED_STAINED_GLASS.createBlockData());
RInteraction interaction = new RInteraction(detonatorComponent.entities, location);
interaction.setCallback((player, entity, action) -> {
Vector vector = new Vector(entity.getX(), entity.getY(), entity.getZ());
DetonatorListener.addLocationToDetonator(vector.toLocation(player.getWorld()).getBlock().getLocation(), player);
});
}); });
} }
@@ -24,9 +24,9 @@ import de.steamwar.bausystem.region.Point;
import de.steamwar.bausystem.region.Region; import de.steamwar.bausystem.region.Region;
import de.steamwar.bausystem.utils.bossbar.BauSystemBossbar; import de.steamwar.bausystem.utils.bossbar.BauSystemBossbar;
import de.steamwar.bausystem.utils.bossbar.BossBarService; import de.steamwar.bausystem.utils.bossbar.BossBarService;
import de.steamwar.entity.RBlockDisplay;
import de.steamwar.entity.REntity; import de.steamwar.entity.REntity;
import de.steamwar.entity.REntityServer; import de.steamwar.entity.REntityServer;
import de.steamwar.entity.RFallingBlockEntity;
import org.bukkit.Bukkit; import org.bukkit.Bukkit;
import org.bukkit.Material; import org.bukkit.Material;
import org.bukkit.World; import org.bukkit.World;
@@ -290,8 +290,8 @@ public class KillcheckerVisualizer {
} }
rEntities.get(point).die(); rEntities.get(point).die();
} }
RFallingBlockEntity entity = new RFallingBlockEntity(outlinePoints.contains(point) ? outline : inner, point.toLocation(WORLD, 0.5, 0, 0.5), MATERIALS[Math.min(count - 1, MATERIALS.length) - 1]); RBlockDisplay entity = new RBlockDisplay(outlinePoints.contains(point) ? outline : inner, point.toLocation(WORLD, 0.5, 0, 0.5));
entity.setNoGravity(true); entity.setBlock(MATERIALS[Math.min(count - 1, MATERIALS.length) - 1].createBlockData());
rEntities.put(point, entity); rEntities.put(point, entity);
if (outlinePoints.contains(point)) outlinePointsCache.add(point); if (outlinePoints.contains(point)) outlinePointsCache.add(point);
killCount.put(point, count); killCount.put(point, count);
@@ -20,6 +20,7 @@
package de.steamwar.bausystem.features.region; package de.steamwar.bausystem.features.region;
import de.steamwar.bausystem.BauSystem; import de.steamwar.bausystem.BauSystem;
import de.steamwar.bausystem.Permission;
import de.steamwar.bausystem.config.BauServer; import de.steamwar.bausystem.config.BauServer;
import de.steamwar.bausystem.region.Region; import de.steamwar.bausystem.region.Region;
import de.steamwar.bausystem.region.RegionUtils; import de.steamwar.bausystem.region.RegionUtils;
@@ -83,6 +84,9 @@ public class ColorCommand extends SWCommand {
@ClassValidator(value = Player.class, local = true) @ClassValidator(value = Player.class, local = true)
public TypeValidator<Player> validator() { public TypeValidator<Player> validator() {
return (commandSender, player, messageSender) -> { return (commandSender, player, messageSender) -> {
if (bauServer.isTeamWorld()) {
return !messageSender.send(!Permission.SUPERVISOR.hasPermission(player), "NO_PERMISSION");
}
return !messageSender.send(!bauServer.getOwner().equals(player.getUniqueId()), "NO_PERMISSION"); return !messageSender.send(!bauServer.getOwner().equals(player.getUniqueId()), "NO_PERMISSION");
}; };
} }
@@ -68,7 +68,7 @@ public class ResetCommand extends SWCommand {
Region region = regionCheck(p); Region region = regionCheck(p);
if (region == null) return; if (region == null) return;
if (!p.getUniqueId().equals(bauServer.getOwner())) { if (bauServer.hasOwner() && !p.getUniqueId().equals(bauServer.getOwner())) {
if (Punishment.isPunished(SteamwarUser.get(bauServer.getOwner()), Punishment.PunishmentType.NoSchemReceiving, punishment -> BauSystem.MESSAGE.send("REGION_TB_NO_SCHEMRECEIVING", p, punishment.getEndTime()))) { if (Punishment.isPunished(SteamwarUser.get(bauServer.getOwner()), Punishment.PunishmentType.NoSchemReceiving, punishment -> BauSystem.MESSAGE.send("REGION_TB_NO_SCHEMRECEIVING", p, punishment.getEndTime()))) {
return; return;
} }
@@ -91,7 +91,7 @@ public class TestblockCommand extends SWCommand {
} }
} }
if (!p.getUniqueId().equals(bauServer.getOwner())) { if (bauServer.hasOwner() && !p.getUniqueId().equals(bauServer.getOwner())) {
if (Punishment.isPunished(SteamwarUser.get(bauServer.getOwner()), Punishment.PunishmentType.NoSchemReceiving, punishment -> BauSystem.MESSAGE.send("REGION_TB_NO_SCHEMRECEIVING", p, punishment.getEndTime()))) { if (Punishment.isPunished(SteamwarUser.get(bauServer.getOwner()), Punishment.PunishmentType.NoSchemReceiving, punishment -> BauSystem.MESSAGE.send("REGION_TB_NO_SCHEMRECEIVING", p, punishment.getEndTime()))) {
return; return;
} }
@@ -80,7 +80,7 @@ public class SimulatorCursor implements Listener {
} }
public SimulatorCursor() { public SimulatorCursor() {
BiFunction<Player, Object, Object> function = (player, object) -> { BiFunction<Player, ServerboundMovePlayerPacket, Object> function = (player, object) -> {
calcCursor(player); calcCursor(player);
return object; return object;
}; };
@@ -21,7 +21,6 @@ package de.steamwar.bausystem.features.slaves.laufbau;
import com.sk89q.worldedit.blocks.SkullBlock; import com.sk89q.worldedit.blocks.SkullBlock;
import com.sk89q.worldedit.world.block.BaseBlock; import com.sk89q.worldedit.world.block.BaseBlock;
import de.steamwar.bausystem.utils.NMSWrapper;
import de.steamwar.inventory.SWItem; import de.steamwar.inventory.SWItem;
import lombok.Getter; import lombok.Getter;
import lombok.ToString; import lombok.ToString;
@@ -99,7 +98,7 @@ public class BlockBoundingBox {
// addPixel(Material.COBWEB.createBlockData(), 0, 0, 0, 0, 0, 0, createItem("LAUFBAU_BLOCK_COBWEB", Material.COBWEB)); // addPixel(Material.COBWEB.createBlockData(), 0, 0, 0, 0, 0, 0, createItem("LAUFBAU_BLOCK_COBWEB", Material.COBWEB));
addPixel(Material.END_STONE.createBlockData(), 0, 0, 0, 16, 16, 16, null); addPixel(Material.END_STONE.createBlockData(), 0, 0, 0, 16, 16, 16, null);
addPixel(NMSWrapper.impl.pathMaterial().createBlockData(), 0, 0, 0, 16, 15, 16, createItem("LAUFBAU_BLOCK_GRASS_PATH", NMSWrapper.impl.pathMaterial())); addPixel(Material.DIRT_PATH.createBlockData(), 0, 0, 0, 16, 15, 16, createItem("LAUFBAU_BLOCK_GRASS_PATH", Material.DIRT_PATH));
addPixel(Material.MUD.createBlockData(), 0, 0, 0, 16, 14, 16, createItem("LAUFBAU_BLOCK_SOUL_SAND", Material.SOUL_SAND)); addPixel(Material.MUD.createBlockData(), 0, 0, 0, 16, 14, 16, createItem("LAUFBAU_BLOCK_SOUL_SAND", Material.SOUL_SAND));
Cocoa cocoaNorth = (Cocoa) Material.COCOA.createBlockData(); Cocoa cocoaNorth = (Cocoa) Material.COCOA.createBlockData();
@@ -31,7 +31,6 @@ import de.steamwar.command.TypeMapper;
import de.steamwar.core.CraftbukkitWrapper; import de.steamwar.core.CraftbukkitWrapper;
import de.steamwar.linkage.Linked; import de.steamwar.linkage.Linked;
import de.steamwar.linkage.LinkedInstance; import de.steamwar.linkage.LinkedInstance;
import de.steamwar.techhider.TechHider;
import net.md_5.bungee.api.ChatMessageType; import net.md_5.bungee.api.ChatMessageType;
import org.bukkit.Bukkit; import org.bukkit.Bukkit;
import org.bukkit.command.CommandSender; import org.bukkit.command.CommandSender;
@@ -39,6 +38,7 @@ import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler; import org.bukkit.event.EventHandler;
import org.bukkit.event.Listener; import org.bukkit.event.Listener;
import org.bukkit.event.player.PlayerQuitEvent; import org.bukkit.event.player.PlayerQuitEvent;
import de.steamwar.techhider.legacy.TechHider;
import java.util.*; import java.util.*;
import java.util.stream.Collectors; import java.util.stream.Collectors;
@@ -19,13 +19,13 @@
package de.steamwar.bausystem.features.tpslimit; package de.steamwar.bausystem.features.tpslimit;
import com.destroystokyo.paper.event.server.ServerTickEndEvent;
import de.steamwar.bausystem.BauSystem; import de.steamwar.bausystem.BauSystem;
import de.steamwar.bausystem.Permission; import de.steamwar.bausystem.Permission;
import de.steamwar.bausystem.SWUtils; import de.steamwar.bausystem.SWUtils;
import de.steamwar.bausystem.linkage.BauGuiItem; import de.steamwar.bausystem.linkage.BauGuiItem;
import de.steamwar.bausystem.region.Region; import de.steamwar.bausystem.region.Region;
import de.steamwar.bausystem.utils.ScoreboardElement; import de.steamwar.bausystem.utils.ScoreboardElement;
import de.steamwar.bausystem.utils.TickEndEvent;
import de.steamwar.bausystem.utils.TickManager; import de.steamwar.bausystem.utils.TickManager;
import de.steamwar.bausystem.utils.bossbar.BauSystemBossbar; import de.steamwar.bausystem.utils.bossbar.BauSystemBossbar;
import de.steamwar.bausystem.utils.bossbar.BossBarService; import de.steamwar.bausystem.utils.bossbar.BossBarService;
@@ -72,7 +72,7 @@ public class TPSSystem implements Listener {
} }
@EventHandler @EventHandler
public void onTickEnd(TickEndEvent event) { public void onTickEnd(ServerTickEndEvent event) {
bossbar(); bossbar();
} }
@@ -25,6 +25,7 @@ import de.steamwar.bausystem.features.tracer.rendering.TraceEntity;
import de.steamwar.bausystem.features.tracer.rendering.ViewFlag; import de.steamwar.bausystem.features.tracer.rendering.ViewFlag;
import de.steamwar.bausystem.region.Region; import de.steamwar.bausystem.region.Region;
import de.steamwar.entity.REntity; import de.steamwar.entity.REntity;
import de.steamwar.entity.REntityAction;
import de.steamwar.entity.REntityServer; import de.steamwar.entity.REntityServer;
import lombok.Getter; import lombok.Getter;
import lombok.Setter; import lombok.Setter;
@@ -143,14 +144,6 @@ public class Trace {
} else { } else {
entityServer = new REntityServer(); entityServer = new REntityServer();
entityServer.addPlayer(player); entityServer.addPlayer(player);
entityServer.setCallback((p, rEntity, entityAction) -> {
if (entityAction != REntityServer.EntityAction.INTERACT) {
return;
}
if (rEntity instanceof TraceEntity) {
((TraceEntity) rEntity).printIntoChat(p);
}
});
entityServerMap.put(player, entityServer); entityServerMap.put(player, entityServer);
} }
render(getRecords(), entityServer, playerTraceShowData); render(getRecords(), entityServer, playerTraceShowData);
@@ -167,14 +160,6 @@ public class Trace {
REntityServer entityServer = entityServerMap.computeIfAbsent(player, k -> { REntityServer entityServer = entityServerMap.computeIfAbsent(player, k -> {
REntityServer newEntityServer = new REntityServer(); REntityServer newEntityServer = new REntityServer();
newEntityServer.addPlayer(k); newEntityServer.addPlayer(k);
newEntityServer.setCallback((p, rEntity, entityAction) -> {
if (entityAction != REntityServer.EntityAction.INTERACT) {
return;
}
if (rEntity instanceof TraceEntity) {
((TraceEntity) rEntity).printIntoChat(p);
}
});
return newEntityServer; return newEntityServer;
}); });
@@ -24,13 +24,18 @@ import de.steamwar.bausystem.configplayer.Config;
import de.steamwar.bausystem.features.tracer.TNTPoint; import de.steamwar.bausystem.features.tracer.TNTPoint;
import de.steamwar.bausystem.features.tracer.Trace; import de.steamwar.bausystem.features.tracer.Trace;
import de.steamwar.bausystem.features.tracer.TraceManager; import de.steamwar.bausystem.features.tracer.TraceManager;
import de.steamwar.entity.RBlockDisplay;
import de.steamwar.entity.REntityAction;
import de.steamwar.entity.REntityServer; import de.steamwar.entity.REntityServer;
import de.steamwar.entity.RFallingBlockEntity; import de.steamwar.entity.RInteraction;
import lombok.Getter; import lombok.Getter;
import net.md_5.bungee.api.chat.ClickEvent; import net.md_5.bungee.api.chat.ClickEvent;
import org.bukkit.Location; import org.bukkit.Location;
import org.bukkit.Material; import org.bukkit.Material;
import org.bukkit.entity.Player; import org.bukkit.entity.Player;
import org.bukkit.util.Transformation;
import org.joml.Quaternionf;
import org.joml.Vector3f;
import yapion.hierarchy.types.YAPIONValue; import yapion.hierarchy.types.YAPIONValue;
import java.util.List; import java.util.List;
@@ -41,7 +46,16 @@ import static de.steamwar.bausystem.features.util.TNTClickListener.TNT_CLICK_DET
/** /**
* Wrapper for the rendering of a record bundle * Wrapper for the rendering of a record bundle
*/ */
public class TraceEntity extends RFallingBlockEntity { public class TraceEntity extends RBlockDisplay {
private static final float TNT_VISUAL_SCALE = 0.98F;
private static final float TNT_VISUAL_OFFSET = -TNT_VISUAL_SCALE / 2.0F;
private static final Transformation TNT_VISUAL_TRANSFORM = new Transformation(
new Vector3f(TNT_VISUAL_OFFSET, 0.0F, TNT_VISUAL_OFFSET),
new Quaternionf(0, 0, 0, 1),
new Vector3f(TNT_VISUAL_SCALE, TNT_VISUAL_SCALE, TNT_VISUAL_SCALE),
new Quaternionf(0, 0, 0, 1)
);
/** /**
* The records represented by this REntity * The records represented by this REntity
@@ -55,13 +69,31 @@ public class TraceEntity extends RFallingBlockEntity {
private final String uniqueTntIdsString; private final String uniqueTntIdsString;
private final Trace trace; private final Trace trace;
private final RInteraction hitbox;
public TraceEntity(REntityServer server, Location location, boolean isExplosion, List<TNTPoint> records, Trace trace) { public TraceEntity(REntityServer server, Location location, boolean isExplosion, List<TNTPoint> records, Trace trace) {
super(server, location, isExplosion ? Material.RED_STAINED_GLASS : Material.TNT); super(server, location);
Material material = isExplosion ? Material.RED_STAINED_GLASS : Material.TNT;
this.records = records; this.records = records;
this.trace = trace; this.trace = trace;
uniqueTntIdsString = records.stream().map(TNTPoint::getTntId).distinct().map(Object::toString).collect(Collectors.joining(" ")); uniqueTntIdsString = records.stream().map(TNTPoint::getTntId).distinct().map(Object::toString).collect(Collectors.joining(" "));
setNoGravity(true); hitbox = new RInteraction(server, location);
hitbox.setInteractionHeight(TNT_VISUAL_SCALE);
hitbox.setInteractionWidth(TNT_VISUAL_SCALE);
hitbox.setCallback((player, action) -> {
if (action == REntityAction.INTERACT) {
printIntoChat(player);
}
});
setTransform(TNT_VISUAL_TRANSFORM);
setBlock(material.createBlockData());
}
@Override
public void die() {
hitbox.die();
super.die();
} }
/** /**
@@ -20,8 +20,8 @@
package de.steamwar.bausystem.features.tracer.rendering; package de.steamwar.bausystem.features.tracer.rendering;
import de.steamwar.bausystem.features.tracer.TNTPoint; import de.steamwar.bausystem.features.tracer.TNTPoint;
import de.steamwar.entity.RBlockDisplay;
import de.steamwar.entity.REntityServer; import de.steamwar.entity.REntityServer;
import de.steamwar.entity.RFallingBlockEntity;
import org.bukkit.Location; import org.bukkit.Location;
import org.bukkit.Material; import org.bukkit.Material;
import org.bukkit.util.Vector; import org.bukkit.util.Vector;
@@ -129,8 +129,8 @@ public abstract class ViewFlag {
Location yLocation = previous.getLocation().clone().add(0, delta.getY(), 0); Location yLocation = previous.getLocation().clone().add(0, delta.getY(), 0);
if (yLocation.distanceSquared(representative.getLocation()) >= 1.0 / 256.0 && yLocation.distanceSquared(previous.getLocation()) >= 1.0 / 256.0) { if (yLocation.distanceSquared(representative.getLocation()) >= 1.0 / 256.0 && yLocation.distanceSquared(previous.getLocation()) >= 1.0 / 256.0) {
RFallingBlockEntity y = new RFallingBlockEntity(server, yLocation, Material.WHITE_STAINED_GLASS); RBlockDisplay y = new RBlockDisplay(server, yLocation);
y.setNoGravity(true); y.setBlock(Material.WHITE_STAINED_GLASS.createBlockData());
} }
Location secoundLocation; Location secoundLocation;
@@ -141,8 +141,8 @@ public abstract class ViewFlag {
} }
if (secoundLocation.distanceSquared(representative.getLocation()) >= 1.0 / 256.0 && secoundLocation.distanceSquared(previous.getLocation()) >= 1.0 / 256.0) { if (secoundLocation.distanceSquared(representative.getLocation()) >= 1.0 / 256.0 && secoundLocation.distanceSquared(previous.getLocation()) >= 1.0 / 256.0) {
RFallingBlockEntity second = new RFallingBlockEntity(server, secoundLocation, Material.WHITE_STAINED_GLASS); RBlockDisplay second = new RBlockDisplay(server, secoundLocation);
second.setNoGravity(true); second.setBlock(Material.WHITE_STAINED_GLASS.createBlockData());
} }
} }
} }
@@ -21,17 +21,22 @@ package de.steamwar.bausystem.features.util;
import com.comphenix.tinyprotocol.TinyProtocol; import com.comphenix.tinyprotocol.TinyProtocol;
import com.mojang.authlib.GameProfile; import com.mojang.authlib.GameProfile;
import de.steamwar.Reflection;
import de.steamwar.bausystem.BauSystem; import de.steamwar.bausystem.BauSystem;
import de.steamwar.bausystem.features.tpslimit.TPSUtils; import de.steamwar.bausystem.features.tpslimit.TPSUtils;
import de.steamwar.bausystem.utils.BauMemberUpdateEvent; import de.steamwar.bausystem.utils.BauMemberUpdateEvent;
import de.steamwar.bausystem.utils.NMSWrapper;
import de.steamwar.command.SWCommand; import de.steamwar.command.SWCommand;
import de.steamwar.core.ProtocolWrapper; import de.steamwar.core.ProtocolWrapper;
import de.steamwar.core.SWPlayer; import de.steamwar.core.SWPlayer;
import de.steamwar.linkage.Linked; import de.steamwar.linkage.Linked;
import net.minecraft.network.protocol.game.*; 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; import org.bukkit.Bukkit;
import org.bukkit.GameMode; import org.bukkit.GameMode;
import org.bukkit.craftbukkit.entity.CraftPlayer;
import org.bukkit.craftbukkit.inventory.CraftItemStack;
import org.bukkit.entity.Player; import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler; import org.bukkit.event.EventHandler;
import org.bukkit.event.Listener; import org.bukkit.event.Listener;
@@ -57,11 +62,11 @@ public class NoClipCommand extends SWCommand implements Listener {
public NoClipCommand() { public NoClipCommand() {
super("noclip", "nc"); super("noclip", "nc");
BiFunction<Player, Object, Object> first = (player, o) -> { BiFunction<Player, ServerboundMovePlayerPacket, Object> first = (player, o) -> {
NoClipData noClipData = SWPlayer.of(player).getComponent(NoClipData.class).orElse(null); NoClipData noClipData = SWPlayer.of(player).getComponent(NoClipData.class).orElse(null);
if (noClipData == null) return o; if (noClipData == null) return o;
if (noClipData.lastTick == TPSUtils.currentTick.get()) return o; if (noClipData.lastTick == TPSUtils.currentTick.get()) return o;
NMSWrapper.impl.setInternalGameMode(player, GameMode.SPECTATOR); setInternalGameMode(player, GameMode.SPECTATOR);
noClipData.lastTick = TPSUtils.currentTick.get(); noClipData.lastTick = TPSUtils.currentTick.get();
return o; return o;
}; };
@@ -71,7 +76,7 @@ public class NoClipCommand extends SWCommand implements Listener {
BiFunction<Player, Object, Object> second = (player, o) -> { BiFunction<Player, Object, Object> second = (player, o) -> {
NoClipData noClipData = SWPlayer.of(player).getComponent(NoClipData.class).orElse(null); NoClipData noClipData = SWPlayer.of(player).getComponent(NoClipData.class).orElse(null);
if (noClipData == null) return o; if (noClipData == null) return o;
NMSWrapper.impl.setInternalGameMode(player, GameMode.CREATIVE); setInternalGameMode(player, GameMode.CREATIVE);
noClipData.lastTick = TPSUtils.currentTick.get(); noClipData.lastTick = TPSUtils.currentTick.get();
return o; return o;
}; };
@@ -79,15 +84,31 @@ public class NoClipCommand extends SWCommand implements Listener {
TinyProtocol.instance.addFilter(ServerboundPlayerActionPacket.class, second); TinyProtocol.instance.addFilter(ServerboundPlayerActionPacket.class, second);
TinyProtocol.instance.addFilter(ServerboundContainerClickPacket.class, second); TinyProtocol.instance.addFilter(ServerboundContainerClickPacket.class, second);
BiFunction<Player, Object, Object> third = (player, o) -> { BiFunction<Player, ServerboundSetCreativeModeSlotPacket, Object> third = (player, o) -> {
if (SWPlayer.of(player).hasComponent(NoClipData.class)) { if (SWPlayer.of(player).hasComponent(NoClipData.class)) {
NMSWrapper.impl.setSlotToItemStack(player, o); int index = o.slotNum();
if (index >= 36 && index <= 44) {
index -= 36;
} else if (index > 44) {
index -= 5;
} else if (index <= 8) {
index = index - 8 + 36;
}
player.getInventory().setItem(index, CraftItemStack.asBukkitCopy(o.itemStack()));
if (index < 9) player.getInventory().setHeldItemSlot(index);
player.updateInventory();
} }
return o; return o;
}; };
TinyProtocol.instance.addFilter(ServerboundSetCreativeModeSlotPacket.class, third); TinyProtocol.instance.addFilter(ServerboundSetCreativeModeSlotPacket.class, third);
} }
private static final Reflection.Field<GameType> 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()));
}
@Register(help = true) @Register(help = true)
public void genericCommand(@Validator Player player) { public void genericCommand(@Validator Player player) {
SWPlayer swPlayer = SWPlayer.of(player); SWPlayer swPlayer = SWPlayer.of(player);
@@ -95,7 +116,9 @@ public class NoClipCommand extends SWCommand implements Listener {
swPlayer.removeComponent(NoClipData.class); swPlayer.removeComponent(NoClipData.class);
} else { } else {
player.setGameMode(GameMode.SPECTATOR); player.setGameMode(GameMode.SPECTATOR);
NMSWrapper.impl.setPlayerBuildAbilities(player); Abilities abilities = (((CraftPlayer) player).getHandle()).getAbilities();
abilities.mayBuild = true;
abilities.mayfly = true;
swPlayer.setComponent(new NoClipData()); swPlayer.setComponent(new NoClipData());
BauSystem.MESSAGE.send("OTHER_NOCLIP_SLOT_INFO", player); BauSystem.MESSAGE.send("OTHER_NOCLIP_SLOT_INFO", player);
@@ -20,6 +20,7 @@
package de.steamwar.bausystem.features.world; package de.steamwar.bausystem.features.world;
import de.steamwar.bausystem.BauSystem; import de.steamwar.bausystem.BauSystem;
import de.steamwar.bausystem.Permission;
import de.steamwar.bausystem.config.BauServer; import de.steamwar.bausystem.config.BauServer;
import de.steamwar.core.CRIUWakeupEvent; import de.steamwar.core.CRIUWakeupEvent;
import de.steamwar.linkage.Linked; import de.steamwar.linkage.Linked;
@@ -36,12 +37,13 @@ public class AntiBauAddMemberFix implements Listener {
@EventHandler(priority = EventPriority.LOW) @EventHandler(priority = EventPriority.LOW)
public void onPlayerJoin(PlayerJoinEvent event) { public void onPlayerJoin(PlayerJoinEvent event) {
if (BauSystem.DEV_SERVER) return; if (BauSystem.DEV_SERVER) return;
if (event.getPlayer().getUniqueId().equals(BauServer.getInstance().getOwner())) { if (Permission.SUPERVISOR.hasPermission(event.getPlayer())) {
return; return;
} }
if (BauweltMember.getBauMember(BauServer.getInstance().getOwner(), event.getPlayer().getUniqueId()) == null) { if (BauweltMember.getBauMember(BauServer.getInstance().getWorldID(), event.getPlayer().getUniqueId()) == null) {
event.getPlayer().kickPlayer(""); event.getPlayer().kickPlayer("");
throw new SecurityException("The player " + event.getPlayer().getName() + " joined on the server of " + SteamwarUser.byId(BauServer.getInstance().getOwnerID()).getUserName() + " without being added!"); String owner = BauServer.getInstance().isTeamWorld() ? "team " + BauServer.getInstance().getTeamID() : SteamwarUser.byId(BauServer.getInstance().getOwnerID()).getUserName();
throw new SecurityException("The player " + event.getPlayer().getName() + " joined on the server of " + owner + " without being added!");
} }
} }
@@ -33,7 +33,7 @@ public class AxiomPermissionCheck implements Listener {
@EventHandler @EventHandler
public void onAxiomHandshake(AxiomHandshakeEvent event) { public void onAxiomHandshake(AxiomHandshakeEvent event) {
if (Permission.SUPERVISOR.hasPermission(event.getPlayer()) || BauServer.getInstance().getOwner().equals(event.getPlayer().getUniqueId())) { if (Permission.SUPERVISOR.hasPermission(event.getPlayer()) || (BauServer.getInstance().hasOwner() && BauServer.getInstance().getOwner().equals(event.getPlayer().getUniqueId()))) {
event.setMaxBufferSize(Short.MAX_VALUE); event.setMaxBufferSize(Short.MAX_VALUE);
return; return;
} }
@@ -28,7 +28,7 @@ import de.steamwar.data.BauLockState;
import de.steamwar.linkage.Linked; import de.steamwar.linkage.Linked;
import de.steamwar.network.packets.PacketHandler; import de.steamwar.network.packets.PacketHandler;
import de.steamwar.network.packets.server.BaulockUpdatePacket; import de.steamwar.network.packets.server.BaulockUpdatePacket;
import de.steamwar.sql.UserConfig; import de.steamwar.sql.SteamwarWorld;
import lombok.Getter; import lombok.Getter;
import org.bukkit.entity.Player; import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler; import org.bukkit.event.EventHandler;
@@ -37,13 +37,13 @@ import org.bukkit.event.Listener;
@Linked @Linked
public class BauLockStateScoreboard extends PacketHandler implements ScoreboardElement, Listener { public class BauLockStateScoreboard extends PacketHandler implements ScoreboardElement, Listener {
private static final String BAU_LOCK_CONFIG_NAME = "baulockstate";
@Getter @Getter
private BauLockState lockState = loadLockState(); private BauLockState lockState = loadLockState();
private BauLockState loadLockState() { private BauLockState loadLockState() {
String state = UserConfig.getConfig(BauServer.getInstance().getOwner(), BAU_LOCK_CONFIG_NAME); if (BauServer.getInstance().isTeamWorld()) return BauLockState.OPEN;
SteamwarWorld world = SteamwarWorld.getWorld(BauServer.getInstance().getWorldID());
String state = world == null ? null : world.getLockState();
return state == null ? BauLockState.OPEN : BauLockState.valueOf(state); return state == null ? BauLockState.OPEN : BauLockState.valueOf(state);
} }
@@ -69,6 +69,7 @@ public class BauLockStateScoreboard extends PacketHandler implements ScoreboardE
@Override @Override
public String get(Region region, Player p) { public String get(Region region, Player p) {
if (BauServer.getInstance().isTeamWorld()) return null;
if (!BauServer.getInstance().getOwner().equals(p.getUniqueId())) { if (!BauServer.getInstance().getOwner().equals(p.getUniqueId())) {
return null; return null;
} }
@@ -28,6 +28,7 @@ import de.steamwar.bausystem.utils.bossbar.BossBarService;
import de.steamwar.linkage.Linked; import de.steamwar.linkage.Linked;
import de.steamwar.network.packets.PacketHandler; import de.steamwar.network.packets.PacketHandler;
import de.steamwar.network.packets.server.BaumemberUpdatePacket; import de.steamwar.network.packets.server.BaumemberUpdatePacket;
import io.papermc.paper.event.player.PlayerPickBlockEvent;
import org.bukkit.Bukkit; import org.bukkit.Bukkit;
import org.bukkit.boss.BarColor; import org.bukkit.boss.BarColor;
import org.bukkit.boss.BarStyle; import org.bukkit.boss.BarStyle;
@@ -35,6 +36,8 @@ import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler; import org.bukkit.event.EventHandler;
import org.bukkit.event.Listener; import org.bukkit.event.Listener;
import org.bukkit.event.entity.PlayerDeathEvent; 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.PlayerItemConsumeEvent;
import org.bukkit.event.player.PlayerJoinEvent; import org.bukkit.event.player.PlayerJoinEvent;
import org.bukkit.event.player.PlayerQuitEvent; import org.bukkit.event.player.PlayerQuitEvent;
@@ -127,4 +130,11 @@ public class BauMemberUpdate extends PacketHandler implements Listener {
} }
}, 1); }, 1);
} }
@EventHandler
public void onPlayerClick(PlayerPickBlockEvent event) {
if(SPECTATORS.contains(event.getPlayer())) {
event.setCancelled(true);
}
}
} }
@@ -19,8 +19,9 @@
package de.steamwar.bausystem.features.world; package de.steamwar.bausystem.features.world;
import de.steamwar.bausystem.utils.NMSWrapper;
import de.steamwar.linkage.Linked; import de.steamwar.linkage.Linked;
import io.papermc.paper.datacomponent.DataComponentTypes;
import io.papermc.paper.datacomponent.item.ItemContainerContents;
import org.bukkit.attribute.Attribute; import org.bukkit.attribute.Attribute;
import org.bukkit.enchantments.Enchantment; import org.bukkit.enchantments.Enchantment;
import org.bukkit.entity.Player; import org.bukkit.entity.Player;
@@ -32,6 +33,8 @@ import org.bukkit.event.player.PlayerJoinEvent;
import org.bukkit.inventory.ItemStack; import org.bukkit.inventory.ItemStack;
import org.bukkit.inventory.meta.ItemMeta; import org.bukkit.inventory.meta.ItemMeta;
import java.util.List;
@Linked @Linked
public class InventoryListener implements Listener { public class InventoryListener implements Listener {
@@ -57,7 +60,7 @@ public class InventoryListener implements Listener {
} }
stack.setItemMeta(meta); stack.setItemMeta(meta);
} }
if (NMSWrapper.impl.checkItemStack(stack)) { if (checkItemStack(stack)) {
e.setCurrentItem(null); e.setCurrentItem(null);
e.setCancelled(true); e.setCancelled(true);
return; return;
@@ -73,7 +76,7 @@ public class InventoryListener implements Listener {
for (int i = 0; i < content.length; i++) { for (int i = 0; i < content.length; i++) {
if (content[i] == null) continue; if (content[i] == null) continue;
int finalI = i; int finalI = i;
if (NMSWrapper.impl.checkItemStack(content[finalI])) { if (checkItemStack(content[finalI])) {
p.getInventory().setItem(i, null); p.getInventory().setItem(i, null);
} }
} }
@@ -82,11 +85,44 @@ public class InventoryListener implements Listener {
@EventHandler(ignoreCancelled = true) @EventHandler(ignoreCancelled = true)
public void onBlockPlace(BlockPlaceEvent event) { public void onBlockPlace(BlockPlaceEvent event) {
Player p = event.getPlayer(); Player p = event.getPlayer();
if (NMSWrapper.impl.checkItemStack(event.getItemInHand())) { if (checkItemStack(event.getItemInHand())) {
event.setCancelled(true); event.setCancelled(true);
event.setBuild(false); event.setBuild(false);
p.getInventory().setItemInMainHand(null); p.getInventory().setItemInMainHand(null);
p.getInventory().setItemInOffHand(null); p.getInventory().setItemInOffHand(null);
} }
} }
private static final int threshold = 2048;
private boolean checkItemStack(ItemStack item) {
ItemContainerContents data = item.getData(DataComponentTypes.CONTAINER);
if (data == null) {
return false;
}
return drillDown(data.contents(), 0, 0) > threshold;
}
private int drillDown(List<ItemStack> items, int layer, int start) {
if (layer > 2) return start + threshold;
int invalid = start;
for (int i = start; i < items.size(); i++) {
ItemStack item = items.get(i);
if (item.isEmpty()) continue;
invalid += item.getAmount();
ItemContainerContents data = item.getData(DataComponentTypes.CONTAINER);
if (data == null) {
continue;
}
List<ItemStack> subItems = data.contents();
if (subItems.size() > 1) {
invalid = drillDown(subItems, layer + 1, invalid);
}
}
return invalid;
}
} }
@@ -42,7 +42,11 @@ public class KickallCommand extends SWCommand {
if (!Permission.OWNER.hasPermission(player)) return; if (!Permission.OWNER.hasPermission(player)) return;
Bukkit.getOnlinePlayers().forEach(p -> { Bukkit.getOnlinePlayers().forEach(p -> {
if (!bauServer.getOwner().equals(p.getUniqueId())) p.kickPlayer(""); if (bauServer.isTeamWorld()) {
if (!Permission.SUPERVISOR.hasPermission(p)) p.kickPlayer("");
} else if (!bauServer.getOwner().equals(p.getUniqueId())) {
p.kickPlayer("");
}
}); });
} }
} }
@@ -20,18 +20,24 @@
package de.steamwar.bausystem.features.world; package de.steamwar.bausystem.features.world;
import com.comphenix.tinyprotocol.TinyProtocol; import com.comphenix.tinyprotocol.TinyProtocol;
import de.steamwar.bausystem.utils.NMSWrapper;
import de.steamwar.linkage.Linked; import de.steamwar.linkage.Linked;
import net.minecraft.network.protocol.game.ClientboundExplodePacket; import net.minecraft.network.protocol.game.ClientboundExplodePacket;
import org.bukkit.GameMode; import org.bukkit.GameMode;
import java.util.Optional;
@Linked @Linked
public class NoCreativeKnockback { public class NoCreativeKnockback {
public NoCreativeKnockback() { public NoCreativeKnockback() {
TinyProtocol.instance.addFilter(ClientboundExplodePacket.class, (player, o) -> { TinyProtocol.instance.addFilter(ClientboundExplodePacket.class, (player, o) -> {
if (player.getGameMode() != GameMode.CREATIVE) return o; if (player.getGameMode() != GameMode.CREATIVE) return o;
return NMSWrapper.impl.resetExplosionKnockback(o); return new ClientboundExplodePacket(
o.center(),
Optional.empty(),
o.explosionParticle(),
o.explosionSound()
);
}); });
} }
} }
@@ -101,7 +101,7 @@ public class SignEdit implements Listener {
} }
{ {
TinyProtocol.instance.addTypedFilter(ServerboundSignUpdatePacket.class, (player, o) -> { TinyProtocol.instance.addFilter(ServerboundSignUpdatePacket.class, (player, o) -> {
Bukkit.getScheduler().runTask(BauSystem.getInstance(), () -> { Bukkit.getScheduler().runTask(BauSystem.getInstance(), () -> {
ServerLevel serverLevel = ((CraftWorld) player.getWorld()).getHandle(); ServerLevel serverLevel = ((CraftWorld) player.getWorld()).getHandle();
Block signLoc = CraftBlock.at(serverLevel, o.getPos()); Block signLoc = CraftBlock.at(serverLevel, o.getPos());
@@ -25,7 +25,6 @@ import de.steamwar.bausystem.config.BauServer;
import de.steamwar.bausystem.utils.BauMemberUpdateEvent; import de.steamwar.bausystem.utils.BauMemberUpdateEvent;
import de.steamwar.linkage.Linked; import de.steamwar.linkage.Linked;
import de.steamwar.sql.BauweltMember; import de.steamwar.sql.BauweltMember;
import de.steamwar.techhider.TechHider;
import org.bukkit.Bukkit; import org.bukkit.Bukkit;
import org.bukkit.Location; import org.bukkit.Location;
import org.bukkit.Material; import org.bukkit.Material;
@@ -40,6 +39,7 @@ import org.bukkit.event.block.BlockPlaceEvent;
import org.bukkit.event.entity.EntityPickupItemEvent; import org.bukkit.event.entity.EntityPickupItemEvent;
import org.bukkit.event.player.*; import org.bukkit.event.player.*;
import org.bukkit.util.Vector; import org.bukkit.util.Vector;
import de.steamwar.techhider.legacy.TechHider;
import java.util.HashSet; import java.util.HashSet;
import java.util.Set; import java.util.Set;
@@ -143,17 +143,14 @@ public class SpectatorListener implements Listener {
public void onPlayerJoin(PlayerJoinEvent event) { public void onPlayerJoin(PlayerJoinEvent event) {
enableOrDisableTechhider(); enableOrDisableTechhider();
if (BauSystem.DEV_SERVER) return; if (BauSystem.DEV_SERVER) return;
if (event.getPlayer().getUniqueId().equals(BauServer.getInstance().getOwner())) { if (Permission.SUPERVISOR.hasPermission(event.getPlayer())) {
return; return;
} }
BauweltMember bauweltMember = BauweltMember.getBauMember(BauServer.getInstance().getOwner(), event.getPlayer().getUniqueId()); BauweltMember bauweltMember = BauweltMember.getBauMember(BauServer.getInstance().getWorldID(), event.getPlayer().getUniqueId());
if (bauweltMember == null) { if (bauweltMember == null) {
event.getPlayer().kickPlayer(""); event.getPlayer().kickPlayer("");
return; return;
} }
if (Permission.SUPERVISOR.hasPermission(event.getPlayer())) {
return;
}
if (!anySupervisorOnline(null)) { if (!anySupervisorOnline(null)) {
Bukkit.getScheduler().runTaskLater(BauSystem.getInstance(), () -> { Bukkit.getScheduler().runTaskLater(BauSystem.getInstance(), () -> {
event.getPlayer().kickPlayer(""); event.getPlayer().kickPlayer("");
@@ -28,7 +28,6 @@ import de.steamwar.command.SWCommand;
import de.steamwar.core.CraftbukkitWrapper; import de.steamwar.core.CraftbukkitWrapper;
import de.steamwar.linkage.Linked; import de.steamwar.linkage.Linked;
import de.steamwar.linkage.LinkedInstance; import de.steamwar.linkage.LinkedInstance;
import de.steamwar.techhider.TechHider;
import net.md_5.bungee.api.ChatMessageType; import net.md_5.bungee.api.ChatMessageType;
import net.minecraft.network.protocol.game.ServerboundMovePlayerPacket; import net.minecraft.network.protocol.game.ServerboundMovePlayerPacket;
import net.minecraft.server.level.ServerPlayer; import net.minecraft.server.level.ServerPlayer;
@@ -41,6 +40,7 @@ import org.bukkit.event.Listener;
import org.bukkit.event.block.Action; import org.bukkit.event.block.Action;
import org.bukkit.event.player.PlayerInteractEvent; import org.bukkit.event.player.PlayerInteractEvent;
import org.bukkit.event.player.PlayerQuitEvent; import org.bukkit.event.player.PlayerQuitEvent;
import de.steamwar.techhider.legacy.TechHider;
import java.util.*; import java.util.*;
import java.util.function.BiFunction; import java.util.function.BiFunction;
@@ -115,8 +115,8 @@ public class XrayCommand extends SWCommand implements Listener, ScoreboardElemen
return packet; return packet;
}; };
TinyProtocol.instance.addTypedFilter(ServerboundMovePlayerPacket.Pos.class, positionSetter); TinyProtocol.instance.addFilter(ServerboundMovePlayerPacket.Pos.class, positionSetter);
TinyProtocol.instance.addTypedFilter(ServerboundMovePlayerPacket.PosRot.class, positionSetter); TinyProtocol.instance.addFilter(ServerboundMovePlayerPacket.PosRot.class, positionSetter);
} }
@EventHandler @EventHandler
@@ -1,117 +0,0 @@
/*
* This file is a part of the SteamWar software.
*
* Copyright (C) 2025 SteamWar.de-Serverteam
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package de.steamwar.bausystem.utils;
import de.steamwar.Reflection;
import io.papermc.paper.datacomponent.DataComponentTypes;
import io.papermc.paper.datacomponent.item.ItemContainerContents;
import net.minecraft.network.protocol.game.ClientboundContainerSetSlotPacket;
import net.minecraft.network.protocol.game.ClientboundExplodePacket;
import net.minecraft.server.level.ServerPlayerGameMode;
import net.minecraft.world.entity.player.Abilities;
import net.minecraft.world.level.GameType;
import org.bukkit.GameMode;
import org.bukkit.Material;
import org.bukkit.craftbukkit.entity.CraftPlayer;
import org.bukkit.craftbukkit.inventory.CraftItemStack;
import org.bukkit.entity.Player;
import org.bukkit.inventory.ItemStack;
import java.util.List;
import java.util.Optional;
public class NMSWrapper {
public static final NMSWrapper impl = new NMSWrapper();
private static final Reflection.Field<GameType> playerGameMode = Reflection.getField(ServerPlayerGameMode.class, GameType.class, 0);
public void setInternalGameMode(Player player, GameMode gameMode) {
playerGameMode.set(((CraftPlayer) player).getHandle().gameMode, GameType.byId(gameMode.getValue()));
}
public void setSlotToItemStack(Player player, Object o) {
ClientboundContainerSetSlotPacket packetPlayInSetCreativeSlot = (ClientboundContainerSetSlotPacket) o;
int index = packetPlayInSetCreativeSlot.getSlot();
if (index >= 36 && index <= 44) {
index -= 36;
} else if (index > 44) {
index -= 5;
} else if (index <= 8) {
index = index - 8 + 36;
}
player.getInventory().setItem(index, CraftItemStack.asBukkitCopy(packetPlayInSetCreativeSlot.getItem()));
if (index < 9) player.getInventory().setHeldItemSlot(index);
player.updateInventory();
}
public void setPlayerBuildAbilities(Player player) {
Abilities abilities = (((CraftPlayer) player).getHandle()).getAbilities();
abilities.mayBuild = true;
abilities.mayfly = true;
}
public Material pathMaterial() {
return Material.DIRT_PATH;
}
private static final int threshold = 2048;
public boolean checkItemStack(ItemStack item) {
ItemContainerContents data = item.getData(DataComponentTypes.CONTAINER);
if (data == null) {
return false;
}
return drillDown(data.contents(), 0, 0) > threshold;
}
private int drillDown(List<ItemStack> items, int layer, int start) {
if (layer > 2) return start + threshold;
int invalid = start;
for (int i = start; i < items.size(); i++) {
ItemStack item = items.get(i);
if (item.isEmpty()) continue;
invalid += item.getAmount();
ItemContainerContents data = item.getData(DataComponentTypes.CONTAINER);
if (data == null) {
continue;
}
List<ItemStack> subItems = data.contents();
if (subItems.size() > 1) {
invalid = drillDown(subItems, layer + 1, invalid);
}
}
return invalid;
}
public Object resetExplosionKnockback(Object packet) {
ClientboundExplodePacket explosion = (ClientboundExplodePacket) packet;
return new ClientboundExplodePacket(
explosion.center(),
Optional.empty(),
explosion.explosionParticle(),
explosion.explosionSound()
);
}
}
@@ -42,7 +42,7 @@ public class TickManager implements Listener {
TinyProtocol.instance.addFilter(ClientboundTickingStatePacket.class, this::blockPacket); TinyProtocol.instance.addFilter(ClientboundTickingStatePacket.class, this::blockPacket);
} }
private Object blockPacket(Player player, Object packet) { private Object blockPacket(Player player, ClientboundTickingStatePacket packet) {
if (blockTpsPacket) { if (blockTpsPacket) {
return new ClientboundTickingStatePacket(20, manager.isFrozen()); return new ClientboundTickingStatePacket(20, manager.isFrozen());
} else { } else {
-9
View File
@@ -31,15 +31,6 @@ dependencies {
implementation(project(":BauSystem:BauSystem_Main")) implementation(project(":BauSystem:BauSystem_Main"))
} }
tasks.register<DevServer>("DevBau20") {
group = "run"
description = "Run a 1.20 Dev Bau"
dependsOn(":SpigotCore:shadowJar")
dependsOn(":BauSystem:shadowJar")
dependsOn(":SchematicSystem:shadowJar")
template = "Bau20"
}
tasks.register<DevServer>("DevBau21") { tasks.register<DevServer>("DevBau21") {
group = "run" group = "run"
description = "Run a 1.21 Dev Bau" description = "Run a 1.21 Dev Bau"
+11 -1
View File
@@ -11,13 +11,23 @@ import de.steamwar.commands.profiler.ProfilerCommand
import de.steamwar.commands.user.UserCommand import de.steamwar.commands.user.UserCommand
import de.steamwar.commands.user.UserInfoCommand import de.steamwar.commands.user.UserInfoCommand
import de.steamwar.commands.user.UserSearchCommand import de.steamwar.commands.user.UserSearchCommand
import de.steamwar.commands.world.MigrationCommand
import de.steamwar.commands.world.SaveToStorageCommand
import de.steamwar.commands.world.TemplateCommand
import de.steamwar.commands.world.TemplateCreateCommand
import de.steamwar.commands.world.WorldCommand
fun main(args: Array<String>) = fun main(args: Array<String>) =
SteamWar() SteamWar()
.subcommands( .subcommands(
DatabaseCommand().subcommands(InfoCommand(), ResetCommand()), DatabaseCommand().subcommands(InfoCommand(), ResetCommand()),
UserCommand().subcommands(UserInfoCommand(), UserSearchCommand()), UserCommand().subcommands(UserInfoCommand(), UserSearchCommand()),
WorldCommand().subcommands(
MigrationCommand(),
SaveToStorageCommand(),
TemplateCommand().subcommands(TemplateCreateCommand())
),
DevCommand(), DevCommand(),
ProfilerCommand() ProfilerCommand()
) )
.main(args) .main(args)
+243
View File
@@ -0,0 +1,243 @@
package de.steamwar.commands.world
import com.github.ajalt.clikt.core.CliktCommand
import de.steamwar.db.Database
import de.steamwar.sql.SteamwarUser
import de.steamwar.sql.SteamwarWorld
import de.steamwar.sql.UserConfig
import org.jetbrains.exposed.v1.jdbc.transactions.transaction
import java.io.File
import java.sql.SQLException
import java.util.UUID
class MigrationCommand : CliktCommand(name = "migrate") {
private val userWorldPattern = Regex("""userworlds(\d+)""")
private val builderWorldPattern = Regex("""builder(\d+)""")
private val trailingVersionPattern = Regex("""(\d+)$""")
override fun run() {
Database.ensureConnected()
val bau = migrateBauWorlds()
val builder = migrateBuilderWorlds()
val arena = migrateArenaWorlds()
migrateBauLockConfig()
migrateBauweltMembers()
echo("Imported ${bau.imported} build worlds, skipped ${bau.skipped}, missing users ${bau.missingOwner}.")
echo("Imported ${builder.imported} builder worlds, skipped ${builder.skipped}.")
echo("Imported ${arena.imported} arena worlds, skipped ${arena.skipped}.")
echo("World migration completed.")
}
private fun migrateBauWorlds(): MigrationStats {
val stats = MigrationStats()
val worldsRoot = File("/worlds")
worldsRoot.listFiles { file -> file.isDirectory && userWorldPattern.matches(file.name) }
?.forEach { versionFolder ->
val version = userWorldPattern.matchEntire(versionFolder.name)!!.groupValues[1].toInt()
versionFolder.listFiles { file -> file.isDirectory && isWorldDirectory(file) }
?.sortedBy { it.name }
?.forEach { legacyWorld ->
val owner = legacyWorld.ownerUser()
if (owner == null) {
stats.missingOwner++
echo("Skipped build world ${legacyWorld.path}: no matching user.")
return@forEach
}
val lockState = UserConfig.getConfig(owner.id.value, BAU_LOCK_CONFIG_NAME)
val world = SteamwarWorld.getOrCreateBauWorld(owner, owner.userName, version, legacyWorld)
if (world.lockState == null && lockState != null) {
world.changeLockState(lockState)
}
stats.imported++
}
} ?: run {
stats.skipped++
echo("Skipped build worlds: /worlds does not exist.")
}
return stats
}
private fun migrateBuilderWorlds(): MigrationStats {
val stats = MigrationStats()
val worldsRoot = File("/worlds")
worldsRoot.listFiles { file -> file.isDirectory && builderWorldPattern.matches(file.name) }
?.forEach { versionFolder ->
val version = builderWorldPattern.matchEntire(versionFolder.name)!!.groupValues[1].toInt()
versionFolder.listFiles { file -> file.isDirectory && isWorldDirectory(file) }
?.sortedBy { it.name }
?.forEach { legacyWorld ->
SteamwarWorld.getOrCreateBuilderWorld(legacyWorld.name, version, legacyWorld)
stats.imported++
}
} ?: run {
stats.skipped++
echo("Skipped builder worlds: /worlds does not exist.")
}
return stats
}
private fun migrateArenaWorlds(): MigrationStats {
val stats = MigrationStats()
val configs = parseGameModeConfigs(File("/configs/GameModes"))
if (configs.isEmpty()) {
stats.skipped++
echo("Skipped arena worlds: no game mode configs found in /configs/GameModes.")
return stats
}
for (config in configs) {
val version = trailingVersionPattern.find(config.serverFolder)?.value?.toIntOrNull()
if (version == null) {
stats.skipped++
echo("Skipped arena mode ${config.modeName}: server folder '${config.serverFolder}' has no version suffix.")
continue
}
val arenaRoot = File("/servers/${config.serverFolder}/arenas")
for (map in config.maps) {
val legacyWorld = File(arenaRoot, map)
if (!isWorldDirectory(legacyWorld)) {
stats.skipped++
continue
}
SteamwarWorld.getOrCreateArenaWorld(config.modeName, map, version, legacyWorld)
stats.imported++
}
}
return stats
}
private fun migrateBauLockConfig() {
transaction(Database.db) {
exec(
"""
UPDATE `world` w
JOIN `UserConfig` uc ON uc.`User` = w.`Owner` AND uc.`Config` = 'baulockstate'
SET w.`LockState` = uc.`Value`
WHERE w.`Owner` IS NOT NULL AND w.`LockState` IS NULL
""".trimIndent()
)
}
}
private fun migrateBauweltMembers() {
if (!columnExists("BauweltMember", "WorldID") || !columnExists("BauweltMember", "BauweltID")) {
echo("Skipped BauweltMember data migration: expected WorldID and BauweltID columns are not both present.")
return
}
execIgnore(
"""
INSERT IGNORE INTO `BauweltMember` (`WorldID`, `MemberID`, `Build`, `WorldEdit`, `World`)
SELECT w.`id`, bm.`MemberID`, bm.`Build`, bm.`WorldEdit`, bm.`World`
FROM `BauweltMember` bm
JOIN `world` w ON w.`Owner` = bm.`BauweltID` AND w.`Deleted` = 0
""".trimIndent(),
"Skipped BauweltMember data migration"
)
}
private fun parseGameModeConfigs(configRoot: File): List<ArenaMigrationConfig> {
if (!configRoot.isDirectory) return emptyList()
return configRoot.listFiles { file -> file.isFile && file.extension == "yml" && !file.name.endsWith(".kits.yml") }
?.mapNotNull { file ->
val server = parseServerBlock(file)
val folder = server.folder ?: return@mapNotNull null
ArenaMigrationConfig(file.nameWithoutExtension, folder, server.maps)
}
?: emptyList()
}
private fun parseServerBlock(file: File): ParsedServerBlock {
var inServer = false
var inMaps = false
var folder: String? = null
val maps = mutableListOf<String>()
file.readLines().forEach { line ->
val trimmed = line.trim()
if (trimmed.isEmpty() || trimmed.startsWith("#")) return@forEach
val indent = line.indexOfFirst { !it.isWhitespace() }.let { if (it == -1) 0 else it }
if (indent == 0) {
inServer = trimmed == "Server:"
inMaps = false
return@forEach
}
if (!inServer) return@forEach
when {
indent <= 2 && trimmed.startsWith("Folder:") -> folder = trimmed.substringAfter(":").cleanYamlValue()
indent <= 2 && trimmed.startsWith("Maps:") -> {
val value = trimmed.substringAfter(":").trim()
inMaps = true
if (value.startsWith("[") && value.endsWith("]")) {
maps += value.removePrefix("[").removeSuffix("]")
.split(",")
.map { it.cleanYamlValue() }
.filter { it.isNotEmpty() }
inMaps = false
}
}
inMaps && trimmed.startsWith("-") -> maps += trimmed.removePrefix("-").cleanYamlValue()
inMaps && indent <= 2 -> inMaps = false
}
}
return ParsedServerBlock(folder, maps)
}
private fun File.ownerUser(): SteamwarUser? {
name.toIntOrNull()?.let { return SteamwarUser.byId(it) }
return runCatching { SteamwarUser.get(UUID.fromString(name)) }.getOrNull()
}
private fun isWorldDirectory(file: File): Boolean =
file.isDirectory && (File(file, "level.dat").isFile || File(file, "region").isDirectory)
private fun String.cleanYamlValue(): String =
trim().trim('"').trim('\'')
private fun columnExists(table: String, column: String): Boolean =
transaction(Database.db) {
exec("SHOW COLUMNS FROM `$table` LIKE '$column'") { result -> result.next() } ?: false
}
private fun execIgnore(sql: String, prefix: String) {
try {
transaction(Database.db) {
exec(sql)
}
} catch (e: SQLException) {
echo("$prefix: ${e.message}")
}
}
private data class MigrationStats(
var imported: Int = 0,
var skipped: Int = 0,
var missingOwner: Int = 0,
)
private data class ArenaMigrationConfig(
val modeName: String,
val serverFolder: String,
val maps: List<String>,
)
private data class ParsedServerBlock(
val folder: String?,
val maps: List<String>,
)
private companion object {
private const val BAU_LOCK_CONFIG_NAME = "baulockstate"
}
}
@@ -0,0 +1,30 @@
package de.steamwar.commands.world
import com.github.ajalt.clikt.core.CliktCommand
import de.steamwar.db.Database
import de.steamwar.sql.SteamwarWorld
import java.time.LocalTime
class SaveToStorageCommand : CliktCommand(name = "saveToStorage") {
override fun run() {
Database.ensureConnected()
var archived = 0
for (world in SteamwarWorld.getWorldsToArchive()) {
if (!beforeCutoff()) {
echo("Stopping before 04:00. Archived $archived worlds.")
return
}
if (world.archiveIfNeeded()) {
archived++
echo("Archived ${world.uuid} (${world.type}/${world.name}).")
}
}
echo("Storage save completed. Archived $archived worlds.")
}
private fun beforeCutoff(): Boolean =
LocalTime.now().isBefore(LocalTime.of(4, 0))
}
+24
View File
@@ -0,0 +1,24 @@
package de.steamwar.commands.world
import com.github.ajalt.clikt.core.CliktCommand
import com.github.ajalt.clikt.parameters.arguments.argument
import com.github.ajalt.clikt.parameters.types.int
import com.github.ajalt.clikt.parameters.types.path
import de.steamwar.db.Database
import de.steamwar.sql.SteamwarWorld
class TemplateCommand : CliktCommand(name = "template") {
override fun run() = Unit
}
class TemplateCreateCommand : CliktCommand(name = "create") {
private val name by argument()
private val version by argument().int()
private val source by argument().path(canBeFile = false, mustExist = true)
override fun run() {
Database.ensureConnected()
SteamwarWorld.getOrCreateTemplateWorld(name, version, source.toFile())
echo("Template world '$name' created.")
}
}
+7
View File
@@ -0,0 +1,7 @@
package de.steamwar.commands.world
import com.github.ajalt.clikt.core.CliktCommand
class WorldCommand : CliktCommand(name = "world") {
override fun run() = Unit
}
@@ -19,7 +19,6 @@
package de.steamwar.sql package de.steamwar.sql
import de.steamwar.sql.BauweltMemberTable.bauweltId
import de.steamwar.sql.internal.useDb import de.steamwar.sql.internal.useDb
import org.jetbrains.exposed.v1.core.and import org.jetbrains.exposed.v1.core.and
import org.jetbrains.exposed.v1.core.dao.id.CompositeID import org.jetbrains.exposed.v1.core.dao.id.CompositeID
@@ -32,45 +31,37 @@ import org.jetbrains.exposed.v1.jdbc.insertIgnore
import java.util.* import java.util.*
object BauweltMemberTable : CompositeIdTable("BauweltMember") { object BauweltMemberTable : CompositeIdTable("BauweltMember") {
val bauweltId = reference("BauweltID", SteamwarUserTable).index() val worldId = reference("WorldID", WorldTable).index()
val memberId = reference("MemberID", SteamwarUserTable).index() val memberId = reference("MemberID", SteamwarUserTable).index()
val build = bool("Build") val build = bool("Build")
val worldEdit = bool("WorldEdit") val worldEdit = bool("WorldEdit")
val world = bool("World") val world = bool("World")
override val primaryKey = PrimaryKey(bauweltId, memberId) override val primaryKey = PrimaryKey(worldId, memberId)
init { init {
addIdColumn(bauweltId) addIdColumn(worldId)
addIdColumn(memberId) addIdColumn(memberId)
} }
} }
class BauweltMember(id: EntityID<CompositeID>) : CompositeEntity(id) { class BauweltMember(id: EntityID<CompositeID>) : CompositeEntity(id) {
companion object : CompositeEntityClass<BauweltMember>(BauweltMemberTable) { companion object : CompositeEntityClass<BauweltMember>(BauweltMemberTable) {
private val cache = mutableMapOf<Int, BauweltMember>() private val cache = mutableMapOf<Pair<UUID, Int>, BauweltMember>()
private fun cache(member: BauweltMember) = private fun cache(member: BauweltMember) =
cache.put(member.memberID, member) cache.put(member.worldID to member.memberID, member)
@JvmStatic @JvmStatic
fun clear() = fun clear() =
cache.clear() cache.clear()
@JvmStatic @JvmStatic
@Deprecated("Use addMember(ownerId: Int, newMemberId: Int)") fun addMember(worldId: UUID, newMemberId: Int) =
fun addMember(ownerId: UUID, newMemberId: UUID) =
addMember(SteamwarUser.get(ownerId)!!.id, SteamwarUser.get(newMemberId)!!.id)
@JvmStatic
fun addMember(ownerId: Int, newMemberId: Int) =
addMember(EntityID(ownerId, SteamwarUserTable), EntityID(newMemberId, SteamwarUserTable))
fun addMember(ownerId: EntityID<Int>, newMemberId: EntityID<Int>) =
useDb { useDb {
BauweltMemberTable.insertIgnore { BauweltMemberTable.insertIgnore {
it[bauweltId] = ownerId it[BauweltMemberTable.worldId] = EntityID(worldId, WorldTable)
it[memberId] = newMemberId it[memberId] = EntityID(newMemberId, SteamwarUserTable)
it[build] = false it[build] = false
it[worldEdit] = false it[worldEdit] = false
it[world] = false it[world] = false
@@ -78,31 +69,32 @@ class BauweltMember(id: EntityID<CompositeID>) : CompositeEntity(id) {
} }
@JvmStatic @JvmStatic
@Deprecated("Use getBauMember(bauwelt: Int, member: Int)") fun addMember(worldId: UUID, newMemberId: UUID) =
fun getBauMember(bauwelt: UUID, member: UUID) = addMember(worldId, SteamwarUser.get(newMemberId)!!.id.value)
@JvmStatic
fun getBauMember(world: UUID, member: Int) =
cache[world to member]
?: useDb {
find { (BauweltMemberTable.worldId eq world) and (BauweltMemberTable.memberId eq member) }.firstOrNull()?.also { cache(it) }
}
@JvmStatic
fun getBauMember(world: UUID, member: UUID) =
getBauMember(world, SteamwarUser.get(member)!!.id.value)
@JvmStatic
fun getWorldMembers(world: UUID) =
useDb { useDb {
find { (bauweltId eq SteamwarUser.get(bauwelt)!!.id) and (BauweltMemberTable.memberId eq SteamwarUser.get(member)!!.id) }.firstOrNull()?.also { cache(it) } find { BauweltMemberTable.worldId eq world }.toList().also { it.forEach { cache(it) } }
} }
@JvmStatic @JvmStatic
fun getBauMember(bauwelt: Int, member: Int) = fun getMembers(world: UUID) =
useDb { getWorldMembers(world)
find { (bauweltId eq bauwelt) and (BauweltMemberTable.memberId eq member) }.firstOrNull()?.also { cache(it) }
}
@JvmStatic
@Deprecated("Use getMembers(bauwelt: Int)")
fun getMembers(bauwelt: UUID) =
getMembers(SteamwarUser.get(bauwelt)!!.id.value)
@JvmStatic
fun getMembers(bauwelt: Int) =
useDb {
find { bauweltId eq bauwelt }.toList().also { it.forEach { cache(it) } }
}
} }
val bauweltID by BauweltMemberTable.bauweltId.transform({ EntityID(it, SteamwarUserTable) }, { it.value }) val worldID by BauweltMemberTable.worldId.transform({ EntityID(it, WorldTable) }, { it.value })
val memberID by BauweltMemberTable.memberId.transform({ EntityID(it, SteamwarUserTable) }, { it.value }) val memberID by BauweltMemberTable.memberId.transform({ EntityID(it, SteamwarUserTable) }, { it.value })
private var worldEditInternal by BauweltMemberTable.worldEdit private var worldEditInternal by BauweltMemberTable.worldEdit
var worldEdit: Boolean var worldEdit: Boolean
@@ -144,4 +136,4 @@ class BauweltMember(id: EntityID<CompositeID>) : CompositeEntity(id) {
useDb { useDb {
delete() delete()
} }
} }
@@ -434,7 +434,7 @@ class SchematicNode(id: EntityID<Int>) : IntEntity(id) {
private var nodeItem by SchematicNodeTable.item private var nodeItem by SchematicNodeTable.item
var item: String var item: String
get() = nodeItem.ifEmpty { get() = nodeItem.ifEmpty {
if (isDir()) "CHEST" else "CAULDRON_ITEM" if (isDir()) "CHEST" else "CAULDRON"
} }
set(value) = useDb { set(value) = useDb {
nodeItem = value nodeItem = value
@@ -25,6 +25,7 @@ import org.jetbrains.exposed.v1.core.dao.id.EntityID
import org.jetbrains.exposed.v1.core.dao.id.IntIdTable import org.jetbrains.exposed.v1.core.dao.id.IntIdTable
import org.jetbrains.exposed.v1.core.eq import org.jetbrains.exposed.v1.core.eq
import org.jetbrains.exposed.v1.core.inList import org.jetbrains.exposed.v1.core.inList
import org.jetbrains.exposed.v1.core.neq
import org.jetbrains.exposed.v1.dao.IntEntity import org.jetbrains.exposed.v1.dao.IntEntity
import org.jetbrains.exposed.v1.dao.IntEntityClass import org.jetbrains.exposed.v1.dao.IntEntityClass
import org.jetbrains.exposed.v1.jdbc.insert import org.jetbrains.exposed.v1.jdbc.insert
@@ -136,6 +137,12 @@ class SteamwarUser(id: EntityID<Int>) : IntEntity(id) {
.select(SteamwarUserTable.fields).where { UserPermTable.perm eq perm }.map { wrapRow(it) } .select(SteamwarUserTable.fields).where { UserPermTable.perm eq perm }.map { wrapRow(it) }
} }
@JvmStatic
fun getUsersWithDiscordId() =
useDb {
find { SteamwarUserTable.discordId neq null }.toList()
}
@JvmStatic @JvmStatic
fun getServerTeam() = fun getServerTeam() =
useDb { useDb {
@@ -175,6 +182,9 @@ class SteamwarUser(id: EntityID<Int>) : IntEntity(id) {
leaderInternal = false leaderInternal = false
} }
fun hasTeam() =
team != 0
private var leaderInternal by SteamwarUserTable.leader private var leaderInternal by SteamwarUserTable.leader
var leader: Boolean var leader: Boolean
get() = leaderInternal get() = leaderInternal
@@ -50,6 +50,8 @@ enum class UserPerm {
PUNISHMENTS, PUNISHMENTS,
TICKET_LOG, TICKET_LOG,
BUILD, BUILD,
BUILD_EXTRA_WORLDS,
BUILD_UNLIMITED_WORLDS,
CHECK, CHECK,
MODERATION, MODERATION,
ADMINISTRATION; ADMINISTRATION;
@@ -63,11 +65,11 @@ enum class UserPerm {
PREFIX_NONE to emptyPrefix, PREFIX_NONE to emptyPrefix,
PREFIX_YOUTUBER to Prefix("§x§8§A§2§B§E§5", "CC"), // 8A2BE5 PREFIX_YOUTUBER to Prefix("§x§8§A§2§B§E§5", "CC"), // 8A2BE5
PREFIX_GUIDE to Prefix("§x§e§7§6§2§e§d", "Guide"), // E762ED PREFIX_GUIDE to Prefix("§x§e§7§6§2§e§d", "Guide"), // E762ED
PREFIX_SUPPORTER to Prefix("§x§6§0§9§5§F§B", "Sup"), // 6095FB PREFIX_SUPPORTER to Prefix("§x§6§0§9§5§F§B", "Sup", true), // 6095FB
PREFIX_MODERATOR to Prefix("§x§F§F§A§2§5§C", "Mod"), // FFA25C PREFIX_MODERATOR to Prefix("§x§F§F§A§2§5§C", "Mod", true), // FFA25C
PREFIX_BUILDER to Prefix("§x§6§0§F§F§6§A", "Arch"), // 60FF6A PREFIX_BUILDER to Prefix("§x§6§0§F§F§6§A", "Arch", true), // 60FF6A
PREFIX_DEVELOPER to Prefix("§x§0§B§B§C§B§3", "Dev"), // 0BBCB3 PREFIX_DEVELOPER to Prefix("§x§0§B§B§C§B§3", "Dev", true), // 0BBCB3
PREFIX_ADMIN to Prefix("§x§F§F§2§B§2§4", "Admin"), // FF2B24 PREFIX_ADMIN to Prefix("§x§F§F§2§B§2§4", "Admin", true), // FF2B24
) )
@JvmStatic @JvmStatic
@@ -94,5 +96,5 @@ enum class UserPerm {
} }
} }
data class Prefix(val colorCode: String, val chatPrefix: String) data class Prefix(val colorCode: String, val chatPrefix: String, val teamPrefix: Boolean = false)
} }
+345
View File
@@ -0,0 +1,345 @@
/*
* This file is a part of the SteamWar software.
*
* Copyright (C) 2026 SteamWar.de-Serverteam
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package de.steamwar.sql
import de.steamwar.sql.internal.useDb
import org.jetbrains.exposed.v1.core.and
import org.jetbrains.exposed.v1.core.dao.id.EntityID
import org.jetbrains.exposed.v1.core.dao.id.java.UUIDTable
import org.jetbrains.exposed.v1.core.eq
import org.jetbrains.exposed.v1.core.not
import org.jetbrains.exposed.v1.core.or
import org.jetbrains.exposed.v1.core.SortOrder
import org.jetbrains.exposed.v1.dao.Entity
import org.jetbrains.exposed.v1.dao.EntityClass
import org.jetbrains.exposed.v1.javatime.CurrentTimestamp
import org.jetbrains.exposed.v1.javatime.timestamp
import java.io.File
import java.time.Instant
import java.time.temporal.ChronoUnit
import java.util.UUID
object WorldTable : UUIDTable("world") {
val name = varchar("Name", 512)
val version = integer("Version")
val type = enumeration<WorldType>("Type")
val owner = reference("Owner", SteamwarUserTable).nullable()
val team = reference("Team", TeamTable).nullable()
val lockState = varchar("LockState", 32).nullable()
val deleted = bool("Deleted").default(false)
val created = timestamp("Created").defaultExpression(CurrentTimestamp)
val lastUsed = timestamp("LastUsed").defaultExpression(CurrentTimestamp)
}
enum class WorldType {
BAU,
BUILDER,
TEAM,
ARENA,
TEMPLATE,
}
class SteamwarWorld(id: EntityID<UUID>) : Entity<UUID>(id) {
var name by WorldTable.name
private set
var version by WorldTable.version
private set
var type by WorldTable.type
private set
var owner by WorldTable.owner
private set
var team by WorldTable.team
private set
var lockState by WorldTable.lockState
private set
var deleted by WorldTable.deleted
private set
val created by WorldTable.created
var lastUsed by WorldTable.lastUsed
private set
val archived: Boolean
get() = !storageDirectory.exists() && archiveFile.exists()
val shouldArchive: Boolean
get() = !archived && (deleted || lastUsed.plus(7, ChronoUnit.DAYS).isBefore(Instant.now()))
val uuid: UUID
get() = id.value
val storageDirectory: File
get() = File(WORLD_STORAGE, id.value.toString())
private val archiveFile: File
get() = File(ARCHIVE_WORLD_STORAGE, "${id.value}.zip")
@JvmOverloads
fun setupAndGetStoragePath(prototype: File? = null): String {
if (archived) {
loadFromArchive()
}
val needsInitialization = !storageDirectory.exists() || storageDirectory.list()?.isEmpty() == true
if (needsInitialization) {
if (prototype != null && prototype.exists()) {
prototype.copyRecursively(storageDirectory, overwrite = true)
} else {
storageDirectory.mkdirs()
}
}
useDb {
lastUsed = Instant.now()
}
return storageDirectory.path
}
fun markDeleted() = useDb {
deleted = true
}
fun rename(newName: String) = useDb {
name = newName
}
fun archiveIfNeeded(): Boolean {
if (!shouldArchive) return false
File(ARCHIVE_WORLD_STORAGE).mkdirs()
return archiveWorld() == 0
}
fun changeVersion(newVersion: Int) = useDb {
version = newVersion
}
fun changeLockState(newLockState: String?) = useDb {
lockState = newLockState
}
private fun archiveWorld() = ProcessBuilder("zip", "-u9oymrqq", "$ARCHIVE_WORLD_STORAGE/${id.value}.zip", id.value.toString())
.directory(File(WORLD_STORAGE))
.inheritIO()
.start()
.waitFor()
private fun loadFromArchive() = ProcessBuilder("unzip", "-qq", "-o", "$ARCHIVE_WORLD_STORAGE/${id.value}.zip", "-d", WORLD_STORAGE)
.inheritIO()
.start()
.waitFor()
companion object : EntityClass<UUID, SteamwarWorld>(WorldTable) {
const val WORLD_STORAGE = "/worlds/storage"
const val ARCHIVE_WORLD_STORAGE = "/mnt/storage/worlds/storage"
const val DEFAULT_BAU_WORLD_LIMIT = 3
const val EXTRA_BAU_WORLD_LIMIT = 10
const val TEAM_WORLD_LIMIT = 2
@JvmStatic
fun getWorld(uuid: UUID) = useDb {
findById(uuid)
}
@JvmStatic
fun getBauWorld(user: SteamwarUser, version: Int) = useDb {
find {
(WorldTable.owner eq user.id) and
(WorldTable.version eq version) and
(WorldTable.type eq WorldType.BAU) and
not(WorldTable.deleted)
}.firstOrNull()
}
@JvmStatic
fun getBauWorld(user: SteamwarUser, name: String, version: Int) = useDb {
find {
(WorldTable.owner eq user.id) and
(WorldTable.name eq name) and
(WorldTable.version eq version) and
(WorldTable.type eq WorldType.BAU) and
not(WorldTable.deleted)
}.firstOrNull()
}
@JvmStatic
fun getBauWorlds(user: SteamwarUser) = useDb {
find {
(WorldTable.owner eq user.id) and
(WorldTable.type eq WorldType.BAU) and
not(WorldTable.deleted)
}.orderBy(WorldTable.created to SortOrder.ASC).toList()
}
@JvmStatic
fun countBauWorlds(user: SteamwarUser) = useDb {
find {
(WorldTable.owner eq user.id) and
(WorldTable.type eq WorldType.BAU) and
not(WorldTable.deleted)
}.count()
}
@JvmStatic
@JvmOverloads
fun getOrCreateBauWorld(user: SteamwarUser, version: Int, prototype: File? = null): SteamwarWorld =
getBauWorld(user, version) ?: createWorld(user, user.userName, version, WorldType.BAU, prototype)
@JvmStatic
@JvmOverloads
fun getOrCreateBauWorld(user: SteamwarUser, name: String, version: Int, prototype: File? = null): SteamwarWorld =
getBauWorld(user, name, version) ?: createWorld(user, name, version, WorldType.BAU, prototype)
@JvmStatic
fun getBuilderWorld(name: String, version: Int) = useDb {
find {
(WorldTable.name eq name) and
(WorldTable.version eq version) and
(WorldTable.type eq WorldType.BUILDER) and
not(WorldTable.deleted)
}.firstOrNull()
}
@JvmStatic
fun getBuilderWorlds(version: Int) = useDb {
find {
(WorldTable.version eq version) and
(WorldTable.type eq WorldType.BUILDER) and
not(WorldTable.deleted)
}.toList()
}
@JvmStatic
@JvmOverloads
fun getOrCreateBuilderWorld(name: String, version: Int, prototype: File? = null): SteamwarWorld =
getBuilderWorld(name, version) ?: createWorld(null, name, version, WorldType.BUILDER, prototype)
@JvmStatic
fun getTeamWorld(team: Team, name: String, version: Int) = useDb {
find {
(WorldTable.team eq team.id) and
(WorldTable.name eq name) and
(WorldTable.version eq version) and
(WorldTable.type eq WorldType.TEAM) and
not(WorldTable.deleted)
}.firstOrNull()
}
@JvmStatic
fun getTeamWorlds(team: Team) = useDb {
find {
(WorldTable.team eq team.id) and
(WorldTable.type eq WorldType.TEAM) and
not(WorldTable.deleted)
}.orderBy(WorldTable.created to SortOrder.ASC).toList()
}
@JvmStatic
@JvmOverloads
fun getOrCreateTeamWorld(team: Team, name: String, version: Int, prototype: File? = null): SteamwarWorld =
getTeamWorld(team, name, version) ?: createWorld(null, name, version, WorldType.TEAM, prototype, team)
@JvmStatic
fun countTeamWorlds(team: Team) = useDb {
find {
(WorldTable.team eq team.id) and
(WorldTable.type eq WorldType.TEAM) and
not(WorldTable.deleted)
}.count()
}
@JvmStatic
fun getArenaWorld(mode: String, map: String, version: Int) = useDb {
find {
(WorldTable.name eq arenaWorldName(mode, map)) and
(WorldTable.version eq version) and
(WorldTable.type eq WorldType.ARENA) and
not(WorldTable.deleted)
}.firstOrNull()
}
@JvmStatic
@JvmOverloads
fun getOrCreateArenaWorld(mode: String, map: String, version: Int, prototype: File? = null): SteamwarWorld =
getArenaWorld(mode, map, version) ?: createWorld(null, arenaWorldName(mode, map), version, WorldType.ARENA, prototype)
@JvmStatic
fun getTemplateWorld(name: String, version: Int) = useDb {
find {
(WorldTable.name eq name) and
(WorldTable.version eq version) and
(WorldTable.type eq WorldType.TEMPLATE) and
not(WorldTable.deleted)
}.firstOrNull()
}
@JvmStatic
fun getTemplateWorlds() = useDb {
find {
(WorldTable.type eq WorldType.TEMPLATE) and
not(WorldTable.deleted)
}.orderBy(WorldTable.name to SortOrder.ASC).toList()
}
@JvmStatic
fun getTemplateWorlds(version: Int) = useDb {
find {
(WorldTable.version eq version) and
(WorldTable.type eq WorldType.TEMPLATE) and
not(WorldTable.deleted)
}.orderBy(WorldTable.name to SortOrder.ASC).toList()
}
@JvmStatic
@JvmOverloads
fun getOrCreateTemplateWorld(name: String, version: Int, prototype: File? = null): SteamwarWorld =
getTemplateWorld(name, version) ?: createWorld(null, name, version, WorldType.TEMPLATE, prototype)
@JvmStatic
fun getAllActiveWorlds() = useDb {
find { not(WorldTable.deleted) }.toList()
}
@JvmStatic
fun getWorldsToArchive() = useDb {
find {
not(WorldTable.deleted) or (WorldTable.deleted eq true)
}.toList().filter { it.shouldArchive }
}
private fun arenaWorldName(mode: String, map: String) = "$mode/$map"
@JvmStatic
@JvmOverloads
fun createWorld(user: SteamwarUser?, name: String, version: Int, type: WorldType, prototype: File? = null, team: Team? = null): SteamwarWorld {
val world = useDb { new {
this.name = name
this.version = version
this.type = type
this.owner = user?.id
this.team = team?.id
} }
world.setupAndGetStoragePath(prototype)
return world
}
}
}
@@ -37,4 +37,5 @@ dependencies {
compileOnly(libs.authlib) compileOnly(libs.authlib)
compileOnly(libs.nms) compileOnly(libs.nms)
compileOnly(libs.fawe) compileOnly(libs.fawe)
compileOnly(libs.datafixer)
} }
@@ -41,6 +41,9 @@ import de.steamwar.sql.SchematicNode;
import lombok.Getter; import lombok.Getter;
import org.bukkit.Bukkit; import org.bukkit.Bukkit;
import org.bukkit.GameRule; import org.bukkit.GameRule;
import org.bukkit.event.EventHandler;
import org.bukkit.event.Listener;
import org.bukkit.event.player.PlayerPickupArrowEvent;
import org.bukkit.plugin.java.JavaPlugin; import org.bukkit.plugin.java.JavaPlugin;
public class FightSystem extends JavaPlugin { public class FightSystem extends JavaPlugin {
@@ -95,13 +98,20 @@ public class FightSystem extends JavaPlugin {
getMessage().broadcast("PISTON_PUSHED_OUTSIDE"); getMessage().broadcast("PISTON_PUSHED_OUTSIDE");
shutdown(); shutdown();
}); });
new StateDependentListener(ArenaMode.All, FightState.All, BountifulWrapper.impl.newDenyArrowPickupListener()); new StateDependentListener(ArenaMode.All, FightState.All, new Listener() {
@EventHandler
public void onArrowPickup(PlayerPickupArrowEvent e) {
if (Fight.fighting(e.getPlayer())) e.setCancelled(true);
}
});
new OneShotStateDependent(ArenaMode.All, FightState.PreSchemSetup, () -> Fight.playSound(SWSound.BLOCK_NOTE_PLING.getSound(), 100.0f, 2.0f)); new OneShotStateDependent(ArenaMode.All, FightState.PreSchemSetup, () -> Fight.playSound(SWSound.BLOCK_NOTE_PLING.getSound(), 100.0f, 2.0f));
new OneShotStateDependent(ArenaMode.Test, FightState.All, WorldEditRendererCUIEditor::new); new OneShotStateDependent(ArenaMode.Test, FightState.All, WorldEditRendererCUIEditor::new);
Config.world.setGameRule(GameRule.REDUCED_DEBUG_INFO, ArenaMode.AntiTest.contains(Config.mode)); Config.world.setGameRule(GameRule.REDUCED_DEBUG_INFO, ArenaMode.AntiTest.contains(Config.mode));
techHider = new TechHiderWrapper();
hullHider = new HullHider(); hullHider = new HullHider();
techHider = new TechHiderWrapper(hullHider);
FightSystem.getHullHider().getHullMap().values().forEach(hull -> hull.fill(true));
FileSource.startReplay(); FileSource.startReplay();
@@ -120,7 +130,7 @@ public class FightSystem extends JavaPlugin {
Fight.getUnrotated().setSchem(SchematicNode.getSchematicNode(Config.PrepareSchemID)); Fight.getUnrotated().setSchem(SchematicNode.getSchematicNode(Config.PrepareSchemID));
} }
CraftbukkitWrapper.impl.setupGamerule(); Config.world.setGameRule(GameRule.LOCATOR_BAR, false);
} }
@Override @Override
@@ -21,8 +21,8 @@ import de.steamwar.fightsystem.ArenaMode;
import de.steamwar.fightsystem.FightSystem; import de.steamwar.fightsystem.FightSystem;
import de.steamwar.fightsystem.states.FightState; import de.steamwar.fightsystem.states.FightState;
import de.steamwar.fightsystem.states.StateDependentCommand; import de.steamwar.fightsystem.states.StateDependentCommand;
import de.steamwar.fightsystem.utils.TpsWarper;
import de.steamwar.linkage.Linked; import de.steamwar.linkage.Linked;
import net.minecraft.server.MinecraftServer;
import org.bukkit.command.Command; import org.bukkit.command.Command;
import org.bukkit.command.CommandExecutor; import org.bukkit.command.CommandExecutor;
import org.bukkit.command.CommandSender; import org.bukkit.command.CommandSender;
@@ -45,8 +45,7 @@ public class TPSWarpCommand implements CommandExecutor {
return false; return false;
} }
TpsWarper warper = TpsWarper.impl; MinecraftServer.getServer().tickRateManager().setTickRate(tps);
warper.warp(tps);
FightSystem.getMessage().broadcastActionbar("TPSWARP_SET", tps); FightSystem.getMessage().broadcastActionbar("TPSWARP_SET", tps);
return false; return false;
@@ -158,6 +158,8 @@ public class FightSchematic extends StateDependent {
FreezeWorld freezer = new FreezeWorld(); FreezeWorld freezer = new FreezeWorld();
team.teleportToSpawn(); team.teleportToSpawn();
// TODO: Implement hull generation based on clipboard content!
FightSystem.getHullHider().fill(team, false);
Vector dims = WorldeditWrapper.impl.getDimensions(clipboard); Vector dims = WorldeditWrapper.impl.getDimensions(clipboard);
WorldeditWrapper.impl.pasteClipboard( WorldeditWrapper.impl.pasteClipboard(
clipboard, clipboard,
@@ -44,6 +44,8 @@ import de.steamwar.sql.SteamwarUser;
import lombok.Getter; import lombok.Getter;
import net.md_5.bungee.api.ChatMessageType; import net.md_5.bungee.api.ChatMessageType;
import org.bukkit.*; import org.bukkit.*;
import org.bukkit.attribute.Attribute;
import org.bukkit.attribute.AttributeInstance;
import org.bukkit.entity.LivingEntity; import org.bukkit.entity.LivingEntity;
import org.bukkit.entity.Player; import org.bukkit.entity.Player;
import org.bukkit.scoreboard.NameTagVisibility; import org.bukkit.scoreboard.NameTagVisibility;
@@ -151,8 +153,8 @@ public class FightTeam {
new TeamArea(this); new TeamArea(this);
team = FightScoreboard.getBukkitTeam(name); team = FightScoreboard.getBukkitTeam(name);
WorldOfColorWrapper.impl.setTeamColor(team, color); team.setColor(color);
BountifulWrapper.impl.setNametagVisibility(team); team.setOption(Team.Option.NAME_TAG_VISIBILITY, Team.OptionStatus.FOR_OWN_TEAM);
team.setNameTagVisibility(NameTagVisibility.HIDE_FOR_OTHER_TEAMS); team.setNameTagVisibility(NameTagVisibility.HIDE_FOR_OTHER_TEAMS);
if (!Config.GameModeConfig.WinConditions.contains(Winconditions.AMONG_US)) { if (!Config.GameModeConfig.WinConditions.contains(Winconditions.AMONG_US)) {
team.setAllowFriendlyFire(false); team.setAllowFriendlyFire(false);
@@ -284,7 +286,8 @@ public class FightTeam {
entity.teleport(spawn); entity.teleport(spawn);
fightPlayer.ifPlayer(player -> { fightPlayer.ifPlayer(player -> {
BountifulWrapper.impl.setAttackSpeed(player); AttributeInstance attribute = player.getAttribute(Attribute.ATTACK_SPEED);
attribute.setBaseValue(16);
player.setFoodLevel(20); player.setFoodLevel(20);
player.getInventory().clear(); player.getInventory().clear();
FightSystem.getHullHider().updatePlayer(player); FightSystem.getHullHider().updatePlayer(player);
@@ -26,7 +26,6 @@ import de.steamwar.fightsystem.listener.Recording;
import de.steamwar.fightsystem.states.FightState; import de.steamwar.fightsystem.states.FightState;
import de.steamwar.fightsystem.states.StateDependent; import de.steamwar.fightsystem.states.StateDependent;
import de.steamwar.fightsystem.utils.CraftbukkitWrapper; import de.steamwar.fightsystem.utils.CraftbukkitWrapper;
import de.steamwar.fightsystem.utils.FlatteningWrapper;
import de.steamwar.linkage.Linked; import de.steamwar.linkage.Linked;
import lombok.Getter; import lombok.Getter;
import org.bukkit.Bukkit; import org.bukkit.Bukkit;
@@ -64,11 +63,18 @@ public class FightWorld extends StateDependent {
public static void forceLoad() { public static void forceLoad() {
Config.ArenaRegion.forEachChunk((cX, cZ) -> { Config.ArenaRegion.forEachChunk((cX, cZ) -> {
Config.world.loadChunk(cX, cZ); Config.world.loadChunk(cX, cZ);
FlatteningWrapper.impl.forceLoadChunk(Config.world, cX, cZ); Config.world.setChunkForceLoaded(cX, cZ, true);
}); });
} }
public static void resetWorld() { public static void resetWorld() {
World backup = new WorldCreator(Config.world.getName() + "/backup").createWorld();
assert backup != null;
Config.ArenaRegion.forEachChunk((x, z) -> {
CraftbukkitWrapper.impl.resetChunk(Config.world, backup, x, z);
});
Bukkit.unloadWorld(backup, false);
List<Entity> entities = new ArrayList<>(); List<Entity> entities = new ArrayList<>();
Recording.iterateOverEntities(Objects::nonNull, entity -> { Recording.iterateOverEntities(Objects::nonNull, entity -> {
if (entity.getType() != EntityType.PLAYER && (!Config.GameModeConfig.Arena.Leaveable || Config.ArenaRegion.inRegion(entity.getLocation()))) { if (entity.getType() != EntityType.PLAYER && (!Config.GameModeConfig.Arena.Leaveable || Config.ArenaRegion.inRegion(entity.getLocation()))) {
@@ -78,14 +84,12 @@ public class FightWorld extends StateDependent {
entities.forEach(Entity::remove); entities.forEach(Entity::remove);
entities.clear(); entities.clear();
World backup = new WorldCreator(Config.world.getName() + "/backup").createWorld(); FightSystem.getHullHider().getHullMap().values().forEach(hull -> hull.fill(true));
assert backup != null;
Config.ArenaRegion.forEachChunk((x, z) -> { Config.ArenaRegion.forEachChunk((x, z) -> {
CraftbukkitWrapper.impl.resetChunk(Config.world, backup, x, z);
for (Player p : Bukkit.getOnlinePlayers()) { for (Player p : Bukkit.getOnlinePlayers()) {
de.steamwar.core.CraftbukkitWrapper.sendChunk(p, x, z); de.steamwar.core.CraftbukkitWrapper.sendChunk(p, x, z);
} }
}); });
Bukkit.unloadWorld(backup, false);
} }
} }
@@ -20,7 +20,6 @@
package de.steamwar.fightsystem.fight; package de.steamwar.fightsystem.fight;
import de.steamwar.fightsystem.FightSystem; import de.steamwar.fightsystem.FightSystem;
import de.steamwar.fightsystem.utils.BountifulWrapper;
import org.bukkit.Bukkit; import org.bukkit.Bukkit;
import org.bukkit.event.EventHandler; import org.bukkit.event.EventHandler;
import org.bukkit.event.EventPriority; import org.bukkit.event.EventPriority;
@@ -30,19 +29,16 @@ import org.bukkit.event.block.*;
import org.bukkit.event.entity.ItemSpawnEvent; import org.bukkit.event.entity.ItemSpawnEvent;
import org.bukkit.event.inventory.InventoryMoveItemEvent; import org.bukkit.event.inventory.InventoryMoveItemEvent;
import org.bukkit.event.player.PlayerInteractEvent; import org.bukkit.event.player.PlayerInteractEvent;
import org.bukkit.event.player.PlayerSwapHandItemsEvent;
public class FreezeWorld implements Listener { public class FreezeWorld implements Listener {
private final Listener denyHandSwap = BountifulWrapper.impl.newDenyHandSwapListener();
public FreezeWorld() { public FreezeWorld() {
Bukkit.getPluginManager().registerEvents(this, FightSystem.getPlugin()); Bukkit.getPluginManager().registerEvents(this, FightSystem.getPlugin());
Bukkit.getPluginManager().registerEvents(denyHandSwap, FightSystem.getPlugin());
} }
public void disable() { public void disable() {
HandlerList.unregisterAll(this); HandlerList.unregisterAll(this);
HandlerList.unregisterAll(denyHandSwap);
} }
@EventHandler @EventHandler
@@ -94,4 +90,9 @@ public class FreezeWorld implements Listener {
public void handlePlayerInteract(PlayerInteractEvent event) { public void handlePlayerInteract(PlayerInteractEvent event) {
event.setCancelled(true); event.setCancelled(true);
} }
@EventHandler
public void onSwapItems(PlayerSwapHandItemsEvent event) {
if (Fight.fighting(event.getPlayer())) event.setCancelled(true);
}
} }
@@ -24,12 +24,12 @@ import de.steamwar.fightsystem.FightSystem;
import de.steamwar.fightsystem.commands.Commands; import de.steamwar.fightsystem.commands.Commands;
import de.steamwar.fightsystem.commands.GUI; import de.steamwar.fightsystem.commands.GUI;
import de.steamwar.fightsystem.listener.PersonalKitCreator; import de.steamwar.fightsystem.listener.PersonalKitCreator;
import de.steamwar.fightsystem.utils.FlatteningWrapper;
import de.steamwar.fightsystem.utils.ReflectionWrapper;
import de.steamwar.inventory.SWInventory; import de.steamwar.inventory.SWInventory;
import de.steamwar.inventory.SWItem; import de.steamwar.inventory.SWItem;
import de.steamwar.sql.PersonalKit; import de.steamwar.sql.PersonalKit;
import de.steamwar.sql.SteamwarUser; import de.steamwar.sql.SteamwarUser;
import io.papermc.paper.datacomponent.DataComponentType;
import io.papermc.paper.datacomponent.DataComponentTypes;
import lombok.Getter; import lombok.Getter;
import org.bukkit.Bukkit; import org.bukkit.Bukkit;
import org.bukkit.Material; import org.bukkit.Material;
@@ -39,6 +39,7 @@ import org.bukkit.configuration.file.YamlConfiguration;
import org.bukkit.enchantments.Enchantment; import org.bukkit.enchantments.Enchantment;
import org.bukkit.entity.Player; import org.bukkit.entity.Player;
import org.bukkit.inventory.ItemStack; import org.bukkit.inventory.ItemStack;
import org.bukkit.inventory.meta.BlockDataMeta;
import org.bukkit.inventory.meta.ItemMeta; import org.bukkit.inventory.meta.ItemMeta;
import org.bukkit.potion.PotionEffect; import org.bukkit.potion.PotionEffect;
@@ -193,13 +194,13 @@ public class Kit {
if (Config.GameModeConfig.Kits.ForbiddenItems.contains(stack.getType())) return true; if (Config.GameModeConfig.Kits.ForbiddenItems.contains(stack.getType())) return true;
//Check for attribute modifiers //Check for attribute modifiers
if (FlatteningWrapper.impl.hasAttributeModifier(stack)) { if (stack.hasItemMeta() && stack.getItemMeta() != null && stack.getItemMeta().hasAttributeModifiers()) {
return true; return true;
} }
if (stack.hasItemMeta()) { if (stack.hasItemMeta()) {
ItemMeta meta = stack.getItemMeta(); ItemMeta meta = stack.getItemMeta();
if (FlatteningWrapper.impl.containsBlockMeta(meta)) return true; //Blocks always upwards slabs etc. if (meta instanceof BlockDataMeta && ((BlockDataMeta) meta).hasBlockData()) return true; //Blocks always upwards slabs etc.
if (hasItems(stack)) return true; //Blocks prefilled inventories if (hasItems(stack)) return true; //Blocks prefilled inventories
} }
@@ -208,8 +209,42 @@ public class Kit {
return !normal.isEnchantmentInKit(stack) && !stack.getEnchantments().isEmpty(); return !normal.isEnchantmentInKit(stack) && !stack.getEnchantments().isEmpty();
} }
private static final Set<DataComponentType> FORBIDDEN_TYPES = new HashSet<>();
static {
FORBIDDEN_TYPES.add(DataComponentTypes.CUSTOM_NAME);
FORBIDDEN_TYPES.add(DataComponentTypes.PROFILE);
FORBIDDEN_TYPES.add(DataComponentTypes.UNBREAKABLE);
FORBIDDEN_TYPES.add(DataComponentTypes.BLOCK_DATA);
FORBIDDEN_TYPES.add(DataComponentTypes.BLOCKS_ATTACKS);
FORBIDDEN_TYPES.add(DataComponentTypes.BUNDLE_CONTENTS);
FORBIDDEN_TYPES.add(DataComponentTypes.CUSTOM_MODEL_DATA);
FORBIDDEN_TYPES.add(DataComponentTypes.ATTRIBUTE_MODIFIERS);
FORBIDDEN_TYPES.add(DataComponentTypes.TOOL);
FORBIDDEN_TYPES.add(DataComponentTypes.WEAPON);
FORBIDDEN_TYPES.add(DataComponentTypes.FOOD);
FORBIDDEN_TYPES.add(DataComponentTypes.CONSUMABLE);
FORBIDDEN_TYPES.add(DataComponentTypes.POTION_CONTENTS);
FORBIDDEN_TYPES.add(DataComponentTypes.STORED_ENCHANTMENTS);
FORBIDDEN_TYPES.add(DataComponentTypes.CAN_BREAK);
FORBIDDEN_TYPES.add(DataComponentTypes.CAN_PLACE_ON);
FORBIDDEN_TYPES.add(DataComponentTypes.MAX_DAMAGE);
FORBIDDEN_TYPES.add(DataComponentTypes.USE_REMAINDER);
FORBIDDEN_TYPES.add(DataComponentTypes.USE_COOLDOWN);
FORBIDDEN_TYPES.add(DataComponentTypes.SUSPICIOUS_STEW_EFFECTS);
FORBIDDEN_TYPES.add(DataComponentTypes.CHARGED_PROJECTILES);
FORBIDDEN_TYPES.add(DataComponentTypes.INTANGIBLE_PROJECTILE);
FORBIDDEN_TYPES.add(DataComponentTypes.FIREWORKS);
FORBIDDEN_TYPES.add(DataComponentTypes.FIREWORK_EXPLOSION);
FORBIDDEN_TYPES.add(DataComponentTypes.EQUIPPABLE);
FORBIDDEN_TYPES.add(DataComponentTypes.REPAIR_COST);
FORBIDDEN_TYPES.add(DataComponentTypes.ENCHANTABLE);
}
public static boolean hasItems(ItemStack stack) { public static boolean hasItems(ItemStack stack) {
return ReflectionWrapper.impl.hasItems(stack); FORBIDDEN_TYPES.forEach(stack::resetData);
return false;
} }
private boolean isEnchantmentInKit(ItemStack stack) { private boolean isEnchantmentInKit(ItemStack stack) {
@@ -22,12 +22,12 @@ package de.steamwar.fightsystem.listener;
import de.steamwar.fightsystem.Config; import de.steamwar.fightsystem.Config;
import de.steamwar.fightsystem.states.FightState; import de.steamwar.fightsystem.states.FightState;
import de.steamwar.fightsystem.states.StateDependentTask; import de.steamwar.fightsystem.states.StateDependentTask;
import de.steamwar.fightsystem.utils.WorldOfColorWrapper;
import de.steamwar.linkage.Linked; import de.steamwar.linkage.Linked;
import net.minecraft.world.entity.projectile.AbstractArrow; import net.minecraft.world.entity.projectile.AbstractArrow;
import org.bukkit.Location; import org.bukkit.Location;
import org.bukkit.block.Block; import org.bukkit.block.Block;
import org.bukkit.block.BlockFace; import org.bukkit.block.BlockFace;
import org.bukkit.entity.Arrow;
import org.bukkit.entity.Player; import org.bukkit.entity.Player;
import org.bukkit.entity.Projectile; import org.bukkit.entity.Projectile;
import org.bukkit.projectiles.ProjectileSource; import org.bukkit.projectiles.ProjectileSource;
@@ -46,6 +46,7 @@ public class ArrowStopper {
private void run() { private void run() {
Recording.iterateOverEntities(AbstractArrow.class::isInstance, entity -> { Recording.iterateOverEntities(AbstractArrow.class::isInstance, entity -> {
Projectile arrow = (Projectile) entity; Projectile arrow = (Projectile) entity;
if(!(arrow.getShooter() instanceof Player)) return;
if (invalidEntity(arrow)) return; if (invalidEntity(arrow)) return;
Location prevLocation = arrow.getLocation().toVector().subtract(arrow.getVelocity()).toLocation(arrow.getWorld()); Location prevLocation = arrow.getLocation().toVector().subtract(arrow.getVelocity()).toLocation(arrow.getWorld());
@@ -93,8 +94,12 @@ public class ArrowStopper {
boolean teamFrom = entity.getVelocity().getZ() > 0; boolean teamFrom = entity.getVelocity().getZ() > 0;
boolean overMid = location.getZ() > Config.SpecSpawn.getZ(); boolean overMid = location.getZ() > Config.SpecSpawn.getZ();
boolean otherSide = teamFrom == overMid; boolean otherSide = teamFrom == overMid;
return otherSide || !Config.ArenaRegion.inRegion(location) || if (otherSide || !Config.ArenaRegion.inRegion(location)) return true;
WorldOfColorWrapper.impl.isInBlock(entity) || boolean result = false;
if (entity instanceof Arrow arrow) {
result = arrow.isInBlock();
}
return result ||
entity.getVelocity().equals(NULL_VECTOR); entity.getVelocity().equals(NULL_VECTOR);
} }
} }
@@ -22,11 +22,11 @@ package de.steamwar.fightsystem.listener;
import de.steamwar.fightsystem.ArenaMode; import de.steamwar.fightsystem.ArenaMode;
import de.steamwar.fightsystem.states.FightState; import de.steamwar.fightsystem.states.FightState;
import de.steamwar.fightsystem.states.StateDependentListener; import de.steamwar.fightsystem.states.StateDependentListener;
import de.steamwar.fightsystem.utils.FlatteningWrapper;
import de.steamwar.linkage.Linked; import de.steamwar.linkage.Linked;
import org.bukkit.Location; import org.bukkit.Location;
import org.bukkit.block.Block; import org.bukkit.block.Block;
import org.bukkit.entity.Player; import org.bukkit.entity.Player;
import org.bukkit.entity.Pose;
import org.bukkit.event.EventHandler; import org.bukkit.event.EventHandler;
import org.bukkit.event.Listener; import org.bukkit.event.Listener;
import org.bukkit.event.block.BlockPlaceEvent; import org.bukkit.event.block.BlockPlaceEvent;
@@ -46,7 +46,7 @@ public class BlockPlaceCollision implements Listener {
// Hitbox size: 0.6xz, 1.8y, 1.5y when sneaking // Hitbox size: 0.6xz, 1.8y, 1.5y when sneaking
Player player = event.getPlayer(); Player player = event.getPlayer();
Location min = player.getLocation().add(-0.3, 0, -0.3); Location min = player.getLocation().add(-0.3, 0, -0.3);
Location max = player.getLocation().add(0.3, FlatteningWrapper.impl.isCrouching(player) ? 0.6 : (player.isSneaking() ? 1.5 : 1.8), 0.3); Location max = player.getLocation().add(0.3, player.getPose() == Pose.SWIMMING ? 0.6 : (player.isSneaking() ? 1.5 : 1.8), 0.3);
Location blockmin = block.getLocation(); Location blockmin = block.getLocation();
Location blockmax = block.getLocation().add(1.0, 1.0, 1.0); Location blockmax = block.getLocation().add(1.0, 1.0, 1.0);
@@ -24,7 +24,6 @@ import de.steamwar.fightsystem.Config;
import de.steamwar.fightsystem.FightSystem; import de.steamwar.fightsystem.FightSystem;
import de.steamwar.fightsystem.states.FightState; import de.steamwar.fightsystem.states.FightState;
import de.steamwar.fightsystem.states.StateDependentTask; import de.steamwar.fightsystem.states.StateDependentTask;
import de.steamwar.fightsystem.utils.FlatteningWrapper;
import de.steamwar.fightsystem.utils.Region; import de.steamwar.fightsystem.utils.Region;
import net.md_5.bungee.api.ChatMessageType; import net.md_5.bungee.api.ChatMessageType;
import org.bukkit.Bukkit; import org.bukkit.Bukkit;
@@ -141,7 +140,7 @@ public class Border {
private void sendChange(Player player, Block block, Material type) { private void sendChange(Player player, Block block, Material type) {
if (block.getType() == Material.AIR) { if (block.getType() == Material.AIR) {
FlatteningWrapper.impl.sendBlockChange(player, block, type); player.sendBlockChange(block.getLocation(), type.createBlockData());
} }
} }
} }
@@ -24,6 +24,7 @@ import de.steamwar.fightsystem.Config;
import de.steamwar.fightsystem.utils.CraftbukkitWrapper; import de.steamwar.fightsystem.utils.CraftbukkitWrapper;
import de.steamwar.linkage.Linked; import de.steamwar.linkage.Linked;
import net.minecraft.network.protocol.game.ServerboundUseItemOnPacket; import net.minecraft.network.protocol.game.ServerboundUseItemOnPacket;
import net.minecraft.network.protocol.game.ServerboundUseItemPacket;
import org.bukkit.entity.Player; import org.bukkit.entity.Player;
import java.io.*; import java.io.*;
@@ -42,7 +43,7 @@ public class ClickAnalyzer {
} }
public ClickAnalyzer() { public ClickAnalyzer() {
TinyProtocol.instance.addFilter(Recording.blockPlacePacket, this::onBlockPlace); TinyProtocol.instance.addFilter(ServerboundUseItemPacket.class, this::onBlockPlace);
TinyProtocol.instance.addFilter(ServerboundUseItemOnPacket.class, this::onBlockPlace); TinyProtocol.instance.addFilter(ServerboundUseItemOnPacket.class, this::onBlockPlace);
} }
@@ -20,24 +20,22 @@
package de.steamwar.fightsystem.listener; package de.steamwar.fightsystem.listener;
import de.steamwar.fightsystem.ArenaMode; import de.steamwar.fightsystem.ArenaMode;
import de.steamwar.fightsystem.fight.Fight;
import de.steamwar.fightsystem.states.FightState; import de.steamwar.fightsystem.states.FightState;
import de.steamwar.fightsystem.states.StateDependentListener; import de.steamwar.fightsystem.states.StateDependentListener;
import de.steamwar.fightsystem.utils.BountifulWrapper;
import de.steamwar.linkage.Linked; import de.steamwar.linkage.Linked;
import org.bukkit.event.EventHandler; import org.bukkit.event.EventHandler;
import org.bukkit.event.Listener; import org.bukkit.event.Listener;
import org.bukkit.event.inventory.InventoryClickEvent; import org.bukkit.event.inventory.InventoryClickEvent;
import org.bukkit.event.inventory.InventoryDragEvent; import org.bukkit.event.inventory.InventoryDragEvent;
import org.bukkit.event.player.PlayerPickupItemEvent; import org.bukkit.event.player.PlayerPickupItemEvent;
import org.bukkit.event.player.PlayerSwapHandItemsEvent;
@Linked @Linked
public class DenyInventoryMovement implements Listener { public class DenyInventoryMovement implements Listener {
public DenyInventoryMovement() { public DenyInventoryMovement() {
new StateDependentListener(ArenaMode.AntiTest, FightState.AntiIngame, this); new StateDependentListener(ArenaMode.AntiTest, FightState.AntiIngame, this);
Listener listener = BountifulWrapper.impl.newDenyHandSwapListener();
new StateDependentListener(ArenaMode.AntiTest, FightState.AntiIngame, listener);
} }
@EventHandler @EventHandler
@@ -54,4 +52,9 @@ public class DenyInventoryMovement implements Listener {
public void onItemPickup(PlayerPickupItemEvent event) { public void onItemPickup(PlayerPickupItemEvent event) {
event.setCancelled(true); event.setCancelled(true);
} }
@EventHandler
public void onSwapItems(PlayerSwapHandItemsEvent event) {
if (Fight.fighting(event.getPlayer())) event.setCancelled(true);
}
} }
@@ -27,13 +27,12 @@ import de.steamwar.fightsystem.fight.FightPlayer;
import de.steamwar.fightsystem.fight.FightTeam; import de.steamwar.fightsystem.fight.FightTeam;
import de.steamwar.fightsystem.states.FightState; import de.steamwar.fightsystem.states.FightState;
import de.steamwar.fightsystem.states.StateDependentListener; import de.steamwar.fightsystem.states.StateDependentListener;
import de.steamwar.fightsystem.utils.BountifulWrapper;
import de.steamwar.fightsystem.utils.FlatteningWrapper;
import de.steamwar.linkage.Linked; import de.steamwar.linkage.Linked;
import net.md_5.bungee.api.ChatMessageType; import net.md_5.bungee.api.ChatMessageType;
import org.bukkit.GameMode; import org.bukkit.GameMode;
import org.bukkit.Material; import org.bukkit.Material;
import org.bukkit.block.Block; import org.bukkit.block.Block;
import org.bukkit.block.data.type.Dispenser;
import org.bukkit.entity.Player; import org.bukkit.entity.Player;
import org.bukkit.entity.TNTPrimed; import org.bukkit.entity.TNTPrimed;
import org.bukkit.event.EventHandler; import org.bukkit.event.EventHandler;
@@ -62,7 +61,7 @@ public class Permanent implements Listener {
private static final Team spectatorTeam = FightScoreboard.getBukkitTeam("Spectator"); private static final Team spectatorTeam = FightScoreboard.getBukkitTeam("Spectator");
static { static {
BountifulWrapper.impl.setNametagVisibility(spectatorTeam); spectatorTeam.setOption(Team.Option.NAME_TAG_VISIBILITY, Team.OptionStatus.FOR_OWN_TEAM);
spectatorTeam.setNameTagVisibility(NameTagVisibility.NEVER); spectatorTeam.setNameTagVisibility(NameTagVisibility.NEVER);
} }
@@ -234,7 +233,7 @@ public class Permanent implements Listener {
return; return;
} }
if (e.getItem().getType() == Material.TNT || FlatteningWrapper.impl.isFacingWater(block)) { if (e.getItem().getType() == Material.TNT || block.getRelative(((Dispenser) block.getBlockData()).getFacing()).isLiquid()) {
e.setCancelled(true); e.setCancelled(true);
} }
} }
@@ -1,74 +0,0 @@
package de.steamwar.fightsystem.listener;
import de.steamwar.fightsystem.states.FightState;
import de.steamwar.fightsystem.states.StateDependentListener;
import de.steamwar.linkage.Linked;
import net.minecraft.network.protocol.game.ClientboundForgetLevelChunkPacket;
import net.minecraft.network.protocol.game.ClientboundLevelChunkWithLightPacket;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.world.level.ChunkPos;
import net.minecraft.world.level.chunk.LevelChunk;
import org.bukkit.Bukkit;
import org.bukkit.Chunk;
import org.bukkit.Location;
import org.bukkit.World;
import org.bukkit.craftbukkit.CraftWorld;
import org.bukkit.craftbukkit.entity.CraftPlayer;
import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
import org.bukkit.event.Listener;
import org.bukkit.event.player.PlayerJoinEvent;
@Linked
public class PlayerJoinListener implements Listener {
public PlayerJoinListener() {
new StateDependentListener(true, FightState.All, this);
}
@EventHandler()
public void onPlayerJoin(PlayerJoinEvent event) {
Player player = event.getPlayer();
World world = player.getWorld();
Location loc = player.getLocation();
int viewDistance = Bukkit.getViewDistance();
int chunkX = loc.getChunk().getX();
int chunkZ = loc.getChunk().getZ();
// Iterate through the chunks around the player and force a resend
for (int x = -viewDistance; x <= viewDistance; x++) {
for (int z = -viewDistance; z <= viewDistance; z++) {
Chunk chunk = world.getChunkAt(chunkX + x, chunkZ + z);
this.forceRefreshChunkForPlayer(player, chunk);
}
}
}
public void forceRefreshChunkForPlayer(Player player, Chunk bukkitChunk) {
ServerPlayer serverPlayer = ((CraftPlayer) player).getHandle();
CraftWorld craftWorld = (CraftWorld) bukkitChunk.getWorld();
int chunkX = bukkitChunk.getX();
int chunkZ = bukkitChunk.getZ();
LevelChunk nmsChunk = craftWorld.getHandle().getChunkSource().getChunk(chunkX, chunkZ, false);
if (nmsChunk == null) {
// Chunk isn't loaded in memory on the server side;
return;
}
ClientboundForgetLevelChunkPacket unloadPacket = new ClientboundForgetLevelChunkPacket(new ChunkPos(chunkX, chunkZ));
serverPlayer.connection.send(unloadPacket);
ClientboundLevelChunkWithLightPacket loadPacket = new ClientboundLevelChunkWithLightPacket(
nmsChunk,
craftWorld.getHandle().getLightEngine(),
null,
null,
true
);
serverPlayer.connection.send(loadPacket);
}
}
@@ -28,12 +28,12 @@ import de.steamwar.fightsystem.fight.FightTeam;
import de.steamwar.fightsystem.states.FightState; import de.steamwar.fightsystem.states.FightState;
import de.steamwar.fightsystem.states.OneShotStateDependent; import de.steamwar.fightsystem.states.OneShotStateDependent;
import de.steamwar.fightsystem.states.StateDependentListener; import de.steamwar.fightsystem.states.StateDependentListener;
import de.steamwar.fightsystem.utils.FlatteningWrapper;
import de.steamwar.fightsystem.utils.Region; import de.steamwar.fightsystem.utils.Region;
import de.steamwar.fightsystem.utils.WorldeditWrapper; import de.steamwar.fightsystem.utils.WorldeditWrapper;
import de.steamwar.linkage.Linked; import de.steamwar.linkage.Linked;
import de.steamwar.sql.SchematicNode; import de.steamwar.sql.SchematicNode;
import org.bukkit.Bukkit; import org.bukkit.Bukkit;
import org.bukkit.Material;
import org.bukkit.entity.Player; import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler; import org.bukkit.event.EventHandler;
import org.bukkit.event.Listener; import org.bukkit.event.Listener;
@@ -52,7 +52,7 @@ public class PrepareSchem implements Listener {
new OneShotStateDependent(ArenaMode.Prepare, FightState.PostSchemSetup, () -> Bukkit.getScheduler().runTaskLater(FightSystem.getPlugin(), () -> { new OneShotStateDependent(ArenaMode.Prepare, FightState.PostSchemSetup, () -> Bukkit.getScheduler().runTaskLater(FightSystem.getPlugin(), () -> {
stationaryMovingPistons.clear(); stationaryMovingPistons.clear();
Fight.getUnrotated().getSchemRegion().forEach((x, y, z) -> { Fight.getUnrotated().getSchemRegion().forEach((x, y, z) -> {
if (FlatteningWrapper.impl.checkPistonMoving(Config.world.getBlockAt(x, y, z))) { if (Config.world.getBlockAt(x, y, z).getType() == Material.MOVING_PISTON) {
stationaryMovingPistons.add(new Vector(x, y, z)); stationaryMovingPistons.add(new Vector(x, y, z));
} }
}); });
@@ -76,7 +76,7 @@ public class PrepareSchem implements Listener {
try { try {
region.forEach((x, y, z) -> { region.forEach((x, y, z) -> {
if (FlatteningWrapper.impl.checkPistonMoving(Config.world.getBlockAt(x, y, z)) && !stationaryMovingPistons.contains(new Vector(x, y, z))) { if (Config.world.getBlockAt(x, y, z).getType() == Material.MOVING_PISTON && !stationaryMovingPistons.contains(new Vector(x, y, z))) {
FightSystem.getMessage().broadcast("PREPARE_ACTIVE_PISTON"); FightSystem.getMessage().broadcast("PREPARE_ACTIVE_PISTON");
Bukkit.shutdown(); Bukkit.shutdown();
throw new IllegalStateException(); throw new IllegalStateException();
@@ -22,6 +22,7 @@ package de.steamwar.fightsystem.listener;
import com.comphenix.tinyprotocol.TinyProtocol; import com.comphenix.tinyprotocol.TinyProtocol;
import de.steamwar.Reflection; import de.steamwar.Reflection;
import de.steamwar.fightsystem.ArenaMode; import de.steamwar.fightsystem.ArenaMode;
import de.steamwar.fightsystem.Config;
import de.steamwar.fightsystem.FightSystem; import de.steamwar.fightsystem.FightSystem;
import de.steamwar.fightsystem.events.TeamDeathEvent; import de.steamwar.fightsystem.events.TeamDeathEvent;
import de.steamwar.fightsystem.events.TeamLeaveEvent; import de.steamwar.fightsystem.events.TeamLeaveEvent;
@@ -34,17 +35,17 @@ import de.steamwar.fightsystem.states.FightState;
import de.steamwar.fightsystem.states.StateDependent; import de.steamwar.fightsystem.states.StateDependent;
import de.steamwar.fightsystem.states.StateDependentListener; import de.steamwar.fightsystem.states.StateDependentListener;
import de.steamwar.fightsystem.states.StateDependentTask; import de.steamwar.fightsystem.states.StateDependentTask;
import de.steamwar.fightsystem.utils.BountifulWrapper;
import de.steamwar.fightsystem.utils.CraftbukkitWrapper;
import de.steamwar.fightsystem.utils.FlatteningWrapper;
import de.steamwar.fightsystem.utils.SWSound; import de.steamwar.fightsystem.utils.SWSound;
import de.steamwar.linkage.Linked; import de.steamwar.linkage.Linked;
import net.minecraft.network.protocol.Packet;
import net.minecraft.network.protocol.game.ServerboundPlayerActionPacket; import net.minecraft.network.protocol.game.ServerboundPlayerActionPacket;
import net.minecraft.network.protocol.game.ServerboundUseItemPacket; import net.minecraft.network.protocol.game.ServerboundUseItemPacket;
import net.minecraft.world.InteractionHand;
import net.minecraft.world.entity.item.PrimedTnt; import net.minecraft.world.entity.item.PrimedTnt;
import org.bukkit.Bukkit; import org.bukkit.Bukkit;
import org.bukkit.Location; import org.bukkit.Location;
import org.bukkit.Material; import org.bukkit.Material;
import org.bukkit.craftbukkit.CraftWorld;
import org.bukkit.entity.Entity; import org.bukkit.entity.Entity;
import org.bukkit.entity.EntityType; import org.bukkit.entity.EntityType;
import org.bukkit.entity.LivingEntity; import org.bukkit.entity.LivingEntity;
@@ -63,6 +64,7 @@ import java.util.Random;
import java.util.function.BiFunction; import java.util.function.BiFunction;
import java.util.function.Consumer; import java.util.function.Consumer;
import java.util.function.Predicate; import java.util.function.Predicate;
import java.util.stream.StreamSupport;
@Linked @Linked
public class Recording implements Listener { public class Recording implements Listener {
@@ -83,12 +85,22 @@ public class Recording implements Listener {
public static final Class<?> primedTnt = PrimedTnt.class; public static final Class<?> primedTnt = PrimedTnt.class;
public static void iterateOverEntities(Predicate<Object> filter, Consumer<Entity> consumer) { public static void iterateOverEntities(Predicate<Object> filter, Consumer<Entity> consumer) {
CraftbukkitWrapper.impl.entityIterator().filter(filter).map(net.minecraft.world.entity.Entity::getBukkitEntity).forEach(consumer); StreamSupport.stream(((CraftWorld) Config.world).getHandle().getEntities().getAll().spliterator(), false).filter(filter).map(net.minecraft.world.entity.Entity::getBukkitEntity).forEach(consumer);
} }
public Recording() { public Recording() {
new StateDependentListener(ArenaMode.AntiReplay, FightState.All, this); new StateDependentListener(ArenaMode.AntiReplay, FightState.All, this);
new StateDependentListener(ArenaMode.AntiReplay, FightState.All, BountifulWrapper.impl.newHandSwapRecorder()); new StateDependentListener(ArenaMode.AntiReplay, FightState.All, new Listener() {
@EventHandler(priority = EventPriority.HIGH, ignoreCancelled = true)
public void onItemSwap(PlayerSwapHandItemsEvent e) {
if (isNotSent(e.getPlayer()))
return;
Player player = e.getPlayer();
GlobalRecorder.getInstance().item(player, disarmNull(e.getMainHandItem()), "MAINHAND");
GlobalRecorder.getInstance().item(player, disarmNull(e.getOffHandItem()), "OFFHAND");
}
});
new StateDependent(ArenaMode.AntiReplay, FightState.Ingame) { new StateDependent(ArenaMode.AntiReplay, FightState.Ingame) {
@Override @Override
public void enable() { public void enable() {
@@ -102,18 +114,18 @@ public class Recording implements Listener {
} }
}.register(); }.register();
new StateDependent(ArenaMode.AntiReplay, FightState.Ingame) { new StateDependent(ArenaMode.AntiReplay, FightState.Ingame) {
private final BiFunction<Player, Object, Object> place = Recording.this::blockPlace; private final BiFunction<Player, ServerboundUseItemPacket, Object> place = Recording.this::blockPlace;
private final BiFunction<Player, Object, Object> dig = Recording.this::blockDig; private final BiFunction<Player, Object, Object> dig = Recording.this::blockDig;
@Override @Override
public void enable() { public void enable() {
TinyProtocol.instance.addFilter(blockPlacePacket, place); TinyProtocol.instance.addFilter(ServerboundUseItemPacket.class, place);
TinyProtocol.instance.addFilter(blockDigPacket, dig); TinyProtocol.instance.addFilter(blockDigPacket, dig);
} }
@Override @Override
public void disable() { public void disable() {
TinyProtocol.instance.removeFilter(blockPlacePacket, place); TinyProtocol.instance.removeFilter(ServerboundUseItemPacket.class, place);
TinyProtocol.instance.removeFilter(blockDigPacket, dig); TinyProtocol.instance.removeFilter(blockDigPacket, dig);
} }
}.register(); }.register();
@@ -131,7 +143,7 @@ public class Recording implements Listener {
GlobalRecorder.getInstance().entitySpeed(entity); GlobalRecorder.getInstance().entitySpeed(entity);
} }
private static final Class<?> blockDigPacket = ServerboundPlayerActionPacket.class; private static final Class<? extends Packet<?>> blockDigPacket = ServerboundPlayerActionPacket.class;
private static final Class<?> playerDigType = blockDigPacket.getDeclaredClasses()[0]; private static final Class<?> playerDigType = blockDigPacket.getDeclaredClasses()[0];
private static final Reflection.Field<?> blockDigType = Reflection.getField(blockDigPacket, playerDigType, 0); private static final Reflection.Field<?> blockDigType = Reflection.getField(blockDigPacket, playerDigType, 0);
private static final Object releaseUseItem = playerDigType.getEnumConstants()[5]; private static final Object releaseUseItem = playerDigType.getEnumConstants()[5];
@@ -143,11 +155,9 @@ public class Recording implements Listener {
return packet; return packet;
} }
public static final Class<?> blockPlacePacket = ServerboundUseItemPacket.class; private Object blockPlace(Player p, ServerboundUseItemPacket packet) {
boolean mainHand = packet.getHand() == InteractionHand.MAIN_HAND;
private Object blockPlace(Player p, Object packet) { if (!isNotSent(p) && (mainHand ? p.getInventory().getItemInMainHand() : p.getInventory().getItemInOffHand()).getType() == Material.BOW) {
boolean mainHand = BountifulWrapper.impl.mainHand(packet);
if (!isNotSent(p) && BountifulWrapper.impl.bowInHand(mainHand, p)) {
GlobalRecorder.getInstance().bowSpan(p, true, !mainHand); GlobalRecorder.getInstance().bowSpan(p, true, !mainHand);
} }
return packet; return packet;
@@ -177,7 +187,7 @@ public class Recording implements Listener {
@EventHandler(priority = EventPriority.HIGH, ignoreCancelled = true) @EventHandler(priority = EventPriority.HIGH, ignoreCancelled = true)
public void onBlockPhysics(BlockPhysicsEvent e) { public void onBlockPhysics(BlockPhysicsEvent e) {
if (FlatteningWrapper.impl.doRecord(e)) { if (e.getBlock() == e.getSourceBlock() || e.getChangedType() == Material.AIR) {
GlobalRecorder.getInstance().blockChange(e.getBlock()); GlobalRecorder.getInstance().blockChange(e.getBlock());
} }
} }
@@ -285,7 +295,8 @@ public class Recording implements Listener {
if (!fp.isLiving()) continue; if (!fp.isLiving()) continue;
fp.ifPlayer(player -> { fp.ifPlayer(player -> {
BountifulWrapper.impl.recordHandItems(player); GlobalRecorder.getInstance().item(player, disarmNull(player.getInventory().getItemInMainHand()), "MAINHAND");
GlobalRecorder.getInstance().item(player, disarmNull(player.getInventory().getItemInOffHand()), "OFFHAND");
GlobalRecorder.getInstance().item(player, disarmNull(player.getInventory().getHelmet()), "HEAD"); GlobalRecorder.getInstance().item(player, disarmNull(player.getInventory().getHelmet()), "HEAD");
GlobalRecorder.getInstance().item(player, disarmNull(player.getInventory().getChestplate()), "CHEST"); GlobalRecorder.getInstance().item(player, disarmNull(player.getInventory().getChestplate()), "CHEST");
GlobalRecorder.getInstance().item(player, disarmNull(player.getInventory().getLeggings()), "LEGS"); GlobalRecorder.getInstance().item(player, disarmNull(player.getInventory().getLeggings()), "LEGS");
@@ -25,11 +25,13 @@ import de.steamwar.fightsystem.fight.Fight;
import de.steamwar.fightsystem.fight.FightTeam; import de.steamwar.fightsystem.fight.FightTeam;
import de.steamwar.fightsystem.states.FightState; import de.steamwar.fightsystem.states.FightState;
import de.steamwar.fightsystem.states.StateDependentListener; import de.steamwar.fightsystem.states.StateDependentListener;
import de.steamwar.fightsystem.utils.FlatteningWrapper;
import de.steamwar.linkage.Linked; import de.steamwar.linkage.Linked;
import org.bukkit.Location; import org.bukkit.Location;
import org.bukkit.Material;
import org.bukkit.block.Block; import org.bukkit.block.Block;
import org.bukkit.block.BlockFace; import org.bukkit.block.BlockFace;
import org.bukkit.block.data.BlockData;
import org.bukkit.block.data.Waterlogged;
import org.bukkit.entity.EntityType; import org.bukkit.entity.EntityType;
import org.bukkit.event.EventHandler; import org.bukkit.event.EventHandler;
import org.bukkit.event.Listener; import org.bukkit.event.Listener;
@@ -101,7 +103,25 @@ public class WaterRemover implements Listener {
if (!Config.BlueExtendRegion.inRegion(b) && !Config.RedExtendRegion.inRegion(b)) return; if (!Config.BlueExtendRegion.inRegion(b) && !Config.RedExtendRegion.inRegion(b)) return;
//checks for water and removes it, if present //checks for water and removes it, if present
if (!FlatteningWrapper.impl.removeWater(b)) return; boolean result = true;
Material type = b.getType();
if (type == Material.WATER || type == Material.LAVA) {
b.setType(Material.AIR);
} else {
BlockData data = b.getBlockData();
if (!(data instanceof Waterlogged)) {
result = false;
} else {
Waterlogged waterlogged = (Waterlogged) data;
if (waterlogged.isWaterlogged()) {
b.setType(Material.AIR);
} else {
result = false;
}
}
}
if (!result) return;
if (b.getY() < MIN_Y) return; if (b.getY() < MIN_Y) return;
@@ -42,11 +42,14 @@ import de.steamwar.techhider.BlockIds;
import net.md_5.bungee.api.ChatMessageType; import net.md_5.bungee.api.ChatMessageType;
import net.md_5.bungee.api.chat.BaseComponent; import net.md_5.bungee.api.chat.BaseComponent;
import net.md_5.bungee.api.chat.TextComponent; import net.md_5.bungee.api.chat.TextComponent;
import net.minecraft.core.BlockPos;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.world.entity.Pose; import net.minecraft.world.entity.Pose;
import org.bukkit.Bukkit; import net.minecraft.world.level.block.Block;
import org.bukkit.Location; import net.minecraft.world.level.block.state.BlockState;
import org.bukkit.Material; import org.bukkit.*;
import org.bukkit.Sound; import org.bukkit.craftbukkit.CraftWorld;
import org.bukkit.craftbukkit.util.CraftMagicNumbers;
import org.bukkit.enchantments.Enchantment; import org.bukkit.enchantments.Enchantment;
import org.bukkit.entity.EntityType; import org.bukkit.entity.EntityType;
import org.bukkit.entity.Player; import org.bukkit.entity.Player;
@@ -72,7 +75,7 @@ public class PacketProcessor implements Listener {
private static final org.bukkit.scoreboard.Team team = FightScoreboard.getBukkitTeam("Replay"); private static final org.bukkit.scoreboard.Team team = FightScoreboard.getBukkitTeam("Replay");
static { static {
BountifulWrapper.impl.setNametagVisibility(team); team.setOption(org.bukkit.scoreboard.Team.Option.NAME_TAG_VISIBILITY, org.bukkit.scoreboard.Team.OptionStatus.FOR_OWN_TEAM);
team.setNameTagVisibility(NameTagVisibility.NEVER); team.setNameTagVisibility(NameTagVisibility.NEVER);
} }
@@ -460,8 +463,15 @@ public class PacketProcessor implements Listener {
if (!Config.ArenaRegion.in2dRegion(x, z)) return; //Outside of the arena if (!Config.ArenaRegion.in2dRegion(x, z)) return; //Outside of the arena
execSync(() -> { execSync(() -> {
BlockIdWrapper.impl.setBlock(Config.world, x, y, z, TechHiderWrapper.ENABLED && hiddenBlockIds.contains(blockState) ? obfuscateWith : blockState); int blockState1 = TechHiderWrapper.ENABLED && hiddenBlockIds.contains(blockState) ? obfuscateWith : blockState;
FightSystem.getHullHider().blockUpdate(Config.world.getBlockAt(x, y, z), BlockIdWrapper.impl.idToMaterial(blockState)); BlockState blockData = Block.stateById(blockState1);
ServerLevel level = ((CraftWorld) Config.world).getHandle();
BlockPos pos = new BlockPos(x, y, z);
level.removeBlockEntity(pos);
level.setBlock(pos, blockData, blockState1);
level.getChunkSource().blockChanged(pos);
FightSystem.getHullHider().blockUpdate(Config.world.getBlockAt(x, y, z), CraftMagicNumbers.getMaterial(Block.stateById(blockState)).getItemType());
}); });
} }
@@ -478,7 +488,7 @@ public class PacketProcessor implements Listener {
double finalX = x; double finalX = x;
double finalZ = z; double finalZ = z;
execSync(() -> BountifulWrapper.impl.spawnParticle(Config.world, particleName, finalX + Config.ArenaRegion.getMinX(), y + Config.BluePasteRegion.getMinY(), finalZ + Config.ArenaRegion.getMinZ())); execSync(() -> Config.world.spawnParticle(Particle.valueOf(particleName), finalX + Config.ArenaRegion.getMinX(), y + Config.BluePasteRegion.getMinY(), finalZ + Config.ArenaRegion.getMinZ(), 1));
} }
private void sound() throws IOException { private void sound() throws IOException {
@@ -502,7 +512,10 @@ public class PacketProcessor implements Listener {
Sound sound = Sound.valueOf(soundName); Sound sound = Sound.valueOf(soundName);
execSync(() -> WorldOfColorWrapper.impl.playSound(new Location(Config.world, x, y, z), sound, soundCategory, volume, pitch)); execSync(() -> {
Location location = new Location(Config.world, x, y, z);
location.getWorld().playSound(location, sound, SoundCategory.valueOf(soundCategory), volume, pitch);
});
} }
private void soundAtPlayer() throws IOException { private void soundAtPlayer() throws IOException {
@@ -603,7 +616,7 @@ public class PacketProcessor implements Listener {
Bukkit.getOnlinePlayers().forEach(p -> { Bukkit.getOnlinePlayers().forEach(p -> {
p.resetTitle(); p.resetTitle();
WorldOfColorWrapper.impl.sendTitle(p, title, subtitle, 5, 40, 5); p.sendTitle(title, subtitle, 5, 40, 5);
}); });
} }
@@ -25,7 +25,6 @@ import de.steamwar.fightsystem.fight.Fight;
import de.steamwar.fightsystem.fight.FightPlayer; import de.steamwar.fightsystem.fight.FightPlayer;
import de.steamwar.fightsystem.fight.FightTeam; import de.steamwar.fightsystem.fight.FightTeam;
import de.steamwar.fightsystem.states.FightState; import de.steamwar.fightsystem.states.FightState;
import de.steamwar.fightsystem.utils.BlockIdWrapper;
import de.steamwar.fightsystem.utils.CraftbukkitWrapper; import de.steamwar.fightsystem.utils.CraftbukkitWrapper;
import de.steamwar.fightsystem.utils.Message; import de.steamwar.fightsystem.utils.Message;
import de.steamwar.fightsystem.utils.SWSound; import de.steamwar.fightsystem.utils.SWSound;
@@ -35,6 +34,7 @@ import de.steamwar.sql.SteamwarUser;
import org.bukkit.Bukkit; import org.bukkit.Bukkit;
import org.bukkit.Location; import org.bukkit.Location;
import org.bukkit.block.Block; import org.bukkit.block.Block;
import org.bukkit.craftbukkit.block.CraftBlockState;
import org.bukkit.entity.Entity; import org.bukkit.entity.Entity;
import org.bukkit.entity.LivingEntity; import org.bukkit.entity.LivingEntity;
import org.bukkit.entity.Player; import org.bukkit.entity.Player;
@@ -219,7 +219,7 @@ public interface Recorder {
} }
default void blockChange(Block block) { default void blockChange(Block block) {
int blockState = BlockIdWrapper.impl.blockToId(block); int blockState = net.minecraft.world.level.block.Block.getId(((CraftBlockState) block.getState()).getHandle());
int shortX = block.getX() - Config.ArenaRegion.getMinX(); int shortX = block.getX() - Config.ArenaRegion.getMinX();
int shortY = block.getY() - Config.BluePasteRegion.getMinY(); int shortY = block.getY() - Config.BluePasteRegion.getMinY();
@@ -1,75 +0,0 @@
/*
* This file is a part of the SteamWar software.
*
* Copyright (C) 2025 SteamWar.de-Serverteam
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package de.steamwar.fightsystem.utils;
import com.comphenix.tinyprotocol.TinyProtocol;
import com.mojang.authlib.GameProfile;
import de.steamwar.core.ProtocolWrapper;
import de.steamwar.fightsystem.FightSystem;
import net.minecraft.core.BlockPos;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.world.level.block.state.BlockState;
import org.bukkit.GameMode;
import org.bukkit.Material;
import org.bukkit.World;
import org.bukkit.block.Block;
import org.bukkit.craftbukkit.CraftWorld;
import org.bukkit.craftbukkit.block.CraftBlockState;
import org.bukkit.craftbukkit.util.CraftMagicNumbers;
import org.bukkit.entity.Entity;
import org.bukkit.entity.Player;
public class BlockIdWrapper {
public static final BlockIdWrapper impl = new BlockIdWrapper();
public Material idToMaterial(int blockState) {
return CraftMagicNumbers.getMaterial(net.minecraft.world.level.block.Block.stateById(blockState)).getItemType();
}
public int blockToId(Block block) {
return net.minecraft.world.level.block.Block.getId(((CraftBlockState) block.getState()).getHandle());
}
public void setBlock(World world, int x, int y, int z, int blockState) {
BlockState blockData = net.minecraft.world.level.block.Block.stateById(blockState);
ServerLevel level = ((CraftWorld) world).getHandle();
BlockPos pos = new BlockPos(x, y, z);
level.removeBlockEntity(pos);
level.setBlock(pos, blockData, blockState);
level.getChunkSource().blockChanged(pos);
}
public void trackEntity(Player player, Entity entity) {
if (entity instanceof Player) {
TinyProtocol.instance.sendPacket(player, ProtocolWrapper.playerInfoPacketConstructor(ProtocolWrapper.PlayerInfoAction.REMOVE, new GameProfile(entity.getUniqueId(), entity.getName()), GameMode.CREATIVE));
}
player.showEntity(FightSystem.getPlugin(), entity);
}
public void untrackEntity(Player player, Entity entity) {
player.hideEntity(FightSystem.getPlugin(), entity);
if (entity instanceof Player) {
TinyProtocol.instance.sendPacket(player, ProtocolWrapper.playerInfoPacketConstructor(ProtocolWrapper.PlayerInfoAction.ADD, new GameProfile(entity.getUniqueId(), entity.getName()), GameMode.CREATIVE));
}
}
}
@@ -1,158 +0,0 @@
/*
* This file is a part of the SteamWar software.
*
* Copyright (C) 2025 SteamWar.de-Serverteam
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package de.steamwar.fightsystem.utils;
import de.steamwar.Reflection;
import de.steamwar.fightsystem.fight.Fight;
import de.steamwar.fightsystem.fight.FightTeam;
import de.steamwar.fightsystem.listener.Recording;
import de.steamwar.fightsystem.record.GlobalRecorder;
import net.minecraft.world.InteractionHand;
import org.bukkit.*;
import org.bukkit.attribute.Attribute;
import org.bukkit.attribute.AttributeInstance;
import org.bukkit.boss.BarColor;
import org.bukkit.boss.BarStyle;
import org.bukkit.boss.BossBar;
import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
import org.bukkit.event.EventPriority;
import org.bukkit.event.Listener;
import org.bukkit.event.player.PlayerPickupArrowEvent;
import org.bukkit.event.player.PlayerSwapHandItemsEvent;
import org.bukkit.scoreboard.Team;
import java.util.HashMap;
import java.util.Map;
public class BountifulWrapper {
public static final BountifulWrapper impl = new BountifulWrapper();
private static final Class<?> enumHand = InteractionHand.class;
private static final Object mainHand = enumHand.getEnumConstants()[0];
private static final Reflection.Field<?> blockPlaceHand = Reflection.getField(Recording.blockPlacePacket, enumHand, 0);
public boolean mainHand(Object packet) {
return blockPlaceHand.get(packet) == mainHand;
}
public boolean bowInHand(boolean mainHand, Player p) {
return (mainHand ? p.getInventory().getItemInMainHand() : p.getInventory().getItemInOffHand()).getType() == Material.BOW;
}
public void setAttackSpeed(Player player) {
AttributeInstance attribute = player.getAttribute(Attribute.ATTACK_SPEED);
attribute.setBaseValue(16);
}
public void setNametagVisibility(Team team) {
team.setOption(Team.Option.NAME_TAG_VISIBILITY, Team.OptionStatus.FOR_OWN_TEAM);
}
public Listener newDenyArrowPickupListener() {
return new Listener() {
@EventHandler
public void onArrowPickup(PlayerPickupArrowEvent e) {
if (Fight.fighting(e.getPlayer())) e.setCancelled(true);
}
};
}
public Listener newDenyHandSwapListener() {
return new Listener() {
@EventHandler
public void onSwapItems(PlayerSwapHandItemsEvent event) {
if (Fight.fighting(event.getPlayer())) event.setCancelled(true);
}
};
}
public void recordHandItems(Player player) {
GlobalRecorder.getInstance().item(player, Recording.disarmNull(player.getInventory().getItemInMainHand()), "MAINHAND");
GlobalRecorder.getInstance().item(player, Recording.disarmNull(player.getInventory().getItemInOffHand()), "OFFHAND");
}
public Listener newHandSwapRecorder() {
return new Listener() {
@EventHandler(priority = EventPriority.HIGH, ignoreCancelled = true)
public void onItemSwap(PlayerSwapHandItemsEvent e) {
if (Recording.isNotSent(e.getPlayer()))
return;
Player player = e.getPlayer();
GlobalRecorder.getInstance().item(player, Recording.disarmNull(e.getMainHandItem()), "MAINHAND");
GlobalRecorder.getInstance().item(player, Recording.disarmNull(e.getOffHandItem()), "OFFHAND");
}
};
}
public void spawnParticle(World world, String particleName, double x, double y, double z) {
world.spawnParticle(Particle.valueOf(particleName), x, y, z, 1);
}
private final Map<Player, BossBar> barMap = new HashMap<>();
public void sendBar(Player player, FightTeam team, double progress, String text) {
barMap.keySet().removeIf(p -> !p.isOnline());
if (!barMap.containsKey(player)) {
BossBar bar = Bukkit.createBossBar(player.getName(), BarColor.WHITE, BarStyle.SOLID);
barMap.put(player, bar);
bar.addPlayer(player);
}
BossBar bar = barMap.get(player);
BarColor color = chat2bar(team.getColor());
if (bar.getColor() != color) bar.setColor(color);
if (bar.getProgress() != progress) bar.setProgress(progress);
if (!bar.getTitle().equals(text)) bar.setTitle(text);
}
private BarColor chat2bar(ChatColor color) {
switch (color) {
case DARK_BLUE:
case DARK_AQUA:
case BLUE:
case AQUA:
return BarColor.BLUE;
case GREEN:
case DARK_GREEN:
return BarColor.GREEN;
case DARK_RED:
case RED:
return BarColor.RED;
case DARK_PURPLE:
return BarColor.PURPLE;
case GOLD:
case YELLOW:
return BarColor.YELLOW;
case LIGHT_PURPLE:
return BarColor.PINK;
case BLACK:
case WHITE:
case GRAY:
case DARK_GRAY:
default:
return BarColor.WHITE;
}
}
}
@@ -19,11 +19,9 @@
package de.steamwar.fightsystem.utils; package de.steamwar.fightsystem.utils;
import de.steamwar.fightsystem.Config;
import net.minecraft.core.BlockPos; import net.minecraft.core.BlockPos;
import net.minecraft.world.level.chunk.LevelChunk; import net.minecraft.world.level.chunk.LevelChunk;
import net.minecraft.world.level.chunk.LevelChunkSection; import net.minecraft.world.level.chunk.LevelChunkSection;
import org.bukkit.GameRule;
import org.bukkit.World; import org.bukkit.World;
import org.bukkit.craftbukkit.CraftWorld; import org.bukkit.craftbukkit.CraftWorld;
import org.bukkit.craftbukkit.entity.CraftEntity; import org.bukkit.craftbukkit.entity.CraftEntity;
@@ -31,26 +29,12 @@ import org.bukkit.entity.Entity;
import java.util.HashSet; import java.util.HashSet;
import java.util.Set; import java.util.Set;
import java.util.stream.Stream;
import java.util.stream.StreamSupport;
public class CraftbukkitWrapper { public class CraftbukkitWrapper {
public static final CraftbukkitWrapper impl = new CraftbukkitWrapper(); public static final CraftbukkitWrapper impl = new CraftbukkitWrapper();
protected net.minecraft.world.entity.Entity getEntity(Entity e) {
return ((CraftEntity) e).getHandle();
}
public float headRotation(Entity e) { public float headRotation(Entity e) {
return getEntity(e).getYHeadRot(); return ((CraftEntity) e).getHandle().getYHeadRot();
}
public Stream<net.minecraft.world.entity.Entity> entityIterator() {
return StreamSupport.stream(((CraftWorld) Config.world).getHandle().getEntities().getAll().spliterator(), false);
}
public void setupGamerule() {
Config.world.setGameRule(GameRule.LOCATOR_BAR, false);
} }
private LevelChunk getChunk(World world, int x, int z) { private LevelChunk getChunk(World world, int x, int z) {
@@ -34,12 +34,12 @@ import de.steamwar.fightsystem.winconditions.Wincondition;
import de.steamwar.linkage.Linked; import de.steamwar.linkage.Linked;
import lombok.Getter; import lombok.Getter;
import org.bukkit.Bukkit; import org.bukkit.Bukkit;
import org.bukkit.boss.BarColor;
import org.bukkit.boss.BarStyle;
import org.bukkit.boss.BossBar;
import org.bukkit.entity.Player; import org.bukkit.entity.Player;
import java.util.ArrayList; import java.util.*;
import java.util.LinkedList;
import java.util.List;
import java.util.Queue;
import java.util.logging.Level; import java.util.logging.Level;
import java.util.stream.Collectors; import java.util.stream.Collectors;
@@ -142,6 +142,7 @@ public class FightUI {
BossBarType.RED_LEFT.text = leftRedText; BossBarType.RED_LEFT.text = leftRedText;
} }
private final Map<Player, BossBar> barMap = new HashMap<>();
private void sendToPlayers() { private void sendToPlayers() {
Bukkit.getOnlinePlayers().forEach(player -> { Bukkit.getOnlinePlayers().forEach(player -> {
BossBarType bar = BossBarType.byAngle(CraftbukkitWrapper.impl.headRotation(player)); BossBarType bar = BossBarType.byAngle(CraftbukkitWrapper.impl.headRotation(player));
@@ -154,7 +155,55 @@ public class FightUI {
} }
} }
BountifulWrapper.impl.sendBar(player, bar.team, bar.progress, FightSystem.getMessage().parse(bar.text.getMsg(), player, params)); String text = FightSystem.getMessage().parse(bar.text.getMsg(), player, params);
barMap.keySet().removeIf(p -> !p.isOnline());
if (!barMap.containsKey(player)) {
BossBar bar1 = Bukkit.createBossBar(player.getName(), BarColor.WHITE, BarStyle.SOLID);
barMap.put(player, bar1);
bar1.addPlayer(player);
}
BossBar bar1 = barMap.get(player);
BarColor color;
switch (bar.team.getColor()) {
case DARK_BLUE:
case DARK_AQUA:
case BLUE:
case AQUA:
color = BarColor.BLUE;
break;
case GREEN:
case DARK_GREEN:
color = BarColor.GREEN;
break;
case DARK_RED:
case RED:
color = BarColor.RED;
break;
case DARK_PURPLE:
color = BarColor.PURPLE;
break;
case GOLD:
case YELLOW:
color = BarColor.YELLOW;
break;
case LIGHT_PURPLE:
color = BarColor.PINK;
break;
case BLACK:
case WHITE:
case GRAY:
case DARK_GRAY:
default:
color = BarColor.WHITE;
break;
}
if (bar1.getColor() != color) bar1.setColor(color);
if (bar1.getProgress() != bar.progress) bar1.setProgress(bar.progress);
if (!bar1.getTitle().equals(text)) bar1.setTitle(text);
}); });
} }
@@ -208,9 +257,17 @@ public class FightUI {
Bukkit.getOnlinePlayers().forEach(Player::resetTitle); Bukkit.getOnlinePlayers().forEach(Player::resetTitle);
if (winner != null) { if (winner != null) {
Bukkit.getOnlinePlayers().forEach(p -> WorldOfColorWrapper.impl.sendTitle(p, FightSystem.getMessage().parse("UI_WIN", p, winner.getColor(), winner.getName()), FightSystem.getMessage().parse(subtitle, p, params), 5, 40, 5)); Bukkit.getOnlinePlayers().forEach(p -> {
String title = FightSystem.getMessage().parse("UI_WIN", p, winner.getColor(), winner.getName());
String subtitle1 = FightSystem.getMessage().parse(subtitle, p, params);
p.sendTitle(title, subtitle1, 5, 40, 5);
});
} else { } else {
Bukkit.getOnlinePlayers().forEach(p -> WorldOfColorWrapper.impl.sendTitle(p, FightSystem.getMessage().parse("UI_DRAW", p), FightSystem.getMessage().parse(subtitle, p, params), 5, 40, 5)); Bukkit.getOnlinePlayers().forEach(p -> {
String title = FightSystem.getMessage().parse("UI_DRAW", p);
String subtitle1 = FightSystem.getMessage().parse(subtitle, p, params);
p.sendTitle(title, subtitle1, 5, 40, 5);
});
} }
} }
@@ -232,7 +289,10 @@ public class FightUI {
} }
Message message = queue.poll(); Message message = queue.poll();
Bukkit.getOnlinePlayers().forEach(p -> WorldOfColorWrapper.impl.sendTitle(p, " ", FightSystem.getMessage().parse(message.getMsg(), p, message.getParams()), 5, 40, 5)); Bukkit.getOnlinePlayers().forEach(p -> {
String subtitle = FightSystem.getMessage().parse(message.getMsg(), p, message.getParams());
p.sendTitle(" ", subtitle, 5, 40, 5);
});
Bukkit.getScheduler().runTaskLater(FightSystem.getPlugin(), FightUI::printSubtitle, 50); Bukkit.getScheduler().runTaskLater(FightSystem.getPlugin(), FightUI::printSubtitle, 50);
subtitleScheduled = true; subtitleScheduled = true;
} }
@@ -1,97 +0,0 @@
/*
* This file is a part of the SteamWar software.
*
* Copyright (C) 2025 SteamWar.de-Serverteam
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package de.steamwar.fightsystem.utils;
import org.bukkit.Material;
import org.bukkit.World;
import org.bukkit.block.Block;
import org.bukkit.block.data.BlockData;
import org.bukkit.block.data.Waterlogged;
import org.bukkit.block.data.type.Dispenser;
import org.bukkit.entity.Player;
import org.bukkit.entity.Pose;
import org.bukkit.event.block.BlockPhysicsEvent;
import org.bukkit.inventory.ItemStack;
import org.bukkit.inventory.meta.BlockDataMeta;
import org.bukkit.inventory.meta.ItemMeta;
public class FlatteningWrapper {
public static final FlatteningWrapper impl = new FlatteningWrapper();
public boolean isWater(Block block) {
if (block.getType() == Material.WATER) return true;
BlockData data = block.getBlockData();
if (!(data instanceof Waterlogged)) return false;
return ((Waterlogged) data).isWaterlogged();
}
public boolean removeWater(Block block) {
Material type = block.getType();
if (type == Material.WATER || type == Material.LAVA) {
block.setType(Material.AIR);
return true;
}
BlockData data = block.getBlockData();
if (!(data instanceof Waterlogged)) return false;
Waterlogged waterlogged = (Waterlogged) data;
if (waterlogged.isWaterlogged()) {
block.setType(Material.AIR);
return true;
}
return false;
}
public boolean containsBlockMeta(ItemMeta meta) {
return meta instanceof BlockDataMeta && ((BlockDataMeta) meta).hasBlockData();
}
public boolean hasAttributeModifier(ItemStack stack) {
return stack.hasItemMeta() && stack.getItemMeta() != null && stack.getItemMeta().hasAttributeModifiers();
}
public boolean doRecord(BlockPhysicsEvent e) {
return e.getBlock() == e.getSourceBlock() || e.getChangedType() == Material.AIR;
}
public void forceLoadChunk(World world, int cX, int cZ) {
world.setChunkForceLoaded(cX, cZ, true);
}
public boolean checkPistonMoving(Block block) {
return block.getType() == Material.MOVING_PISTON;
}
public boolean isFacingWater(Block dispenser) {
return dispenser.getRelative(((Dispenser) dispenser.getBlockData()).getFacing()).isLiquid();
}
public boolean isCrouching(Player player) {
return player.getPose() == Pose.SWIMMING;
}
public void sendBlockChange(Player player, Block block, Material type) {
player.sendBlockChange(block.getLocation(), type.createBlockData());
}
}
@@ -20,15 +20,25 @@
package de.steamwar.fightsystem.utils; package de.steamwar.fightsystem.utils;
import com.comphenix.tinyprotocol.TinyProtocol; import com.comphenix.tinyprotocol.TinyProtocol;
import com.mojang.authlib.GameProfile;
import de.steamwar.core.ProtocolWrapper;
import de.steamwar.entity.REntity; import de.steamwar.entity.REntity;
import de.steamwar.fightsystem.Config; import de.steamwar.fightsystem.Config;
import de.steamwar.fightsystem.FightSystem; import de.steamwar.fightsystem.FightSystem;
import de.steamwar.fightsystem.fight.FightTeam; import de.steamwar.fightsystem.fight.FightTeam;
import it.unimi.dsi.fastutil.shorts.Short2ObjectArrayMap;
import lombok.AllArgsConstructor; import lombok.AllArgsConstructor;
import lombok.Getter; import lombok.Getter;
import net.minecraft.core.BlockPos;
import net.minecraft.core.SectionPos;
import net.minecraft.network.protocol.game.ClientboundBlockUpdatePacket;
import net.minecraft.network.protocol.game.ClientboundSectionBlocksUpdatePacket;
import net.minecraft.world.level.block.state.BlockState;
import org.bukkit.GameMode;
import org.bukkit.Location; import org.bukkit.Location;
import org.bukkit.Material; import org.bukkit.Material;
import org.bukkit.block.Block; import org.bukkit.block.Block;
import org.bukkit.craftbukkit.block.data.CraftBlockData;
import org.bukkit.entity.Entity; import org.bukkit.entity.Entity;
import org.bukkit.entity.Player; import org.bukkit.entity.Player;
@@ -91,7 +101,7 @@ public class Hull {
public void addPlayer(Player player) { public void addPlayer(Player player) {
if (players.add(player)) { if (players.add(player)) {
for (Entity entity : entities) { for (Entity entity : entities) {
BlockIdWrapper.impl.untrackEntity(player, entity); untrackEntity(player, entity);
} }
} }
} }
@@ -99,7 +109,7 @@ public class Hull {
public void removePlayer(Player player, boolean activeRemoval) { public void removePlayer(Player player, boolean activeRemoval) {
if (players.remove(player) && activeRemoval) { if (players.remove(player) && activeRemoval) {
for (Entity entity : entities) { for (Entity entity : entities) {
BlockIdWrapper.impl.trackEntity(player, entity); trackEntity(player, entity);
} }
// techhider triggers block change sending // techhider triggers block change sending
} }
@@ -110,18 +120,34 @@ public class Hull {
if (region.inRegion(location) && !visibility.get(new IntVector(location).toId(region))) { if (region.inRegion(location) && !visibility.get(new IntVector(location).toId(region))) {
if (entities.add(entity)) { if (entities.add(entity)) {
for (Player player : players) { for (Player player : players) {
BlockIdWrapper.impl.untrackEntity(player, entity); untrackEntity(player, entity);
} }
} }
} else { } else {
if (entities.remove(entity)) { if (entities.remove(entity)) {
for (Player player : players) { for (Player player : players) {
BlockIdWrapper.impl.trackEntity(player, entity); trackEntity(player, entity);
} }
} }
} }
} }
public void trackEntity(Player player, Entity entity) {
if (entity instanceof Player) {
TinyProtocol.instance.sendPacket(player, ProtocolWrapper.playerInfoPacketConstructor(ProtocolWrapper.PlayerInfoAction.REMOVE, new GameProfile(entity.getUniqueId(), entity.getName()), GameMode.CREATIVE));
}
player.showEntity(FightSystem.getPlugin(), entity);
}
public void untrackEntity(Player player, Entity entity) {
player.hideEntity(FightSystem.getPlugin(), entity);
if (entity instanceof Player) {
TinyProtocol.instance.sendPacket(player, ProtocolWrapper.playerInfoPacketConstructor(ProtocolWrapper.PlayerInfoAction.ADD, new GameProfile(entity.getUniqueId(), entity.getName()), GameMode.CREATIVE));
}
}
public void removeEntity(Entity entity) { public void removeEntity(Entity entity) {
entities.remove(entity); entities.remove(entity);
} }
@@ -139,6 +165,15 @@ public class Hull {
rentities.remove(entity); rentities.remove(entity);
} }
public void fill(boolean visible) {
visibility.set(0, visibility.size(), visible);
occluding.set(0, occluding.size(), !visible);
uncoveredSurface.clear();
for (BitSet directionalVisibility : visibilityDirections.values()) {
directionalVisibility.set(0, directionalVisibility.size(), visible);
}
}
public void initialize() { public void initialize() {
visibility.clear(); visibility.clear();
occluding.clear(); occluding.clear();
@@ -189,7 +224,34 @@ public class Hull {
uncoveredSurface.clear(); uncoveredSurface.clear();
for (Map.Entry<IntVector, List<IntVector>> entry : sectionWise.entrySet()) { for (Map.Entry<IntVector, List<IntVector>> entry : sectionWise.entrySet()) {
Object packet = HullHiderWrapper.impl.generateBlockChangePacket(entry.getValue()); Object result;
List<IntVector> changes = entry.getValue();
Object[] blockdata = new Object[changes.size()];
for (int i = 0; i < blockdata.length; i++) {
IntVector change = changes.get(i);
blockdata[i] = ((CraftBlockData) Config.world.getBlockData(change.getX(), change.getY(), change.getZ())).getState();
}
if (changes.size() > 1) {
IntVector section = changes.get(0);
section = new IntVector(section.getX() >> 4, section.getY() >> 4, section.getZ() >> 4);
int xOffset = 16 * section.getX();
int yOffset = 16 * section.getY();
int zOffset = 16 * section.getZ();
short[] pos = new short[changes.size()];
for (int i = 0; i < changes.size(); i++) {
IntVector change = changes.get(i);
pos[i] = (short) (((change.getX() - xOffset) << 8) + ((change.getZ() - zOffset) << 4) + (change.getY() - yOffset));
}
result = new ClientboundSectionBlocksUpdatePacket(SectionPos.of(section.getX(), section.getY(), section.getZ()), new Short2ObjectArrayMap<>(pos, blockdata, blockdata.length));
} else {
IntVector pos = changes.get(0);
result = new ClientboundBlockUpdatePacket(new BlockPos(pos.getX(), pos.getY(), pos.getZ()), (BlockState) blockdata[0]);
}
Object packet = result;
players.forEach(player -> TinyProtocol.instance.sendPacket(player, packet)); players.forEach(player -> TinyProtocol.instance.sendPacket(player, packet));
} }
} }
@@ -19,8 +19,6 @@
package de.steamwar.fightsystem.utils; package de.steamwar.fightsystem.utils;
import com.comphenix.tinyprotocol.TinyProtocol;
import de.steamwar.Reflection;
import de.steamwar.entity.REntity; import de.steamwar.entity.REntity;
import de.steamwar.fightsystem.Config; import de.steamwar.fightsystem.Config;
import de.steamwar.fightsystem.fight.Fight; import de.steamwar.fightsystem.fight.Fight;
@@ -28,18 +26,9 @@ import de.steamwar.fightsystem.fight.FightPlayer;
import de.steamwar.fightsystem.fight.FightTeam; import de.steamwar.fightsystem.fight.FightTeam;
import de.steamwar.fightsystem.listener.Recording; import de.steamwar.fightsystem.listener.Recording;
import de.steamwar.fightsystem.states.FightState; import de.steamwar.fightsystem.states.FightState;
import de.steamwar.fightsystem.states.StateDependent;
import de.steamwar.fightsystem.states.StateDependentListener; import de.steamwar.fightsystem.states.StateDependentListener;
import de.steamwar.fightsystem.states.StateDependentTask; import de.steamwar.fightsystem.states.StateDependentTask;
import de.steamwar.techhider.TechHider;
import lombok.Getter; import lombok.Getter;
import net.minecraft.core.Vec3i;
import net.minecraft.network.protocol.game.ClientboundExplodePacket;
import net.minecraft.network.protocol.game.ClientboundLevelEventPacket;
import net.minecraft.network.protocol.game.ClientboundLevelParticlesPacket;
import net.minecraft.network.protocol.game.ClientboundSoundPacket;
import org.bukkit.Bukkit;
import org.bukkit.Location;
import org.bukkit.Material; import org.bukkit.Material;
import org.bukkit.block.Block; import org.bukkit.block.Block;
import org.bukkit.entity.Player; import org.bukkit.entity.Player;
@@ -55,17 +44,12 @@ import org.bukkit.event.player.PlayerQuitEvent;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; import java.util.Map;
import java.util.Objects; import java.util.Objects;
import java.util.function.BiFunction;
import java.util.function.Function;
public class HullHider implements Listener { public class HullHider implements Listener {
@Getter @Getter
private final Map<FightTeam, Hull> hullMap = new HashMap<>(); private final Map<FightTeam, Hull> hullMap = new HashMap<>();
private final Hull[] hulls; private final Hull[] hulls;
private final Map<Class<?>, BiFunction<Player, Object, Object>> packetHiders = new HashMap<>();
private static final Class<?> packetPlayOutExplosion = ClientboundExplodePacket.class;
public HullHider() { public HullHider() {
if (!TechHiderWrapper.ENABLED) { if (!TechHiderWrapper.ENABLED) {
@@ -76,34 +60,20 @@ public class HullHider implements Listener {
Fight.teams().forEach(team -> hullMap.put(team, new Hull(team))); Fight.teams().forEach(team -> hullMap.put(team, new Hull(team)));
hulls = hullMap.values().toArray(new Hull[0]); hulls = hullMap.values().toArray(new Hull[0]);
packetHiders.put(packetPlayOutWorldEvent, this::worldEventHider);
packetHiders.put(packetPlayOutExplosion, this::explosionHider);
posHiderGenerator(ClientboundLevelParticlesPacket.class, double.class, 1.0);
posHiderGenerator(ClientboundSoundPacket.class, int.class, 8.0);
new StateDependentListener(TechHiderWrapper.ENABLED, FightState.Schem, this); new StateDependentListener(TechHiderWrapper.ENABLED, FightState.Schem, this);
new StateDependent(TechHiderWrapper.ENABLED, FightState.Schem) {
@Override
public void enable() {
packetHiders.forEach(TinyProtocol.instance::addFilter);
Bukkit.getOnlinePlayers().forEach(HullHider.this::updatePlayer);
}
@Override
public void disable() {
Bukkit.getOnlinePlayers().forEach(player -> removePlayer(player, true));
packetHiders.forEach(TinyProtocol.instance::removeFilter);
}
}.register();
new StateDependentTask(TechHiderWrapper.ENABLED, FightState.Schem, this::onTick, 0, 1); new StateDependentTask(TechHiderWrapper.ENABLED, FightState.Schem, this::onTick, 0, 1);
} }
public void initialize(FightTeam team) { public void initialize(FightTeam team) {
if (!TechHiderWrapper.ENABLED) return; if (!TechHiderWrapper.ENABLED) return;
hullMap.get(team).initialize(); hullMap.get(team).initialize();
} }
public void fill(FightTeam team, boolean visible) {
if (!TechHiderWrapper.ENABLED) return;
hullMap.get(team).fill(visible);
}
@EventHandler(priority = EventPriority.HIGH) @EventHandler(priority = EventPriority.HIGH)
public void onJoin(PlayerJoinEvent e) { public void onJoin(PlayerJoinEvent e) {
@@ -138,7 +108,7 @@ public class HullHider implements Listener {
@EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true) @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
public void onBlockPhysic(BlockPhysicsEvent e) { public void onBlockPhysic(BlockPhysicsEvent e) {
if (FlatteningWrapper.impl.doRecord(e)) { if (e.getBlock() == e.getSourceBlock() || e.getChangedType() == Material.AIR) {
blockUpdate(e.getBlock(), e.getChangedType()); blockUpdate(e.getBlock(), e.getChangedType());
} }
} }
@@ -161,18 +131,6 @@ public class HullHider implements Listener {
return false; return false;
} }
public boolean blockPrecise(Player player, int chunkX, int chunkY, int chunkZ) {
if (!TechHiderWrapper.ENABLED) return false;
for (Hull hull : hulls) {
if (hull.blockPrecise(player, chunkX, chunkY, chunkZ)) {
return true;
}
}
return false;
}
@EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true) @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
public void onSpawn(EntitySpawnEvent e) { public void onSpawn(EntitySpawnEvent e) {
for (Hull hull : hulls) { for (Hull hull : hulls) {
@@ -210,39 +168,4 @@ public class HullHider implements Listener {
hull.removeREntity(e); hull.removeREntity(e);
} }
} }
private static final Class<?> packetPlayOutWorldEvent = ClientboundLevelEventPacket.class;
private static final Reflection.Field<?> worldEventPosition = Reflection.getField(packetPlayOutWorldEvent, TechHider.blockPosition, 0);
public static final Reflection.Field<Integer> blockPositionY = Reflection.getField(Vec3i.class, int.class, 1);
private Object worldEventHider(Player player, Object packet) {
Object baseBlock = worldEventPosition.get(packet);
return packetHider(player, packet, new Location(Config.world, TechHider.blockPositionX.get(baseBlock), blockPositionY.get(baseBlock), TechHider.blockPositionZ.get(baseBlock)));
}
private Object explosionHider(Player player, Object packet) {
return ReflectionWrapper.impl.explosionHider(player, packet, this::packetHider);
}
private void posHiderGenerator(Class<?> type, Class<? extends Number> posType, double factor) {
Function<Object, Location> location = posPacketToLocation(type, posType, factor);
packetHiders.put(type, (player, packet) -> packetHider(player, packet, location.apply(packet)));
}
public static Function<Object, Location> posPacketToLocation(Class<?> type, Class<? extends Number> posType, double factor) {
Reflection.Field<? extends Number> x = Reflection.getField(type, posType, 0);
Reflection.Field<? extends Number> y = Reflection.getField(type, posType, 1);
Reflection.Field<? extends Number> z = Reflection.getField(type, posType, 2);
return packet -> new Location(Config.world, x.get(packet).doubleValue() / factor, y.get(packet).doubleValue() / factor, z.get(packet).doubleValue() / factor);
}
private Object packetHider(Player player, Object packet, Location location) {
for (Hull hull : hulls) {
if (hull.isLocationHidden(player, location)) return null;
}
return packet;
}
} }
@@ -1,71 +0,0 @@
/*
* This file is a part of the SteamWar software.
*
* Copyright (C) 2025 SteamWar.de-Serverteam
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package de.steamwar.fightsystem.utils;
import de.steamwar.fightsystem.Config;
import it.unimi.dsi.fastutil.shorts.Short2ObjectArrayMap;
import net.minecraft.core.BlockPos;
import net.minecraft.core.SectionPos;
import net.minecraft.network.protocol.game.ClientboundBlockUpdatePacket;
import net.minecraft.network.protocol.game.ClientboundSectionBlocksUpdatePacket;
import net.minecraft.world.level.block.state.BlockState;
import org.bukkit.craftbukkit.block.data.CraftBlockData;
import java.util.List;
public class HullHiderWrapper {
public static final HullHiderWrapper impl = new HullHiderWrapper();
public Object generateBlockChangePacket(List<Hull.IntVector> changes) {
Object[] blockdata = new Object[changes.size()];
for (int i = 0; i < blockdata.length; i++) {
Hull.IntVector change = changes.get(i);
blockdata[i] = ((CraftBlockData) Config.world.getBlockData(change.getX(), change.getY(), change.getZ())).getState();
}
return generateBlockChangePacket(changes, blockdata);
}
private Object generateBlockChangePacket(List<Hull.IntVector> changes, Object[] blockdata) {
if (changes.size() > 1) {
Hull.IntVector section = changes.get(0);
section = new Hull.IntVector(section.getX() >> 4, section.getY() >> 4, section.getZ() >> 4);
int xOffset = 16 * section.getX();
int yOffset = 16 * section.getY();
int zOffset = 16 * section.getZ();
short[] pos = new short[changes.size()];
for (int i = 0; i < changes.size(); i++) {
Hull.IntVector change = changes.get(i);
pos[i] = (short) (((change.getX() - xOffset) << 8) + ((change.getZ() - zOffset) << 4) + (change.getY() - yOffset));
}
return constructMultiBlockChange(section, pos, blockdata);
} else {
Hull.IntVector pos = changes.get(0);
return new ClientboundBlockUpdatePacket(new BlockPos(pos.getX(), pos.getY(), pos.getZ()), (BlockState) blockdata[0]);
}
}
protected Object constructMultiBlockChange(Hull.IntVector section, short[] pos, Object[] blockdata) {
return new ClientboundSectionBlocksUpdatePacket(SectionPos.of(section.getX(), section.getY(), section.getZ()), new Short2ObjectArrayMap<>(pos, blockdata, blockdata.length));
}
}
@@ -1,79 +0,0 @@
/*
* This file is a part of the SteamWar software.
*
* Copyright (C) 2025 SteamWar.de-Serverteam
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package de.steamwar.fightsystem.utils;
import io.papermc.paper.datacomponent.DataComponentType;
import io.papermc.paper.datacomponent.DataComponentTypes;
import org.bukkit.Location;
import org.bukkit.entity.Player;
import org.bukkit.inventory.ItemStack;
import java.util.HashSet;
import java.util.Set;
public class ReflectionWrapper {
public static final ReflectionWrapper impl = new ReflectionWrapper();
private static final Set<DataComponentType> FORBIDDEN_TYPES = new HashSet<>();
static {
FORBIDDEN_TYPES.add(DataComponentTypes.CUSTOM_NAME);
FORBIDDEN_TYPES.add(DataComponentTypes.PROFILE);
FORBIDDEN_TYPES.add(DataComponentTypes.UNBREAKABLE);
FORBIDDEN_TYPES.add(DataComponentTypes.BLOCK_DATA);
FORBIDDEN_TYPES.add(DataComponentTypes.BLOCKS_ATTACKS);
FORBIDDEN_TYPES.add(DataComponentTypes.BUNDLE_CONTENTS);
FORBIDDEN_TYPES.add(DataComponentTypes.CUSTOM_MODEL_DATA);
FORBIDDEN_TYPES.add(DataComponentTypes.ATTRIBUTE_MODIFIERS);
FORBIDDEN_TYPES.add(DataComponentTypes.TOOL);
FORBIDDEN_TYPES.add(DataComponentTypes.WEAPON);
FORBIDDEN_TYPES.add(DataComponentTypes.FOOD);
FORBIDDEN_TYPES.add(DataComponentTypes.CONSUMABLE);
FORBIDDEN_TYPES.add(DataComponentTypes.POTION_CONTENTS);
FORBIDDEN_TYPES.add(DataComponentTypes.STORED_ENCHANTMENTS);
FORBIDDEN_TYPES.add(DataComponentTypes.CAN_BREAK);
FORBIDDEN_TYPES.add(DataComponentTypes.CAN_PLACE_ON);
FORBIDDEN_TYPES.add(DataComponentTypes.MAX_DAMAGE);
FORBIDDEN_TYPES.add(DataComponentTypes.USE_REMAINDER);
FORBIDDEN_TYPES.add(DataComponentTypes.USE_COOLDOWN);
FORBIDDEN_TYPES.add(DataComponentTypes.SUSPICIOUS_STEW_EFFECTS);
FORBIDDEN_TYPES.add(DataComponentTypes.CHARGED_PROJECTILES);
FORBIDDEN_TYPES.add(DataComponentTypes.INTANGIBLE_PROJECTILE);
FORBIDDEN_TYPES.add(DataComponentTypes.FIREWORKS);
FORBIDDEN_TYPES.add(DataComponentTypes.FIREWORK_EXPLOSION);
FORBIDDEN_TYPES.add(DataComponentTypes.EQUIPPABLE);
FORBIDDEN_TYPES.add(DataComponentTypes.REPAIR_COST);
FORBIDDEN_TYPES.add(DataComponentTypes.ENCHANTABLE);
}
public Object explosionHider(Player player, Object packet, PacketHiderFunction packetHiderFunction) {
return packet;
}
public boolean hasItems(ItemStack stack) {
FORBIDDEN_TYPES.forEach(stack::resetData);
return false;
}
public interface PacketHiderFunction {
Object hide(Player player, Object packet, Location location);
}
}
@@ -19,9 +19,9 @@
package de.steamwar.fightsystem.utils; package de.steamwar.fightsystem.utils;
import de.steamwar.Reflection;
import de.steamwar.core.CraftbukkitWrapper; import de.steamwar.core.CraftbukkitWrapper;
import de.steamwar.fightsystem.Config; import de.steamwar.fightsystem.Config;
import de.steamwar.fightsystem.FightSystem;
import de.steamwar.fightsystem.events.BoardingEvent; import de.steamwar.fightsystem.events.BoardingEvent;
import de.steamwar.fightsystem.events.TeamLeaveEvent; import de.steamwar.fightsystem.events.TeamLeaveEvent;
import de.steamwar.fightsystem.events.TeamSpawnEvent; import de.steamwar.fightsystem.events.TeamSpawnEvent;
@@ -30,56 +30,119 @@ import de.steamwar.fightsystem.fight.FightTeam;
import de.steamwar.fightsystem.states.FightState; import de.steamwar.fightsystem.states.FightState;
import de.steamwar.fightsystem.states.StateDependent; import de.steamwar.fightsystem.states.StateDependent;
import de.steamwar.fightsystem.states.StateDependentListener; import de.steamwar.fightsystem.states.StateDependentListener;
import de.steamwar.sql.SteamwarUser; import de.steamwar.techhider.AccessPrivilegeProvider;
import de.steamwar.techhider.TechHider; import de.steamwar.techhider.TechHider;
import lombok.Getter; import lombok.Getter;
import net.minecraft.core.Holder;
import net.minecraft.core.registries.BuiltInRegistries;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.world.level.block.Block;
import net.minecraft.world.level.block.entity.BlockEntityType;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.level.material.FluidState;
import net.minecraft.world.level.material.Fluids;
import org.bukkit.GameMode; import org.bukkit.GameMode;
import org.bukkit.Material;
import org.bukkit.craftbukkit.CraftWorld;
import org.bukkit.craftbukkit.util.CraftMagicNumbers;
import org.bukkit.entity.Player; import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler; import org.bukkit.event.EventHandler;
import org.bukkit.event.Listener; import org.bukkit.event.Listener;
import org.bukkit.event.player.PlayerQuitEvent; import org.bukkit.event.player.PlayerQuitEvent;
import javax.crypto.*; import java.util.Collection;
import javax.crypto.spec.SecretKeySpec; import java.util.Optional;
import java.io.File; import java.util.Set;
import java.io.IOException;
import java.nio.file.Files;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.Collectors;
import java.util.stream.Stream;
public class TechHiderWrapper extends StateDependent implements TechHider.LocationEvaluator, Listener { public class TechHiderWrapper extends StateDependent implements Listener {
public static final boolean ENABLED = !Config.OnlyPublicSchematics && !Config.test() && Config.GameModeConfig.Techhider.Active; public static final boolean ENABLED = !Config.OnlyPublicSchematics && !Config.test() && Config.GameModeConfig.Techhider.Active;
@Getter @Getter
private final ConcurrentHashMap<Player, Region> hiddenRegion = new ConcurrentHashMap<>(); private final ConcurrentHashMap<Player, Region> hiddenRegion = new ConcurrentHashMap<>();
private final ConcurrentHashMap<Player, Long> patterns = new ConcurrentHashMap<>(); private final HullHider hullHider;
private final TechHider techHider;
private final SecretKey key;
public TechHiderWrapper() { public TechHiderWrapper(HullHider hullHider) {
super(ENABLED, FightState.Schem); super(ENABLED, FightState.All);
techHider = new TechHider(this, Config.GameModeConfig.Techhider.ObfuscateWith, Config.GameModeConfig.Techhider.HiddenBlocks, Config.GameModeConfig.Techhider.HiddenBlockEntities);
try { this.hullHider = hullHider;
key = new SecretKeySpec(Files.readAllBytes(new File(System.getProperty("user.home"), "hullhider.key").toPath()), "AES");
} catch (IOException e) {
throw new SecurityException(e);
}
new StateDependentListener(ENABLED, FightState.Schem, this); new StateDependentListener(ENABLED, FightState.All, this);
register(); register();
} }
@Override @Override
public void enable() { public void enable() {
techHider.enable(); Set<BlockState> blockStatesToObfuscate = getBlockStatesToHideFromMaterials(Config.GameModeConfig.Techhider.HiddenBlocks);
Set<Block> blocksToObfuscate = Config.GameModeConfig.Techhider.HiddenBlocks.stream()
.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<BlockEntityType<?>> blockEntityTypeToObfuscate = Config.GameModeConfig.Techhider.HiddenBlockEntities.stream()
.map((id) -> {
ResourceLocation loc = ResourceLocation.parse(id);
return ((Optional<Holder.Reference<BlockEntityType<?>>>) method.invoke(blockEntityType, loc)).get().value();
})
.collect(Collectors.toUnmodifiableSet());
new TechHider(CraftMagicNumbers.getBlock(Config.GameModeConfig.Techhider.ObfuscateWith), new AccessPrivilegeProvider() {
@Override
public boolean isEveryonePrivilegedToAccessAllDataWithinChunk(int chunkX, int chunkZ) {
return Fight.teams().stream().map(FightTeam::getExtendRegion).allMatch(region -> region.chunkOutside(chunkX, chunkZ));
}
@Override
public boolean isPlayerPrivilegedToAccessPosition(Player p, int blockX, int blockY, int blockZ) {
return !hullHider.isBlockHidden(p, blockX, blockY, blockZ);
}
@Override
public boolean isPlayerPrivilegedToAccessBlock(Player p, int blockX, int blockY, int blockZ, Block block) {
return !getHiddenRegion(p).inRegion(blockX, blockY, blockZ) || !blocksToObfuscate.contains(block);
}
@Override
public boolean isPlayerPrivilegedToAccessBlockState(Player p, int blockX, int blockY, int blockZ, BlockState blockState) {
return !getHiddenRegion(p).inRegion(blockX, blockY, blockZ) || !blockStatesToObfuscate.contains(blockState);
}
// TODO will require entity tracking on the netty thread to prevent future race conditions
@Override
public boolean isPlayerPrivilegedToAccessEntity(Player p, int entityId) {
net.minecraft.world.entity.Entity nmsEntity = ((CraftWorld) p.getWorld()).getHandle().moonrise$getEntityLookup().get(entityId);
if (nmsEntity != null) {
return !hullHider.isBlockHidden(p, nmsEntity.getBlockX(), nmsEntity.getBlockY(), nmsEntity.getBlockZ());
} else {
return true;
}
}
@Override
public boolean isPlayerPrivilegedToAccessBlockEntity(Player p, int blockX, int blockY, int blockZ, BlockEntityType<?> type) {
return !getHiddenRegion(p).inRegion(blockX, blockY, blockZ) || !blockEntityTypeToObfuscate.contains(type);
}
@Override
public boolean isPlayerPrivilegedToPerformAction(Player p) {
return p.getGameMode() != GameMode.SPECTATOR;
}
});
} }
@Override @Override
public void disable() { public void disable() {
techHider.disable();
hiddenRegion.clear(); hiddenRegion.clear();
} }
@@ -118,72 +181,6 @@ public class TechHiderWrapper extends StateDependent implements TechHider.Locati
}); });
} }
@Override
public boolean suppressInteractions(Player player) {
return player.getGameMode() == GameMode.SPECTATOR;
}
@Override
public boolean skipChunk(Player player, int chunkX, int chunkZ) {
return getHiddenRegion(player).chunkOutside(chunkX, chunkZ);
}
@Override
public boolean skipChunkSection(Player player, int chunkX, int chunkY, int chunkZ) {
return getHiddenRegion(player).chunkSectionOutside(chunkX, chunkY, chunkZ);
}
@Override
public TechHider.State check(Player player, int x, int y, int z) {
if (hiddenRegion.computeIfAbsent(player, this::getHiddenRegion).inRegion(x, y, z)) {
if (FightSystem.getHullHider().isBlockHidden(player, x, y, z)) {
int id = ((y & 3) << 4) + ((z & 3) << 2) + (x & 3);
return (patterns.computeIfAbsent(player, this::getPattern) & (1L << id)) == 0 ? TechHider.State.HIDE_AIR : TechHider.State.HIDE;
} else {
return TechHider.State.CHECK;
}
} else {
return TechHider.State.SKIP;
}
}
public long getPattern(Player player) {
long pattern = SteamwarUser.get(player.getUniqueId()).getId();
byte[] bytes = new byte[]{
(byte) (pattern & 0xFF),
(byte) ((pattern >>> 8) & 0xFF),
(byte) ((pattern >>> 16) & 0xFF),
(byte) ((pattern >>> 24) & 0xFF)
};
try {
Cipher cipher = Cipher.getInstance("AES_256/CFB/NoPadding");
cipher.init(Cipher.ENCRYPT_MODE, key);
bytes = cipher.doFinal(bytes);
} catch (IllegalBlockSizeException | BadPaddingException | NoSuchAlgorithmException | NoSuchPaddingException |
InvalidKeyException e) {
throw new SecurityException(e);
}
pattern = bytes[0] & 0xFFL |
((bytes[1] & 0xFFL) << 8) |
((bytes[2] & 0xFFL) << 16) |
((bytes[3] & 0xFFL) << 24);
/* XXXO OOOX
* XXXO OOOX
* XXOX OOXO
* OOXX XXOO */
pattern |= 0b1110_1110_1101_0011___0001_0001_0010_1100___0000_0000_0000_0000___0000_0000_0000_0000L;
return pattern;
}
@Override
public boolean blockPrecise(Player player, int chunkX, int chunkY, int chunkZ) {
return FightSystem.getHullHider().blockPrecise(player, chunkX, chunkY, chunkZ);
}
private Region getHiddenRegion(Player player) { private Region getHiddenRegion(Player player) {
if (Config.isReferee(player)) return Region.EMPTY; if (Config.isReferee(player)) return Region.EMPTY;
@@ -194,4 +191,27 @@ public class TechHiderWrapper extends StateDependent implements TechHider.Locati
return Fight.getOpposite(team).getExtendRegion(); return Fight.getOpposite(team).getExtendRegion();
} }
private Stream<BlockState> getWaterloggedBlockStates() {
FluidState waterFluidState = Fluids.WATER.getSource(false);
return BuiltInRegistries.BLOCK.stream()
.map((block) -> block.getStateDefinition().getPossibleStates())
.flatMap(Collection::stream)
.filter((blockState -> blockState.getFluidState() == waterFluidState));
}
private Set<BlockState> getBlockStatesToHideFromMaterials(Set<Material> materials) {
Stream<BlockState> allStatesFromMaterials = materials.stream()
.map(CraftMagicNumbers::getBlock)
.map((block) -> block.getStateDefinition().getPossibleStates())
.flatMap(Collection::stream);
if(materials.contains(Material.WATER)) {
return Stream.concat(allStatesFromMaterials, getWaterloggedBlockStates()).collect(Collectors.toUnmodifiableSet());
}
else {
return allStatesFromMaterials.collect(Collectors.toUnmodifiableSet());
}
}
} }
@@ -1,11 +0,0 @@
package de.steamwar.fightsystem.utils;
import net.minecraft.server.MinecraftServer;
public class TpsWarper {
public static final TpsWarper impl = new TpsWarper();
public void warp(float tps) {
MinecraftServer.getServer().tickRateManager().setTickRate(tps);
}
}
@@ -1,50 +0,0 @@
/*
* This file is a part of the SteamWar software.
*
* Copyright (C) 2025 SteamWar.de-Serverteam
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package de.steamwar.fightsystem.utils;
import org.bukkit.ChatColor;
import org.bukkit.Location;
import org.bukkit.Sound;
import org.bukkit.SoundCategory;
import org.bukkit.entity.Arrow;
import org.bukkit.entity.Player;
import org.bukkit.entity.Projectile;
import org.bukkit.scoreboard.Team;
public class WorldOfColorWrapper {
public static final WorldOfColorWrapper impl = new WorldOfColorWrapper();
public void setTeamColor(Team team, ChatColor color) {
team.setColor(color);
}
public boolean isInBlock(Projectile e) {
if (e instanceof Arrow arrow) return arrow.isInBlock();
return false;
}
public void playSound(Location location, Sound sound, String soundCategory, float volume, float pitch) {
location.getWorld().playSound(location, sound, SoundCategory.valueOf(soundCategory), volume, pitch);
}
public void sendTitle(Player player, String title, String subtitle, int start, int hold, int stop) {
player.sendTitle(title, subtitle, start, hold, stop);
}
}
@@ -19,13 +19,22 @@
package de.steamwar.fightsystem.winconditions; package de.steamwar.fightsystem.winconditions;
import de.steamwar.fightsystem.utils.FlatteningWrapper;
import de.steamwar.linkage.Linked; import de.steamwar.linkage.Linked;
import org.bukkit.Material;
import org.bukkit.block.data.BlockData;
import org.bukkit.block.data.Waterlogged;
@Linked @Linked
public class WinconditionWaterTechKO extends WinconditionBlocks { public class WinconditionWaterTechKO extends WinconditionBlocks {
public WinconditionWaterTechKO() { public WinconditionWaterTechKO() {
super(Winconditions.WATER_TECH_KO, "WaterTechKO", "BAR_WATER", FlatteningWrapper.impl::isWater); super(Winconditions.WATER_TECH_KO, "WaterTechKO", "BAR_WATER", block -> {
if (block.getType() == Material.WATER) return true;
BlockData data = block.getBlockData();
if (!(data instanceof Waterlogged)) return false;
return ((Waterlogged) data).isWaterlogged();
});
} }
} }
+2 -1
View File
@@ -57,9 +57,10 @@ tasks.register<FightServer>("WarGear21") {
description = "Run a WarGear 1.21 Fight Server" description = "Run a WarGear 1.21 Fight Server"
dependsOn(":SpigotCore:shadowJar") dependsOn(":SpigotCore:shadowJar")
dependsOn(":FightSystem:shadowJar") dependsOn(":FightSystem:shadowJar")
dependsOn(":KotlinCore:shadowJar")
template = "WarGear21" template = "WarGear21"
worldName = "arenas/Pentraki" worldName = "arenas/Pentraki"
config = "WarGear20.yml" config = "WarGear21.yml"
jar = "/jars/paper-1.21.6.jar" jar = "/jars/paper-1.21.6.jar"
} }
@@ -20,7 +20,9 @@
package de.steamwar.lobby.boatrace; package de.steamwar.lobby.boatrace;
import de.steamwar.entity.REntity; import de.steamwar.entity.REntity;
import de.steamwar.entity.REntityAction;
import de.steamwar.entity.REntityServer; import de.steamwar.entity.REntityServer;
import de.steamwar.entity.RInteraction;
import de.steamwar.lobby.LobbySystem; import de.steamwar.lobby.LobbySystem;
import de.steamwar.lobby.util.LeaderboardManager; import de.steamwar.lobby.util.LeaderboardManager;
import de.steamwar.sql.SteamwarUser; import de.steamwar.sql.SteamwarUser;
@@ -58,11 +60,12 @@ public class BoatRace implements EventListener, Listener {
static { static {
boatNpcServer = new REntityServer(); boatNpcServer = new REntityServer();
REntity starter = new REntity(boatNpcServer, EntityType.VILLAGER, BoatRacePositions.NPC); new REntity(boatNpcServer, EntityType.VILLAGER, BoatRacePositions.NPC);
boatNpcServer.setCallback((player, rEntity, entityAction) -> { RInteraction interaction = new RInteraction(boatNpcServer, BoatRacePositions.NPC.clone().subtract(0.5, 0, 0.5));
if (rEntity != starter) return; interaction.setInteractionHeight(1.95f);
Bukkit.getWorlds().get(0).getEntities().stream().filter(entity -> entity.getType() == EntityType.END_CRYSTAL).forEach(Entity::remove); interaction.setCallback((player, entity, action) -> {
if (entityAction == REntityServer.EntityAction.INTERACT && !oneNotStarted) { Bukkit.getWorlds().get(0).getEntities().stream().filter(e -> e.getType() == EntityType.END_CRYSTAL).forEach(Entity::remove);
if (action == REntityAction.INTERACT && !oneNotStarted) {
oneNotStarted = true; oneNotStarted = true;
new BoatRace(player); new BoatRace(player);
} }
@@ -1,26 +1,8 @@
/*
* This file is a part of the SteamWar software.
*
* Copyright (C) 2025 SteamWar.de-Serverteam
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.comphenix.tinyprotocol; package com.comphenix.tinyprotocol;
import com.google.common.collect.MapMaker; import com.google.common.collect.MapMaker;
import de.steamwar.Reflection; import de.steamwar.Reflection;
import de.steamwar.core.CRIUWakeupEvent;
import de.steamwar.core.Core; import de.steamwar.core.Core;
import io.netty.channel.*; import io.netty.channel.*;
import net.minecraft.network.Connection; import net.minecraft.network.Connection;
@@ -76,6 +58,7 @@ public class TinyProtocol {
public static final TinyProtocol instance = new TinyProtocol(Core.getInstance()); public static final TinyProtocol instance = new TinyProtocol(Core.getInstance());
private final Map<Class<?>, List<BiFunction<Player, Object, Object>>> packetFilters = new HashMap<>(); private final Map<Class<?>, List<BiFunction<Player, Object, Object>>> packetFilters = new HashMap<>();
private final Set<BiFunction<Player, Object, Object>> globalClientboundFilters = new HashSet<>();
public static void init() { public static void init() {
// enforce init // enforce init
@@ -182,6 +165,10 @@ public class TinyProtocol {
} }
} }
@EventHandler
public void onCRIUWakeup(CRIUWakeupEvent event) {
registerChannelHandler();
}
}; };
plugin.getServer().getPluginManager().registerEvents(listener, plugin); plugin.getServer().getPluginManager().registerEvents(listener, plugin);
@@ -240,19 +227,18 @@ public class TinyProtocol {
} }
} }
public <T> void addTypedFilter(Class<T> packetType, BiFunction<Player, ? super T, Object> filter) { public <T extends Packet<?>> void addFilter(Class<T> packetType, BiFunction<Player, ? super T, Object> filter) {
packetFilters.computeIfAbsent(packetType, c -> new CopyOnWriteArrayList<>()).add((BiFunction) filter); packetFilters.computeIfAbsent(packetType, c -> new CopyOnWriteArrayList<>()).add((BiFunction) filter);
} }
@Deprecated
public void addFilter(Class<?> packetType, BiFunction<Player, Object, Object> filter) {
packetFilters.computeIfAbsent(packetType, c -> new CopyOnWriteArrayList<>()).add(filter);
}
public void removeFilter(Class<?> packetType, BiFunction<Player, ?, Object> filter) { public void removeFilter(Class<?> packetType, BiFunction<Player, ?, Object> filter) {
packetFilters.getOrDefault(packetType, Collections.emptyList()).remove(filter); packetFilters.getOrDefault(packetType, Collections.emptyList()).remove(filter);
} }
public void addGlobalClientboundFilter(BiFunction<Player, Packet<?>, Object> filter) {
globalClientboundFilters.add((BiFunction) filter);
}
/** /**
* Send a packet to a particular player. * Send a packet to a particular player.
* *
@@ -356,6 +342,8 @@ public class TinyProtocol {
// Lookup channel again // Lookup channel again
if (channel == null) { if (channel == null) {
System.out.println(((CraftPlayer) player).getHandle());
System.out.println(((CraftPlayer) player).getHandle().connection);
Channel playerChannel = ((CraftPlayer) player).getHandle().connection.connection.channel; Channel playerChannel = ((CraftPlayer) player).getHandle().connection.connection.channel;
channelLookup.put(player.getName(), channel = playerChannel); channelLookup.put(player.getName(), channel = playerChannel);
} }
@@ -464,6 +452,13 @@ public class TinyProtocol {
public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception { public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception {
try { try {
msg = filterPacket(player, msg); msg = filterPacket(player, msg);
if (msg instanceof Packet<?>) {
for (BiFunction<Player, Object, Object> filter : globalClientboundFilters) {
msg = filter.apply(player, msg);
if (msg == null) break;
}
}
} catch (Exception e) { } catch (Exception e) {
plugin.getLogger().log(Level.SEVERE, "Error in onPacketOutAsync().", e); plugin.getLogger().log(Level.SEVERE, "Error in onPacketOutAsync().", e);
} }
@@ -79,11 +79,14 @@ class CheckpointUtilsJ9 {
return; return;
} }
e.printStackTrace();
Bukkit.shutdown(); Bukkit.shutdown();
if (!message.contains("Can't dump ghost file") && !message.contains("Can't create link remap")) // File/Jar has been updated if (!message.contains("Can't dump ghost file") && !message.contains("Can't create link remap")) // File/Jar has been updated
throw new SecurityException(e); throw new SecurityException(e);
} finally { } finally {
if (true) return;
// Delete checkpoint // Delete checkpoint
try (Stream<Path> stream = Files.walk(path)) { try (Stream<Path> stream = Files.walk(path)) {
stream.sorted(Comparator.reverseOrder()).map(Path::toFile).forEach(File::delete); stream.sorted(Comparator.reverseOrder()).map(Path::toFile).forEach(File::delete);
@@ -43,9 +43,8 @@ public class WorldIdentifier {
} }
public WorldIdentifier() { public WorldIdentifier() {
TinyProtocol.instance.addFilter(ClientboundLoginPacket.class, (player, o) -> { TinyProtocol.instance.addFilter(ClientboundLoginPacket.class, (player, packet) -> {
if (resourceKey == null) return o; if (resourceKey == null) return packet;
ClientboundLoginPacket packet = (ClientboundLoginPacket) o;
return new ClientboundLoginPacket(packet.playerId(), return new ClientboundLoginPacket(packet.playerId(),
packet.hardcore(), packet.hardcore(),
@@ -26,6 +26,7 @@ import de.steamwar.linkage.Linked;
import de.steamwar.sql.SWException; import de.steamwar.sql.SWException;
import de.steamwar.techhider.ProtocolUtils; import de.steamwar.techhider.ProtocolUtils;
import de.steamwar.techhider.TechHider; import de.steamwar.techhider.TechHider;
import net.minecraft.core.BlockPos;
import net.minecraft.network.protocol.game.ServerboundPlayerActionPacket; import net.minecraft.network.protocol.game.ServerboundPlayerActionPacket;
import net.minecraft.network.protocol.game.ServerboundUseItemOnPacket; import net.minecraft.network.protocol.game.ServerboundUseItemOnPacket;
import net.minecraft.world.phys.BlockHitResult; import net.minecraft.world.phys.BlockHitResult;
@@ -45,7 +46,7 @@ public class AntiNocom implements Listener {
private final Map<Player, Integer> flags = new ConcurrentHashMap<>(); private final Map<Player, Integer> flags = new ConcurrentHashMap<>();
public AntiNocom() { public AntiNocom() {
TinyProtocol.instance.addFilter(blockDig, this::onDig); TinyProtocol.instance.addFilter(ServerboundPlayerActionPacket.class, this::onDig);
registerUseItem(); registerUseItem();
} }
@@ -55,26 +56,15 @@ public class AntiNocom implements Listener {
} }
private void registerUseItem() { private void registerUseItem() {
Class<?> useItem = ServerboundUseItemOnPacket.class; TinyProtocol.instance.addFilter(ServerboundUseItemOnPacket.class, (player, packet) -> {
BlockPos pos = packet.getHitResult().getBlockPos();
Class<?> movingObjectPositionBlock = BlockHitResult.class; return isValid(player, "UseItem", pos.getX(), pos.getZ()) ? packet : null;
Reflection.Field<?> useItemPosition = Reflection.getField(useItem, movingObjectPositionBlock, 0);
Reflection.Field<?> movingBlockPosition = Reflection.getField(movingObjectPositionBlock, TechHider.blockPosition, 0);
Function<Object, Object> getPosition = (packet) -> movingBlockPosition.get(useItemPosition.get(packet));
TinyProtocol.instance.addFilter(useItem, (player, packet) -> {
Object pos = getPosition.apply(packet);
return isValid(player, "UseItem", TechHider.blockPositionX.get(pos), TechHider.blockPositionZ.get(pos)) ? packet : null;
}); });
} }
private static final Class<?> blockDig = ServerboundPlayerActionPacket.class; private ServerboundPlayerActionPacket onDig(Player player, ServerboundPlayerActionPacket packet) {
private static final Reflection.Field<?> digPosition = Reflection.getField(blockDig, TechHider.blockPosition, 0); BlockPos pos = packet.getPos();
return isValid(player, "Dig", pos.getX(), pos.getZ()) ? packet : null;
private Object onDig(Player player, Object packet) {
Object pos = digPosition.get(packet);
return isValid(player, "Dig", TechHider.blockPositionX.get(pos), TechHider.blockPositionZ.get(pos)) ? packet : null;
} }
private boolean isValid(Player player, String type, int x, int z) { private boolean isValid(Player player, String type, int x, int z) {
@@ -20,6 +20,7 @@
package de.steamwar.entity; package de.steamwar.entity;
import lombok.Getter; import lombok.Getter;
import lombok.Setter;
import net.minecraft.network.syncher.EntityDataAccessor; import net.minecraft.network.syncher.EntityDataAccessor;
import net.minecraft.network.syncher.EntityDataSerializers; import net.minecraft.network.syncher.EntityDataSerializers;
import org.bukkit.Location; import org.bukkit.Location;
@@ -27,13 +28,16 @@ import org.bukkit.entity.EntityType;
import java.util.function.Consumer; import java.util.function.Consumer;
public class RArmorStand extends REntity { @Getter
public class RArmorStand extends REntity implements RInteractableEntity<RArmorStand> {
private static final EntityDataAccessor<Byte> sizeWatcher = new EntityDataAccessor<>(15, EntityDataSerializers.BYTE); private static final EntityDataAccessor<Byte> sizeWatcher = new EntityDataAccessor<>(15, EntityDataSerializers.BYTE);
@Getter
private final Size size; private final Size size;
@Setter
private REntityActionListener<RArmorStand> callback = null;
public RArmorStand(REntityServer server, Location location, Size size) { public RArmorStand(REntityServer server, Location location, Size size) {
super(server, EntityType.ARMOR_STAND, location, 0); super(server, EntityType.ARMOR_STAND, location, 0);
this.size = size; this.size = size;
@@ -1,7 +1,7 @@
/* /*
* This file is a part of the SteamWar software. * 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 * 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 * it under the terms of the GNU Affero General Public License as published by
@@ -17,20 +17,9 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
package de.steamwar.bausystem.utils; package de.steamwar.entity;
import org.bukkit.event.Event; public enum REntityAction {
import org.bukkit.event.HandlerList; INTERACT,
ATTACK,
public class TickStartEvent extends Event {
private static final HandlerList handlers = new HandlerList();
public HandlerList getHandlers() {
return handlers;
}
public static HandlerList getHandlerList() {
return handlers;
}
} }
@@ -1,7 +1,7 @@
/* /*
* This file is a part of the SteamWar software. * 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 * 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 * it under the terms of the GNU Affero General Public License as published by
@@ -17,20 +17,10 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
package de.steamwar.bausystem.utils; package de.steamwar.entity;
import org.bukkit.event.Event; import org.bukkit.entity.Player;
import org.bukkit.event.HandlerList;
public class TickEndEvent extends Event { public interface REntityActionListener<E extends REntity> {
void onAction(Player player, E entity, REntityAction action);
private static final HandlerList handlers = new HandlerList();
public HandlerList getHandlers() {
return handlers;
}
public static HandlerList getHandlerList() {
return handlers;
}
} }
@@ -51,7 +51,6 @@ public class REntityServer implements Listener {
private final HashMap<Player, Location> lastLocation = new HashMap<>(); private final HashMap<Player, Location> lastLocation = new HashMap<>();
private final HashMap<Player, Integer> viewDistance = new HashMap<>(); private final HashMap<Player, Integer> viewDistance = new HashMap<>();
private EntityActionListener callback = null;
private final Set<Player> playersThatClicked = Collections.synchronizedSet(new HashSet<>()); private final Set<Player> playersThatClicked = Collections.synchronizedSet(new HashSet<>());
private final BiFunction<Player, ServerboundInteractPacket, Object> filter = (player, packet) -> { private final BiFunction<Player, ServerboundInteractPacket, Object> filter = (player, packet) -> {
@@ -61,25 +60,19 @@ public class REntityServer implements Listener {
if (playersThatClicked.contains(player)) return null; if (playersThatClicked.contains(player)) return null;
playersThatClicked.add(player); playersThatClicked.add(player);
EntityAction action = packet.isAttack() ? EntityAction.ATTACK : EntityAction.INTERACT; REntityAction action = packet.isAttack() ? REntityAction.ATTACK : REntityAction.INTERACT;
Bukkit.getScheduler().runTask(Core.getInstance(), () -> { Bukkit.getScheduler().runTask(Core.getInstance(), () -> {
playersThatClicked.remove(player); playersThatClicked.remove(player);
callback.onAction(player, entity, action); if (entity instanceof RInteractableEntity interactable && interactable.getCallback() != null) {
interactable.getCallback().onAction(player, entity, action);
}
}); });
return null; return null;
}; };
public REntityServer() { public REntityServer() {
Core.getInstance().getServer().getPluginManager().registerEvents(this, Core.getInstance()); Core.getInstance().getServer().getPluginManager().registerEvents(this, Core.getInstance());
} TinyProtocol.instance.addFilter(ServerboundInteractPacket.class, filter);
public void setCallback(EntityActionListener callback) {
boolean uninitialized = this.callback == null;
this.callback = callback;
if (uninitialized) {
TinyProtocol.instance.addTypedFilter(ServerboundInteractPacket.class, filter);
}
} }
public void addPlayer(Player player) { public void addPlayer(Player player) {
@@ -293,13 +286,4 @@ public class REntityServer implements Listener {
private long chunkToId(int x, int z) { private long chunkToId(int x, int z) {
return ((long) x << 32) + z; return ((long) x << 32) + z;
} }
public enum EntityAction {
INTERACT,
ATTACK,
}
public interface EntityActionListener {
void onAction(Player player, REntity entity, EntityAction action);
}
} }
@@ -20,16 +20,21 @@
package de.steamwar.entity; package de.steamwar.entity;
import de.steamwar.techhider.BlockIds; import de.steamwar.techhider.BlockIds;
import lombok.AccessLevel;
import lombok.Getter; import lombok.Getter;
import lombok.Setter;
import org.bukkit.Location; import org.bukkit.Location;
import org.bukkit.Material; import org.bukkit.Material;
import org.bukkit.entity.EntityType; import org.bukkit.entity.EntityType;
@Getter @Getter
public class RFallingBlockEntity extends REntity { public class RFallingBlockEntity extends REntity implements RInteractableEntity<RFallingBlockEntity> {
private final Material material; private final Material material;
@Setter
private REntityActionListener<RFallingBlockEntity> callback = null;
public RFallingBlockEntity(REntityServer server, Location location, Material material) { public RFallingBlockEntity(REntityServer server, Location location, Material material) {
super(server, EntityType.FALLING_BLOCK, location, BlockIds.impl.materialToId(material)); super(server, EntityType.FALLING_BLOCK, location, BlockIds.impl.materialToId(material));
this.material = material; this.material = material;
@@ -0,0 +1,34 @@
/*
* This file is a part of the SteamWar software.
*
* Copyright (C) 2026 SteamWar.de-Serverteam
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package de.steamwar.entity;
import org.bukkit.entity.Player;
import java.util.function.BiConsumer;
public interface RInteractableEntity<E extends REntity> {
REntityActionListener<E> getCallback();
void setCallback(REntityActionListener<E> callback);
default void setCallback(BiConsumer<Player, REntityAction> callback) {
setCallback((player, interaction, entityAction) -> callback.accept(player, entityAction));
}
}
@@ -0,0 +1,116 @@
/*
* This file is a part of the SteamWar software.
*
* Copyright (C) 2026 SteamWar.de-Serverteam
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package de.steamwar.entity;
import de.steamwar.core.BountifulWrapper;
import lombok.Getter;
import lombok.Setter;
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;
/**
* !! This class cannot be used in Versions lower than or equal to 1.19.4 !!
*/
@Getter
public class RInteraction extends REntity implements RInteractableEntity<RInteraction> {
protected final Consumer<Object> updatePacketSink = o -> server.updateEntity(this, o);
@Setter
private REntityActionListener<RInteraction> callback = null;
private float interactionWidth = 1.0f;
private float interactionHeight = 1.0f;
private boolean responsive = false;
public RInteraction(REntityServer server, Location location) {
super(server, EntityType.INTERACTION, location);
server.addEntity(this);
}
@Override
protected void postSpawn(Consumer<Object> packetSink) {
super.postSpawn(packetSink);
sendPacket(packetSink,
this::getInteractionWidthData,
this::getInteractionHeightData,
this::getResponsiveData
);
}
@SafeVarargs
protected final void sendPacket(Consumer<Object> packetSink, BiConsumer<Boolean, BiConsumer<Object, Object>>... dataSinkSinks) {
List<Object> keyValueData = new ArrayList<>();
boolean ignoreDefault = packetSink == updatePacketSink;
for (BiConsumer<Boolean, BiConsumer<Object, Object>> dataSinkSink : dataSinkSinks) {
dataSinkSink.accept(ignoreDefault, (dataWatcher, value) -> {
keyValueData.add(dataWatcher);
keyValueData.add(value);
});
}
if (!keyValueData.isEmpty()) {
packetSink.accept(getDataWatcherPacket(keyValueData.toArray()));
}
}
public void setInteractionWidth(float interactionWidth) {
this.interactionWidth = interactionWidth;
sendPacket(updatePacketSink, this::getInteractionWidthData);
}
private static final Object interactionWidthWatcher = BountifulWrapper.impl.getDataWatcherObject(8, Float.class);
private void getInteractionWidthData(boolean ignoreDefault, BiConsumer<Object, Object> dataSink) {
if (ignoreDefault || interactionWidth != 1.0) {
dataSink.accept(interactionWidthWatcher, interactionWidth);
}
}
public void setInteractionHeight(float interactionHeight) {
this.interactionHeight = interactionHeight;
sendPacket(updatePacketSink, this::getInteractionHeightData);
}
private static final Object interactionHeightWatcher = BountifulWrapper.impl.getDataWatcherObject(9, Float.class);
private void getInteractionHeightData(boolean ignoreDefault, BiConsumer<Object, Object> dataSink) {
if (ignoreDefault || interactionHeight != 1.0) {
dataSink.accept(interactionHeightWatcher, interactionHeight);
}
}
public void setResponsive(boolean responsive) {
this.responsive = responsive;
sendPacket(updatePacketSink, this::getResponsiveData);
}
private static final Object responsiveWatcher = BountifulWrapper.impl.getDataWatcherObject(10, Boolean.class);
private void getResponsiveData(boolean ignoreDefault, BiConsumer<Object, Object> dataSink) {
if (ignoreDefault || !responsive) {
dataSink.accept(responsiveWatcher, responsive);
}
}
}
@@ -27,6 +27,7 @@ import de.steamwar.network.CoreNetworkHandler;
import de.steamwar.network.NetworkSender; import de.steamwar.network.NetworkSender;
import de.steamwar.network.packets.common.PlayerSkinRequestPacket; import de.steamwar.network.packets.common.PlayerSkinRequestPacket;
import lombok.Getter; import lombok.Getter;
import lombok.Setter;
import net.minecraft.network.protocol.game.ClientboundAddEntityPacket; import net.minecraft.network.protocol.game.ClientboundAddEntityPacket;
import net.minecraft.world.phys.Vec3; import net.minecraft.world.phys.Vec3;
import org.bukkit.GameMode; import org.bukkit.GameMode;
@@ -39,7 +40,7 @@ import java.util.Map;
import java.util.UUID; import java.util.UUID;
import java.util.function.Consumer; import java.util.function.Consumer;
public class RPlayer extends REntity { public class RPlayer extends REntity implements RInteractableEntity<RPlayer> {
private static final Object skinPartsDataWatcher = BountifulWrapper.impl.getDataWatcherObject(17, Byte.class); private static final Object skinPartsDataWatcher = BountifulWrapper.impl.getDataWatcherObject(17, Byte.class);
@@ -48,6 +49,10 @@ public class RPlayer extends REntity {
@Getter @Getter
private final String name; private final String name;
@Setter
@Getter
private REntityActionListener<RPlayer> callback = null;
public RPlayer(REntityServer server, UUID uuid, String name, Location location) { public RPlayer(REntityServer server, UUID uuid, String name, Location location) {
super(server, EntityType.PLAYER, UUID.nameUUIDFromBytes(uuid.toString().getBytes(StandardCharsets.UTF_8)), location, 0); super(server, EntityType.PLAYER, UUID.nameUUIDFromBytes(uuid.toString().getBytes(StandardCharsets.UTF_8)), location, 0);
this.actualUUID = uuid; this.actualUUID = uuid;
@@ -20,14 +20,54 @@
package de.steamwar.providers; package de.steamwar.providers;
import de.steamwar.sql.SteamwarUser; import de.steamwar.sql.SteamwarUser;
import de.steamwar.sql.WorldType;
import org.bukkit.Bukkit; import org.bukkit.Bukkit;
import java.util.UUID;
public class BauServerInfo { public class BauServerInfo {
private static Integer bauOwner = null; private static Integer bauOwner = null;
private static Integer bauTeam = null;
private static UUID bauWorld = null;
private static WorldType bauType = null;
static { static {
String ownerProperty = System.getProperty("bauOwner");
if (ownerProperty != null && !ownerProperty.isBlank()) {
try {
bauOwner = Integer.parseInt(ownerProperty);
} catch (NumberFormatException ignored) {
}
}
String teamProperty = System.getProperty("bauTeam");
if (teamProperty != null && !teamProperty.isBlank()) {
try {
bauTeam = Integer.parseInt(teamProperty);
} catch (NumberFormatException ignored) {
}
}
String worldProperty = System.getProperty("bauWorld");
if (worldProperty != null && !worldProperty.isBlank()) {
try {
bauWorld = UUID.fromString(worldProperty);
} catch (IllegalArgumentException ignored) {
}
}
String typeProperty = System.getProperty("bauType");
if (typeProperty != null && !typeProperty.isBlank()) {
try {
bauType = WorldType.valueOf(typeProperty);
} catch (IllegalArgumentException ignored) {
}
}
try { try {
bauOwner = Integer.parseInt(Bukkit.getWorlds().get(0).getName()); if (bauOwner == null) {
bauOwner = Integer.parseInt(Bukkit.getWorlds().get(0).getName());
}
} catch (NumberFormatException ignored) { } catch (NumberFormatException ignored) {
} }
} }
@@ -37,11 +77,23 @@ public class BauServerInfo {
} }
public static boolean isBauServer() { public static boolean isBauServer() {
return bauOwner != null; return bauOwner != null || bauWorld != null || bauTeam != null;
} }
public static SteamwarUser getOwnerUser() { public static SteamwarUser getOwnerUser() {
if (bauOwner == null) return null; if (bauOwner == null) return null;
return SteamwarUser.byId(bauOwner); return SteamwarUser.byId(bauOwner);
} }
public static UUID getWorldId() {
return bauWorld;
}
public static Integer getTeamId() {
return bauTeam;
}
public static WorldType getWorldType() {
return bauType;
}
} }
@@ -0,0 +1,41 @@
/*
* This file is a part of the SteamWar software.
*
* Copyright (C) 2026 SteamWar.de-Serverteam
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package de.steamwar.techhider;
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.entity.Player;
public interface AccessPrivilegeProvider {
boolean isEveryonePrivilegedToAccessAllDataWithinChunk(int chunkX, int chunkZ);
boolean isPlayerPrivilegedToAccessPosition(Player p, int blockX, int blockY, int blockZ);
boolean isPlayerPrivilegedToAccessBlock(Player p, int blockX, int blockY, int blockZ, Block block);
boolean isPlayerPrivilegedToAccessBlockState(Player p, int blockX, int blockY, int blockZ, BlockState blockState);
boolean isPlayerPrivilegedToAccessEntity(Player p, int entityId);
boolean isPlayerPrivilegedToAccessBlockEntity(Player p, int blockX, int blockY, int blockZ, BlockEntityType<?> type);
boolean isPlayerPrivilegedToPerformAction(Player p);
}
@@ -19,8 +19,7 @@
package de.steamwar.techhider; package de.steamwar.techhider;
import de.steamwar.Reflection; import net.minecraft.core.registries.BuiltInRegistries;
import net.minecraft.core.IdMapper;
import net.minecraft.world.level.block.Block; import net.minecraft.world.level.block.Block;
import net.minecraft.world.level.block.state.BlockState; import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.level.material.FluidState; import net.minecraft.world.level.material.FluidState;
@@ -39,18 +38,20 @@ public class BlockIds {
} }
private static final FluidState water = Fluids.WATER.getSource(false); private static final FluidState water = Fluids.WATER.getSource(false);
private static final Iterable<BlockState> registryBlockId = (Iterable<BlockState>) Reflection.getField(TechHider.block, IdMapper.class, 0).get(null);
public Set<Integer> materialToAllIds(Material material) { public Set<Integer> materialToAllIds(Material material) {
Set<Integer> ids = new HashSet<>(); Set<Integer> ids = new HashSet<>();
for (BlockState data : getBlock(material).getStateDefinition().getPossibleStates()) { for (BlockState data : getBlock(material).getStateDefinition().getPossibleStates()) {
ids.add(getCombinedId(data)); ids.add(getCombinedId(data));
} }
if (material == Material.WATER) { if (material == Material.WATER) {
for (BlockState data : registryBlockId) { for (Block block : BuiltInRegistries.BLOCK) {
if (data.getFluidState() == water) { for (BlockState data : block.getStateDefinition().getPossibleStates()) {
ids.add(getCombinedId(data)); if (data.getFluidState() == water) {
ids.add(getCombinedId(data));
}
} }
} }
} }
@@ -22,295 +22,284 @@ package de.steamwar.techhider;
import de.steamwar.Reflection; import de.steamwar.Reflection;
import io.netty.buffer.ByteBuf; import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled; import io.netty.buffer.Unpooled;
import lombok.Getter; import net.minecraft.core.SectionPos;
import net.minecraft.core.Registry;
import net.minecraft.core.registries.BuiltInRegistries;
import net.minecraft.network.protocol.game.ClientboundLevelChunkPacketData; import net.minecraft.network.protocol.game.ClientboundLevelChunkPacketData;
import net.minecraft.network.protocol.game.ClientboundLevelChunkWithLightPacket; import net.minecraft.network.protocol.game.ClientboundLevelChunkWithLightPacket;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.util.SimpleBitStorage; import net.minecraft.util.SimpleBitStorage;
import net.minecraft.world.level.block.Block;
import net.minecraft.world.level.block.entity.BlockEntityType; import net.minecraft.world.level.block.entity.BlockEntityType;
import net.minecraft.world.level.block.state.BlockState;
import org.bukkit.entity.Player; import org.bukkit.entity.Player;
import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.Set;
import java.util.function.BiFunction;
import java.util.function.UnaryOperator; import java.util.function.UnaryOperator;
import java.util.stream.Collectors;
public class ChunkHider { public class ChunkHider {
public static final ChunkHider impl = new ChunkHider(); private static final UnaryOperator<Object> chunkPacketShallowCloner = ProtocolUtils.shallowCloneGenerator(ClientboundLevelChunkWithLightPacket.class);
private static final UnaryOperator<Object> chunkDataShallowCloner = ProtocolUtils.shallowCloneGenerator(ClientboundLevelChunkPacketData.class);
public Class<?> mapChunkPacket() { private static final Reflection.Field<ClientboundLevelChunkPacketData> levelChunkPacketDataField = Reflection.getField(ClientboundLevelChunkWithLightPacket.class, ClientboundLevelChunkPacketData.class, 0);
return ClientboundLevelChunkWithLightPacket.class;
private static final Reflection.Field<byte[]> chunkBlockDataField = Reflection.getField(ClientboundLevelChunkPacketData.class, byte[].class, 0);
private static final Reflection.Field<List> chunkBlockEntitiesDataField = Reflection.getField(ClientboundLevelChunkPacketData.class, List.class, 0);
private final int SECTION_SPAN_SIZE = 16;
private final byte BIT_PER_BLOCK_INDIRECTION_LIMIT = 8;
private final byte BIT_PER_BIOME_INDIRECTION_LIMIT = 3;
private final int BLOCKS_PER_SECTION = 4096;
private final int BIOMES_PER_SECTION = 64;
private final int blockIdUsedForHiding;
private final AccessPrivilegeProvider accessPrivilegeProvider;
public ChunkHider(Block blockUsedForObfuscation, AccessPrivilegeProvider accessPrivilegeProvider) {
blockIdUsedForHiding = Block.BLOCK_STATE_REGISTRY.getId(blockUsedForObfuscation.defaultBlockState());
this.accessPrivilegeProvider = accessPrivilegeProvider;
} }
private static final UnaryOperator<Object> chunkPacketCloner = ProtocolUtils.shallowCloneGenerator(ClientboundLevelChunkWithLightPacket.class); private int getLongsRequiredToEncodeEntries(int bitsPerEntry, int entryCount) {
private static final UnaryOperator<Object> chunkDataCloner = ProtocolUtils.shallowCloneGenerator(ClientboundLevelChunkPacketData.class); int entriesPerLong = Long.SIZE / bitsPerEntry;
int dataLengthAsLongCount = (entryCount + entriesPerLong - 1) / entriesPerLong;
private static final Reflection.Field<Integer> chunkXField = Reflection.getField(ClientboundLevelChunkWithLightPacket.class, int.class, 0); return dataLengthAsLongCount;
private static final Reflection.Field<Integer> chunkZField = Reflection.getField(ClientboundLevelChunkWithLightPacket.class, int.class, 1);
private static final Reflection.Field<ClientboundLevelChunkPacketData> chunkData = Reflection.getField(ClientboundLevelChunkWithLightPacket.class, ClientboundLevelChunkPacketData.class, 0);
private static final Reflection.Field<byte[]> dataField = Reflection.getField(ClientboundLevelChunkPacketData.class, byte[].class, 0);
private static final Reflection.Field<List> tileEntities = Reflection.getField(ClientboundLevelChunkPacketData.class, List.class, 0);
public BiFunction<Player, Object, Object> chunkHiderGenerator(TechHider techHider) {
return (p, packet) -> {
int chunkX = chunkXField.get(packet);
int chunkZ = chunkZField.get(packet);
if (techHider.getLocationEvaluator().skipChunk(p, chunkX, chunkZ)) {
return packet;
}
packet = chunkPacketCloner.apply(packet);
Object dataWrapper = chunkDataCloner.apply(chunkData.get(packet));
Set<String> hiddenBlockEntities = techHider.getHiddenBlockEntities();
tileEntities.set(dataWrapper, ((List<?>) tileEntities.get(dataWrapper)).stream().filter(te -> tileEntityVisible(hiddenBlockEntities, te)).collect(Collectors.toList()));
ByteBuf in = Unpooled.wrappedBuffer(dataField.get(dataWrapper));
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);
section.copyBlockCount();
blocks(section);
biomes(section);
}
if (in.readableBytes() != 0) {
throw new IllegalStateException("ChunkHider21: Incomplete chunk data, " + in.readableBytes() + " bytes left");
}
byte[] data = new byte[out.readableBytes()];
out.readBytes(data);
dataField.set(dataWrapper, data);
chunkData.set(packet, dataWrapper);
return packet;
};
} }
private static final Registry<BlockEntityType<?>> registry = Reflection.getField(BuiltInRegistries.class, "BLOCK_ENTITY_TYPE", Registry.class).get(null); private long[] readSectionDataFromBuffer(ByteBuf dataSource, short bitsPerEntry, int entryCount) {
private static final Reflection.Method getKey = Reflection.getTypedMethod(Reflection.getClass("net.minecraft.core.Registry"), "getKey", ResourceLocation.class, Object.class); int dataLengthAsLongCount = getLongsRequiredToEncodeEntries(bitsPerEntry, entryCount);
public static final Class<?> tileEntity = Reflection.getClass("net.minecraft.network.protocol.game.ClientboundLevelChunkPacketData$BlockEntityInfo"); long[] dataArray = new long[dataLengthAsLongCount];
protected static final Reflection.Field<BlockEntityType> entityType = Reflection.getField(tileEntity, BlockEntityType.class, 0);
protected boolean tileEntityVisible(Set<String> hiddenBlockEntities, Object tile) { for (int i = 0; i < dataLengthAsLongCount; i++) {
BlockEntityType type = entityType.get(tile); dataArray[i] = dataSource.readLong();
String path = ((ResourceLocation) getKey.invoke(registry, type)).getPath();
return !hiddenBlockEntities.contains(path);
}
private void blocks(SectionHider section) {
section.copyBitsPerBlock();
boolean singleValued = section.getBitsPerBlock() == 0;
if (singleValued) {
int value = ProtocolUtils.readVarInt(section.getIn());
ProtocolUtils.writeVarInt(section.getOut(), !section.isSkipSection() && section.getObfuscate().contains(value) ? section.getTarget() : value);
return;
} else if (section.getBitsPerBlock() < 9) {
// Indirect (paletted) storage only present when bitsPerBlock < 9 in 1.21+
section.processPalette();
} }
if (section.isSkipSection() || (!section.blockPrecise() && section.isPaletted())) { return dataArray;
section.skipNewDataArray(4096); }
return;
public ClientboundLevelChunkWithLightPacket processLevelChunkWithLightPacket(Player player, ClientboundLevelChunkWithLightPacket packet) {
int chunkX = packet.getX();
int chunkZ = packet.getZ();
ClientboundLevelChunkPacketData chunkData = packet.getChunkData();
ByteBuf in = Unpooled.wrappedBuffer(chunkData.getReadBuffer());
ByteBuf out = Unpooled.buffer(in.readableBytes() + 64);
int worldMinHeight = player.getWorld().getMinHeight();
int worldMaxHeight = player.getWorld().getMaxHeight();
for (int yOffset = worldMinHeight; yOffset < worldMaxHeight; yOffset += SECTION_SPAN_SIZE) {
short blockCount = in.readShort();
byte bitsPerBlock = in.readByte();
if (bitsPerBlock == 0) {
int sectionBlockId = ProtocolUtils.readVarInt(in);
out.writeShort(blockCount);
out.writeByte(bitsPerBlock);
ProtocolUtils.writeVarInt(out, sectionBlockId);
} else if (bitsPerBlock <= BIT_PER_BLOCK_INDIRECTION_LIMIT) {
int palletLength = ProtocolUtils.readVarInt(in);
int[] pallet = ProtocolUtils.readVarIntArray(in, palletLength);
long[] rawData = readSectionDataFromBuffer(in, bitsPerBlock, BLOCKS_PER_SECTION);
SimpleBitStorage data = new SimpleBitStorage(bitsPerBlock, BLOCKS_PER_SECTION, rawData);
int[] resolvedData = new int[BLOCKS_PER_SECTION];
for (int i = 0; i < BLOCKS_PER_SECTION; i++) {
int palletReference = data.get(i);
resolvedData[i] = pallet[palletReference];
}
int[] obfuscatedBlockData = obfuscateBlockDataArray(player, chunkX, chunkZ, yOffset, resolvedData);
long[] reEncodedData = encodeDirectBlockDataArray(obfuscatedBlockData);
out.writeShort(blockCount);
out.writeByte(15);
for (long rawDataSegment : reEncodedData) {
out.writeLong(rawDataSegment);
}
/*
Int2IntMap blockIdToPalletIndex = new Int2IntOpenHashMap();
IntArrayList newPallet = new IntArrayList();
for(int blockId : obfuscatedData) {
if(!blockIdToPalletIndex.containsKey(blockId)) {
newPallet.add(blockId);
blockIdToPalletIndex.put(blockId, newPallet.size());
}
}
byte newBitsPerBlock = (byte) getUnsignedBitLength(newPallet.size());
int[] newPalletRaw = newPallet.toArray(new int[newPallet.size()]);
SimpleBitStorage reEncodedData = new SimpleBitStorage(bitsPerBlock, BLOCKS_PER_SECTION, new long[rawData.length]);
for(int i = 0; i < obfuscatedData.length; i++) {
int blockId = obfuscatedData[i];
int palletReference = blockIdToPalletIndex.get(blockId);
reEncodedData.set(i, palletReference);
}
out.writeShort(blockCount);
out.writeByte(newBitsPerBlock);
ProtocolUtils.writeVarInt(out, palletLength);
ProtocolUtils.writeVarIntArray(out, newPalletRaw);
for(long rawDataSegment : reEncodedData.getRaw()) {
out.writeLong(rawDataSegment);
}*/
} else {
long[] rawData = readSectionDataFromBuffer(in, bitsPerBlock, BLOCKS_PER_SECTION);
int[] blockData = resolveDirectBlockDataArray(rawData, bitsPerBlock);
int[] obfuscatedBlockData = obfuscateBlockDataArray(player, chunkX, chunkZ, yOffset, blockData);
long[] reEncodedData = encodeDirectBlockDataArray(obfuscatedBlockData);
out.writeShort(blockCount);
out.writeByte(15);
for (long rawDataSegment : reEncodedData) {
out.writeLong(rawDataSegment);
}
}
copyOverSectionBiomeData(in, out);
} }
SimpleBitStorage values = new SimpleBitStorage(section.getBitsPerBlock(), 4096, section.readNewDataArray(4096)); if (in.readableBytes() != 0) {
return null;
}
for (int y = 0; y < 16; y++) { byte[] data = new byte[out.readableBytes()];
for (int z = 0; z < 16; z++) { out.readBytes(data);
for (int x = 0; x < 16; x++) {
int pos = (((y * 16) + z) * 16) + x;
TechHider.State test = section.test(x, y, z); List<Object> blockEntities = chunkBlockEntitiesDataField.get(chunkData);
List<Object> filteredBlockEntities = filterBlockEntities(player, blockEntities, chunkX, chunkZ);
switch (test) { return buildNewChunkPacket(packet, data, filteredBlockEntities);
case SKIP:
break; }
case CHECK:
if (!section.getObfuscate().contains(values.get(pos))) { private int[] obfuscateBlockDataArray(Player player, int chunkX, int chunkZ, int yOffset, int[] blockDataArray) {
break; int[] obfuscatedData = new int[BLOCKS_PER_SECTION];
} for (int sectionY = 0; sectionY < SECTION_SPAN_SIZE; sectionY++) {
case HIDE: for (int sectionZ = 0; sectionZ < SECTION_SPAN_SIZE; sectionZ++) {
values.set(pos, section.getTarget()); for (int sectionX = 0; sectionX < SECTION_SPAN_SIZE; sectionX++) {
break; int blockDataIndex = (((sectionY * SECTION_SPAN_SIZE) + sectionZ) * SECTION_SPAN_SIZE) + sectionX;
case HIDE_AIR:
default: int worldX = sectionX + (SECTION_SPAN_SIZE * chunkX);
values.set(pos, section.getAir()); int worldY = sectionY + yOffset;
int worldZ = sectionZ + (SECTION_SPAN_SIZE * chunkZ);
int blockId = blockDataArray[blockDataIndex];
BlockState blockState = Block.BLOCK_STATE_REGISTRY.byId(blockId);
Block block = blockState.getBlock();
if (accessPrivilegeProvider.isPlayerPrivilegedToAccessPosition(player, worldX, worldY, worldZ) && accessPrivilegeProvider.isPlayerPrivilegedToAccessBlock(player, worldX, worldY, worldZ, block) && accessPrivilegeProvider.isPlayerPrivilegedToAccessBlockState(player, worldX, worldY, worldZ, blockState)) {
obfuscatedData[blockDataIndex] = blockId;
} else {
obfuscatedData[blockDataIndex] = blockIdUsedForHiding;
} }
} }
} }
} }
section.writeDataArray(values.getRaw()); return obfuscatedData;
} }
private void biomes(SectionHider section) { private int[] resolveDirectBlockDataArray(long[] rawDataArray, byte bitsPerBlock) {
section.copyBitsPerBlock(); SimpleBitStorage data = new SimpleBitStorage(bitsPerBlock, BLOCKS_PER_SECTION, rawDataArray);
if (section.getBitsPerBlock() == 0) {
section.copyVarInt(); int[] resolvedData = new int[BLOCKS_PER_SECTION];
} else if (section.getBitsPerBlock() < 6) { for (int i = 0; i < BLOCKS_PER_SECTION; i++) {
section.skipPalette(); resolvedData[i] = data.get(i);
section.skipNewDataArray(64); }
return resolvedData;
}
private long[] encodeDirectBlockDataArray(int[] blockDataArray) {
int longsRequiredToEncodeData = getLongsRequiredToEncodeEntries(15, blockDataArray.length);
SimpleBitStorage reEncodedData = new SimpleBitStorage(15, BLOCKS_PER_SECTION, new long[longsRequiredToEncodeData]);
for (int i = 0; i < blockDataArray.length; i++) {
int blockId = blockDataArray[i];
reEncodedData.set(i, blockId);
}
return reEncodedData.getRaw();
}
private ClientboundLevelChunkWithLightPacket buildNewChunkPacket(ClientboundLevelChunkWithLightPacket originalPacket, byte[] newBlockDataBuffer, List<Object> newBlockEntities) {
ClientboundLevelChunkWithLightPacket clonedPacket = (ClientboundLevelChunkWithLightPacket) chunkPacketShallowCloner.apply(originalPacket);
ClientboundLevelChunkPacketData clonedPacketChunkData = (ClientboundLevelChunkPacketData) chunkDataShallowCloner.apply(originalPacket.getChunkData());
chunkBlockDataField.set(clonedPacketChunkData, newBlockDataBuffer);
chunkBlockEntitiesDataField.set(clonedPacketChunkData, newBlockEntities);
levelChunkPacketDataField.set(clonedPacket, clonedPacketChunkData);
return clonedPacket;
}
private static final Class<?> blockEntitiyInfoClass = Reflection.getClass("net.minecraft.network.protocol.game.ClientboundLevelChunkPacketData$BlockEntityInfo");
private static final Reflection.Field<BlockEntityType> blockEntityInfoTypeField = Reflection.getField(blockEntitiyInfoClass, BlockEntityType.class, 0);
private static final Reflection.Field<Integer> packedXZField = Reflection.getField(blockEntitiyInfoClass, int.class, 0);
private static final Reflection.Field<Integer> yField = Reflection.getField(blockEntitiyInfoClass, int.class, 1);
private List<Object> filterBlockEntities(Player player, List<Object> blockEntities, int chunkX, int chunkZ) {
int fourBitBitmask = 0b0000_1111;
return blockEntities.stream()
.filter((blockEntityInfo) -> {
BlockEntityType<?> type = blockEntityInfoTypeField.get(blockEntityInfo);
int packedXZ = packedXZField.get(blockEntityInfo);
int localX = (packedXZ >> 4) & fourBitBitmask;
int localZ = packedXZ & fourBitBitmask;
int worldX = (chunkX * SECTION_SPAN_SIZE) + localX;
int worldZ = (chunkZ * SECTION_SPAN_SIZE) + localZ;
int worldY = yField.get(blockEntityInfo);
return accessPrivilegeProvider.isPlayerPrivilegedToAccessPosition(player, worldX, worldY, worldZ) && accessPrivilegeProvider.isPlayerPrivilegedToAccessBlockEntity(player, worldX, worldY, worldZ, type);
}).toList();
}
private void copyOverSectionBiomeData(ByteBuf oldData, ByteBuf newData) {
short bitsPerBiome = oldData.readByte();
newData.writeByte(bitsPerBiome);
if (bitsPerBiome == 0) {
int sectionBiomeId = ProtocolUtils.readVarInt(oldData);
ProtocolUtils.writeVarInt(newData, sectionBiomeId);
} else if (bitsPerBiome <= BIT_PER_BIOME_INDIRECTION_LIMIT) {
int palletLength = ProtocolUtils.readVarInt(oldData);
ProtocolUtils.writeVarInt(newData, palletLength);
for (int i = 0; i < palletLength; i++) {
int palletEntry = ProtocolUtils.readVarInt(oldData);
ProtocolUtils.writeVarInt(newData, palletEntry);
}
long[] rawData = readSectionDataFromBuffer(oldData, bitsPerBiome, BIOMES_PER_SECTION);
for (long rawDataSegment : rawData) {
newData.writeLong(rawDataSegment);
}
} else { } else {
// Direct (global) biome IDs no palette present long[] rawData = readSectionDataFromBuffer(oldData, bitsPerBiome, BIOMES_PER_SECTION);
section.skipNewDataArray(64); for (long rawDataSegment : rawData) {
} newData.writeLong(rawDataSegment);
}
@Getter
class SectionHider {
private final Player player;
private final TechHider techHider;
private final ByteBuf in;
private final ByteBuf out;
private final int chunkX;
private final int chunkY;
private final int chunkZ;
private final int offsetX;
private final int offsetY;
private final int offsetZ;
private final boolean skipSection;
private boolean paletted;
private int bitsPerBlock;
private int blockCount;
private int air;
private int target;
private Set<Integer> obfuscate;
public SectionHider(Player player, TechHider techHider, ByteBuf in, ByteBuf out, int chunkX, int chunkY, int chunkZ) {
this.player = player;
this.techHider = techHider;
this.in = in;
this.out = out;
this.chunkX = chunkX;
this.chunkY = chunkY;
this.chunkZ = chunkZ;
this.offsetX = 16 * chunkX;
this.offsetY = 16 * chunkY;
this.offsetZ = 16 * chunkZ;
this.skipSection = techHider.getLocationEvaluator().skipChunkSection(player, chunkX, chunkY, chunkZ);
this.paletted = false;
this.bitsPerBlock = 0;
this.air = TechHider.AIR_ID;
this.target = techHider.getObfuscationTargetId();
this.obfuscate = techHider.getObfuscateIds();
}
public boolean blockPrecise() {
return techHider.getLocationEvaluator().blockPrecise(player, chunkX, chunkY, chunkZ);
}
public TechHider.State test(int x, int y, int z) {
return techHider.getLocationEvaluator().check(player, offsetX + x, offsetY + y, offsetZ + z);
}
public void copyBlockCount() {
this.blockCount = in.readShort();
out.writeShort(blockCount);
}
public void copyBitsPerBlock() {
bitsPerBlock = in.readByte();
out.writeByte(bitsPerBlock);
}
public int copyVarInt() {
int value = ProtocolUtils.readVarInt(in);
ProtocolUtils.writeVarInt(out, value);
return value;
}
public void skipPalette() {
int paletteLength = copyVarInt();
for (int i = 0; i < paletteLength; i++) {
copyVarInt();
}
}
public void processPalette() {
if (skipSection) {
skipPalette();
return;
}
int paletteLength = copyVarInt();
if (paletteLength == 0) return;
paletted = true;
air = 0;
target = 0;
for (int i = 0; i < paletteLength; i++) {
int entry = ProtocolUtils.readVarInt(in);
if (obfuscate.contains(entry)) {
entry = techHider.getObfuscationTargetId();
}
if (entry == TechHider.AIR_ID) {
air = i;
} else if (entry == techHider.getObfuscationTargetId()) {
target = i;
}
ProtocolUtils.writeVarInt(out, entry);
}
obfuscate = Collections.emptySet();
}
public void skipDataArray() {
int dataArrayLength = copyVarInt();
out.writeBytes(in, dataArrayLength * 8);
}
public void skipNewDataArray(int entries) {
if (bitsPerBlock == 0) {
return;
}
char valuesPerLong = (char) (64 / bitsPerBlock);
int i1 = (entries + valuesPerLong - 1) / valuesPerLong;
out.writeBytes(in, i1 * Long.BYTES);
}
public long[] readDataArray() {
long[] array = new long[copyVarInt()];
for (int i = 0; i < array.length; i++) {
array[i] = in.readLong();
}
return array;
}
public long[] readNewDataArray(int entries) {
if (bitsPerBlock == 0) {
return new long[entries];
}
char valuesPerLong = (char) (64 / bitsPerBlock);
int i1 = (entries + valuesPerLong - 1) / valuesPerLong;
long[] array = new long[i1];
for (int i = 0; i < i1; i++) {
array[i] = in.readLong();
}
return array;
}
public void writeDataArray(long[] array) {
for (long l : array) {
out.writeLong(l);
} }
} }
} }
@@ -140,6 +140,16 @@ public class ProtocolUtils {
return result; return result;
} }
public static int[] readVarIntArray(ByteBuf buffer, int length) {
int[] array = new int[length];
for (int i = 0; i < length; i++) {
array[i] = readVarInt(buffer);
}
return array;
}
public static void writeVarInt(ByteBuf buf, int value) { public static void writeVarInt(ByteBuf buf, int value) {
do { do {
int temp = value & 0b01111111; int temp = value & 0b01111111;
@@ -152,6 +162,12 @@ public class ProtocolUtils {
} while (value != 0); } while (value != 0);
} }
public static void writeVarIntArray(ByteBuf buf, int[] values) {
for (int varInt : values) {
writeVarInt(buf, varInt);
}
}
@Deprecated @Deprecated
public static int readVarIntLength(byte[] array, int startPos) { public static int readVarIntLength(byte[] array, int startPos) {
int numRead = 0; int numRead = 0;
@@ -1,7 +1,7 @@
/* /*
* This file is a part of the SteamWar software. * 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 * 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 * it under the terms of the GNU Affero General Public License as published by
@@ -21,161 +21,529 @@ package de.steamwar.techhider;
import com.comphenix.tinyprotocol.TinyProtocol; import com.comphenix.tinyprotocol.TinyProtocol;
import de.steamwar.Reflection; import de.steamwar.Reflection;
import lombok.Getter; import it.unimi.dsi.fastutil.ints.IntArrayList;
import it.unimi.dsi.fastutil.ints.IntList;
import it.unimi.dsi.fastutil.shorts.ShortArraySet;
import it.unimi.dsi.fastutil.shorts.ShortSets;
import net.minecraft.core.BlockPos; import net.minecraft.core.BlockPos;
import net.minecraft.core.Vec3i; import net.minecraft.core.SectionPos;
import net.minecraft.network.PacketListener;
import net.minecraft.network.protocol.Packet;
import net.minecraft.network.protocol.common.*;
import net.minecraft.network.protocol.configuration.ClientboundFinishConfigurationPacket;
import net.minecraft.network.protocol.configuration.ClientboundRegistryDataPacket;
import net.minecraft.network.protocol.configuration.ClientboundSelectKnownPacks;
import net.minecraft.network.protocol.configuration.ClientboundUpdateEnabledFeaturesPacket;
import net.minecraft.network.protocol.cookie.ClientboundCookieRequestPacket;
import net.minecraft.network.protocol.game.*; import net.minecraft.network.protocol.game.*;
import net.minecraft.network.protocol.login.*;
import net.minecraft.world.level.block.Block; import net.minecraft.world.level.block.Block;
import net.minecraft.world.level.block.entity.BlockEntityType;
import net.minecraft.world.level.block.state.BlockState; import net.minecraft.world.level.block.state.BlockState;
import org.bukkit.Material; import net.minecraft.world.phys.Vec3;
import org.bukkit.craftbukkit.util.CraftMagicNumbers;
import org.bukkit.entity.Player; import org.bukkit.entity.Player;
import java.util.HashMap; import java.util.*;
import java.util.Map;
import java.util.Set;
import java.util.function.BiFunction; import java.util.function.BiFunction;
import java.util.function.Function; import java.util.function.Function;
import java.util.function.UnaryOperator; import java.util.function.ToIntFunction;
import java.util.stream.Collectors;
/**
* The TechHider follows the default-deny security principle,
* any packet must be explicitly whitelisted or processed in a
* way that is also compliant with the principle of default-deny.
*/
public class TechHider { public class TechHider {
private final Set<Class<?>> bypassingPackets;
private final Map<Class<? extends Packet<? extends PacketListener>>, BiFunction<Player, Packet<? extends PacketListener>, Packet<? extends PacketListener>>> packetProcessors;
public static final Class<?> blockPosition = BlockPos.class; private final BlockState blockStateUsedForObfuscation;
private static final Class<?> baseBlockPosition = Vec3i.class; private final ChunkHider chunkHider;
public static final Reflection.Field<Integer> blockPositionX = Reflection.getField(baseBlockPosition, int.class, 0);
public static final Reflection.Field<Integer> blockPositionY = Reflection.getField(baseBlockPosition, int.class, 1);
public static final Reflection.Field<Integer> blockPositionZ = Reflection.getField(baseBlockPosition, int.class, 2);
public static final Class<?> iBlockData = BlockState.class; private final AccessPrivilegeProvider privilegeProvider;
public static final Class<?> block = Block.class;
public boolean iBlockDataHidden(BlockState iBlockData) { // TODO handle packet bundle
return obfuscateIds.contains(BlockIds.impl.getCombinedId(iBlockData)); public TechHider(Block blockUsedForObfuscation, AccessPrivilegeProvider privilegeProvider) {
} this.blockStateUsedForObfuscation = blockUsedForObfuscation.defaultBlockState();
public static final Object AIR = CraftMagicNumbers.getBlock(Material.AIR).defaultBlockState(); this.chunkHider = new ChunkHider(blockUsedForObfuscation, privilegeProvider);
public static final int AIR_ID = BlockIds.impl.materialToId(Material.AIR); this.privilegeProvider = privilegeProvider;
private final Map<Class<?>, BiFunction<Player, Object, Object>> techhiders = new HashMap<>(); this.bypassingPackets = new HashSet<>(List.of(
@Getter // --- 5.1.x Login Protocol ---
private final LocationEvaluator locationEvaluator; ClientboundLoginDisconnectPacket.class, // 5.1.1 Disconnect
@Getter ClientboundHelloPacket.class, // 5.1.2 Encryption Request
private final Object obfuscationTarget; ClientboundLoginFinishedPacket.class, // 5.1.3 Login Success
@Getter ClientboundLoginCompressionPacket.class, // 5.1.4 Set Compression
private final int obfuscationTargetId; ClientboundCustomQueryPacket.class, // 5.1.5 Login Plugin Request
@Getter ClientboundCookieRequestPacket.class, // 5.1.6 Cookie Request
private final Set<Integer> obfuscateIds;
@Getter
private final Set<String> hiddenBlockEntities;
public TechHider(LocationEvaluator locationEvaluator, Material obfuscationTarget, Set<Material> obfuscate, Set<String> hiddenBlockEntities) { // --- 6.1.x Configuration Protocol ---
this.locationEvaluator = locationEvaluator; ClientboundSelectKnownPacks.class, ClientboundCustomPayloadPacket.class, // 6.1.2 Clientbound Plugin Message
this.obfuscateIds = obfuscate.stream().flatMap(m -> BlockIds.impl.materialToAllIds(m).stream()).collect(Collectors.toSet()); ClientboundFinishConfigurationPacket.class, // 6.1.4 Finish Configuration
this.hiddenBlockEntities = hiddenBlockEntities; ClientboundKeepAlivePacket.class, // 6.1.5 Clientbound Keep Alive
this.obfuscationTarget = CraftMagicNumbers.getBlock(obfuscationTarget).defaultBlockState(); ClientboundPingPacket.class, // 6.1.6 Ping
this.obfuscationTargetId = BlockIds.impl.materialToId(obfuscationTarget); ClientboundRegistryDataPacket.class, // 6.1.8 Registry Data
ClientboundResourcePackPopPacket.class, // 6.1.9 Remove Resource Pack
ClientboundResourcePackPushPacket.class, // 6.1.10 Add Resource Pack
ClientboundStoreCookiePacket.class, // 6.1.11 Store Cookie
ClientboundTransferPacket.class, // 6.1.12 Transfer
ClientboundUpdateEnabledFeaturesPacket.class, // 6.1.13 Feature Flags
ClientboundCustomReportDetailsPacket.class, // 6.1.16 Custom Report Details
ClientboundServerLinksPacket.class, // 6.1.17 Server Links
ClientboundSystemChatPacket.class, // 6.1.18/19 Dialogs are often handled via System Chat or Custom
// Payloads
ClientboundServerDataPacket.class, // 6.1.20 Code of Conduct is usually in Server Data
techhiders.put(blockActionPacket, this::blockActionHider); // --- 7.1.x Play Protocol ---
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(ServerboundUseItemOnPacket.class, (p, packet) -> locationEvaluator.suppressInteractions(p) ? null : packet);
techhiders.put(ServerboundInteractPacket.class, (p, packet) -> locationEvaluator.suppressInteractions(p) ? null : packet);
} // --- Safe packets; Not involved with a critical subdomain ---
ClientboundBundleDelimiterPacket.class, // 7.1.1 Bundle Delimiter
ClientboundBossEventPacket.class, // 7.1.10 Boss Bar
ClientboundChangeDifficultyPacket.class, // 7.1.11 Change Difficulty
ClientboundClearTitlesPacket.class, // 7.1.15 Clear Titles
ClientboundCommandSuggestionsPacket.class, // 7.1.16 Command Suggestions Response
ClientboundCommandsPacket.class, // 7.1.17 Commands
ClientboundCookieRequestPacket.class, // 7.1.22 Cookie Request (play)
ClientboundCooldownPacket.class, // 7.1.23 Set Cooldown
ClientboundCustomChatCompletionsPacket.class, // 7.1.24 Chat Suggestions
ClientboundDeleteChatPacket.class, // 7.1.32 Delete Message
ClientboundDisconnectPacket.class, // 7.1.33 Disconnect (play)
ClientboundDisguisedChatPacket.class, // 7.1.34 Disguised Chat Message
ClientboundGameEventPacket.class, // 7.1.39 Game Event (like ElderGuardian effect, rain, thunder, etc.)
ClientboundHorseScreenOpenPacket.class, // 7.1.41 Open Horse Screen: entity id based container.
ClientboundInitializeBorderPacket.class, // 7.1.43 Initialize World Border
ClientboundKeepAlivePacket.class, // 7.1.44 Clientbound Keep Alive (play)
ClientboundLoginPacket.class, // 7.1.49 Login (play)
ClientboundMerchantOffersPacket.class, // 7.1.51 Merchant Offers
ClientboundOpenBookPacket.class, // 7.1.57 Open Book
ClientboundOpenScreenPacket.class, // 7.1.58 Open Screen
ClientboundPingPacket.class, // 7.1.60 Ping (play)
ClientboundPlaceGhostRecipePacket.class, // 7.1.62 Place Ghost Recipe
ClientboundPlayerAbilitiesPacket.class, // 7.1.63 Player Abilities (clientbound)
ClientboundPlayerChatPacket.class, // 7.1.64 Player Chat Message
ClientboundPlayerCombatEndPacket.class, // 7.1.65 End Combat
ClientboundPlayerCombatEnterPacket.class, // 7.1.66 Enter Combat
ClientboundPlayerLookAtPacket.class, // 7.1.70 Look At (Player owning channel)
ClientboundPlayerPositionPacket.class, // 7.1.71 Synchronize Player Position (Player owning the channel)
ClientboundPlayerRotationPacket.class, // 7.1.72 Player Rotation (Player owning the channel)
ClientboundRecipeBookAddPacket.class, // 7.1.73 Recipe Book Add
ClientboundRecipeBookRemovePacket.class, // 7.1.74 Recipe Book Remove
ClientboundRecipeBookSettingsPacket.class, // 7.1.75 Recipe Book Settings
ClientboundResetScorePacket.class, // 7.1.78 Reset Score
ClientboundResourcePackPopPacket.class, // 7.1.79 Remove Resource Pack (play)
ClientboundResourcePackPushPacket.class, // 7.1.80 Add Resource Pack (play)
ClientboundRespawnPacket.class, // 7.1.81 Respawn
ClientboundSelectAdvancementsTabPacket.class, // 7.1.84 Select Advancements Tab
ClientboundServerDataPacket.class, // 7.1.85 Server Data
ClientboundSetActionBarTextPacket.class, // 7.1.86 Set Action Bar Text
ClientboundSetBorderCenterPacket.class, // 7.1.87 Set Border Center
ClientboundSetBorderLerpSizePacket.class, // 7.1.88 Set Border Lerp Size
ClientboundSetBorderSizePacket.class, // 7.1.89 Set Border Size
ClientboundSetBorderWarningDelayPacket.class, // 7.1.90 Set Border Warning Delay
ClientboundSetBorderWarningDistancePacket.class, // 7.1.91 Set Border Warning Distance
ClientboundSetCameraPacket.class, // 7.1.92 Set Camera
ClientboundSetChunkCacheRadiusPacket.class, // 7.1.94 Set Render Distance
ClientboundSetCursorItemPacket.class, // 7.1.95 Set Cursor Item
ClientboundSetDefaultSpawnPositionPacket.class, // 7.1.96 Set Default Spawn Position
ClientboundSetDisplayObjectivePacket.class, // 7.1.97 Display Objective
ClientboundSetExperiencePacket.class, // 7.1.102 Set Experience
ClientboundSetHealthPacket.class, // 7.1.103 Set Health, Saturation and food (Player owning the channel)
ClientboundSetHeldSlotPacket.class, // 7.1.104 Set Held Item (Player owning the channel)
ClientboundSetObjectivePacket.class, // 7.1.105 Update Objectives
ClientboundSetPlayerInventoryPacket.class, // 7.1.107 Set Player Inventory Slot (Player owning the channel)
ClientboundSetPlayerTeamPacket.class, // 7.1.108 Update Teams
ClientboundSetScorePacket.class, // 7.1.109 Update Score
ClientboundSetSimulationDistancePacket.class, // 7.1.110 Set Simulation Distance
ClientboundSetSubtitleTextPacket.class, // 7.1.111 Set Subtitle Text
ClientboundSetTimePacket.class, // 7.1.112 Update Time
ClientboundSetTitleTextPacket.class, // 7.1.113 Set Title Text
ClientboundSetTitlesAnimationPacket.class, // 7.1.114 Set Title Animation Times
ClientboundStartConfigurationPacket.class, // 7.1.117 Start Configuration
ClientboundStoreCookiePacket.class, // 7.1.119 Store Cookie (play)
ClientboundSystemChatPacket.class, // 7.1.120 System Chat Message
ClientboundTabListPacket.class, // 7.1.121 Set Tab List Header And Footer
ClientboundTickingStatePacket.class, // 7.1.126 Set Ticking State
ClientboundTickingStepPacket.class, // 7.1.127 Step Tick
ClientboundTransferPacket.class, // 7.1.128 Transfer (play)
ClientboundUpdateAdvancementsPacket.class, // 7.1.129 Update Advancements
ClientboundUpdateRecipesPacket.class, // 7.1.132 Update Recipes
ClientboundCustomReportDetailsPacket.class, // 7.1.135 Custom Report Details
ClientboundServerLinksPacket.class, // 7.1.136 Server Links
ClientboundClearDialogPacket.class, // 7.1.138 Clear Dialog (play)
ClientboundShowDialogPacket.class, // 7.1.139 Show Dialog (play)
public void enable() { // --- Mostly safe packets; Are involved with critical subdomain, but in not critical way ---
techhiders.forEach(TinyProtocol.instance::addFilter); ClientboundBlockChangedAckPacket.class, // 7.1.5 Acknowledge Block Change
} ClientboundChunkBatchFinishedPacket.class, // 7.1.12 Chunk Batch Finished (Delimiter)
ClientboundChunkBatchStartPacket.class, // 7.1.13 Chunk Batch Start (Delimiter)
ClientboundChunksBiomesPacket.class, // 7.1.14 Chunk Biomes
ClientboundContainerClosePacket.class, // 7.1.18 Close Container
ClientboundSetChunkCacheCenterPacket.class, // 7.1.93 Set Center Chunk
ClientboundForgetLevelChunkPacket.class, // 7.1.38 Unload Chunk
ClientboundUpdateTagsPacket.class, // 7.1.133 Update Tags (play)
ClientboundPlayerInfoRemovePacket.class, // 7.1.68 Player Info Remove
ClientboundPlayerInfoUpdatePacket.class, // 7.1.69 Player Info Update
ClientboundMoveVehiclePacket.class, // 7.1.56 Move Vehicle (vehicle the player is in)
ClientboundStopSoundPacket.class, // 7.1.118 Stop Sound: sound state side channel
public void disable() { ClientboundLightUpdatePacket.class, // 7.1.48 Update Light
techhiders.forEach(TinyProtocol.instance::removeFilter);
}
public static final Class<?> multiBlockChangePacket = ClientboundSectionBlocksUpdatePacket.class; ClientboundContainerSetContentPacket.class, // 7.1.19 Set Container Content
public static final UnaryOperator<Object> multiBlockChangeCloner = ProtocolUtils.shallowCloneGenerator(TechHider.multiBlockChangePacket); ClientboundContainerSetDataPacket.class, // 7.1.20 Set Container Property
ClientboundContainerSetSlotPacket.class // 7.1.21 Set Container Slot
));
private static final Class<?> blockChangePacket = ClientboundBlockUpdatePacket.class; BiFunction<Player, Packet<? extends PacketListener>, Packet<? extends PacketListener>> tossPacket = (p, packet) -> null;
private static final Function<Object, Object> blockChangeCloner = ProtocolUtils.shallowCloneGenerator(blockChangePacket); Map<Class<? extends Packet<? extends PacketListener>>, BiFunction<Player, Packet<? extends PacketListener>, Packet<? extends PacketListener>>> processors = new HashMap<>();
private static final Reflection.Field<?> blockChangePosition = Reflection.getField(blockChangePacket, blockPosition, 0);
private static final Reflection.Field<?> blockChangeBlockData = Reflection.getField(blockChangePacket, iBlockData, 0);
private Object blockChangeHider(Player p, Object packet) { // --- Entity packets -
switch (locationEvaluator.checkBlockPos(p, blockChangePosition.get(packet))) { // 7.1.2 Spawn Entity: entity type and position can reveal hidden contraptions.
case SKIP: processors.put(ClientboundAddEntityPacket.class, (p, packet) -> this.processAddEntityPacket(p, (ClientboundAddEntityPacket) packet));
return packet; // 7.1.3 Entity Animation: entity id based signal, keep blocked until entity visibility is modeled.
case CHECK: processors.put(ClientboundAnimatePacket.class, this.buildEntityPacketProcessor(ClientboundAnimatePacket::getId));
if (!iBlockDataHidden((BlockState) blockChangeBlockData.get(packet))) { // 7.1.35 Entity Event: entity id based signal.
return packet; processors.put(ClientboundEntityEventPacket.class, this.buildEntityPacketProcessor(ClientboundEntityEventPacket::getEventId));
// 7.1.36 Teleport Entity: entity id and absolute position.
processors.put(ClientboundTeleportEntityPacket.class, this.buildEntityPacketProcessor(ClientboundTeleportEntityPacket::id));
// 7.1.42 Hurt Animation: entity id based signal.
processors.put(ClientboundHurtAnimationPacket.class, this.buildEntityPacketProcessor(ClientboundHurtAnimationPacket::id));
// 7.1.100 Set Entity Velocity: entity id and movement.
processors.put(ClientboundSetEntityMotionPacket.class, this.buildEntityPacketProcessor(ClientboundSetEntityMotionPacket::getId));
// 7.1.101 Set Equipment: entity equipment can reveal wargear.
processors.put(ClientboundSetEquipmentPacket.class, this.buildEntityPacketProcessor(ClientboundSetEquipmentPacket::getEntity));
// 7.1.106 Set Passengers: entity relationship signal.
processors.put(ClientboundSetPassengersPacket.class, this.buildEntityPacketProcessor(ClientboundSetPassengersPacket::getVehicle));
// 7.1.130 Update Attributes: entity id and attribute state.
processors.put(ClientboundUpdateAttributesPacket.class, this.buildEntityPacketProcessor(ClientboundUpdateAttributesPacket::getEntityId));
// 7.1.131 Entity Effect: entity id and effect state.
processors.put(ClientboundUpdateMobEffectPacket.class, this.buildEntityPacketProcessor(ClientboundUpdateMobEffectPacket::getEntityId));
// 7.1.77 Remove Entity Effect: entity id and effect state.
processors.put(ClientboundRemoveMobEffectPacket.class, this.buildEntityPacketProcessor(ClientboundRemoveMobEffectPacket::entityId));
// 7.1.98 Set Entity Metadata: entity state can reveal blocks/items/displays.
processors.put(ClientboundSetEntityDataPacket.class, this.buildEntityPacketProcessor(ClientboundSetEntityDataPacket::id));
// 7.1.26 Damage Event: entity ids and damage source location can leak hide activity.
processors.put(ClientboundDamageEventPacket.class, this.buildEntityPacketProcessor(ClientboundDamageEventPacket::entityId));
// 7.1.54 Move Minecart Along Track: entity path and position signal.
processors.put(ClientboundMoveMinecartPacket.class, this.buildEntityPacketProcessor(ClientboundMoveMinecartPacket::entityId));
// 7.1.124 Synchronize Vehicle Position: entity/vehicle position.
processors.put(ClientboundEntityPositionSyncPacket.class, this.buildEntityPacketProcessor(ClientboundEntityPositionSyncPacket::id));
// 7.1.134 Projectile Power: projectile/entity signal.
processors.put(ClientboundProjectilePowerPacket.class, this.buildEntityPacketProcessor(ClientboundProjectilePowerPacket::getId));
// 7.1.123 Pickup Item: item/entity ids and pickup activity.
processors.put(ClientboundTakeItemEntityPacket.class, this.buildEntityPacketProcessor(ClientboundTakeItemEntityPacket::getItemId));
// 7.1.67 Combat Death: entity/player ids and death message context.
processors.put(ClientboundPlayerCombatKillPacket.class, this.buildEntityPacketProcessor(ClientboundPlayerCombatKillPacket::playerId));
// 7.1.52/53/55 Update Entity Position/Rotation: entity id and movement signal.
processors.put(ClientboundMoveEntityPacket.class, (p, packet) -> this.processMoveEntityPacket(p, (ClientboundMoveEntityPacket) packet));
processors.put(ClientboundMoveEntityPacket.Pos.class, (p, packet) -> this.processMoveEntityPacket(p, (ClientboundMoveEntityPacket.Pos) packet));
processors.put(ClientboundMoveEntityPacket.Rot.class, (p, packet) -> this.processMoveEntityPacket(p, (ClientboundMoveEntityPacket.Rot) packet));
processors.put(ClientboundMoveEntityPacket.PosRot.class, (p, packet) -> this.processMoveEntityPacket(p, (ClientboundMoveEntityPacket.PosRot) packet));
// 7.1.82 Set Head Rotation: entity id and rotation.
processors.put(ClientboundRotateHeadPacket.class, (p, packet) -> this.processRotateHeadPacket(p, (ClientboundRotateHeadPacket) packet));
// 7.1.76 Remove Entities: entity id visibility side channel.
processors.put(ClientboundRemoveEntitiesPacket.class, (p, packet) -> this.processRemoveEntitiesPacket(p, (ClientboundRemoveEntitiesPacket) packet));
// 7.1.99 Link Entities: entity relationship signal.
processors.put(ClientboundSetEntityLinkPacket.class, (p, packet) -> this.processSetEntityLinkPacket(p, (ClientboundSetEntityLinkPacket) packet));
// --- Block entity packets ---
// 7.1.7 Block Entity Data: currently filtered by block position and allowed tile-entity actions.
processors.put(ClientboundBlockEntityDataPacket.class, (p, packet) -> processBlockEntityDataPacket(p, (ClientboundBlockEntityDataPacket) packet));
// 7.1.122 Tag Query Response: block-entity query result.
processors.put(ClientboundTagQueryPacket.class, tossPacket);
// --- Block packets ---
// 7.1.6 Set Block Destroy Stage: block position and mining progress can reveal activity.
processors.put(ClientboundBlockDestructionPacket.class, (p, packet) -> proccessBlockDestructionPacket(p, (ClientboundBlockDestructionPacket) packet));
// 7.1.8 Block Action: currently filtered by block position.
processors.put(ClientboundBlockEventPacket.class, (p, packet) -> processBlockEventPacket(p, (ClientboundBlockEventPacket) packet));
// 7.1.9 Block Update: currently filtered/obfuscated by block position and block id.
processors.put(ClientboundBlockUpdatePacket.class, (p, packet) -> processBlockUpdatePacket(p, (ClientboundBlockUpdatePacket) packet));
// --- Chunk packets ---
// 7.1.45 Chunk Data and Update Light: currently filtered/obfuscated by chunk data processor.
processors.put(ClientboundLevelChunkWithLightPacket.class, (p, packet) -> processChunkWithLight(p, (ClientboundLevelChunkWithLightPacket) packet));
// --- Section packets ---
// 7.1.83 Update Section Blocks: currently filtered/obfuscated by section block processor.
processors.put(ClientboundSectionBlocksUpdatePacket.class, (p, packet) -> processSectionUpdate(p, (ClientboundSectionBlocksUpdatePacket) packet));
// --- Particle packets ---
// 7.1.47 Particle: particle type and position can reveal hidden machinery.
processors.put(ClientboundLevelParticlesPacket.class, (p, packet) -> processLevelParticlesPacket(p, (ClientboundLevelParticlesPacket) packet));
// --- Sound packets ---
// 7.1.115 Entity Sound Effect: entity id and sound.
processors.put(ClientboundSoundEntityPacket.class, this.buildEntityPacketProcessor(ClientboundSoundEntityPacket::getId));
// 7.1.116 Sound Effect: sound type and position.
processors.put(ClientboundSoundPacket.class, this.<ClientboundSoundPacket>buildPositionBasedPacketProcessor(packet -> {
return new BlockPos.MutableBlockPos(packet.getX(), packet.getY(), packet.getZ()).immutable();
}));
// --- Others ---
// 7.1.137 Waypoint: world/entity tracking signal.
processors.put(ClientboundTrackedWaypointPacket.class, tossPacket);
// 7.1.50 Map Data: map pixels/icons can reveal player/world state.
processors.put(ClientboundMapItemDataPacket.class, tossPacket);
// 7.1.46 World Event: block position/event id can leak activity.
processors.put(ClientboundLevelEventPacket.class, buildPositionBasedPacketProcessor(ClientboundLevelEventPacket::getPos));
// 7.1.59 Open Sign Editor: block position.
processors.put(ClientboundOpenSignEditorPacket.class, buildPositionBasedPacketProcessor(ClientboundOpenSignEditorPacket::getPos));
// 7.1.37 Explosion: position, affected blocks, and knockback can reveal TNT/tech.
processors.put(ClientboundExplodePacket.class, (p, rawPacket) -> {
ClientboundExplodePacket packet = (ClientboundExplodePacket) rawPacket;
Vec3 pos = packet.center();
int blockX = (int) pos.x;
int blockY = (int) pos.y;
int blockZ = (int) pos.z;
return proccessPositionBasedPacket(p, blockX, blockY, blockZ, packet);
});
this.packetProcessors = processors;
TinyProtocol.instance.addGlobalClientboundFilter((player, rawPacket) -> {
try {
if (rawPacket instanceof ClientboundBundlePacket bundlePacket) {
List<Packet<? super ClientGamePacketListener>> processedPackets = new ArrayList<>();
for (Packet<? super ClientGamePacketListener> packet : bundlePacket.subPackets()) {
Packet<? super ClientGamePacketListener> processedPacket = (Packet<? super ClientGamePacketListener>) processPacket(player, packet);
if (processedPacket != null) {
processedPackets.add(processedPacket);
}
}
return new ClientboundBundlePacket(processedPackets);
} else {
return processPacket(player, rawPacket);
} }
case HIDE: } catch (Throwable t) {
packet = blockChangeCloner.apply(packet); t.printStackTrace();
blockChangeBlockData.set(packet, obfuscationTarget);
return packet;
case HIDE_AIR:
default:
packet = blockChangeCloner.apply(packet);
blockChangeBlockData.set(packet, 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) {
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))) {
case SKIP:
return packet;
case CHECK:
if (ProtocolWrapper.impl.unfilteredTileEntityDataAction(packet)) {
return packet;
}
default:
return null; return null;
}
});
TinyProtocol.instance.addFilter(ServerboundUseItemOnPacket.class, (p, packet) -> privilegeProvider.isPlayerPrivilegedToPerformAction(p) ? packet : null);
TinyProtocol.instance.addFilter(ServerboundInteractPacket.class, (p, packet) -> privilegeProvider.isPlayerPrivilegedToPerformAction(p) ? packet : null);
}
private Packet<?> processPacket(Player player, Packet<?> packet) {
if (bypassingPackets.contains(packet.getClass())) {
return packet;
} else if (packetProcessors.containsKey(packet.getClass())) {
return packetProcessors.get(packet.getClass()).apply(player, packet);
} else {
return null;
} }
} }
public enum State { private Packet<? extends PacketListener> processAddEntityPacket(Player player, ClientboundAddEntityPacket packet) {
SKIP, if (privilegeProvider.isPlayerPrivilegedToAccessEntity(player, packet.getId()) && privilegeProvider.isPlayerPrivilegedToAccessPosition(player, (int) packet.getX(), (int) packet.getY(), (int) packet.getZ())) {
CHECK, return packet;
HIDE, } else {
HIDE_AIR return null;
}
} }
public interface LocationEvaluator { private Packet<? extends PacketListener> processEntityPacket(Player player, int entityId, Packet<? extends PacketListener> packet) {
default boolean suppressInteractions(Player player) { if (privilegeProvider.isPlayerPrivilegedToAccessEntity(player, entityId)) {
return false; return packet;
} else {
return null;
}
}
private <TTargetPacket extends Packet<?>> BiFunction<Player, Packet<? extends PacketListener>, Packet<? extends PacketListener>> buildEntityPacketProcessor(ToIntFunction<TTargetPacket> entityIdExtractor) {
return (p, rawPacket) -> {
TTargetPacket packet = (TTargetPacket) rawPacket;
return processEntityPacket(p, entityIdExtractor.applyAsInt(packet), packet);
};
}
private final Reflection.Field<Integer> 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)) {
return packet;
} else {
return null;
}
}
private final Reflection.Field<Integer> 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)) {
return packet;
} else {
return null;
}
}
private Packet<?> processRemoveEntitiesPacket(Player player, ClientboundRemoveEntitiesPacket packet) {
IntList entityIdsToRemove = packet.getEntityIds();
IntList filteredEntitiesToRemove = entityIdsToRemove.intStream().filter((id) -> privilegeProvider.isPlayerPrivilegedToAccessEntity(player, id)).collect(IntArrayList::new, IntList::add, IntList::addAll);
return new ClientboundRemoveEntitiesPacket(filteredEntitiesToRemove);
}
public Packet<?> processSetEntityLinkPacket(Player player, ClientboundSetEntityLinkPacket packet) {
int fromEntityId = packet.getSourceId();
int toEntityId = packet.getDestId();
if (privilegeProvider.isPlayerPrivilegedToAccessEntity(player, fromEntityId) && privilegeProvider.isPlayerPrivilegedToAccessEntity(player, toEntityId)) {
return packet;
} else {
return null;
}
}
private Packet<?> processBlockEventPacket(Player player, ClientboundBlockEventPacket packet) {
BlockPos blockPos = packet.getPos();
Block block = packet.getBlock();
int blockX = blockPos.getX();
int blockY = blockPos.getY();
int blockZ = blockPos.getZ();
if (privilegeProvider.isPlayerPrivilegedToAccessPosition(player, blockX, blockY, blockZ) && privilegeProvider.isPlayerPrivilegedToAccessBlock(player, blockX, blockY, blockZ, block)) {
return packet;
} else {
return null;
}
}
private Packet<?> processBlockUpdatePacket(Player player, ClientboundBlockUpdatePacket packet) {
BlockPos blockPos = packet.getPos();
BlockState blockState = packet.getBlockState();
Block block = blockState.getBlock();
int blockX = blockPos.getX();
int blockY = blockPos.getY();
int blockZ = blockPos.getZ();
if (privilegeProvider.isPlayerPrivilegedToAccessPosition(player, blockX, blockY, blockZ) && privilegeProvider.isPlayerPrivilegedToAccessBlock(player, blockX, blockY, blockZ, block) && privilegeProvider.isPlayerPrivilegedToAccessBlockState(player, blockX, blockY, blockZ, blockState)) {
return packet;
} else if (privilegeProvider.isPlayerPrivilegedToAccessPosition(player, blockX, blockY, blockZ)) {
return new ClientboundBlockUpdatePacket(blockPos, blockStateUsedForObfuscation);
} else {
return null;
}
}
private Packet<?> processBlockEntityDataPacket(Player player, ClientboundBlockEntityDataPacket packet) {
BlockPos blockPos = packet.getPos();
BlockEntityType<?> blockEntityType = packet.getType();
int blockX = blockPos.getX();
int blockY = blockPos.getY();
int blockZ = blockPos.getZ();
if (privilegeProvider.isPlayerPrivilegedToAccessPosition(player, blockX, blockY, blockZ) && privilegeProvider.isPlayerPrivilegedToAccessBlockEntity(player, blockX, blockY, blockZ, blockEntityType)) {
return packet;
} else {
return null;
}
}
private Packet<?> proccessBlockDestructionPacket(Player player, ClientboundBlockDestructionPacket packet) {
BlockPos blockPos = packet.getPos();
int entityBreakingBlockId = packet.getId();
if (privilegeProvider.isPlayerPrivilegedToAccessEntity(player, entityBreakingBlockId) && privilegeProvider.isPlayerPrivilegedToAccessPosition(player, blockPos.getX(), blockPos.getY(), blockPos.getZ())) {
return packet;
} else {
return null;
}
}
private final Reflection.Field<SectionPos> sectionPosField = Reflection.getField(ClientboundSectionBlocksUpdatePacket.class, SectionPos.class, 0);
private final Reflection.Field<short[]> oldPosField = Reflection.getField(ClientboundSectionBlocksUpdatePacket.class, short[].class, 0);
private final Reflection.Field<BlockState[]> 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);
List<Short> filteredPos = new ArrayList<>(oldPos.length);
List<BlockState> filteredStates = new ArrayList<>(oldStates.length);
for (int i = 0; i < oldPos.length; i++) {
short posShort = oldPos[i];
BlockState state = oldStates[i];
Block block = state.getBlock();
int worldX = sectionPos.relativeToBlockX(posShort);
int worldY = sectionPos.relativeToBlockY(posShort);
int worldZ = sectionPos.relativeToBlockZ(posShort);
if (privilegeProvider.isPlayerPrivilegedToAccessPosition(p, worldX, worldY, worldZ) && privilegeProvider.isPlayerPrivilegedToAccessBlock(p, worldX, worldY, worldZ, block) && privilegeProvider.isPlayerPrivilegedToAccessBlockState(p, worldX, worldY, worldZ, state)) {
filteredPos.add(posShort);
filteredStates.add(state);
} else if (privilegeProvider.isPlayerPrivilegedToAccessPosition(p, worldX, worldY, worldZ)) {
filteredPos.add(posShort);
filteredStates.add(blockStateUsedForObfuscation);
}
} }
boolean skipChunk(Player player, int x, int z); if (filteredStates.isEmpty()) {
return null;
default boolean skipChunkSection(Player player, int x, int y, int z) {
return skipChunk(player, x, z);
} }
default State check(Player player, int x, int y, int z) { short[] newPos = new short[filteredPos.size()];
return skipChunkSection(player, ProtocolUtils.posToChunk(x), ProtocolUtils.posToChunk(y), ProtocolUtils.posToChunk(z)) ? State.SKIP : State.CHECK; for (int i = 0; i < newPos.length; i++) {
newPos[i] = filteredPos.get(i);
} }
default State checkBlockPos(Player player, Object pos) { BlockState[] newStates = filteredStates.toArray(new BlockState[0]);
return check(player, blockPositionX.get(pos), blockPositionY.get(pos), blockPositionZ.get(pos));
}
default boolean blockPrecise(Player player, int x, int y, int z) { return new ClientboundSectionBlocksUpdatePacket(sectionPos, ShortSets.unmodifiable(new ShortArraySet(newPos)), newStates);
return false; }
private Packet<?> processLevelParticlesPacket(Player player, ClientboundLevelParticlesPacket packet) {
int blockX = (int) packet.getX();
int blockY = (int) packet.getY();
int blockZ = (int) packet.getZ();
if (privilegeProvider.isPlayerPrivilegedToAccessPosition(player, blockX, blockY, blockZ)) {
return packet;
} else {
return null;
} }
} }
private ClientboundLevelChunkWithLightPacket processChunkWithLight(Player p, ClientboundLevelChunkWithLightPacket packet) {
if (privilegeProvider.isEveryonePrivilegedToAccessAllDataWithinChunk(packet.getX(), packet.getZ())) {
return packet;
} else {
return chunkHider.processLevelChunkWithLightPacket(p, packet);
}
}
private Packet<? extends PacketListener> proccessPositionBasedPacket(Player player, int blockX, int blockY, int blockZ, Packet<? extends PacketListener> packet) {
if (privilegeProvider.isPlayerPrivilegedToAccessPosition(player, blockX, blockY, blockZ)) {
return packet;
} else {
return null;
}
}
private <TTargetPacket extends Packet<?>> BiFunction<Player, Packet<?>, Packet<?>> buildPositionBasedPacketProcessor(Function<TTargetPacket, BlockPos> positionExtractor) {
return (p, rawPacket) -> {
TTargetPacket packet = (TTargetPacket) rawPacket;
BlockPos pos = positionExtractor.apply(packet);
int blockX = pos.getX();
int blockY = pos.getY();
int blockZ = pos.getZ();
return proccessPositionBasedPacket(p, blockX, blockY, blockZ, packet);
};
}
} }
@@ -0,0 +1,319 @@
/*
* This file is a part of the SteamWar software.
*
* Copyright (C) 2025 SteamWar.de-Serverteam
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package de.steamwar.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;
import java.util.Collections;
import java.util.List;
import java.util.Set;
import java.util.function.BiFunction;
import java.util.function.UnaryOperator;
import java.util.stream.Collectors;
@Deprecated
public class ChunkHider {
public static final ChunkHider impl = new ChunkHider();
public Class<?> mapChunkPacket() {
return ClientboundLevelChunkWithLightPacket.class;
}
private static final UnaryOperator<Object> chunkPacketCloner = ProtocolUtils.shallowCloneGenerator(ClientboundLevelChunkWithLightPacket.class);
private static final UnaryOperator<Object> chunkDataCloner = ProtocolUtils.shallowCloneGenerator(ClientboundLevelChunkPacketData.class);
private static final Reflection.Field<Integer> chunkXField = Reflection.getField(ClientboundLevelChunkWithLightPacket.class, int.class, 0);
private static final Reflection.Field<Integer> chunkZField = Reflection.getField(ClientboundLevelChunkWithLightPacket.class, int.class, 1);
private static final Reflection.Field<ClientboundLevelChunkPacketData> chunkData = Reflection.getField(ClientboundLevelChunkWithLightPacket.class, ClientboundLevelChunkPacketData.class, 0);
private static final Reflection.Field<byte[]> dataField = Reflection.getField(ClientboundLevelChunkPacketData.class, byte[].class, 0);
private static final Reflection.Field<List> tileEntities = Reflection.getField(ClientboundLevelChunkPacketData.class, List.class, 0);
public BiFunction<Player, Object, Object> chunkHiderGenerator(TechHider techHider) {
return (p, packet) -> {
int chunkX = chunkXField.get(packet);
int chunkZ = chunkZField.get(packet);
if (techHider.getLocationEvaluator().skipChunk(p, chunkX, chunkZ)) {
return packet;
}
packet = chunkPacketCloner.apply(packet);
Object dataWrapper = chunkDataCloner.apply(chunkData.get(packet));
Set<String> hiddenBlockEntities = techHider.getHiddenBlockEntities();
tileEntities.set(dataWrapper, ((List<?>) tileEntities.get(dataWrapper)).stream().filter(te -> tileEntityVisible(hiddenBlockEntities, te)).collect(Collectors.toList()));
ByteBuf in = Unpooled.wrappedBuffer(dataField.get(dataWrapper));
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);
section.copyBlockCount();
blocks(section);
biomes(section);
}
if (in.readableBytes() != 0) {
throw new IllegalStateException("ChunkHider21: Incomplete chunk data, " + in.readableBytes() + " bytes left");
}
byte[] data = new byte[out.readableBytes()];
out.readBytes(data);
dataField.set(dataWrapper, data);
chunkData.set(packet, dataWrapper);
return packet;
};
}
private static final Registry<BlockEntityType<?>> 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<BlockEntityType> entityType = Reflection.getField(tileEntity, BlockEntityType.class, 0);
protected boolean tileEntityVisible(Set<String> hiddenBlockEntities, Object tile) {
BlockEntityType type = entityType.get(tile);
String path = ((ResourceLocation) getKey.invoke(registry, type)).getPath();
return !hiddenBlockEntities.contains(path);
}
private void blocks(SectionHider section) {
section.copyBitsPerBlock();
boolean singleValued = section.getBitsPerBlock() == 0;
if (singleValued) {
int value = ProtocolUtils.readVarInt(section.getIn());
ProtocolUtils.writeVarInt(section.getOut(), !section.isSkipSection() && section.getObfuscate().contains(value) ? section.getTarget() : value);
return;
} else if (section.getBitsPerBlock() < 9) {
// Indirect (paletted) storage only present when bitsPerBlock < 9 in 1.21+
section.processPalette();
}
if (section.isSkipSection() || (!section.blockPrecise() && section.isPaletted())) {
section.skipNewDataArray(4096);
return;
}
SimpleBitStorage values = new SimpleBitStorage(section.getBitsPerBlock(), 4096, section.readNewDataArray(4096));
for (int y = 0; y < 16; y++) {
for (int z = 0; z < 16; z++) {
for (int x = 0; x < 16; x++) {
int pos = (((y * 16) + z) * 16) + x;
TechHider.State test = section.test(x, y, z);
switch (test) {
case SKIP:
break;
case CHECK:
if (!section.getObfuscate().contains(values.get(pos))) {
break;
}
case HIDE:
values.set(pos, section.getTarget());
break;
case HIDE_AIR:
default:
values.set(pos, section.getAir());
}
}
}
}
section.writeDataArray(values.getRaw());
}
private void biomes(SectionHider section) {
section.copyBitsPerBlock();
if (section.getBitsPerBlock() == 0) {
section.copyVarInt();
} else if (section.getBitsPerBlock() < 6) {
section.skipPalette();
section.skipNewDataArray(64);
} else {
// Direct (global) biome IDs no palette present
section.skipNewDataArray(64);
}
}
@Getter
class SectionHider {
private final Player player;
private final TechHider techHider;
private final ByteBuf in;
private final ByteBuf out;
private final int chunkX;
private final int chunkY;
private final int chunkZ;
private final int offsetX;
private final int offsetY;
private final int offsetZ;
private final boolean skipSection;
private boolean paletted;
private int bitsPerBlock;
private int blockCount;
private int air;
private int target;
private Set<Integer> obfuscate;
public SectionHider(Player player, TechHider techHider, ByteBuf in, ByteBuf out, int chunkX, int chunkY, int chunkZ) {
this.player = player;
this.techHider = techHider;
this.in = in;
this.out = out;
this.chunkX = chunkX;
this.chunkY = chunkY;
this.chunkZ = chunkZ;
this.offsetX = 16 * chunkX;
this.offsetY = 16 * chunkY;
this.offsetZ = 16 * chunkZ;
this.skipSection = techHider.getLocationEvaluator().skipChunkSection(player, chunkX, chunkY, chunkZ);
this.paletted = false;
this.bitsPerBlock = 0;
this.air = TechHider.AIR_ID;
this.target = techHider.getObfuscationTargetId();
this.obfuscate = techHider.getObfuscateIds();
}
public boolean blockPrecise() {
return techHider.getLocationEvaluator().blockPrecise(player, chunkX, chunkY, chunkZ);
}
public TechHider.State test(int x, int y, int z) {
return techHider.getLocationEvaluator().check(player, offsetX + x, offsetY + y, offsetZ + z);
}
public void copyBlockCount() {
this.blockCount = in.readShort();
out.writeShort(blockCount);
}
public void copyBitsPerBlock() {
bitsPerBlock = in.readByte();
out.writeByte(bitsPerBlock);
}
public int copyVarInt() {
int value = ProtocolUtils.readVarInt(in);
ProtocolUtils.writeVarInt(out, value);
return value;
}
public void skipPalette() {
int paletteLength = copyVarInt();
for (int i = 0; i < paletteLength; i++) {
copyVarInt();
}
}
public void processPalette() {
if (skipSection) {
skipPalette();
return;
}
int paletteLength = copyVarInt();
if (paletteLength == 0) return;
paletted = true;
air = 0;
target = 0;
for (int i = 0; i < paletteLength; i++) {
int entry = ProtocolUtils.readVarInt(in);
if (obfuscate.contains(entry)) {
entry = techHider.getObfuscationTargetId();
}
if (entry == TechHider.AIR_ID) {
air = i;
} else if (entry == techHider.getObfuscationTargetId()) {
target = i;
}
ProtocolUtils.writeVarInt(out, entry);
}
obfuscate = Collections.emptySet();
}
public void skipDataArray() {
int dataArrayLength = copyVarInt();
out.writeBytes(in, dataArrayLength * 8);
}
public void skipNewDataArray(int entries) {
if (bitsPerBlock == 0) {
return;
}
char valuesPerLong = (char) (64 / bitsPerBlock);
int i1 = (entries + valuesPerLong - 1) / valuesPerLong;
out.writeBytes(in, i1 * Long.BYTES);
}
public long[] readDataArray() {
long[] array = new long[copyVarInt()];
for (int i = 0; i < array.length; i++) {
array[i] = in.readLong();
}
return array;
}
public long[] readNewDataArray(int entries) {
if (bitsPerBlock == 0) {
return new long[entries];
}
char valuesPerLong = (char) (64 / bitsPerBlock);
int i1 = (entries + valuesPerLong - 1) / valuesPerLong;
long[] array = new long[i1];
for (int i = 0; i < i1; i++) {
array[i] = in.readLong();
}
return array;
}
public void writeDataArray(long[] array) {
for (long l : array) {
out.writeLong(l);
}
}
}
}
@@ -17,7 +17,7 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
package de.steamwar.techhider; package de.steamwar.techhider.legacy;
import de.steamwar.Reflection; import de.steamwar.Reflection;
import net.minecraft.core.SectionPos; import net.minecraft.core.SectionPos;
@@ -29,6 +29,7 @@ import org.bukkit.entity.Player;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.function.BiFunction; import java.util.function.BiFunction;
@Deprecated
public class ProtocolWrapper { public class ProtocolWrapper {
public static final ProtocolWrapper impl = new ProtocolWrapper(); public static final ProtocolWrapper impl = new ProtocolWrapper();
@@ -88,8 +89,4 @@ public class ProtocolWrapper {
public boolean unfilteredTileEntityDataAction(Object packet) { public boolean unfilteredTileEntityDataAction(Object packet) {
return tileEntityType.get(packet) != signType; return tileEntityType.get(packet) != signType;
} }
public BiFunction<Player, Object, Object> blockBreakHiderGenerator(Class<?> blockBreakPacket, TechHider techHider) {
return null;
}
} }
@@ -0,0 +1,184 @@
/*
* This file is a part of the SteamWar software.
*
* Copyright (C) 2025 SteamWar.de-Serverteam
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package de.steamwar.techhider.legacy;
import com.comphenix.tinyprotocol.TinyProtocol;
import de.steamwar.Reflection;
import de.steamwar.techhider.BlockIds;
import de.steamwar.techhider.ProtocolUtils;
import lombok.Getter;
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.state.BlockState;
import org.bukkit.Material;
import org.bukkit.craftbukkit.util.CraftMagicNumbers;
import org.bukkit.entity.Player;
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;
@Deprecated
public class TechHider {
public static final Class<?> blockPosition = BlockPos.class;
private static final Class<?> baseBlockPosition = Vec3i.class;
public static final Reflection.Field<Integer> blockPositionX = Reflection.getField(baseBlockPosition, int.class, 0);
public static final Reflection.Field<Integer> blockPositionY = Reflection.getField(baseBlockPosition, int.class, 1);
public static final Reflection.Field<Integer> blockPositionZ = Reflection.getField(baseBlockPosition, int.class, 2);
public static final Class<?> iBlockData = BlockState.class;
public static final Class<?> block = Block.class;
public boolean iBlockDataHidden(BlockState iBlockData) {
return obfuscateIds.contains(BlockIds.impl.getCombinedId(iBlockData));
}
public static final Object AIR = CraftMagicNumbers.getBlock(Material.AIR).defaultBlockState();
public static final int AIR_ID = BlockIds.impl.materialToId(Material.AIR);
private final Map<Class<?>, BiFunction<Player, Object, Object>> techhiders = new HashMap<>();
@Getter
private final LocationEvaluator locationEvaluator;
@Getter
private final Object obfuscationTarget;
@Getter
private final int obfuscationTargetId;
@Getter
private final Set<Integer> obfuscateIds;
@Getter
private final Set<String> hiddenBlockEntities;
public TechHider(LocationEvaluator locationEvaluator, Material obfuscationTarget, Set<Material> obfuscate, Set<String> hiddenBlockEntities) {
this.locationEvaluator = locationEvaluator;
this.obfuscateIds = obfuscate.stream().flatMap(m -> BlockIds.impl.materialToAllIds(m).stream()).collect(Collectors.toSet());
this.hiddenBlockEntities = hiddenBlockEntities;
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(ServerboundUseItemOnPacket.class, (p, packet) -> locationEvaluator.suppressInteractions(p) ? null : packet);
techhiders.put(ServerboundInteractPacket.class, (p, packet) -> locationEvaluator.suppressInteractions(p) ? null : packet);
}
public void enable() {
techhiders.forEach((type, playerObjectBiFunction) -> TinyProtocol.instance.addFilter((Class) type, playerObjectBiFunction));
}
public void disable() {
techhiders.forEach(TinyProtocol.instance::removeFilter);
}
public static final Class<?> multiBlockChangePacket = ClientboundSectionBlocksUpdatePacket.class;
public static final UnaryOperator<Object> multiBlockChangeCloner = ProtocolUtils.shallowCloneGenerator(TechHider.multiBlockChangePacket);
private static final Class<?> blockChangePacket = ClientboundBlockUpdatePacket.class;
private static final Function<Object, Object> 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 Object blockChangeHider(Player p, Object packet) {
switch (locationEvaluator.checkBlockPos(p, blockChangePosition.get(packet))) {
case SKIP:
return packet;
case CHECK:
if (!iBlockDataHidden((BlockState) blockChangeBlockData.get(packet))) {
return packet;
}
case HIDE:
packet = blockChangeCloner.apply(packet);
blockChangeBlockData.set(packet, obfuscationTarget);
return packet;
case HIDE_AIR:
default:
packet = blockChangeCloner.apply(packet);
blockChangeBlockData.set(packet, 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) {
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))) {
case SKIP:
return packet;
case CHECK:
if (ProtocolWrapper.impl.unfilteredTileEntityDataAction(packet)) {
return packet;
}
default:
return null;
}
}
public enum State {
SKIP,
CHECK,
HIDE,
HIDE_AIR
}
public interface LocationEvaluator {
default boolean suppressInteractions(Player player) {
return false;
}
boolean skipChunk(Player player, int x, int z);
default boolean skipChunkSection(Player player, int x, int y, int z) {
return skipChunk(player, x, z);
}
default State check(Player player, int x, int y, int z) {
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 boolean blockPrecise(Player player, int x, int y, int z) {
return false;
}
}
}
@@ -28,6 +28,9 @@ import net.md_5.bungee.api.chat.ClickEvent
import org.bukkit.entity.Player import org.bukkit.entity.Player
object InviteCommand : SWCommand("invite") { object InviteCommand : SWCommand("invite") {
init {
message = de.steamwar.tntleague.message
}
@Register @Register
fun invitePlayer(@Validator("isLeader") sender: Player, target: Player) { fun invitePlayer(@Validator("isLeader") sender: Player, target: Player) {

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