Compare commits

...
97 Commits
Author SHA1 Message Date
Chaoscaot 1cb2e70ede Revert "Remove deprecated world management commands and associated SQL logic"
Pull Request Build / Build (pull_request) Failing after 58s
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
Pull Request Build / Build (pull_request) Successful in 1m9s
2026-05-31 20:16:17 +02:00
YoyoNow a9fb982143 Merge pull request 'Fix WorldDir of Event servers' (#405) from VelocityCore/FixEventWorldDir into main
Deploy / Build (push) Successful in 1m56s
Deploy / Deploy (push) Successful in 11s
Reviewed-on: #405
Reviewed-by: D4rkr34lm <dark@steamwar.de>
2026-05-30 18:33:17 +02:00
YoyoNow 5b8d881e01 Fix WorldDir of Event servers
Pull Request Build / Build (pull_request) Successful in 1m7s
Backport CommonCore / Create CommonCore backport PRs (pull_request) Successful in 7s
2026-05-30 18:30:51 +02:00
YoyoNow eb55f4b395 Merge pull request 'Remove global Entity Interact callback' (#404) from SpigotCore/RemoveGlobalEntityServerCallback into main
Deploy / Build (push) Successful in 1m56s
Deploy / Deploy (push) Successful in 10s
Reviewed-on: #404
Reviewed-by: D4rkr34lm <dark@steamwar.de>
2026-05-30 15:27:45 +02:00
YoyoNow 273db91d06 Fix compile
Pull Request Build / Build (pull_request) Successful in 1m6s
Backport CommonCore / Create CommonCore backport PRs (pull_request) Successful in 10s
2026-05-30 15:24:20 +02:00
YoyoNow 983ad544c1 Remove global Entity Interact callback
Pull Request Build / Build (pull_request) Failing after 55s
2026-05-30 15:18:37 +02:00
YoyoNow a6a34b2221 Fix Schem add on WGS Schems
Deploy / Build (push) Successful in 1m52s
Deploy / Deploy (push) Successful in 11s
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
Deploy / Build (push) Successful in 1m53s
Deploy / Deploy (push) Successful in 11s
Reviewed-on: #398
Reviewed-by: YoyoNow <4+yoyonow@noreply.localhost>
2026-05-30 13:35:35 +02:00
D4rkr34lm 89e05cd109 Remove remnatn of old impl
Pull Request Build / Build (pull_request) Successful in 1m11s
Backport CommonCore / Create CommonCore backport PRs (pull_request) Successful in 7s
2026-05-30 13:30:47 +02:00
D4rkr34lm ea9d7ac584 Refactor to use better impl of interaction entity in REntity system
Pull Request Build / Build (pull_request) Failing after 57s
2026-05-30 13:29:50 +02:00
D4rkr34lm 17e1cf53b0 Merge branch 'main' into BauSystem/fix-interaction-with-trace-entity
Pull Request Build / Build (pull_request) Successful in 1m9s
2026-05-30 13:24:43 +02:00
YoyoNow f64f337f17 Merge pull request 'Add RInteraction Entity' (#403) from SpigotCore/RInteraction into main
Deploy / Build (push) Successful in 1m59s
Deploy / Deploy (push) Successful in 11s
Reviewed-on: #403
Reviewed-by: D4rkr34lm <dark@steamwar.de>
2026-05-30 13:22:35 +02:00
YoyoNow c5f5be7d58 Add RInteraction Entity
Pull Request Build / Build (pull_request) Successful in 1m8s
Backport CommonCore / Create CommonCore backport PRs (pull_request) Successful in 8s
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
Deploy / Build (push) Successful in 1m58s
Deploy / Deploy (push) Successful in 12s
Reviewed-on: #402
Reviewed-by: Chaoscaot <max@chaoscaot.de>
2026-05-30 12:11:12 +02:00
D4rkr34lm 1de17d27f4 Fix arrow stopper
Pull Request Build / Build (pull_request) Successful in 1m15s
Backport CommonCore / Create CommonCore backport PRs (pull_request) Successful in 7s
2026-05-30 11:55:59 +02:00
D4rkr34lm 3cd0db9bdf Merge branch 'main' into BauSystem/fix-interaction-with-trace-entity
Pull Request Build / Build (pull_request) Successful in 1m6s
2026-05-25 21:32:12 +02:00
D4rkr34lm 9711b48f2f Fix interaction with trace entity not showing detail text
Pull Request Build / Build (pull_request) Failing after 1m3s
2026-05-25 18:20:42 +02:00
Chaoscaot 9934b8bbb2 Fix TNTLeague InviteCommand.kt
Deploy / Build (push) Successful in 1m55s
Deploy / Deploy (push) Successful in 12s
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
Deploy / Build (push) Successful in 1m57s
Deploy / Deploy (push) Successful in 12s
Reviewed-on: #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
Pull Request Build / Build (pull_request) Successful in 1m8s
Backport CommonCore / Create CommonCore backport PRs (pull_request) Successful in 34s
2026-05-24 23:39:50 +02:00
Chaoscaot 52e95e2649 Merge pull request 'fix(VelocityCore): disable replays' (#395) from disable-replays into main
Deploy / Build (push) Successful in 2m10s
Deploy / Deploy (push) Successful in 11s
Reviewed-on: #395
Reviewed-by: Chaoscaot <max@chaoscaot.de>
2026-05-24 13:45:32 +02:00
D4rkr34lm de6ac2cf20 fix
Backport CommonCore / Create CommonCore backport PRs (pull_request) Successful in 10s
Pull Request Build / Build (pull_request) Successful in 1m20s
2026-05-24 13:44:57 +02:00
D4rkr34lm 628001e693 Disable Replays
Pull Request Build / Build (pull_request) Successful in 1m8s
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
Deploy / Build (push) Successful in 1m56s
Deploy / Deploy (push) Successful in 11s
Reviewed-on: #393
Reviewed-by: Chaoscaot <max@chaoscaot.de>
2026-05-23 16:33:11 +02:00
D4rkr34lm 78617d5a98 another fix
Pull Request Build / Build (pull_request) Successful in 1m9s
Backport CommonCore / Create CommonCore backport PRs (pull_request) Successful in 9s
2026-05-23 16:15:31 +02:00
D4rkr34lm 69b924ded6 fix techhider not hiding waterlogged blocks if neccecary
Pull Request Build / Build (pull_request) Successful in 1m7s
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
Deploy / Build (push) Successful in 2m1s
Deploy / Deploy (push) Successful in 10s
Reviewed-on: #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
Pull Request Build / Build (pull_request) Successful in 1m6s
Backport CommonCore / Create CommonCore backport PRs (pull_request) Successful in 7s
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
Deploy / Build (push) Successful in 2m3s
Deploy / Deploy (push) Successful in 11s
Reviewed-on: #338
2026-05-22 21:37:03 +02:00
YoyoNow 23b5ab3e82 Fix build
Pull Request Build / Build (pull_request) Successful in 1m15s
Backport CommonCore / Create CommonCore backport PRs (pull_request) Successful in 9s
2026-05-22 21:35:01 +02:00
YoyoNow 4f9fe07951 Fix final formatting stuff
Pull Request Build / Build (pull_request) Failing after 1m7s
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
Pull Request Build / Build (pull_request) Successful in 1m25s
Reviewed-on: #385
Reviewed-by: YoyoNow <4+yoyonow@noreply.localhost>
2026-05-22 20:52:11 +02:00
D4rkr34lm 1810cb7546 Fix inlineing
Pull Request Build / Build (pull_request) Successful in 1m21s
2026-05-22 20:51:24 +02:00
D4rkr34lm e55ca911c4 Inline get all regions
Pull Request Build / Build (pull_request) Successful in 1m12s
2026-05-22 20:41:05 +02:00
D4rkr34lm 793f2de6c3 Sync with base branch
Pull Request Build / Build (pull_request) Successful in 1m13s
2026-05-22 19:42:36 +02:00
D4rkr34lm 54fa47bd99 Ensure parity with old techhider by suppressing select packets
Pull Request Build / Build (pull_request) Successful in 1m11s
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
Deploy / Build (push) Successful in 1m53s
Deploy / Deploy (push) Successful in 12s
Reviewed-on: #391
2026-05-22 18:56:30 +02:00
D4rkr34lm 7b3a04f4eb Remove unused custom event
Pull Request Build / Build (pull_request) Successful in 1m6s
Backport CommonCore / Create CommonCore backport PRs (pull_request) Successful in 7s
2026-05-22 18:49:51 +02:00
D4rkr34lm f2a06057a8 Fix tick step bossbar remaining visable
Pull Request Build / Build (pull_request) Successful in 1m9s
2026-05-22 18:46:36 +02:00
D4rkr34lm e11f3f7cbc Fix preschem state and techider not being always active
Pull Request Build / Build (pull_request) Successful in 1m12s
2026-05-22 18:27:53 +02:00
D4rkr34lm ebc10c1ce4 Fix constructor visiblity
Pull Request Build / Build (pull_request) Successful in 1m13s
2026-05-22 17:57:15 +02:00
D4rkr34lm d682e35159 Resolve open comments
Pull Request Build / Build (pull_request) Successful in 1m12s
2026-05-22 17:55:12 +02:00
D4rkr34lm 7d74eb0c09 add old techhider to skip supporting bausystem for now
Pull Request Build / Build (pull_request) Successful in 1m11s
2026-05-22 17:40:33 +02:00
D4rkr34lm 7e18207b87 fix entities not despawning
Pull Request Build / Build (pull_request) Failing after 1m5s
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
Pull Request Build / Build (pull_request) Failing after 1m6s
2026-05-22 16:24:34 +02:00
D4rkr34lm 21f6394359 Fix section packet being unnececarily reconstructed
Pull Request Build / Build (pull_request) Failing after 1m6s
2026-05-21 22:20:40 +02:00
D4rkr34lm 29d0c8978c Rmeove old player join quickfix
Pull Request Build / Build (pull_request) Failing after 1m6s
2026-05-21 22:00:15 +02:00
D4rkr34lm 7366f83b15 Merge branch 'FightSystem/fix-tech-and-hull-hider' into FightSystem/Optimize-new-techhider
Pull Request Build / Build (pull_request) Failing after 1m8s
2026-05-21 21:57:41 +02:00
YoyoNow 9240bcfd5f Add blockUsedForObfuscation
Pull Request Build / Build (pull_request) Failing after 1m5s
2026-05-21 20:57:59 +02:00
D4rkr34lm bede8caa82 Add chunk skipping optimisation
Pull Request Build / Build (pull_request) Failing after 1m8s
2026-05-21 20:20:18 +02:00
D4rkr34lm 108aa2af9a remove unused reflection class
Pull Request Build / Build (pull_request) Failing after 1m5s
2026-05-21 18:46:00 +02:00
D4rkr34lm d07a86c13f remove container hiding as it has no lacalaity mapping posibility
Pull Request Build / Build (pull_request) Failing after 1m5s
2026-05-21 18:41:55 +02:00
D4rkr34lm 29106d5bb5 Add bundle packet handling
Pull Request Build / Build (pull_request) Failing after 1m5s
2026-05-21 18:20:15 +02:00
YoyoNow 32fd4b4630 Add Sound handling
Pull Request Build / Build (pull_request) Failing after 1m5s
2026-05-21 18:11:42 +02:00
YoyoNow c241aef932 Add BlockEntityType handling
Pull Request Build / Build (pull_request) Failing after 1m6s
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
Pull Request Build / Build (pull_request) Failing after 1m6s
2026-05-21 15:08:41 +02:00
D4rkr34lm a2caa79f99 Erste bugs ausgemerzt 2026-05-21 15:06:33 +02:00
YoyoNow 1f44b3169e Remove unused stuff
Pull Request Build / Build (pull_request) Failing after 1m7s
2026-05-21 13:05:16 +02:00
YoyoNow 49fafa5955 Fix build
Pull Request Build / Build (pull_request) Failing after 1m9s
2026-05-21 13:00:53 +02:00
YoyoNow c81e6b15e5 Merge branch 'main' into FightSystem/fix-tech-and-hull-hider
Pull Request Build / Build (pull_request) Failing after 1m5s
# 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
Pull Request Build / Build (pull_request) Failing after 55s
2026-05-21 12:13:41 +02:00
D4rkr34lm a1d22b7d77 twast
Pull Request Build / Build (pull_request) Failing after 53s
2026-05-21 10:26:46 +02:00
D4rkr34lm 8424f842c3 resolve last compile issue in techhider
Pull Request Build / Build (pull_request) Failing after 1m4s
2026-05-21 00:18:12 +02:00
D4rkr34lm 72df919312 cut down old protocol wrapper
Pull Request Build / Build (pull_request) Failing after 57s
2026-05-20 21:47:17 +02:00
D4rkr34lm 7ed36fe56e removed reflections form anti nocom guard
Pull Request Build / Build (pull_request) Failing after 59s
2026-05-20 21:44:28 +02:00
D4rkr34lm a7a8c4d051 Fix imports in tinyProtocol
Pull Request Build / Build (pull_request) Failing after 59s
2026-05-20 21:33:01 +02:00
D4rkr34lm 5da3767378 Started embedding new techhider into fight system
Pull Request Build / Build (pull_request) Failing after 58s
2026-05-20 21:30:44 +02:00
D4rkr34lm ba29e5cf23 Add tinyProtocol Handler for techhider
Pull Request Build / Build (pull_request) Failing after 55s
2026-05-20 18:28:39 +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
D4rkr34lm 32a2cbb4dd Finish initial implementation of logic
Pull Request Build / Build (pull_request) Failing after 56s
2026-05-19 22:47:22 +02:00
D4rkr34lm cf7a1ee086 Implement logic for hiding chunk data (not finished)
Pull Request Build / Build (pull_request) Failing after 55s
2026-05-19 22:17:41 +02:00
Chaoscaot 9aa363de6f Add world-based permissions and migration commands
Pull Request Build / Build (pull_request) Successful in 1m7s
- 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
Chaoscaot 5a5090c74d Remove unused import and fix incorrect import path in BuilderCloudCommand and World classes.
Pull Request Build / Build (pull_request) Successful in 1m8s
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
D4rkr34lm 270d82eb71 Merge branch 'main' into FightSystem/fix-tech-and-hull-hider
Pull Request Build / Build (pull_request) Failing after 34s
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
Pull Request Build / Build (pull_request) Failing after 42s
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
SteamWarCI Build failed
2026-05-15 00:48:45 +02:00
Manuel Frohn 57c21122e6 Classify packets by scope
SteamWarCI Build failed
2026-05-15 00:32:09 +02:00
Manuel Frohn 42faba70d9 Add remaining packets with toss handler for now
SteamWarCI Build failed
2026-05-14 22:08:17 +02:00
Manuel Frohn 99e1f01869 Update bypass list with harmless play-client bound packets
SteamWarCI Build failed
2026-05-14 21:38:37 +02:00
Manuel Frohn 13bddbe359 Update bypass list with full set of configuration and login packets
SteamWarCI Build failed
2026-05-14 20:21:36 +02:00
D4rkr34lm 51ff0e1a9f Fix remaining local issues
SteamWarCI Build failed
2026-05-14 11:57:26 +02:00
D4rkr34lm 6766862a00 Refactored over all functionality
SteamWarCI Build failed
2026-05-13 22:48:44 +02:00
Manuel Frohn c30aad179b Continue refactor
SteamWarCI Build failed
2026-05-13 17:20:32 +02:00
Manuel Frohn d3aad6a198 Move over signle block event package processing
SteamWarCI Build failed
2026-05-13 16:24:45 +02:00
Manuel Frohn 744dc67ca9 Move over identified packages from old impl
SteamWarCI Build failed
2026-05-13 15:33:08 +02:00
Manuel Frohn 164a7f5826 Replace tinyProtocolLib with newest version
SteamWarCI Build failed
2026-05-13 14:04:38 +02:00
Chaoscaot 936ee38503 Migrate builder and bau worlds to shared world records
SteamWarCI Build successful
- 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
76 changed files with 3357 additions and 1059 deletions
@@ -133,7 +133,12 @@ public class BauSystem extends JavaPlugin implements Listener {
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);
}
@@ -224,4 +229,4 @@ public class BauSystem extends JavaPlugin implements Listener {
AtomicReference<BukkitTask> task = new AtomicReference<>();
task.set(runTaskTimer(plugin, () -> consumer.accept(task.get()), delay, period));
}
}
}
@@ -60,11 +60,16 @@ public enum Permission {
}
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;
}
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;
return permissionPredicate.test(bauweltMember);
}
}
}
@@ -34,16 +34,41 @@ public class BauServer {
}
private Integer owner;
private UUID world;
private Integer team;
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
if (owner == null) {
owner = BauServerInfo.getOwnerId();
}
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
public ItemStack getItem(Player player) {
SWItem itemStack;
if (!player.getName().endsWith("")) {
if (!player.getName().endsWith("") && !BauServer.getInstance().isTeamWorld()) {
itemStack = SWItem.getPlayerSkull(SteamwarUser.get(BauServer.getInstance().getOwner()).getUserName());
} else {
itemStack = new SWItem(Material.PLAYER_HEAD, "");
@@ -47,7 +47,9 @@ public class InfoCommand extends SWCommand {
@Register(description = "BAU_INFO_COMMAND_HELP")
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());
for (Flag flag : Flag.getFlags()) {
if (!region.getRegionData().has(flag).isApplicable()) continue;
@@ -58,7 +60,7 @@ public class InfoCommand extends SWCommand {
}
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<>();
members.forEach(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.region.Region;
import de.steamwar.entity.REntity;
import de.steamwar.entity.REntityServer;
import de.steamwar.entity.RFallingBlockEntity;
import de.steamwar.entity.*;
import net.md_5.bungee.api.ChatMessageType;
import org.bukkit.Bukkit;
import org.bukkit.Location;
@@ -59,15 +57,15 @@ public class DesignEndStone {
.filter(material -> material.getBlastResistance() > region.getGameModeConfig().Schematic.MaxDesignBlastResistance)
.collect(Collectors.toSet());
calculateFromBottom = region.getGameModeConfig().Arena.NoFloor;
}
entityServer.setCallback((player, rEntity, entityAction) -> {
if (entityAction != REntityServer.EntityAction.ATTACK) return;
Location location = new Location(WORLD, rEntity.getX(), rEntity.getY(), rEntity.getZ());
Bukkit.getScheduler().runTaskLater(BauSystem.getInstance(), () -> {
location.getBlock().breakNaturally();
calc();
}, 1);
});
private void interact(Player player, RInteraction entity, REntityAction action) {
if (action != REntityAction.ATTACK) return;
Location location = new Location(WORLD, entity.getX(), entity.getY(), entity.getZ());
Bukkit.getScheduler().runTaskLater(BauSystem.getInstance(), () -> {
location.getBlock().breakNaturally();
calc();
}, 1);
}
public void calc() {
@@ -110,12 +108,15 @@ public class DesignEndStone {
Material material = WORLD.getBlockAt(cx, cy, cz).getType();
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;
RFallingBlockEntity entity = new RFallingBlockEntity(entityServer, location, Material.RED_STAINED_GLASS);
entity.setNoGravity(true);
RBlockDisplay entity = new RBlockDisplay(entityServer, location);
entity.setBlock(Material.RED_STAINED_GLASS.createBlockData());
entity.setGlowing(true);
entities.add(entity);
RInteraction interaction = new RInteraction(entityServer, location);
interaction.setCallback(this::interact);
entities.add(interaction);
break;
} else if (!material.isAir() && material != Material.WATER && material != Material.LAVA) {
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.ItemStorage;
import de.steamwar.core.SWPlayer;
import de.steamwar.entity.RBlockDisplay;
import de.steamwar.entity.REntityServer;
import de.steamwar.entity.RFallingBlockEntity;
import de.steamwar.entity.RInteraction;
import lombok.Getter;
import lombok.Setter;
import lombok.experimental.UtilityClass;
@@ -58,10 +59,6 @@ public class Detonator {
@Override
public void onMount(SWPlayer player) {
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
@@ -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) {
return ItemStorage.isDetonator(itemStack);
}
@@ -79,8 +74,14 @@ public class Detonator {
public static void showDetonator(Player p, List<Location> locs) {
DetonatorComponent detonatorComponent = SWPlayer.of(p).getComponentOrDefault(DetonatorComponent.class, DetonatorComponent::new);
locs.forEach(location -> {
RFallingBlockEntity entity = new RFallingBlockEntity(detonatorComponent.entities, location.clone().add(HALF), Material.RED_STAINED_GLASS);
entity.setNoGravity(true);
RBlockDisplay blockDisplay = new RBlockDisplay(detonatorComponent.entities, location);
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.utils.bossbar.BauSystemBossbar;
import de.steamwar.bausystem.utils.bossbar.BossBarService;
import de.steamwar.entity.RBlockDisplay;
import de.steamwar.entity.REntity;
import de.steamwar.entity.REntityServer;
import de.steamwar.entity.RFallingBlockEntity;
import org.bukkit.Bukkit;
import org.bukkit.Material;
import org.bukkit.World;
@@ -290,8 +290,8 @@ public class KillcheckerVisualizer {
}
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]);
entity.setNoGravity(true);
RBlockDisplay entity = new RBlockDisplay(outlinePoints.contains(point) ? outline : inner, point.toLocation(WORLD, 0.5, 0, 0.5));
entity.setBlock(MATERIALS[Math.min(count - 1, MATERIALS.length) - 1].createBlockData());
rEntities.put(point, entity);
if (outlinePoints.contains(point)) outlinePointsCache.add(point);
killCount.put(point, count);
@@ -20,6 +20,7 @@
package de.steamwar.bausystem.features.region;
import de.steamwar.bausystem.BauSystem;
import de.steamwar.bausystem.Permission;
import de.steamwar.bausystem.config.BauServer;
import de.steamwar.bausystem.region.Region;
import de.steamwar.bausystem.region.RegionUtils;
@@ -83,6 +84,9 @@ public class ColorCommand extends SWCommand {
@ClassValidator(value = Player.class, local = true)
public TypeValidator<Player> validator() {
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");
};
}
@@ -68,7 +68,7 @@ public class ResetCommand extends SWCommand {
Region region = regionCheck(p);
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()))) {
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()))) {
return;
}
@@ -31,7 +31,6 @@ import de.steamwar.command.TypeMapper;
import de.steamwar.core.CraftbukkitWrapper;
import de.steamwar.linkage.Linked;
import de.steamwar.linkage.LinkedInstance;
import de.steamwar.techhider.TechHider;
import net.md_5.bungee.api.ChatMessageType;
import org.bukkit.Bukkit;
import org.bukkit.command.CommandSender;
@@ -39,6 +38,7 @@ import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
import org.bukkit.event.Listener;
import org.bukkit.event.player.PlayerQuitEvent;
import de.steamwar.techhider.legacy.TechHider;
import java.util.*;
import java.util.stream.Collectors;
@@ -19,13 +19,13 @@
package de.steamwar.bausystem.features.tpslimit;
import com.destroystokyo.paper.event.server.ServerTickEndEvent;
import de.steamwar.bausystem.BauSystem;
import de.steamwar.bausystem.Permission;
import de.steamwar.bausystem.SWUtils;
import de.steamwar.bausystem.linkage.BauGuiItem;
import de.steamwar.bausystem.region.Region;
import de.steamwar.bausystem.utils.ScoreboardElement;
import de.steamwar.bausystem.utils.TickEndEvent;
import de.steamwar.bausystem.utils.TickManager;
import de.steamwar.bausystem.utils.bossbar.BauSystemBossbar;
import de.steamwar.bausystem.utils.bossbar.BossBarService;
@@ -72,7 +72,7 @@ public class TPSSystem implements Listener {
}
@EventHandler
public void onTickEnd(TickEndEvent event) {
public void onTickEnd(ServerTickEndEvent event) {
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.region.Region;
import de.steamwar.entity.REntity;
import de.steamwar.entity.REntityAction;
import de.steamwar.entity.REntityServer;
import lombok.Getter;
import lombok.Setter;
@@ -143,14 +144,6 @@ public class Trace {
} else {
entityServer = new REntityServer();
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);
}
render(getRecords(), entityServer, playerTraceShowData);
@@ -167,14 +160,6 @@ public class Trace {
REntityServer entityServer = entityServerMap.computeIfAbsent(player, k -> {
REntityServer newEntityServer = new REntityServer();
newEntityServer.addPlayer(k);
newEntityServer.setCallback((p, rEntity, entityAction) -> {
if (entityAction != REntityServer.EntityAction.INTERACT) {
return;
}
if (rEntity instanceof TraceEntity) {
((TraceEntity) rEntity).printIntoChat(p);
}
});
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.Trace;
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.RFallingBlockEntity;
import de.steamwar.entity.RInteraction;
import lombok.Getter;
import net.md_5.bungee.api.chat.ClickEvent;
import org.bukkit.Location;
import org.bukkit.Material;
import org.bukkit.entity.Player;
import org.bukkit.util.Transformation;
import org.joml.Quaternionf;
import org.joml.Vector3f;
import yapion.hierarchy.types.YAPIONValue;
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
*/
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
@@ -55,13 +69,31 @@ public class TraceEntity extends RFallingBlockEntity {
private final String uniqueTntIdsString;
private final Trace trace;
private final RInteraction hitbox;
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.trace = trace;
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;
import de.steamwar.bausystem.features.tracer.TNTPoint;
import de.steamwar.entity.RBlockDisplay;
import de.steamwar.entity.REntityServer;
import de.steamwar.entity.RFallingBlockEntity;
import org.bukkit.Location;
import org.bukkit.Material;
import org.bukkit.util.Vector;
@@ -129,8 +129,8 @@ public abstract class ViewFlag {
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) {
RFallingBlockEntity y = new RFallingBlockEntity(server, yLocation, Material.WHITE_STAINED_GLASS);
y.setNoGravity(true);
RBlockDisplay y = new RBlockDisplay(server, yLocation);
y.setBlock(Material.WHITE_STAINED_GLASS.createBlockData());
}
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) {
RFallingBlockEntity second = new RFallingBlockEntity(server, secoundLocation, Material.WHITE_STAINED_GLASS);
second.setNoGravity(true);
RBlockDisplay second = new RBlockDisplay(server, secoundLocation);
second.setBlock(Material.WHITE_STAINED_GLASS.createBlockData());
}
}
}
@@ -20,6 +20,7 @@
package de.steamwar.bausystem.features.world;
import de.steamwar.bausystem.BauSystem;
import de.steamwar.bausystem.Permission;
import de.steamwar.bausystem.config.BauServer;
import de.steamwar.core.CRIUWakeupEvent;
import de.steamwar.linkage.Linked;
@@ -36,12 +37,13 @@ public class AntiBauAddMemberFix implements Listener {
@EventHandler(priority = EventPriority.LOW)
public void onPlayerJoin(PlayerJoinEvent event) {
if (BauSystem.DEV_SERVER) return;
if (event.getPlayer().getUniqueId().equals(BauServer.getInstance().getOwner())) {
if (Permission.SUPERVISOR.hasPermission(event.getPlayer())) {
return;
}
if (BauweltMember.getBauMember(BauServer.getInstance().getOwner(), event.getPlayer().getUniqueId()) == null) {
if (BauweltMember.getBauMember(BauServer.getInstance().getWorldID(), event.getPlayer().getUniqueId()) == null) {
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
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);
return;
}
@@ -28,7 +28,7 @@ import de.steamwar.data.BauLockState;
import de.steamwar.linkage.Linked;
import de.steamwar.network.packets.PacketHandler;
import de.steamwar.network.packets.server.BaulockUpdatePacket;
import de.steamwar.sql.UserConfig;
import de.steamwar.sql.SteamwarWorld;
import lombok.Getter;
import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
@@ -37,13 +37,13 @@ import org.bukkit.event.Listener;
@Linked
public class BauLockStateScoreboard extends PacketHandler implements ScoreboardElement, Listener {
private static final String BAU_LOCK_CONFIG_NAME = "baulockstate";
@Getter
private BauLockState lockState = 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);
}
@@ -69,6 +69,7 @@ public class BauLockStateScoreboard extends PacketHandler implements ScoreboardE
@Override
public String get(Region region, Player p) {
if (BauServer.getInstance().isTeamWorld()) return null;
if (!BauServer.getInstance().getOwner().equals(p.getUniqueId())) {
return null;
}
@@ -42,7 +42,11 @@ public class KickallCommand extends SWCommand {
if (!Permission.OWNER.hasPermission(player)) return;
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("");
}
});
}
}
@@ -25,7 +25,6 @@ import de.steamwar.bausystem.config.BauServer;
import de.steamwar.bausystem.utils.BauMemberUpdateEvent;
import de.steamwar.linkage.Linked;
import de.steamwar.sql.BauweltMember;
import de.steamwar.techhider.TechHider;
import org.bukkit.Bukkit;
import org.bukkit.Location;
import org.bukkit.Material;
@@ -40,6 +39,7 @@ import org.bukkit.event.block.BlockPlaceEvent;
import org.bukkit.event.entity.EntityPickupItemEvent;
import org.bukkit.event.player.*;
import org.bukkit.util.Vector;
import de.steamwar.techhider.legacy.TechHider;
import java.util.HashSet;
import java.util.Set;
@@ -143,17 +143,14 @@ public class SpectatorListener implements Listener {
public void onPlayerJoin(PlayerJoinEvent event) {
enableOrDisableTechhider();
if (BauSystem.DEV_SERVER) return;
if (event.getPlayer().getUniqueId().equals(BauServer.getInstance().getOwner())) {
if (Permission.SUPERVISOR.hasPermission(event.getPlayer())) {
return;
}
BauweltMember bauweltMember = BauweltMember.getBauMember(BauServer.getInstance().getOwner(), event.getPlayer().getUniqueId());
BauweltMember bauweltMember = BauweltMember.getBauMember(BauServer.getInstance().getWorldID(), event.getPlayer().getUniqueId());
if (bauweltMember == null) {
event.getPlayer().kickPlayer("");
return;
}
if (Permission.SUPERVISOR.hasPermission(event.getPlayer())) {
return;
}
if (!anySupervisorOnline(null)) {
Bukkit.getScheduler().runTaskLater(BauSystem.getInstance(), () -> {
event.getPlayer().kickPlayer("");
@@ -28,7 +28,6 @@ import de.steamwar.command.SWCommand;
import de.steamwar.core.CraftbukkitWrapper;
import de.steamwar.linkage.Linked;
import de.steamwar.linkage.LinkedInstance;
import de.steamwar.techhider.TechHider;
import net.md_5.bungee.api.ChatMessageType;
import net.minecraft.network.protocol.game.ServerboundMovePlayerPacket;
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.player.PlayerInteractEvent;
import org.bukkit.event.player.PlayerQuitEvent;
import de.steamwar.techhider.legacy.TechHider;
import java.util.*;
import java.util.function.BiFunction;
+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.UserInfoCommand
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>) =
SteamWar()
.subcommands(
DatabaseCommand().subcommands(InfoCommand(), ResetCommand()),
UserCommand().subcommands(UserInfoCommand(), UserSearchCommand()),
WorldCommand().subcommands(
MigrationCommand(),
SaveToStorageCommand(),
TemplateCommand().subcommands(TemplateCreateCommand())
),
DevCommand(),
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
import de.steamwar.sql.BauweltMemberTable.bauweltId
import de.steamwar.sql.internal.useDb
import org.jetbrains.exposed.v1.core.and
import org.jetbrains.exposed.v1.core.dao.id.CompositeID
@@ -32,45 +31,37 @@ import org.jetbrains.exposed.v1.jdbc.insertIgnore
import java.util.*
object BauweltMemberTable : CompositeIdTable("BauweltMember") {
val bauweltId = reference("BauweltID", SteamwarUserTable).index()
val worldId = reference("WorldID", WorldTable).index()
val memberId = reference("MemberID", SteamwarUserTable).index()
val build = bool("Build")
val worldEdit = bool("WorldEdit")
val world = bool("World")
override val primaryKey = PrimaryKey(bauweltId, memberId)
override val primaryKey = PrimaryKey(worldId, memberId)
init {
addIdColumn(bauweltId)
addIdColumn(worldId)
addIdColumn(memberId)
}
}
class BauweltMember(id: EntityID<CompositeID>) : CompositeEntity(id) {
companion object : CompositeEntityClass<BauweltMember>(BauweltMemberTable) {
private val cache = mutableMapOf<Int, BauweltMember>()
private val cache = mutableMapOf<Pair<UUID, Int>, BauweltMember>()
private fun cache(member: BauweltMember) =
cache.put(member.memberID, member)
cache.put(member.worldID to member.memberID, member)
@JvmStatic
fun clear() =
cache.clear()
@JvmStatic
@Deprecated("Use addMember(ownerId: Int, 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>) =
fun addMember(worldId: UUID, newMemberId: Int) =
useDb {
BauweltMemberTable.insertIgnore {
it[bauweltId] = ownerId
it[memberId] = newMemberId
it[BauweltMemberTable.worldId] = EntityID(worldId, WorldTable)
it[memberId] = EntityID(newMemberId, SteamwarUserTable)
it[build] = false
it[worldEdit] = false
it[world] = false
@@ -78,31 +69,32 @@ class BauweltMember(id: EntityID<CompositeID>) : CompositeEntity(id) {
}
@JvmStatic
@Deprecated("Use getBauMember(bauwelt: Int, member: Int)")
fun getBauMember(bauwelt: UUID, member: UUID) =
fun addMember(worldId: UUID, newMemberId: 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 {
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
fun getBauMember(bauwelt: Int, member: Int) =
useDb {
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) } }
}
fun getMembers(world: UUID) =
getWorldMembers(world)
}
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 })
private var worldEditInternal by BauweltMemberTable.worldEdit
var worldEdit: Boolean
@@ -144,4 +136,4 @@ class BauweltMember(id: EntityID<CompositeID>) : CompositeEntity(id) {
useDb {
delete()
}
}
}
@@ -50,6 +50,8 @@ enum class UserPerm {
PUNISHMENTS,
TICKET_LOG,
BUILD,
BUILD_EXTRA_WORLDS,
BUILD_UNLIMITED_WORLDS,
CHECK,
MODERATION,
ADMINISTRATION;
@@ -95,4 +97,4 @@ enum class UserPerm {
}
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.nms)
compileOnly(libs.fawe)
compileOnly(libs.datafixer)
}
@@ -108,8 +108,10 @@ public class FightSystem extends JavaPlugin {
new OneShotStateDependent(ArenaMode.Test, FightState.All, WorldEditRendererCUIEditor::new);
Config.world.setGameRule(GameRule.REDUCED_DEBUG_INFO, ArenaMode.AntiTest.contains(Config.mode));
techHider = new TechHiderWrapper();
hullHider = new HullHider();
techHider = new TechHiderWrapper(hullHider);
FightSystem.getHullHider().getHullMap().values().forEach(hull -> hull.fill(true));
FileSource.startReplay();
@@ -158,6 +158,8 @@ public class FightSchematic extends StateDependent {
FreezeWorld freezer = new FreezeWorld();
team.teleportToSpawn();
// TODO: Implement hull generation based on clipboard content!
FightSystem.getHullHider().fill(team, false);
Vector dims = WorldeditWrapper.impl.getDimensions(clipboard);
WorldeditWrapper.impl.pasteClipboard(
clipboard,
@@ -68,6 +68,13 @@ public class FightWorld extends StateDependent {
}
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<>();
Recording.iterateOverEntities(Objects::nonNull, entity -> {
if (entity.getType() != EntityType.PLAYER && (!Config.GameModeConfig.Arena.Leaveable || Config.ArenaRegion.inRegion(entity.getLocation()))) {
@@ -77,14 +84,12 @@ public class FightWorld extends StateDependent {
entities.forEach(Entity::remove);
entities.clear();
World backup = new WorldCreator(Config.world.getName() + "/backup").createWorld();
assert backup != null;
FightSystem.getHullHider().getHullMap().values().forEach(hull -> hull.fill(true));
Config.ArenaRegion.forEachChunk((x, z) -> {
CraftbukkitWrapper.impl.resetChunk(Config.world, backup, x, z);
for (Player p : Bukkit.getOnlinePlayers()) {
de.steamwar.core.CraftbukkitWrapper.sendChunk(p, x, z);
}
});
Bukkit.unloadWorld(backup, false);
}
}
@@ -46,6 +46,7 @@ public class ArrowStopper {
private void run() {
Recording.iterateOverEntities(AbstractArrow.class::isInstance, entity -> {
Projectile arrow = (Projectile) entity;
if(!(arrow.getShooter() instanceof Player)) return;
if (invalidEntity(arrow)) return;
Location prevLocation = arrow.getLocation().toVector().subtract(arrow.getVelocity()).toLocation(arrow.getWorld());
@@ -24,6 +24,7 @@ import de.steamwar.fightsystem.Config;
import de.steamwar.fightsystem.utils.CraftbukkitWrapper;
import de.steamwar.linkage.Linked;
import net.minecraft.network.protocol.game.ServerboundUseItemOnPacket;
import net.minecraft.network.protocol.game.ServerboundUseItemPacket;
import org.bukkit.entity.Player;
import java.io.*;
@@ -42,7 +43,7 @@ public class ClickAnalyzer {
}
public ClickAnalyzer() {
TinyProtocol.instance.addFilter(Recording.blockPlacePacket, this::onBlockPlace);
TinyProtocol.instance.addFilter(ServerboundUseItemPacket.class, this::onBlockPlace);
TinyProtocol.instance.addFilter(ServerboundUseItemOnPacket.class, this::onBlockPlace);
}
@@ -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);
}
}
@@ -37,6 +37,7 @@ import de.steamwar.fightsystem.states.StateDependentListener;
import de.steamwar.fightsystem.states.StateDependentTask;
import de.steamwar.fightsystem.utils.SWSound;
import de.steamwar.linkage.Linked;
import net.minecraft.network.protocol.Packet;
import net.minecraft.network.protocol.game.ServerboundPlayerActionPacket;
import net.minecraft.network.protocol.game.ServerboundUseItemPacket;
import net.minecraft.world.InteractionHand;
@@ -124,7 +125,7 @@ public class Recording implements Listener {
@Override
public void disable() {
TinyProtocol.instance.removeFilter(blockPlacePacket, place);
TinyProtocol.instance.removeFilter(ServerboundUseItemPacket.class, place);
TinyProtocol.instance.removeFilter(blockDigPacket, dig);
}
}.register();
@@ -142,7 +143,7 @@ public class Recording implements Listener {
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 Reflection.Field<?> blockDigType = Reflection.getField(blockDigPacket, playerDigType, 0);
private static final Object releaseUseItem = playerDigType.getEnumConstants()[5];
@@ -154,8 +155,6 @@ public class Recording implements Listener {
return packet;
}
public static final Class<?> blockPlacePacket = ServerboundUseItemPacket.class;
private Object blockPlace(Player p, ServerboundUseItemPacket packet) {
boolean mainHand = packet.getHand() == InteractionHand.MAIN_HAND;
if (!isNotSent(p) && (mainHand ? p.getInventory().getItemInMainHand() : p.getInventory().getItemInOffHand()).getType() == Material.BOW) {
@@ -165,6 +165,15 @@ public class Hull {
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() {
visibility.clear();
occluding.clear();
@@ -19,8 +19,6 @@
package de.steamwar.fightsystem.utils;
import com.comphenix.tinyprotocol.TinyProtocol;
import de.steamwar.Reflection;
import de.steamwar.entity.REntity;
import de.steamwar.fightsystem.Config;
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.listener.Recording;
import de.steamwar.fightsystem.states.FightState;
import de.steamwar.fightsystem.states.StateDependent;
import de.steamwar.fightsystem.states.StateDependentListener;
import de.steamwar.fightsystem.states.StateDependentTask;
import de.steamwar.techhider.TechHider;
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.block.Block;
import org.bukkit.entity.Player;
@@ -55,17 +44,12 @@ import org.bukkit.event.player.PlayerQuitEvent;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
import java.util.function.BiFunction;
import java.util.function.Function;
public class HullHider implements Listener {
@Getter
private final Map<FightTeam, Hull> hullMap = new HashMap<>();
private final Hull[] hulls;
private final Map<Class<?>, BiFunction<Player, Object, Object>> packetHiders = new HashMap<>();
private static final Class<?> packetPlayOutExplosion = ClientboundExplodePacket.class;
public HullHider() {
if (!TechHiderWrapper.ENABLED) {
@@ -76,34 +60,20 @@ public class HullHider implements Listener {
Fight.teams().forEach(team -> hullMap.put(team, new Hull(team)));
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 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);
}
public void initialize(FightTeam team) {
if (!TechHiderWrapper.ENABLED) return;
hullMap.get(team).initialize();
}
public void fill(FightTeam team, boolean visible) {
if (!TechHiderWrapper.ENABLED) return;
hullMap.get(team).fill(visible);
}
@EventHandler(priority = EventPriority.HIGH)
public void onJoin(PlayerJoinEvent e) {
@@ -161,18 +131,6 @@ public class HullHider implements Listener {
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)
public void onSpawn(EntitySpawnEvent e) {
for (Hull hull : hulls) {
@@ -210,39 +168,4 @@ public class HullHider implements Listener {
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 packet;
}
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;
}
}
@@ -19,9 +19,9 @@
package de.steamwar.fightsystem.utils;
import de.steamwar.Reflection;
import de.steamwar.core.CraftbukkitWrapper;
import de.steamwar.fightsystem.Config;
import de.steamwar.fightsystem.FightSystem;
import de.steamwar.fightsystem.events.BoardingEvent;
import de.steamwar.fightsystem.events.TeamLeaveEvent;
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.StateDependent;
import de.steamwar.fightsystem.states.StateDependentListener;
import de.steamwar.sql.SteamwarUser;
import de.steamwar.techhider.AccessPrivilegeProvider;
import de.steamwar.techhider.TechHider;
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.Material;
import org.bukkit.craftbukkit.CraftWorld;
import org.bukkit.craftbukkit.util.CraftMagicNumbers;
import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
import org.bukkit.event.Listener;
import org.bukkit.event.player.PlayerQuitEvent;
import javax.crypto.*;
import javax.crypto.spec.SecretKeySpec;
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.util.Collection;
import java.util.Optional;
import java.util.Set;
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;
@Getter
private final ConcurrentHashMap<Player, Region> hiddenRegion = new ConcurrentHashMap<>();
private final ConcurrentHashMap<Player, Long> patterns = new ConcurrentHashMap<>();
private final TechHider techHider;
private final SecretKey key;
private final HullHider hullHider;
public TechHiderWrapper() {
super(ENABLED, FightState.Schem);
techHider = new TechHider(this, Config.GameModeConfig.Techhider.ObfuscateWith, Config.GameModeConfig.Techhider.HiddenBlocks, Config.GameModeConfig.Techhider.HiddenBlockEntities);
public TechHiderWrapper(HullHider hullHider) {
super(ENABLED, FightState.All);
try {
key = new SecretKeySpec(Files.readAllBytes(new File(System.getProperty("user.home"), "hullhider.key").toPath()), "AES");
} catch (IOException e) {
throw new SecurityException(e);
}
this.hullHider = hullHider;
new StateDependentListener(ENABLED, FightState.Schem, this);
new StateDependentListener(ENABLED, FightState.All, this);
register();
}
@Override
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
public void disable() {
techHider.disable();
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) {
if (Config.isReferee(player)) return Region.EMPTY;
@@ -194,4 +191,27 @@ public class TechHiderWrapper extends StateDependent implements TechHider.Locati
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());
}
}
}
+2 -1
View File
@@ -57,9 +57,10 @@ tasks.register<FightServer>("WarGear21") {
description = "Run a WarGear 1.21 Fight Server"
dependsOn(":SpigotCore:shadowJar")
dependsOn(":FightSystem:shadowJar")
dependsOn(":KotlinCore:shadowJar")
template = "WarGear21"
worldName = "arenas/Pentraki"
config = "WarGear20.yml"
config = "WarGear21.yml"
jar = "/jars/paper-1.21.6.jar"
}
@@ -20,7 +20,9 @@
package de.steamwar.lobby.boatrace;
import de.steamwar.entity.REntity;
import de.steamwar.entity.REntityAction;
import de.steamwar.entity.REntityServer;
import de.steamwar.entity.RInteraction;
import de.steamwar.lobby.LobbySystem;
import de.steamwar.lobby.util.LeaderboardManager;
import de.steamwar.sql.SteamwarUser;
@@ -58,11 +60,12 @@ public class BoatRace implements EventListener, Listener {
static {
boatNpcServer = new REntityServer();
REntity starter = new REntity(boatNpcServer, EntityType.VILLAGER, BoatRacePositions.NPC);
boatNpcServer.setCallback((player, rEntity, entityAction) -> {
if (rEntity != starter) return;
Bukkit.getWorlds().get(0).getEntities().stream().filter(entity -> entity.getType() == EntityType.END_CRYSTAL).forEach(Entity::remove);
if (entityAction == REntityServer.EntityAction.INTERACT && !oneNotStarted) {
new REntity(boatNpcServer, EntityType.VILLAGER, BoatRacePositions.NPC);
RInteraction interaction = new RInteraction(boatNpcServer, BoatRacePositions.NPC.clone().subtract(0.5, 0, 0.5));
interaction.setInteractionHeight(1.95f);
interaction.setCallback((player, entity, action) -> {
Bukkit.getWorlds().get(0).getEntities().stream().filter(e -> e.getType() == EntityType.END_CRYSTAL).forEach(Entity::remove);
if (action == REntityAction.INTERACT && !oneNotStarted) {
oneNotStarted = true;
new BoatRace(player);
}
@@ -1,22 +1,3 @@
/*
* 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;
import com.google.common.collect.MapMaker;
@@ -77,6 +58,7 @@ public class TinyProtocol {
public static final TinyProtocol instance = new TinyProtocol(Core.getInstance());
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() {
// enforce init
@@ -245,7 +227,7 @@ public class TinyProtocol {
}
}
public <T> void addFilter(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);
}
@@ -253,6 +235,10 @@ public class TinyProtocol {
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.
*
@@ -466,6 +452,13 @@ public class TinyProtocol {
public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception {
try {
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) {
plugin.getLogger().log(Level.SEVERE, "Error in onPacketOutAsync().", e);
}
@@ -56,21 +56,15 @@ public class AntiNocom implements Listener {
}
private void registerUseItem() {
Class<?> movingObjectPositionBlock = BlockHitResult.class;
Reflection.Field<?> useItemPosition = Reflection.getField(ServerboundUseItemOnPacket.class, movingObjectPositionBlock, 0);
Reflection.Field<?> movingBlockPosition = Reflection.getField(movingObjectPositionBlock, TechHider.blockPosition, 0);
Function<Object, Object> getPosition = (packet) -> movingBlockPosition.get(useItemPosition.get(packet));
TinyProtocol.instance.addFilter(ServerboundUseItemOnPacket.class, (player, packet) -> {
Object pos = getPosition.apply(packet);
return isValid(player, "UseItem", TechHider.blockPositionX.get(pos), TechHider.blockPositionZ.get(pos)) ? packet : null;
BlockPos pos = packet.getHitResult().getBlockPos();
return isValid(player, "UseItem", pos.getX(), pos.getZ()) ? packet : null;
});
}
private Object onDig(Player player, ServerboundPlayerActionPacket packet) {
private ServerboundPlayerActionPacket onDig(Player player, ServerboundPlayerActionPacket packet) {
BlockPos pos = packet.getPos();
return isValid(player, "Dig", TechHider.blockPositionX.get(pos), TechHider.blockPositionZ.get(pos)) ? packet : null;
return isValid(player, "Dig", pos.getX(), pos.getZ()) ? packet : null;
}
private boolean isValid(Player player, String type, int x, int z) {
@@ -20,6 +20,7 @@
package de.steamwar.entity;
import lombok.Getter;
import lombok.Setter;
import net.minecraft.network.syncher.EntityDataAccessor;
import net.minecraft.network.syncher.EntityDataSerializers;
import org.bukkit.Location;
@@ -27,13 +28,16 @@ import org.bukkit.entity.EntityType;
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);
@Getter
private final Size size;
@Setter
private REntityActionListener<RArmorStand> callback = null;
public RArmorStand(REntityServer server, Location location, Size size) {
super(server, EntityType.ARMOR_STAND, location, 0);
this.size = size;
@@ -1,7 +1,7 @@
/*
* This file is a part of the SteamWar software.
*
* Copyright (C) 2025 SteamWar.de-Serverteam
* Copyright (C) 2026 SteamWar.de-Serverteam
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
@@ -17,20 +17,9 @@
* 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.event.HandlerList;
public class TickStartEvent extends Event {
private static final HandlerList handlers = new HandlerList();
public HandlerList getHandlers() {
return handlers;
}
public static HandlerList getHandlerList() {
return handlers;
}
public enum REntityAction {
INTERACT,
ATTACK,
}
@@ -1,7 +1,7 @@
/*
* This file is a part of the SteamWar software.
*
* Copyright (C) 2025 SteamWar.de-Serverteam
* Copyright (C) 2026 SteamWar.de-Serverteam
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
@@ -17,20 +17,10 @@
* 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.event.HandlerList;
import org.bukkit.entity.Player;
public class TickEndEvent extends Event {
private static final HandlerList handlers = new HandlerList();
public HandlerList getHandlers() {
return handlers;
}
public static HandlerList getHandlerList() {
return handlers;
}
public interface REntityActionListener<E extends REntity> {
void onAction(Player player, E entity, REntityAction action);
}
@@ -51,7 +51,6 @@ public class REntityServer implements Listener {
private final HashMap<Player, Location> lastLocation = 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 BiFunction<Player, ServerboundInteractPacket, Object> filter = (player, packet) -> {
@@ -61,25 +60,19 @@ public class REntityServer implements Listener {
if (playersThatClicked.contains(player)) return null;
playersThatClicked.add(player);
EntityAction action = packet.isAttack() ? EntityAction.ATTACK : EntityAction.INTERACT;
REntityAction action = packet.isAttack() ? REntityAction.ATTACK : REntityAction.INTERACT;
Bukkit.getScheduler().runTask(Core.getInstance(), () -> {
playersThatClicked.remove(player);
callback.onAction(player, entity, action);
if (entity instanceof RInteractableEntity interactable && interactable.getCallback() != null) {
interactable.getCallback().onAction(player, entity, action);
}
});
return null;
};
public REntityServer() {
Core.getInstance().getServer().getPluginManager().registerEvents(this, Core.getInstance());
}
public void setCallback(EntityActionListener callback) {
boolean uninitialized = this.callback == null;
this.callback = callback;
if (uninitialized) {
TinyProtocol.instance.addFilter(ServerboundInteractPacket.class, filter);
}
TinyProtocol.instance.addFilter(ServerboundInteractPacket.class, filter);
}
public void addPlayer(Player player) {
@@ -293,13 +286,4 @@ public class REntityServer implements Listener {
private long chunkToId(int x, int 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;
import de.steamwar.techhider.BlockIds;
import lombok.AccessLevel;
import lombok.Getter;
import lombok.Setter;
import org.bukkit.Location;
import org.bukkit.Material;
import org.bukkit.entity.EntityType;
@Getter
public class RFallingBlockEntity extends REntity {
public class RFallingBlockEntity extends REntity implements RInteractableEntity<RFallingBlockEntity> {
private final Material material;
@Setter
private REntityActionListener<RFallingBlockEntity> callback = null;
public RFallingBlockEntity(REntityServer server, Location location, Material material) {
super(server, EntityType.FALLING_BLOCK, location, BlockIds.impl.materialToId(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.packets.common.PlayerSkinRequestPacket;
import lombok.Getter;
import lombok.Setter;
import net.minecraft.network.protocol.game.ClientboundAddEntityPacket;
import net.minecraft.world.phys.Vec3;
import org.bukkit.GameMode;
@@ -39,7 +40,7 @@ import java.util.Map;
import java.util.UUID;
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);
@@ -48,6 +49,10 @@ public class RPlayer extends REntity {
@Getter
private final String name;
@Setter
@Getter
private REntityActionListener<RPlayer> callback = null;
public RPlayer(REntityServer server, UUID uuid, String name, Location location) {
super(server, EntityType.PLAYER, UUID.nameUUIDFromBytes(uuid.toString().getBytes(StandardCharsets.UTF_8)), location, 0);
this.actualUUID = uuid;
@@ -20,14 +20,54 @@
package de.steamwar.providers;
import de.steamwar.sql.SteamwarUser;
import de.steamwar.sql.WorldType;
import org.bukkit.Bukkit;
import java.util.UUID;
public class BauServerInfo {
private static Integer bauOwner = null;
private static Integer bauTeam = null;
private static UUID bauWorld = null;
private static WorldType bauType = null;
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 {
bauOwner = Integer.parseInt(Bukkit.getWorlds().get(0).getName());
if (bauOwner == null) {
bauOwner = Integer.parseInt(Bukkit.getWorlds().get(0).getName());
}
} catch (NumberFormatException ignored) {
}
}
@@ -37,11 +77,23 @@ public class BauServerInfo {
}
public static boolean isBauServer() {
return bauOwner != null;
return bauOwner != null || bauWorld != null || bauTeam != null;
}
public static SteamwarUser getOwnerUser() {
if (bauOwner == null) return null;
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;
import de.steamwar.Reflection;
import net.minecraft.core.IdMapper;
import net.minecraft.core.registries.BuiltInRegistries;
import net.minecraft.world.level.block.Block;
import net.minecraft.world.level.block.state.BlockState;
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 Iterable<BlockState> registryBlockId = (Iterable<BlockState>) Reflection.getField(TechHider.block, IdMapper.class, 0).get(null);
public Set<Integer> materialToAllIds(Material material) {
Set<Integer> ids = new HashSet<>();
for (BlockState data : getBlock(material).getStateDefinition().getPossibleStates()) {
ids.add(getCombinedId(data));
}
if (material == Material.WATER) {
for (BlockState data : registryBlockId) {
if (data.getFluidState() == water) {
ids.add(getCombinedId(data));
for (Block block : BuiltInRegistries.BLOCK) {
for (BlockState data : block.getStateDefinition().getPossibleStates()) {
if (data.getFluidState() == water) {
ids.add(getCombinedId(data));
}
}
}
}
@@ -22,295 +22,284 @@ package de.steamwar.techhider;
import de.steamwar.Reflection;
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.core.SectionPos;
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.Block;
import net.minecraft.world.level.block.entity.BlockEntityType;
import net.minecraft.world.level.block.state.BlockState;
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;
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() {
return ClientboundLevelChunkWithLightPacket.class;
private static final Reflection.Field<ClientboundLevelChunkPacketData> levelChunkPacketDataField = Reflection.getField(ClientboundLevelChunkWithLightPacket.class, ClientboundLevelChunkPacketData.class, 0);
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 static final UnaryOperator<Object> chunkDataCloner = ProtocolUtils.shallowCloneGenerator(ClientboundLevelChunkPacketData.class);
private int getLongsRequiredToEncodeEntries(int bitsPerEntry, int entryCount) {
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);
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;
};
return dataLengthAsLongCount;
}
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);
private long[] readSectionDataFromBuffer(ByteBuf dataSource, short bitsPerEntry, int entryCount) {
int dataLengthAsLongCount = getLongsRequiredToEncodeEntries(bitsPerEntry, entryCount);
long[] dataArray = new long[dataLengthAsLongCount];
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();
for (int i = 0; i < dataLengthAsLongCount; i++) {
dataArray[i] = dataSource.readLong();
}
if (section.isSkipSection() || (!section.blockPrecise() && section.isPaletted())) {
section.skipNewDataArray(4096);
return;
return dataArray;
}
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++) {
for (int z = 0; z < 16; z++) {
for (int x = 0; x < 16; x++) {
int pos = (((y * 16) + z) * 16) + x;
byte[] data = new byte[out.readableBytes()];
out.readBytes(data);
TechHider.State test = section.test(x, y, z);
List<Object> blockEntities = chunkBlockEntitiesDataField.get(chunkData);
List<Object> filteredBlockEntities = filterBlockEntities(player, blockEntities, chunkX, chunkZ);
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());
return buildNewChunkPacket(packet, data, filteredBlockEntities);
}
private int[] obfuscateBlockDataArray(Player player, int chunkX, int chunkZ, int yOffset, int[] blockDataArray) {
int[] obfuscatedData = new int[BLOCKS_PER_SECTION];
for (int sectionY = 0; sectionY < SECTION_SPAN_SIZE; sectionY++) {
for (int sectionZ = 0; sectionZ < SECTION_SPAN_SIZE; sectionZ++) {
for (int sectionX = 0; sectionX < SECTION_SPAN_SIZE; sectionX++) {
int blockDataIndex = (((sectionY * SECTION_SPAN_SIZE) + sectionZ) * SECTION_SPAN_SIZE) + sectionX;
int worldX = sectionX + (SECTION_SPAN_SIZE * chunkX);
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) {
section.copyBitsPerBlock();
if (section.getBitsPerBlock() == 0) {
section.copyVarInt();
} else if (section.getBitsPerBlock() < 6) {
section.skipPalette();
section.skipNewDataArray(64);
private int[] resolveDirectBlockDataArray(long[] rawDataArray, byte bitsPerBlock) {
SimpleBitStorage data = new SimpleBitStorage(bitsPerBlock, BLOCKS_PER_SECTION, rawDataArray);
int[] resolvedData = new int[BLOCKS_PER_SECTION];
for (int i = 0; i < BLOCKS_PER_SECTION; i++) {
resolvedData[i] = data.get(i);
}
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 {
// 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);
long[] rawData = readSectionDataFromBuffer(oldData, bitsPerBiome, BIOMES_PER_SECTION);
for (long rawDataSegment : rawData) {
newData.writeLong(rawDataSegment);
}
}
}
@@ -140,6 +140,16 @@ public class ProtocolUtils {
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) {
do {
int temp = value & 0b01111111;
@@ -152,6 +162,12 @@ public class ProtocolUtils {
} while (value != 0);
}
public static void writeVarIntArray(ByteBuf buf, int[] values) {
for (int varInt : values) {
writeVarInt(buf, varInt);
}
}
@Deprecated
public static int readVarIntLength(byte[] array, int startPos) {
int numRead = 0;
@@ -1,7 +1,7 @@
/*
* This file is a part of the SteamWar software.
*
* Copyright (C) 2025 SteamWar.de-Serverteam
* Copyright (C) 2026 SteamWar.de-Serverteam
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
@@ -21,161 +21,529 @@ package de.steamwar.techhider;
import com.comphenix.tinyprotocol.TinyProtocol;
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.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.login.*;
import net.minecraft.world.level.block.Block;
import net.minecraft.world.level.block.entity.BlockEntityType;
import net.minecraft.world.level.block.state.BlockState;
import org.bukkit.Material;
import org.bukkit.craftbukkit.util.CraftMagicNumbers;
import net.minecraft.world.phys.Vec3;
import org.bukkit.entity.Player;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import java.util.*;
import java.util.function.BiFunction;
import java.util.function.Function;
import java.util.function.UnaryOperator;
import java.util.stream.Collectors;
import java.util.function.ToIntFunction;
/**
* 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 {
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 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);
private final BlockState blockStateUsedForObfuscation;
private final ChunkHider chunkHider;
public static final Class<?> iBlockData = BlockState.class;
public static final Class<?> block = Block.class;
private final AccessPrivilegeProvider privilegeProvider;
public boolean iBlockDataHidden(BlockState iBlockData) {
return obfuscateIds.contains(BlockIds.impl.getCombinedId(iBlockData));
}
// TODO handle packet bundle
public TechHider(Block blockUsedForObfuscation, AccessPrivilegeProvider privilegeProvider) {
this.blockStateUsedForObfuscation = blockUsedForObfuscation.defaultBlockState();
public static final Object AIR = CraftMagicNumbers.getBlock(Material.AIR).defaultBlockState();
public static final int AIR_ID = BlockIds.impl.materialToId(Material.AIR);
this.chunkHider = new ChunkHider(blockUsedForObfuscation, privilegeProvider);
this.privilegeProvider = privilegeProvider;
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;
this.bypassingPackets = new HashSet<>(List.of(
// --- 5.1.x Login Protocol ---
ClientboundLoginDisconnectPacket.class, // 5.1.1 Disconnect
ClientboundHelloPacket.class, // 5.1.2 Encryption Request
ClientboundLoginFinishedPacket.class, // 5.1.3 Login Success
ClientboundLoginCompressionPacket.class, // 5.1.4 Set Compression
ClientboundCustomQueryPacket.class, // 5.1.5 Login Plugin Request
ClientboundCookieRequestPacket.class, // 5.1.6 Cookie Request
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);
// --- 6.1.x Configuration Protocol ---
ClientboundSelectKnownPacks.class, ClientboundCustomPayloadPacket.class, // 6.1.2 Clientbound Plugin Message
ClientboundFinishConfigurationPacket.class, // 6.1.4 Finish Configuration
ClientboundKeepAlivePacket.class, // 6.1.5 Clientbound Keep Alive
ClientboundPingPacket.class, // 6.1.6 Ping
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);
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);
// --- 7.1.x Play Protocol ---
}
// --- 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() {
techhiders.forEach(TinyProtocol.instance::addFilter);
}
// --- Mostly safe packets; Are involved with critical subdomain, but in not critical way ---
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() {
techhiders.forEach(TinyProtocol.instance::removeFilter);
}
ClientboundLightUpdatePacket.class, // 7.1.48 Update Light
public static final Class<?> multiBlockChangePacket = ClientboundSectionBlocksUpdatePacket.class;
public static final UnaryOperator<Object> multiBlockChangeCloner = ProtocolUtils.shallowCloneGenerator(TechHider.multiBlockChangePacket);
ClientboundContainerSetContentPacket.class, // 7.1.19 Set Container Content
ClientboundContainerSetDataPacket.class, // 7.1.20 Set Container Property
ClientboundContainerSetSlotPacket.class // 7.1.21 Set Container Slot
));
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);
BiFunction<Player, Packet<? extends PacketListener>, Packet<? extends PacketListener>> tossPacket = (p, packet) -> null;
Map<Class<? extends Packet<? extends PacketListener>>, BiFunction<Player, Packet<? extends PacketListener>, Packet<? extends PacketListener>>> processors = new HashMap<>();
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;
// --- Entity packets -
// 7.1.2 Spawn Entity: entity type and position can reveal hidden contraptions.
processors.put(ClientboundAddEntityPacket.class, (p, packet) -> this.processAddEntityPacket(p, (ClientboundAddEntityPacket) packet));
// 7.1.3 Entity Animation: entity id based signal, keep blocked until entity visibility is modeled.
processors.put(ClientboundAnimatePacket.class, this.buildEntityPacketProcessor(ClientboundAnimatePacket::getId));
// 7.1.35 Entity Event: entity id based signal.
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:
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:
} catch (Throwable t) {
t.printStackTrace();
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 {
SKIP,
CHECK,
HIDE,
HIDE_AIR
private Packet<? extends PacketListener> processAddEntityPacket(Player player, ClientboundAddEntityPacket packet) {
if (privilegeProvider.isPlayerPrivilegedToAccessEntity(player, packet.getId()) && privilegeProvider.isPlayerPrivilegedToAccessPosition(player, (int) packet.getX(), (int) packet.getY(), (int) packet.getZ())) {
return packet;
} else {
return null;
}
}
public interface LocationEvaluator {
default boolean suppressInteractions(Player player) {
return false;
private Packet<? extends PacketListener> processEntityPacket(Player player, int entityId, Packet<? extends PacketListener> packet) {
if (privilegeProvider.isPlayerPrivilegedToAccessEntity(player, entityId)) {
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);
default boolean skipChunkSection(Player player, int x, int y, int z) {
return skipChunk(player, x, z);
if (filteredStates.isEmpty()) {
return null;
}
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;
short[] newPos = new short[filteredPos.size()];
for (int i = 0; i < newPos.length; i++) {
newPos[i] = filteredPos.get(i);
}
default State checkBlockPos(Player player, Object pos) {
return check(player, blockPositionX.get(pos), blockPositionY.get(pos), blockPositionZ.get(pos));
}
BlockState[] newStates = filteredStates.toArray(new BlockState[0]);
default boolean blockPrecise(Player player, int x, int y, int z) {
return false;
return new ClientboundSectionBlocksUpdatePacket(sectionPos, ShortSets.unmodifiable(new ShortArraySet(newPos)), newStates);
}
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/>.
*/
package de.steamwar.techhider;
package de.steamwar.techhider.legacy;
import de.steamwar.Reflection;
import net.minecraft.core.SectionPos;
@@ -29,6 +29,7 @@ import org.bukkit.entity.Player;
import java.util.ArrayList;
import java.util.function.BiFunction;
@Deprecated
public class ProtocolWrapper {
public static final ProtocolWrapper impl = new ProtocolWrapper();
@@ -88,8 +89,4 @@ public class ProtocolWrapper {
public boolean unfilteredTileEntityDataAction(Object packet) {
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
object InviteCommand : SWCommand("invite") {
init {
message = de.steamwar.tntleague.message
}
@Register
fun invitePlayer(@Validator("isLeader") sender: Player, target: Player) {
@@ -29,6 +29,7 @@ import java.util.function.Consumer;
@Getter
public class Bauserver extends Subserver {
private static final Map<UUID, Bauserver> servers = new HashMap<>();
private static final Map<UUID, Bauserver> serversByWorld = new HashMap<>();
public static Bauserver get(UUID owner) {
synchronized (servers) {
@@ -36,25 +37,43 @@ public class Bauserver extends Subserver {
}
}
public static Bauserver getByWorld(UUID world) {
synchronized (serversByWorld) {
return serversByWorld.get(world);
}
}
private final UUID owner;
private final UUID world;
public Bauserver(String serverName, UUID owner, int port, ProcessBuilder processBuilder, Runnable shutdownCallback) {
this(serverName, owner, port, processBuilder, shutdownCallback, null);
this(serverName, owner, owner, port, processBuilder, shutdownCallback, null);
}
public Bauserver(String serverName, UUID owner, int port, ProcessBuilder processBuilder, Runnable shutdownCallback, Consumer<Exception> failureCallback) {
this(serverName, owner, owner, port, processBuilder, shutdownCallback, failureCallback);
}
public Bauserver(String serverName, UUID owner, UUID world, int port, ProcessBuilder processBuilder, Runnable shutdownCallback, Consumer<Exception> failureCallback) {
super(serverName, port, processBuilder, shutdownCallback, failureCallback);
this.owner = owner;
this.world = world;
synchronized (servers) {
servers.put(owner, this);
servers.putIfAbsent(owner, this);
}
synchronized (serversByWorld) {
serversByWorld.put(world, this);
}
}
@Override
protected void unregister() {
synchronized (servers) {
servers.remove(owner);
servers.remove(owner, this);
}
synchronized (serversByWorld) {
serversByWorld.remove(world);
}
super.unregister();
}
+3 -3
View File
@@ -37,8 +37,8 @@ if __name__ == "__main__":
with open(configfile, 'r') as file:
gamemode = yaml.load(file)
builderworld = path.expanduser(f'/worlds/builder{version}/{worldname}')
arenaworld = f'/servers/{gamemode["Server"]["Folder"]}/arenas/{worldname}'
builderworld = sys.argv[4] if len(sys.argv) > 4 else path.expanduser(f'/worlds/builder{version}/{worldname}')
arenaworld = sys.argv[5] if len(sys.argv) > 5 else f'/servers/{gamemode["Server"]["Folder"]}/arenas/{worldname}'
if path.exists(arenaworld):
backupworld = path.expanduser(f'/mnt/storage/backup/arenas/{datetime.datetime.now()}-{worldname}-{version}.tar.xz')
@@ -91,7 +91,7 @@ if __name__ == "__main__":
if path.exists(arenaworld):
shutil.rmtree(arenaworld)
os.makedirs(f'{arenaworld}/backup')
os.makedirs(f'{arenaworld}/backup', exist_ok=True)
shutil.copy2(f'{builderworld}/level.dat', f'{arenaworld}/backup/level.dat')
shutil.copytree(f'{builderworld}/region', f'{arenaworld}/backup/region')
@@ -219,22 +219,22 @@ IGNORE_ALREADY = §cYou are already ignoring this player.
IGNORE_MESSAGE = §7You are now ignoring §e{0}§8.
#BauCommand
BAU_ADDMEMBER_USAGE = §8/§7build addmember §8[§eplayer§8]
BAU_ADDMEMBER_USAGE = §8/§7build addmember §8[§eworld/all§8] §8[§eplayer§8]
BAU_ADDMEMBER_SELFADD = §cYou don't have to add yourself!
BAU_ADDMEMBER_ISADDED = §cThis player is already a member of your world.
BAU_ADDMEMBER_ADDED = §aThe player was added to your world.
BAU_ADDMEMBER_ADDED_TARGET = §aYou have been added to the world of §e{0}§a.
BAU_TP_USAGE = §8/§7build tp §8[§eplayer§8]
BAU_TP_USAGE = §8/§7build tp §8[§eplayer§8] §8<§eworld§8>
BAU_TP_NOALLOWED = §cYou are not allowed to teleport to this player's world.
BAU_LOCKED_NOALLOWED = §cThe build server is currently locked.
BAU_LOCK_BLOCKED = §cYour build lock has prevented §e{0} §cfrom joining.
BAU_LOCKED_OPTIONS = §7Build server lock options§8: §cnobody§8, §eserverteam§8, §eteam_and_serverteam§8, §eteam§8, §aopen
BAU_LOCKED_OPTIONS = §8/§7build lock §8[§eworld§8] §8[§eoption§8] §7Options§8: §cnobody§8, §eserverteam§8, §eteam_and_serverteam§8, §eteam§8, §aopen
BAU_LOCKED_NOBODY = §7You have locked your build server for all players.
BAU_LOCKED_SERVERTEAM = §7You have locked your build server for all players except added server team members.
BAU_LOCKED_TEAM_AND_SERVERTEAM = §7You have locked your build server for all players except added team members and server team members.
BAU_LOCKED_TEAM = §7You have locked your build server for all players except added team members.
BAU_LOCKED_OPEN = §7You have opened your build server for all added players.
BAU_DELMEMBER_USAGE = §8/§7build delmember §8[§eplayer§8]
BAU_DELMEMBER_USAGE = §8/§7build delmember §8[§eworld/all§8] §8[§eplayer§8]
BAU_DELMEMBER_SELFDEL = §cYou cannot remove yourself!
BAU_DELMEMBER_DELETED = §cPlayer was removed.
BAU_DELMEMBER_DELETED_TARGET = §cYou were removed from the world of §e{0}.
@@ -244,9 +244,23 @@ BAU_DELETE_DELETED = §aYour world is being reset.
BAU_DELETE_GUI_NAME = §eDo you really want to delete the world?
BAU_DELETE_GUI_CANCEL = §cCancel
BAU_DELETE_GUI_DELETE = §aDelete
BAU_WORLD_GUI_TITLE = §eYour worlds
BAU_WORLD_CREATE_USAGE = §8/§7build world create §8[§ename§8] §8[§eversion§8] §8[§etemplate§8]
BAU_WORLD_START_USAGE = §8/§7build world start §8[§eworld§8]
BAU_WORLD_RENAME_USAGE = §8/§7build world rename §8[§eworld§8] §8[§enew name§8]
BAU_WORLD_DELETE_USAGE = §8/§7build world delete §8[§eworld§8]
BAU_WORLD_UPGRADE_USAGE = §8/§7build world upgrade §8[§eworld§8] §8[§eversion§8]
BAU_WORLD_VERSION = §cUnknown version.
BAU_WORLD_UNKNOWN = §cUnknown world.
BAU_WORLD_EXISTS = §cA world with this name already exists for this version.
BAU_WORLD_LIMIT = §cYou have reached your build world limit of §e{0}§c. Delete one first.
BAU_WORLD_OVER_LIMIT_DELETE = §cYou currently have more than your allowed §e{0}§c build worlds. Delete one before creating, renaming, or upgrading worlds.
BAU_WORLD_CREATED = §aWorld created.
BAU_WORLD_RENAMED = §aWorld renamed.
BAU_WORLD_UPGRADED = §aWorld version updated.
BAU_START_ALREADY = §cThis server is already starting.
BAU_MEMBER_NOMEMBER = §cThis player is no member of your world!
BAU_MEMBER_SET_USAGE = §8/§7build {0} §8[§eplayer§8]
BAU_MEMBER_SET_USAGE = §8/§7build {0} §8[§eworld/all§8] §8[§eplayer§8]
BAU_MEMBER_SET_TARGET = §7You are now a §e{1}§7 on the world of §e{0}§7.
BAU_MEMBER_SET = §7The player is now a §e{0}§7.
BAU_MEMBER_SET_SPECTATOR = spectator
@@ -524,6 +538,20 @@ TEAM_EVENT_LEFT = §7Your team no longer takes part in this event
TEAM_EVENT_JOINED = §7Your team now takes part in the event §e{0}§7!
TEAM_EVENT_HOW_TO_LEAVE = §7To cancel the participation repeat the command.
#Team Worlds
TEAM_WORLD_GUI_TITLE = §eTeam worlds
TEAM_WORLD_CREATE_USAGE = §8/§7team world create §8[§ename§8] §8[§eversion§8] §8[§etemplate§8]
TEAM_WORLD_RENAME_USAGE = §8/§7team world rename §8[§eworld§8] §8[§enew name§8]
TEAM_WORLD_UPGRADE_USAGE = §8/§7team world upgrade §8[§eworld§8] §8[§eversion§8]
TEAM_WORLD_DELETE_USAGE = §8/§7team world delete §8[§eworld§8]
TEAM_WORLD_EXISTS = §cA team world with this name already exists for this version.
TEAM_WORLD_LIMIT = §cYour team already has the maximum of §e{0}§c worlds. Delete one first.
TEAM_WORLD_OVER_LIMIT_DELETE = §cYour team currently has more than the allowed §e{0}§c worlds. Delete one before creating, renaming, or upgrading worlds.
TEAM_WORLD_CREATED = §aTeam world created.
TEAM_WORLD_RENAMED = §aTeam world renamed.
TEAM_WORLD_UPGRADED = §aTeam world version updated.
TEAM_WORLD_DELETED = §aTeam world deleted.
#Team Color
TEAM_COLOR_TITLE = Choose color
@@ -682,7 +710,7 @@ LOCK_LOCALE_CHANGED = §aLanguage saved
#Builder Cloud
BUILDERCLOUD_USAGE = §8/§7buildercloud §8[§eversion§8] §8[§emap§8]
BUILDERCLOUD_CREATE_USAGE = §8/§7buildercloud create §8[§eversion§8] §8[§emap§8] §8<§7generator§8>
BUILDERCLOUD_CREATE_USAGE = §8/§7buildercloud create §8[§eversion§8] §8[§emap§8] §8[§etemplate§8]
BUILDERCLOUD_RENAME_USAGE = §8/§7buildercloud rename §8[§eversion§8] §8[§emap§8] §8[§enew name§8]
BUILDERCLOUD_DEPLOY_USAGE = §8/§7deployarena §8[§egamemode§8] §8[§eversion§8] §8[§emap§8]
BUILDERCLOUD_DEPLOY_FINISHED = §7Map deployment finished.
@@ -201,22 +201,22 @@ IGNORE_ALREADY = §cDu ignorierst diesen Spieler bereits.
IGNORE_MESSAGE = §7Du ignorierst nun §e{0}§8.
#BauCommand
BAU_ADDMEMBER_USAGE = §8/§7bau addmember §8[§eSpieler§8]
BAU_ADDMEMBER_USAGE = §8/§7bau addmember §8[§eWelt/all§8] §8[§eSpieler§8]
BAU_ADDMEMBER_SELFADD = §cDu brauchst dich nicht selbst hinzufügen!
BAU_ADDMEMBER_ISADDED = §cDieser Spieler ist bereits Mitglied auf deiner Welt.
BAU_ADDMEMBER_ADDED = §aDer Spieler wurde zu deiner Welt hinzugefügt.
BAU_ADDMEMBER_ADDED_TARGET = §aDu wurdest zu der Welt von §e{0} §ahinzugefügt.
BAU_TP_USAGE = §8/§7bau tp §8[§eSpieler§8]
BAU_TP_USAGE = §8/§7bau tp §8[§eSpieler§8] §8<§eWelt§8>
BAU_TP_NOALLOWED = §cDu darfst dich nicht auf diese Welt teleportieren.
BAU_LOCKED_NOALLOWED = §cDer Bauserver ist momentan gesperrt.
BAU_LOCK_BLOCKED = §cDeine Bausperre hat den Beitritt von §e{0} §cverhindert.
BAU_LOCKED_OPTIONS = §7Bauserver-Sperroptionen§8: §cnobody§8, §eserverteam§8, §eteam_and_serverteam§8, §eteam§8, §aopen
BAU_LOCKED_OPTIONS = §8/§7bau lock §8[§eWelt§8] §8[§eOption§8] §7Optionen§8: §cnobody§8, §eserverteam§8, §eteam_and_serverteam§8, §eteam§8, §aopen
BAU_LOCKED_NOBODY = §7Du hast deinen Bau für alle Spieler geschlossen.
BAU_LOCKED_SERVERTEAM = §7Du hast deinen Bau für alle außer hinzugefügte Serverteammitglieder gesperrt.
BAU_LOCKED_TEAM_AND_SERVERTEAM = §7Du hast deinen Bau für alle außer hinzugefügte Teammitglieder und Serverteammitglieder gesperrt.
BAU_LOCKED_TEAM = §7Du hast deinen Bau für alle außer hinzugefügte Teammitglieder gesperrt.
BAU_LOCKED_OPEN = §7Du hast deinen Bau für alle hinzugefügten Spieler geöffnet.
BAU_DELMEMBER_USAGE = §8/§7bau delmember §8[§eSpieler§8]
BAU_DELMEMBER_USAGE = §8/§7bau delmember §8[§eWelt/all§8] §8[§eSpieler§8]
BAU_DELMEMBER_SELFDEL = §cDu kannst dich nicht selbst entfernen!
BAU_DELMEMBER_DELETED = §cDer Spieler wurde entfernt.
BAU_DELMEMBER_DELETED_TARGET = §cDu wurdest von der Welt von §e{0} §centfernt.
@@ -226,9 +226,23 @@ BAU_DELETE_DELETED = §aDeine Welt wird zurückgesetzt.
BAU_DELETE_GUI_NAME = §eWirklich Welt löschen?
BAU_DELETE_GUI_CANCEL = §cAbbrechen
BAU_DELETE_GUI_DELETE = §aLöschen
BAU_WORLD_GUI_TITLE = §eDeine Welten
BAU_WORLD_CREATE_USAGE = §8/§7bau world create §8[§eName§8] §8[§eVersion§8] §8[§eTemplate§8]
BAU_WORLD_START_USAGE = §8/§7bau world start §8[§eWelt§8]
BAU_WORLD_RENAME_USAGE = §8/§7bau world rename §8[§eWelt§8] §8[§eNeuer Name§8]
BAU_WORLD_DELETE_USAGE = §8/§7bau world delete §8[§eWelt§8]
BAU_WORLD_UPGRADE_USAGE = §8/§7bau world upgrade §8[§eWelt§8] §8[§eVersion§8]
BAU_WORLD_VERSION = §cUnbekannte Version.
BAU_WORLD_UNKNOWN = §cUnbekannte Welt.
BAU_WORLD_EXISTS = §cEine Welt mit diesem Namen existiert bereits in dieser Version.
BAU_WORLD_LIMIT = §cDu hast dein Bauwelt-Limit von §e{0}§c erreicht. Lösche zuerst eine Welt.
BAU_WORLD_OVER_LIMIT_DELETE = §cDu hast aktuell mehr als die erlaubten §e{0}§c Bauwelten. Lösche eine Welt, bevor du Welten erstellst, umbenennst oder upgradest.
BAU_WORLD_CREATED = §aWelt erstellt.
BAU_WORLD_RENAMED = §aWelt umbenannt.
BAU_WORLD_UPGRADED = §aWelt-Version aktualisiert.
BAU_START_ALREADY = §cDer Server startet bereits.
BAU_MEMBER_NOMEMBER = §cDer Spieler ist kein Mitglied deiner Welt!
BAU_MEMBER_SET_USAGE = §8/§7bau {0} §8[§eSpieler§8]
BAU_MEMBER_SET_USAGE = §8/§7bau {0} §8[§eWelt/all§8] §8[§eSpieler§8]
BAU_MEMBER_SET_TARGET = §7Du bist nun ein §e{1}§7 auf der Welt von §e{0}§7.
BAU_MEMBER_SET = §7Der Spieler ist nun §e{0}§7.
BAU_MEMBER_SET_SPECTATOR = Zuschauer
@@ -491,6 +505,20 @@ TEAM_EVENT_LEFT = §7Dein Team nimmt nicht mehr am Event teil
TEAM_EVENT_JOINED = §7Dein Team nimmt nun am Event §e{0} §7 teil!
TEAM_EVENT_HOW_TO_LEAVE = §7Um die Teilnahme abzusagen, wiederhole den Befehl.
#Team Worlds
TEAM_WORLD_GUI_TITLE = §eTeamwelten
TEAM_WORLD_CREATE_USAGE = §8/§7team world create §8[§eName§8] §8[§eVersion§8] §8[§eTemplate§8]
TEAM_WORLD_RENAME_USAGE = §8/§7team world rename §8[§eWelt§8] §8[§eNeuer Name§8]
TEAM_WORLD_UPGRADE_USAGE = §8/§7team world upgrade §8[§eWelt§8] §8[§eVersion§8]
TEAM_WORLD_DELETE_USAGE = §8/§7team world delete §8[§eWelt§8]
TEAM_WORLD_EXISTS = §cEine Teamwelt mit diesem Namen existiert bereits in dieser Version.
TEAM_WORLD_LIMIT = §cDein Team hat bereits das Maximum von §e{0}§c Welten. Lösche zuerst eine Welt.
TEAM_WORLD_OVER_LIMIT_DELETE = §cDein Team hat aktuell mehr als die erlaubten §e{0}§c Welten. Lösche eine Welt, bevor du Welten erstellst, umbenennst oder upgradest.
TEAM_WORLD_CREATED = §aTeamwelt erstellt.
TEAM_WORLD_RENAMED = §aTeamwelt umbenannt.
TEAM_WORLD_UPGRADED = §aTeamwelt-Version aktualisiert.
TEAM_WORLD_DELETED = §aTeamwelt gelöscht.
#Team Color
TEAM_COLOR_TITLE = Farbe wählen
@@ -644,7 +672,7 @@ LOCK_LOCALE_CHANGED = §aSprache gespeichert
#Builder Cloud
BUILDERCLOUD_USAGE = §8/§7buildercloud §8[§eVersion§8] §8[§eWelt§8]
BUILDERCLOUD_CREATE_USAGE = §8/§7buildercloud create §8[§eVersion§8] §8[§eWelt§8] §8<§7Generator§8>
BUILDERCLOUD_CREATE_USAGE = §8/§7buildercloud create §8[§eVersion§8] §8[§eWelt§8] §8[§eTemplate§8]
BUILDERCLOUD_RENAME_USAGE = §8/§7buildercloud rename §8[§eVersion§8] §8[§eWElt§8] §8[§eNeuer Name§8]
BUILDERCLOUD_VERSION = §cUnbekannte Version.
BUILDERCLOUD_EXISTING_MAP = §cWelt existiert bereits.
@@ -51,11 +51,9 @@ public class ServerStarter {
private static final String EVENT_PATH = TMP_DATA + "event/";
public static final String TEMP_WORLD_PATH = TMP_DATA + "arenaserver/";
private static final String WORLDS_FOLDER = "/worlds";
public static final String WORLDS_BASE_PATH = WORLDS_FOLDER + "/userworlds";
public static final String BUILDER_BASE_PATH = WORLDS_FOLDER + "/builder";
private static final String WORLDS_STORAGE_BASE_PATH = "/mnt/storage/worlds/userworlds";
public static final String WORLDS_BASE_PATH = SteamwarWorld.WORLD_STORAGE + "/";
public static final String LEGACY_WORLDS_BASE_PATH = "/worlds/userworlds";
public static final String LEGACY_BUILDER_BASE_PATH = "/worlds/builder";
private File directory = null;
private String worldDir = null;
@@ -91,7 +89,8 @@ public class ServerStarter {
gameMode = mode.configFile.getName().replace(".yml", "");
directory = new File(SERVER_PATH, mode.Server.Folder);
arguments.put("config", mode.configFile.getName());
tempWorld(SERVER_PATH + mode.Server.Folder + "/arenas/" + map);
SteamwarWorld arenaWorld = SteamwarWorld.getOrCreateArenaWorld(gameMode, map, version.getVersionSuffix());
tempWorld(arenaWorld.setupAndGetStoragePath());
startCondition = () -> {
if (playersToSend.stream().anyMatch(player -> Subserver.isArena(Subserver.getSubserver(player)))) {
playersToSend.forEach(player -> Chatter.of(player).system("FIGHT_IN_ARENA"));
@@ -106,7 +105,7 @@ public class ServerStarter {
public ServerStarter event(EventFight eventFight) {
arena(ArenaMode.getByInternal(eventFight.getSpielmodus()), eventFight.getMap());
node = VelocityCore.local;
worldDir = EVENT_PATH;
worldDir = EVENT_PATH + "/" + serverToWorldName(Event.byId(eventFight.getEventID()).getEventName()) + "/" + eventFight.getStartTime().toLocalDateTime().format(DateTimeFormatter.ISO_LOCAL_DATE);
worldCleanup = () -> {
};
arguments.put("fightID", String.valueOf(eventFight.getFightID()));
@@ -153,30 +152,36 @@ public class ServerStarter {
}
public ServerStarter build(ServerVersion version, UUID owner) {
this.version = version;
SteamwarUser user = SteamwarUser.get(owner);
return build(version, SteamwarWorld.getOrCreateBauWorld(user, version.getVersionSuffix()));
}
public ServerStarter build(ServerVersion version, SteamwarWorld world) {
this.version = version;
directory = version.getServerDirectory("Bau");
worldDir = version.getWorldFolder(WORLDS_BASE_PATH);
worldName = String.valueOf(SteamwarUser.get(owner).getId());
SteamwarUser user = world.getOwner() == null ? null : SteamwarUser.byId(world.getOwner().getValue());
worldDir = WORLDS_BASE_PATH;
worldName = world.getUuid().toString();
checkpoint = true;
build(owner);
if (world.getType() == WorldType.TEAM) {
build(world.getUuid());
Team team = Team.byId(world.getTeam().getValue());
serverNameProvider = port -> team.getTeamKuerzel() + "s Bau";
arguments.put("bauTeam", String.valueOf(world.getTeam().getValue()));
} else {
build(user.getUUID(), world.getUuid());
arguments.put("bauOwner", String.valueOf(user.getId()));
}
arguments.put("bauWorld", world.getUuid().toString());
arguments.put("bauType", world.getType().name());
worldSetup = () -> {
File world = new File(worldDir, worldName);
if (!world.exists()) {
File storage = new File(version.getWorldFolder(WORLDS_STORAGE_BASE_PATH), worldName);
if (storage.exists()) {
node.execute("mv", storage.getPath(), world.getPath());
} else {
copyWorld(node, new File(directory, "Bauwelt").getPath(), world.getPath());
}
}
};
worldSetup = () -> world.setupAndGetStoragePath(new File(directory, "Bauwelt"));
// Send players to existing server
startCondition = () -> {
Bauserver subserver = Bauserver.get(owner);
Bauserver subserver = Bauserver.getByWorld(world.getUuid());
if (subserver != null) {
for (Player p : playersToSend) {
SubserverSystem.sendPlayer(subserver, p);
@@ -184,9 +189,13 @@ public class ServerStarter {
return false;
}
boolean atLeastOneSupervisor = playersToSend.stream().anyMatch(player -> {
if (player.getUniqueId().equals(owner)) return true;
BauweltMember bauweltMember = BauweltMember.getBauMember(owner, player.getUniqueId());
return bauweltMember.isSupervisor();
SteamwarUser playerUser = SteamwarUser.get(player.getUniqueId());
if (world.getType() == WorldType.TEAM) {
return playerUser.getTeam() == world.getTeam().getValue();
}
if (user != null && playerUser.getId() == user.getId()) return true;
BauweltMember bauweltMember = BauweltMember.getBauMember(world.getUuid(), playerUser.getId());
return bauweltMember != null && bauweltMember.isSupervisor();
});
if (!atLeastOneSupervisor) {
for (Player p : playersToSend) {
@@ -220,15 +229,20 @@ public class ServerStarter {
}
private void build(UUID owner) {
constructor = (serverName, port, builder, shutdownCallback, failureCallback) -> new Bauserver(serverName, owner, port, builder, shutdownCallback, failureCallback);
build(owner, owner);
}
private void build(UUID owner, UUID world) {
constructor = (serverName, port, builder, shutdownCallback, failureCallback) -> new Bauserver(serverName, owner, world, port, builder, shutdownCallback, failureCallback);
serverNameProvider = port -> bauServerName(SteamwarUser.get(owner));
}
public ServerStarter builder(ServerVersion version, String map, File generator) {
public ServerStarter builder(ServerVersion version, String map, File prototype) {
this.version = version;
directory = version.getServerDirectory("Builder");
worldDir = version.getWorldFolder(BUILDER_BASE_PATH);
worldName = map;
SteamwarWorld world = SteamwarWorld.getOrCreateBuilderWorld(map, version.getVersionSuffix());
worldDir = WORLDS_BASE_PATH;
worldName = world.getUuid().toString();
serverNameProvider = port -> "*" + map;
checkpoint = true;
constructor = (serverName, port, builder, shutdownCallback, failureCallback) -> new Builderserver(serverName, worldName, port, builder, shutdownCallback, failureCallback);
@@ -245,16 +259,7 @@ public class ServerStarter {
return true;
};
if (generator != null) {
worldSetup = () -> {
File leveldat = new File(new File(worldDir, worldName), "level.dat");
try {
Files.copy(generator.toPath(), leveldat.toPath());
} catch (IOException e) {
throw new SecurityException(e);
}
};
}
worldSetup = () -> world.setupAndGetStoragePath(prototype != null ? prototype : legacyBuilderWorld(version, map));
return this;
}
@@ -351,13 +356,29 @@ public class ServerStarter {
}
public static String serverToWorldName(String serverName) {
return serverName.replace(' ', '_').replace("[", "").replace("]", "");
return serverName.replace(' ', '_').replace("[", "").replace("]", "").replace(".", "");
}
public static void copyWorld(Node node, String template, String target) {
node.execute("cp", "-r", template, target);
}
private static File legacyBauWorld(ServerVersion version, SteamwarUser user) {
File legacyIdWorld = new File(version.getWorldFolder(LEGACY_WORLDS_BASE_PATH), String.valueOf(user.getId()));
if(legacyIdWorld.exists())
return legacyIdWorld;
File legacyUuidWorld = new File(version.getWorldFolder(LEGACY_WORLDS_BASE_PATH), user.getUUID().toString());
if(legacyUuidWorld.exists())
return legacyUuidWorld;
return new File(version.getServerDirectory("Bau"), "Bauwelt");
}
public static File legacyBuilderWorld(ServerVersion version, String map) {
return new File(version.getWorldFolder(LEGACY_BUILDER_BASE_PATH), map);
}
private interface ServerConstructor {
Subserver construct(String serverName, int port, ProcessBuilder builder, Runnable shutdownCallback, Consumer<Exception> failureCallback);
}
@@ -396,4 +417,4 @@ public class ServerStarter {
}
}
}
}
@@ -35,16 +35,19 @@ import de.steamwar.persistent.Bauserver;
import de.steamwar.sql.BauweltMember;
import de.steamwar.sql.GameModeConfig;
import de.steamwar.sql.SteamwarUser;
import de.steamwar.velocitycore.ServerStarter;
import de.steamwar.velocitycore.ServerVersion;
import de.steamwar.velocitycore.SubserverSystem;
import de.steamwar.velocitycore.VelocityCore;
import de.steamwar.sql.SteamwarWorld;
import de.steamwar.sql.UserPerm;
import de.steamwar.velocitycore.*;
import de.steamwar.velocitycore.inventory.SWInventory;
import de.steamwar.velocitycore.inventory.SWItem;
import de.steamwar.velocitycore.inventory.SWListInv;
import de.steamwar.velocitycore.network.NetworkSender;
import de.steamwar.velocitycore.util.BauLock;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Locale;
import java.util.function.Consumer;
@Linked
@@ -64,13 +67,13 @@ public class BauCommand extends SWCommand {
}
@Register
public void toBau(PlayerChatter sender, @OptionalValue(value = "", onlyUINIG = true) ServerVersion version) {
new ServerStarter().build(version, sender.user().getUUID()).send(sender.getPlayer()).start();
public void toBau(PlayerChatter sender, @Mapper("ownBauWorld") @OptionalValue(value = "", onlyUINIG = true) @AllowNull SteamwarWorld world) {
startPersonalWorld(sender, world);
}
@Register(value = "addmember", description = "BAU_ADDMEMBER_USAGE")
public void addmember(Chatter sender, @Validator("addMemberTarget") SteamwarUser target) {
BauweltMember.addMember(sender.user().getUUID(), target.getUUID());
public void addmember(Chatter sender, @Mapper("bauWorldSelector") String world, @Validator("addMemberTarget") SteamwarUser target) {
if (!forEachSelectedWorld(sender, world, bauworld -> BauweltMember.addMember(bauworld.getUuid(), target.getId()))) return;
sender.system("BAU_ADDMEMBER_ADDED");
Chatter.of(target.getUUID()).system("BAU_ADDMEMBER_ADDED_TARGET", sender);
}
@@ -86,18 +89,31 @@ public class BauCommand extends SWCommand {
messageSender.send("BAU_ADDMEMBER_SELFADD");
return false;
}
if (BauweltMember.getBauMember(sender.user().getId(), value.getId()) != null) {
messageSender.send("BAU_ADDMEMBER_ISADDED");
return false;
}
return true;
};
}
@Register(value = "tp", description = "BAU_TP_USAGE")
@Register("teleport")
public void teleport(PlayerChatter sender, @Validator("teleportTarget") SteamwarUser worldOwner, @OptionalValue(value = "", onlyUINIG = true) ServerVersion version) {
new ServerStarter().build(version, worldOwner.getUUID()).send(sender.getPlayer()).start();
public void teleport(PlayerChatter sender, @Validator("teleportTarget") SteamwarUser worldOwner, @Mapper("targetBauWorld") @OptionalValue(value = "", onlyUINIG = true) @AllowNull SteamwarWorld world) {
if (world == null) {
world = defaultPersonalWorld(worldOwner);
}
if (world == null) {
sender.system("BAU_WORLD_UNKNOWN");
return;
}
if (sender.user().getId() != worldOwner.getId() && BauLock.isLocked(world, sender.user())) {
sender.system("BAU_LOCKED_NOALLOWED");
Chatter.of(worldOwner.getUUID()).system("BAU_LOCK_BLOCKED", sender);
return;
}
if (sender.user().getId() != worldOwner.getId() && BauweltMember.getBauMember(world.getUuid(), sender.user().getId()) == null) {
SubserverSystem.sendDeniedMessage(sender, worldOwner.getUUID());
sender.system("BAU_TP_NOALLOWED");
return;
}
new ServerStarter().build(ServerVersion.get(world.getVersion()), world).send(sender.getPlayer()).start();
}
@Validator(value = "teleportTarget", local = true)
@@ -107,17 +123,6 @@ public class BauCommand extends SWCommand {
messageSender.send("UNKNOWN_PLAYER");
return false;
}
if (sender.user().getId() != owner.getId() && BauweltMember.getBauMember(owner.getId(), sender.user().getId()) == null) {
SubserverSystem.sendDeniedMessage(sender, owner.getUUID());
messageSender.send("BAU_TP_NOALLOWED");
return false;
}
if (BauLock.isLocked(owner, sender.user())) {
messageSender.send("BAU_LOCKED_NOALLOWED");
Chatter.of(owner.getUUID()).system("BAU_LOCK_BLOCKED", sender);
return false;
}
return true;
};
}
@@ -128,39 +133,39 @@ public class BauCommand extends SWCommand {
}
@Register("setspectator")
public void setSpectator(Chatter sender, @Mapper("addedUsers") @AllowNull @OptionalValue("") SteamwarUser user) {
setPerms(sender, user, "setspectator", "BAU_MEMBER_SET_SPECTATOR", member -> {
public void setSpectator(Chatter sender, @Mapper("bauWorldSelector") String world, @Mapper("addedUsers") @AllowNull @OptionalValue("") SteamwarUser user) {
setPerms(sender, world, user, "setspectator", "BAU_MEMBER_SET_SPECTATOR", member -> {
member.setBuild(false);
member.setSupervisor(false);
});
}
@Register("setbuilder")
public void setBuilder(Chatter sender, @Mapper("addedUsers") @AllowNull @OptionalValue("") SteamwarUser user) {
setPerms(sender, user, "setbuild", "BAU_MEMBER_SET_BUILDER", member -> {
public void setBuilder(Chatter sender, @Mapper("bauWorldSelector") String world, @Mapper("addedUsers") @AllowNull @OptionalValue("") SteamwarUser user) {
setPerms(sender, world, user, "setbuild", "BAU_MEMBER_SET_BUILDER", member -> {
member.setBuild(true);
member.setSupervisor(false);
});
}
@Register("setsupervisor")
public void setSupervisor(Chatter sender, @Mapper("addedUsers") @AllowNull @OptionalValue("") SteamwarUser user) {
setPerms(sender, user, "setsupervisor", "BAU_MEMBER_SET_SUPERVISOR", member -> {
public void setSupervisor(Chatter sender, @Mapper("bauWorldSelector") String world, @Mapper("addedUsers") @AllowNull @OptionalValue("") SteamwarUser user) {
setPerms(sender, world, user, "setsupervisor", "BAU_MEMBER_SET_SUPERVISOR", member -> {
member.setBuild(true);
member.setSupervisor(true);
});
}
private void setPerms(Chatter owner, SteamwarUser user, String name, String permName, Consumer<BauweltMember> setter) {
private void setPerms(Chatter owner, String world, SteamwarUser user, String name, String permName, Consumer<BauweltMember> setter) {
if (user == null) {
owner.system("BAU_MEMBER_SET_USAGE", name);
return;
}
withMember(owner, user, target -> {
withMembers(owner, world, user, target -> {
setter.accept(target);
Bauserver bauserver = Bauserver.get(owner.user().getUUID());
Bauserver bauserver = Bauserver.getByWorld(target.getWorldID());
if (bauserver != null) {
bauserver.getRegisteredServer().getPlayersConnected().stream().findAny().ifPresent(player -> NetworkSender.send(player, new BaumemberUpdatePacket()));
}
@@ -171,11 +176,11 @@ public class BauCommand extends SWCommand {
}
@Register(value = "delmember", description = "BAU_DELMEMBER_USAGE")
public void delmember(Chatter owner, @Mapper("addedUsers") SteamwarUser user) {
withMember(owner, user, target -> {
public void delmember(Chatter owner, @Mapper("bauWorldSelector") String world, @Mapper("addedUsers") SteamwarUser user) {
withMembers(owner, world, user, target -> {
target.remove();
Bauserver bauserver = Bauserver.get(owner.user().getUUID());
Bauserver bauserver = Bauserver.getByWorld(target.getWorldID());
Chatter member = Chatter.of(user.getUUID());
member.withPlayer(player -> {
if (bauserver != null && bauserver.getRegisteredServer().getPlayersConnected().contains(player)) {
@@ -192,6 +197,29 @@ public class BauCommand extends SWCommand {
});
}
@Mapper(value = "bauWorldSelector", local = true)
public TypeMapper<String> bauWorldSelector() {
return new TypeMapper<>() {
@Override
public String map(Chatter sender, PreviousArguments previousArguments, String s) {
if ("all".equalsIgnoreCase(s)) return "all";
return SteamwarWorld.getBauWorlds(sender.user()).stream()
.map(SteamwarWorld::getName)
.filter(name -> name.equalsIgnoreCase(s))
.findFirst()
.orElse(null);
}
@Override
public Collection<String> tabCompletes(Chatter sender, PreviousArguments previousArguments, String s) {
List<String> worlds = new ArrayList<>();
worlds.add("all");
SteamwarWorld.getBauWorlds(sender.user()).stream().map(SteamwarWorld::getName).forEach(worlds::add);
return worlds;
}
};
}
@Mapper(value = "addedUsers", local = true)
public TypeMapper<SteamwarUser> addedUsers() {
return new TypeMapper<SteamwarUser>() {
@@ -202,8 +230,10 @@ public class BauCommand extends SWCommand {
@Override
public Collection<String> tabCompletes(Chatter sender, PreviousArguments previousArguments, String s) {
return BauweltMember.getMembers(sender.user().getId()).stream()
return selectedWorlds(sender, previousArguments.userArgs.length == 0 ? "all" : previousArguments.userArgs[previousArguments.userArgs.length - 1]).stream()
.flatMap(world -> BauweltMember.getWorldMembers(world.getUuid()).stream())
.map(bauweltMember -> SteamwarUser.byId(bauweltMember.getMemberID()).getUserName())
.distinct()
.toList();
}
};
@@ -213,9 +243,11 @@ public class BauCommand extends SWCommand {
public void stop(PlayerChatter sender) {
VelocityCore.schedule(() -> {
sender.system("BAU_STOPPING");
Bauserver subserver = Bauserver.get(sender.user().getUUID());
if (subserver != null) {
subserver.stop();
for (SteamwarWorld world : SteamwarWorld.getBauWorlds(sender.user())) {
Bauserver subserver = Bauserver.getByWorld(world.getUuid());
if (subserver != null) {
subserver.stop();
}
}
sender.system("BAU_STOPPED");
@@ -224,25 +256,50 @@ public class BauCommand extends SWCommand {
@Register("resetall")
@Register("delete")
public void delete(PlayerChatter sender, ServerVersion version) {
SWInventory inventory = new SWInventory(sender, 9, new Message("BAU_DELETE_GUI_NAME"));
inventory.addItem(0, new SWItem("LIME_DYE", new Message("BAU_DELETE_GUI_DELETE")), click -> {
String world = version.getWorldFolder(ServerStarter.WORLDS_BASE_PATH) + sender.user().getId();
public void delete(PlayerChatter sender, @Mapper("ownBauWorld") SteamwarWorld world) {
openDeleteWorld(sender, world);
}
VelocityCore.schedule(() -> {
Bauserver subserver = Bauserver.get(sender.user().getUUID());
if (subserver != null) {
subserver.stop();
}
@Register("worlds")
@Register("world")
public void worlds(PlayerChatter sender) {
openWorldList(sender);
}
SubserverSystem.deleteFolder(VelocityCore.local, world);
sender.system("BAU_DELETE_DELETED");
}).schedule();
@Register(value = "world create", description = "BAU_WORLD_CREATE_USAGE")
public void createWorld(PlayerChatter sender, String name, @ErrorMessage("BAU_WORLD_VERSION") ServerVersion version, @Mapper("templateWorld") SteamwarWorld template) {
SteamwarWorld world = createPersonalWorld(sender, name, version, template);
if (world != null) {
sender.system("BAU_WORLD_CREATED");
}
}
inventory.close();
});
inventory.addItem(8, new SWItem("RED_DYE", new Message("BAU_DELETE_GUI_CANCEL")), click -> inventory.close());
inventory.open();
@Register(value = "world start", description = "BAU_WORLD_START_USAGE")
public void startWorld(PlayerChatter sender, @Mapper("ownBauWorld") SteamwarWorld world) {
new ServerStarter().build(ServerVersion.get(world.getVersion()), world).send(sender.getPlayer()).start();
}
@Register(value = "world rename", description = "BAU_WORLD_RENAME_USAGE")
public void renameWorld(Chatter sender, @Mapper("ownBauWorld") SteamwarWorld world, String newName) {
if (hasTooManyPersonalWorlds(sender)) return;
if (SteamwarWorld.getBauWorld(sender.user(), newName, world.getVersion()) != null) {
sender.system("BAU_WORLD_EXISTS");
return;
}
world.rename(newName);
sender.system("BAU_WORLD_RENAMED");
}
@Register(value = "world delete", description = "BAU_WORLD_DELETE_USAGE")
public void deleteWorld(PlayerChatter sender, @Mapper("ownBauWorld") SteamwarWorld world) {
openDeleteWorld(sender, world);
}
@Register(value = "world upgrade", description = "BAU_WORLD_UPGRADE_USAGE")
public void upgradeWorld(Chatter sender, @Mapper("ownBauWorld") SteamwarWorld world, @ErrorMessage("BAU_WORLD_VERSION") ServerVersion version) {
if (hasTooManyPersonalWorlds(sender)) return;
world.changeVersion(version.getVersionSuffix());
sender.system("BAU_WORLD_UPGRADED");
}
@Register("test")
@@ -254,27 +311,244 @@ public class BauCommand extends SWCommand {
}
@Register(value = "lock", description = "BAU_LOCKED_OPTIONS")
public void lock(Chatter sender, BauLockState bauLockState) {
BauLock.setLocked(sender, bauLockState);
public void lock(Chatter sender, @Mapper("ownBauWorld") SteamwarWorld world, BauLockState bauLockState) {
BauLock.setLocked(sender, world, bauLockState);
}
@Register("unlock")
public void unlock(Chatter sender) {
BauLock.setLocked(sender, BauLockState.OPEN);
public void unlock(Chatter sender, @Mapper("ownBauWorld") SteamwarWorld world) {
BauLock.setLocked(sender, world, BauLockState.OPEN);
}
private static void withMember(Chatter owner, SteamwarUser member, Consumer<BauweltMember> function) {
private static void withMembers(Chatter owner, String worldSelector, SteamwarUser member, Consumer<BauweltMember> function) {
if (member == null) {
owner.system("UNKNOWN_PLAYER");
return;
}
BauweltMember target = BauweltMember.getBauMember(owner.user().getId(), member.getId());
if (target == null) {
owner.system("BAU_MEMBER_NOMEMBER");
List<SteamwarWorld> worlds = selectedWorlds(owner, worldSelector);
if (worlds.isEmpty()) {
owner.system("BAU_WORLD_UNKNOWN");
return;
}
function.accept(target);
boolean found = false;
for (SteamwarWorld world : worlds) {
BauweltMember target = BauweltMember.getBauMember(world.getUuid(), member.getId());
if (target == null) continue;
found = true;
function.accept(target);
}
if (!found) {
owner.system("BAU_MEMBER_NOMEMBER");
}
}
@Mapper(value = "ownBauWorld", local = true)
private TypeMapper<SteamwarWorld> ownBauWorldMapper() {
return new TypeMapper<SteamwarWorld>() {
@Override
public SteamwarWorld map(Chatter sender, PreviousArguments previousArguments, String s) {
if (s == null || s.isEmpty()) return defaultPersonalWorld(sender);
return SteamwarWorld.getBauWorlds(sender.user()).stream()
.filter(world -> world.getName().equalsIgnoreCase(s) || world.getUuid().toString().equalsIgnoreCase(s))
.findFirst()
.orElse(null);
}
@Override
public Collection<String> tabCompletes(Chatter sender, PreviousArguments previousArguments, String s) {
return SteamwarWorld.getBauWorlds(sender.user()).stream().map(SteamwarWorld::getName).toList();
}
};
}
@Mapper(value = "targetBauWorld", local = true)
private TypeMapper<SteamwarWorld> targetBauWorldMapper() {
return new TypeMapper<>() {
@Override
public SteamwarWorld map(Chatter sender, PreviousArguments previousArguments, String s) {
SteamwarUser owner = previousArguments.getAll(SteamwarUser.class).stream().findFirst().orElse(null);
if (owner == null) return null;
if (s == null || s.isEmpty()) return defaultPersonalWorld(owner);
return SteamwarWorld.getBauWorlds(owner).stream()
.filter(world -> world.getName().equalsIgnoreCase(s) || world.getUuid().toString().equalsIgnoreCase(s))
.findFirst()
.orElse(null);
}
@Override
public Collection<String> tabCompletes(Chatter sender, PreviousArguments previousArguments, String s) {
SteamwarUser owner = previousArguments.getAll(SteamwarUser.class).stream().findFirst().orElse(null);
if (owner == null) return List.of();
return SteamwarWorld.getBauWorlds(owner).stream().map(SteamwarWorld::getName).toList();
}
};
}
@Mapper(value = "templateWorld", local = true)
private TypeMapper<SteamwarWorld> templateWorldMapper() {
return new TypeMapper<>() {
@Override
public SteamwarWorld map(Chatter sender, PreviousArguments previousArguments, String s) {
ServerVersion version = previousArguments.getAll(ServerVersion.class).stream().findFirst().orElse(null);
if (version == null) return null;
return SteamwarWorld.getTemplateWorld(s, version.getVersionSuffix());
}
@Override
public Collection<String> tabCompletes(Chatter sender, PreviousArguments previousArguments, String s) {
ServerVersion version = previousArguments.getAll(ServerVersion.class).stream().findFirst().orElse(null);
if (version == null) return List.of();
return SteamwarWorld.getTemplateWorlds(version.getVersionSuffix()).stream().map(SteamwarWorld::getName).toList();
}
};
}
private void openWorldList(PlayerChatter sender) {
List<SWListInv.SWListEntry<SteamwarWorld>> entries = new ArrayList<>();
for (SteamwarWorld world : SteamwarWorld.getBauWorlds(sender.user())) {
SWItem item = new SWItem("GRASS_BLOCK", new Message("PLAIN_STRING", "§e" + world.getName()))
.addLore(new Message("PLAIN_STRING", "§7Version: §e" + world.getVersion()))
.addLore(new Message("PLAIN_STRING", "§7Left: start, right: manage"));
entries.add(new SWListInv.SWListEntry<>(item, world));
}
SWListInv<SteamwarWorld> inventory = new SWListInv<>(sender, new Message("BAU_WORLD_GUI_TITLE"), entries, (click, world) -> {
if (click.isRightClick()) {
openWorldManagement(sender, world);
} else {
new ServerStarter().build(ServerVersion.get(world.getVersion()), world).send(sender.getPlayer()).start();
}
});
inventory.open();
}
private void openWorldManagement(PlayerChatter sender, SteamwarWorld world) {
SWInventory inventory = new SWInventory(sender, 9, new Message("PLAIN_STRING", "§e" + world.getName()));
inventory.addItem(0, new SWItem("ENDER_PEARL", new Message("PLAIN_STRING", "§aStart")), click -> {
inventory.close();
new ServerStarter().build(ServerVersion.get(world.getVersion()), world).send(sender.getPlayer()).start();
});
inventory.addItem(3, new SWItem("EXPERIENCE_BOTTLE", new Message("PLAIN_STRING", "§eUpgrade to latest")), click -> {
if (hasTooManyPersonalWorlds(sender)) {
inventory.close();
return;
}
world.changeVersion(ServerVersion.PAPER_21.getVersionSuffix());
sender.system("BAU_WORLD_UPGRADED");
inventory.close();
});
inventory.addItem(8, new SWItem("BARRIER", new Message("PLAIN_STRING", "§cDelete")), click -> openDeleteWorld(sender, world));
inventory.open();
}
private void openDeleteWorld(PlayerChatter sender, SteamwarWorld world) {
SWInventory inventory = new SWInventory(sender, 9, new Message("BAU_DELETE_GUI_NAME"));
inventory.addItem(0, new SWItem("LIME_DYE", new Message("BAU_DELETE_GUI_DELETE")), click -> {
VelocityCore.schedule(() -> {
Bauserver subserver = Bauserver.getByWorld(world.getUuid());
if (subserver != null) {
subserver.stop();
}
world.markDeleted();
SubserverSystem.deleteFolder(VelocityCore.local, world.getStorageDirectory().getPath());
sender.system("BAU_DELETE_DELETED");
}).schedule();
inventory.close();
});
inventory.addItem(8, new SWItem("RED_DYE", new Message("BAU_DELETE_GUI_CANCEL")), click -> inventory.close());
inventory.open();
}
private void startPersonalWorld(PlayerChatter sender, SteamwarWorld world) {
if (world == null) {
world = defaultPersonalWorld(sender);
}
if (world == null) {
sender.system("BAU_WORLD_UNKNOWN");
return;
}
new ServerStarter().build(ServerVersion.get(world.getVersion()), world).send(sender.getPlayer()).start();
}
private static SteamwarWorld defaultPersonalWorld(Chatter sender) {
return defaultPersonalWorld(sender.user());
}
private static SteamwarWorld defaultPersonalWorld(SteamwarUser user) {
return SteamwarWorld.getBauWorlds(user).stream().findFirst().orElse(null);
}
private static List<SteamwarWorld> selectedWorlds(Chatter sender, String selector) {
List<SteamwarWorld> worlds = SteamwarWorld.getBauWorlds(sender.user());
if ("all".equalsIgnoreCase(selector)) {
return worlds;
}
String normalized = selector.toLowerCase(Locale.ROOT);
return worlds.stream()
.filter(world -> world.getName().toLowerCase(Locale.ROOT).equals(normalized))
.toList();
}
private static boolean forEachSelectedWorld(Chatter sender, String selector, Consumer<SteamwarWorld> consumer) {
List<SteamwarWorld> worlds = selectedWorlds(sender, selector);
if (worlds.isEmpty()) {
sender.system("BAU_WORLD_UNKNOWN");
return false;
}
worlds.forEach(consumer);
return true;
}
private SteamwarWorld createPersonalWorld(Chatter sender, String name, ServerVersion version, SteamwarWorld template) {
if (template == null || template.getVersion() != version.getVersionSuffix()) {
sender.system("BAU_WORLD_UNKNOWN");
return null;
}
if (sender.user().hasPerm(UserPerm.BUILD_UNLIMITED_WORLDS)) {
if (SteamwarWorld.getBauWorld(sender.user(), name, version.getVersionSuffix()) != null) {
sender.system("BAU_WORLD_EXISTS");
return null;
}
return SteamwarWorld.getOrCreateBauWorld(sender.user(), name, version.getVersionSuffix(), template.getStorageDirectory());
}
long limit = personalWorldLimit(sender);
long count = SteamwarWorld.countBauWorlds(sender.user());
if (count > limit) {
sender.system("BAU_WORLD_OVER_LIMIT_DELETE", limit);
return null;
}
if (count >= limit) {
sender.system("BAU_WORLD_LIMIT", limit);
return null;
}
if (SteamwarWorld.getBauWorld(sender.user(), name, version.getVersionSuffix()) != null) {
sender.system("BAU_WORLD_EXISTS");
return null;
}
return SteamwarWorld.getOrCreateBauWorld(sender.user(), name, version.getVersionSuffix(), template.getStorageDirectory());
}
private boolean hasTooManyPersonalWorlds(Chatter sender) {
if (sender.user().hasPerm(UserPerm.BUILD_UNLIMITED_WORLDS)) {
return false;
}
long limit = personalWorldLimit(sender);
if (SteamwarWorld.countBauWorlds(sender.user()) <= limit) {
return false;
}
sender.system("BAU_WORLD_OVER_LIMIT_DELETE", limit);
return true;
}
private long personalWorldLimit(Chatter sender) {
return sender.user().hasPerm(UserPerm.BUILD_EXTRA_WORLDS) ? SteamwarWorld.EXTRA_BAU_WORLD_LIMIT : SteamwarWorld.DEFAULT_BAU_WORLD_LIMIT;
}
}
@@ -26,6 +26,7 @@ import de.steamwar.linkage.Linked;
import de.steamwar.messages.Chatter;
import de.steamwar.messages.PlayerChatter;
import de.steamwar.sql.GameModeConfig;
import de.steamwar.sql.SteamwarWorld;
import de.steamwar.sql.UserPerm;
import de.steamwar.velocitycore.ArenaMode;
import de.steamwar.velocitycore.ServerStarter;
@@ -33,11 +34,11 @@ import de.steamwar.velocitycore.ServerVersion;
import de.steamwar.velocitycore.VelocityCore;
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.LinkedHashSet;
import java.util.Set;
@Linked
public class BuilderCloudCommand extends SWCommand {
@@ -47,14 +48,23 @@ public class BuilderCloudCommand extends SWCommand {
}
@Register(value = "create", description = "BUILDERCLOUD_CREATE_USAGE")
public void create(Chatter sender, @ErrorMessage("BUILDERCLOUD_VERSION") ServerVersion version, @Mapper("map") String map, @OptionalValue("") @Mapper("generator") @AllowNull File generator) {
mapFile(version, map).mkdir();
sender.withPlayer(p -> new ServerStarter().builder(version, map, generator).send(p).start());
public void create(Chatter sender, @ErrorMessage("BUILDERCLOUD_VERSION") ServerVersion version, @Mapper("map") String map, @Mapper("templateWorld") SteamwarWorld template) {
if(mapExists(version, map)) {
sender.system("BUILDERCLOUD_EXISTING_MAP");
return;
}
if (template == null || template.getVersion() != version.getVersionSuffix()) {
sender.system("BAU_WORLD_UNKNOWN");
return;
}
SteamwarWorld.getOrCreateBuilderWorld(map, version.getVersionSuffix(), template.getStorageDirectory());
sender.withPlayer(p -> new ServerStarter().builder(version, map, template.getStorageDirectory()).send(p).start());
}
@Register(description = "BUILDERCLOUD_USAGE")
public void start(PlayerChatter sender, @ErrorMessage("BUILDERCLOUD_VERSION") ServerVersion version, @Mapper("map") String map) {
if (!mapFile(version, map).exists()) {
if (!mapExists(version, map)) {
sender.system("BUILDERCLOUD_UNKNOWN_MAP");
return;
}
@@ -64,36 +74,34 @@ public class BuilderCloudCommand extends SWCommand {
@Register(value = "rename", description = "BUILDERCLOUD_RENAME_USAGE")
public void rename(Chatter sender, @ErrorMessage("BUILDERCLOUD_VERSION") ServerVersion version, @Mapper("map") String oldName, String newName) {
File oldMap = mapFile(version, oldName);
if (!oldMap.exists()) {
SteamwarWorld oldMap = builderWorld(version, oldName);
if (oldMap == null) {
sender.system("BUILDERCLOUD_UNKNOWN_MAP");
return;
}
File newMap = mapFile(version, newName);
if (newMap.exists()) {
if (mapExists(version, newName)) {
sender.system("BUILDERCLOUD_EXISTING_MAP");
return;
}
try {
Files.move(oldMap.toPath(), newMap.toPath());
} catch (IOException e) {
throw new SecurityException(e);
}
oldMap.rename(newName);
sender.system("BUILDERCLOUD_RENAMED");
}
@Register(value = "deploy", description = "BUILDERCLOUD_DEPLOY_USAGE")
public void deploy(Chatter sender, @Mapper("nonHistoricArenaMode") GameModeConfig<String, String> arenaMode, @ErrorMessage("BUILDERCLOUD_VERSION") ServerVersion version, @Mapper("map") String map) {
if (!mapFile(version, map).exists()) {
SteamwarWorld builderWorld = builderWorld(version, map);
if (builderWorld == null) {
sender.system("BUILDERCLOUD_UNKNOWN_MAP");
return;
}
VelocityCore.schedule(() -> {
VelocityCore.local.execute("deployarena.py", arenaMode.configFile.getName(), Integer.toString(version.getVersionSuffix()), map);
String modeName = arenaMode.configFile.getName().replace(".yml", "");
SteamwarWorld arenaWorld = SteamwarWorld.getOrCreateArenaWorld(modeName, map, version.getVersionSuffix());
VelocityCore.local.execute("deployarena.py", arenaMode.configFile.getName(), Integer.toString(version.getVersionSuffix()), map, builderWorld.setupAndGetStoragePath(), arenaWorld.setupAndGetStoragePath());
ArenaMode.init();
sender.system("BUILDERCLOUD_DEPLOY_FINISHED");
}).schedule();
@@ -111,60 +119,78 @@ public class BuilderCloudCommand extends SWCommand {
@Override
public Collection<String> tabCompletes(Chatter sender, PreviousArguments previousArguments, String s) {
File folder = getWorldFolder(previousArguments, 1);
String[] files;
if (folder == null || (files = folder.list()) == null) {
ServerVersion version = getVersion(previousArguments, 1);
if (version == null) {
return Collections.emptyList();
}
return Arrays.stream(files).filter(file -> new File(folder, file).isDirectory()).filter(file -> s.startsWith(".") || !file.startsWith(".")).toList();
Set<String> maps = new LinkedHashSet<>();
SteamwarWorld.getBuilderWorlds(version.getVersionSuffix()).stream()
.map(SteamwarWorld::getName)
.forEach(maps::add);
File legacyFolder = new File(version.getWorldFolder(ServerStarter.LEGACY_BUILDER_BASE_PATH));
String[] files = legacyFolder.list();
if(files != null)
Arrays.stream(files).filter(file -> new File(legacyFolder, file).isDirectory()).forEach(maps::add);
return maps.stream()
.filter(file -> s.startsWith(".") || !file.startsWith("."))
.toList();
}
};
}
@Cached(global = true)
@Mapper(value = "generator", local = true)
private TypeMapper<File> generatorTypeMapper() {
@Mapper(value = "templateWorld", local = true)
private TypeMapper<SteamwarWorld> templateWorldMapper() {
return new TypeMapper<File>() {
return new TypeMapper<>() {
@Override
public File map(Chatter sender, PreviousArguments previousArguments, String s) {
if (s.isEmpty()) return null;
File folder = getWorldFolder(previousArguments, 2);
if (folder == null) throw new SecurityException();
File generator = new File(folder, s + ".dat");
if (!generator.exists() || !generator.isFile()) {
throw new SecurityException();
}
return generator;
public SteamwarWorld map(Chatter sender, PreviousArguments previousArguments, String s) {
ServerVersion version = previousArguments.getAll(ServerVersion.class).stream().findFirst().orElse(null);
if (version == null) return null;
return SteamwarWorld.getTemplateWorld(s, version.getVersionSuffix());
}
@Override
public Collection<String> tabCompletes(Chatter sender, PreviousArguments previousArguments, String s) {
File folder = getWorldFolder(previousArguments, 2);
String[] files;
if (folder == null || (files = folder.list()) == null) {
ServerVersion version = previousArguments.getAll(ServerVersion.class).stream().findFirst().orElse(null);
if (version == null) {
return Collections.emptyList();
}
return Arrays.stream(files).filter(file -> new File(folder, file).isFile()).filter(file -> file.endsWith(".dat")).map(file -> file.substring(0, file.length() - 4)).toList();
return SteamwarWorld.getTemplateWorlds(version.getVersionSuffix()).stream()
.map(SteamwarWorld::getName)
.toList();
}
};
}
private File mapFile(ServerVersion version, String map) {
return new File(version.getWorldFolder(ServerStarter.BUILDER_BASE_PATH), map);
private SteamwarWorld builderWorld(ServerVersion version, String map) {
SteamwarWorld world = SteamwarWorld.getBuilderWorld(map, version.getVersionSuffix());
if(world != null)
return world;
File legacyWorld = mapFile(version, map);
if(!legacyWorld.exists())
return null;
return SteamwarWorld.getOrCreateBuilderWorld(map, version.getVersionSuffix(), legacyWorld);
}
private File getWorldFolder(PreviousArguments previousArguments, int offset) {
ServerVersion v = ServerVersion.get(previousArguments.userArgs[previousArguments.userArgs.length - offset]);
if (v == null) return null;
return new File(v.getWorldFolder(ServerStarter.BUILDER_BASE_PATH));
private boolean mapExists(ServerVersion version, String map) {
return SteamwarWorld.getBuilderWorld(map, version.getVersionSuffix()) != null || mapFile(version, map).exists();
}
private File mapFile(ServerVersion version, String map) {
return new File(version.getWorldFolder(ServerStarter.LEGACY_BUILDER_BASE_PATH), map);
}
private ServerVersion getVersion(PreviousArguments previousArguments, int offset) {
if (previousArguments.userArgs.length < offset) {
return null;
}
return ServerVersion.get(previousArguments.userArgs[previousArguments.userArgs.length - offset]);
}
}
@@ -307,6 +307,13 @@ public class CheckCommand extends SWCommand {
return;
}
// Remove any added players from the schematic in the folder
for (SchematicNode schematicNode : SchematicNode.getSchematicNodeInNode(teamFolder.getNodeId())) {
for (NodeMember nodeMember : NodeMember.getNodeMembers(schematicNode.getNodeId())) {
nodeMember.delete();
}
}
// Copy Schem into team folder of -1 user
String name = DateTimeFormatter.ofPattern("yyyy.MM.dd_HH:mm:ss").format(schematic.getLastUpdate().toLocalDateTime());
NodeData data = NodeData.getLatest(schematic);
@@ -316,14 +323,8 @@ public class CheckCommand extends SWCommand {
// Accept the team folder schematic and set other to Normal as well as adding the original owner on the schematic
node.setSchemtype(GameModeConfig.getBySchematicType(schematic.getSchemtype()).Schematic.Type);
NodeMember.createNodeMember(node.getNodeId(), schematic.getOwner());
// Remove any added players from the schematic in the folder
for (SchematicNode schematicNode : SchematicNode.getSchematicNodeInNode(teamFolder.getNodeId())) {
if (schematicNode.getNodeId() == node.getNodeId()) continue;
for (NodeMember nodeMember : NodeMember.getNodeMembers(schematicNode.getNodeId())) {
NodeMember.createNodeMember(node.getNodeId(), nodeMember.getMember());
nodeMember.delete();
}
for (NodeMember nodeMember : NodeMember.getNodeMembers(schematic.getId())) {
NodeMember.createNodeMember(node.getNodeId(), nodeMember.getMember());
}
// Conclude by setting send in schematic to normal and broadcast
@@ -24,11 +24,16 @@ import de.steamwar.linkage.Linked;
import de.steamwar.messages.Chatter;
import de.steamwar.messages.PlayerChatter;
import de.steamwar.sql.SteamwarUser;
import de.steamwar.sql.SteamwarWorld;
import de.steamwar.sql.UserPerm;
import de.steamwar.sql.internal.Statement;
import de.steamwar.velocitycore.ServerStarter;
import de.steamwar.velocitycore.ServerVersion;
import de.steamwar.velocitycore.VelocityCore;
import java.io.*;
import java.util.LinkedHashSet;
import java.util.Set;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;
@@ -64,12 +69,21 @@ public class GDPRQuery extends SWCommand {
copy(getClass().getClassLoader().getResourceAsStream("GDPRQueryREADME.md"), out, "README.txt");
sender.system("GDPR_STATUS_WORLD");
copyBauwelt(user, out, "/home/minecraft/userworlds/" + user.getUUID().toString(), "BuildWorld12");
copyBauwelt(user, out, "/home/minecraft/userworlds15/" + user.getId(), "BuildWorld15");
Set<Integer> exportedVersions = new LinkedHashSet<>();
for(ServerVersion version : ServerVersion.values()) {
int versionSuffix = version.getVersionSuffix();
if(!exportedVersions.add(versionSuffix))
continue;
SteamwarWorld world = SteamwarWorld.getBauWorld(user, versionSuffix);
if(world != null)
copyBauwelt(user, out, world.setupAndGetStoragePath(), "BuildWorld" + versionSuffix);
else
copyLegacyBauwelt(user, out, version);
}
sender.system("GDPR_STATUS_INVENTORIES");
copyPlayerdata(user, out, "/home/minecraft/userworlds", "BuildInventories12");
copyPlayerdata(user, out, "/home/minecraft/userworlds15", "BuildInventories15");
copyPlayerdata(user, out, SteamwarWorld.WORLD_STORAGE, "BuildInventories");
sender.system("GDPR_STATUS_DATABASE");
sqlCSV(user, out, bannedIPs, "BannedIPs.csv");
@@ -99,8 +113,8 @@ public class GDPRQuery extends SWCommand {
}
private static final Statement bannedIPs = new Statement("SELECT Timestamp, IP FROM BannedUserIPs WHERE UserID = ?");
private static final Statement bauweltMember = new Statement("SELECT BauweltID AS Bauwelt, WorldEdit, World FROM BauweltMember WHERE MemberID = ?");
private static final Statement bauweltMembers = new Statement("SELECT u.UserName AS 'User', m.WorldEdit AS WorldEdit, m.World AS World FROM BauweltMember m INNER JOIN UserData u ON m.MemberID = u.id WHERE m.BauweltID = ?");
private static final Statement bauweltMember = new Statement("SELECT WorldID AS Bauwelt, WorldEdit, World FROM BauweltMember WHERE MemberID = ?");
private static final Statement bauweltMembers = new Statement("SELECT w.Name AS World, u.UserName AS 'User', m.WorldEdit AS WorldEdit, m.World AS World FROM BauweltMember m INNER JOIN UserData u ON m.MemberID = u.id INNER JOIN world w ON m.WorldID = w.id WHERE w.Owner = ?");
private static final Statement checkedSchems = new Statement("SELECT NodeName AS Schematic, StartTime, EndTime, DeclineReason AS Result FROM CheckedSchematic WHERE NodeOwner = ? ORDER BY StartTime ASC");
private static final Statement fights = new Statement("SELECT p.Team AS Team, p.Kit AS Kit, p.Kills AS Kills, p.IsOut AS Died, f.GameMode AS GameMode, f.Server AS Server, f.StartTime AS StartTime, f.Duration AS Duration, (f.BlueLeader = p.UserID) AS IsBlueLeader, (f.RedLeader = p.UserID) AS IsRedLeader, f.Win AS Winner, f.WinCondition AS WinCondition FROM Fight f INNER JOIN FightPlayer p ON f.FightID = p.FightID WHERE p.UserID = ? ORDER BY StartTime ASC");
private static final Statement ignoredPlayers = new Statement("SELECT u.UserName AS IgnoredPlayer FROM IgnoredPlayers i INNER JOIN UserData u ON i.Ignored = u.id WHERE Ignorer = ?");
@@ -230,6 +244,18 @@ public class GDPRQuery extends SWCommand {
}
}
private void copyLegacyBauwelt(SteamwarUser user, ZipOutputStream out, ServerVersion version) throws IOException {
File legacyIdWorld = new File(version.getWorldFolder(ServerStarter.LEGACY_WORLDS_BASE_PATH), String.valueOf(user.getId()));
if(legacyIdWorld.exists()) {
copyBauwelt(user, out, legacyIdWorld.getPath(), "BuildWorld" + version.getVersionSuffix());
return;
}
File legacyUuidWorld = new File(version.getWorldFolder(ServerStarter.LEGACY_WORLDS_BASE_PATH), user.getUUID().toString());
if(legacyUuidWorld.exists())
copyBauwelt(user, out, legacyUuidWorld.getPath(), "BuildWorld" + version.getVersionSuffix());
}
private void copyPlayerdata(SteamwarUser user, ZipOutputStream out, String inDir, String outDir) throws IOException {
File worlds = new File(inDir);
String path = "playerdata/" + user.getUUID().toString() + ".dat";
@@ -42,6 +42,13 @@ public class ReplayCommand extends SWCommand {
super("replay");
}
@Register
public void genericCommand(PlayerChatter sender) {
sender.system("REPLAY_UNAVAILABLE");
return;
}
/*
@Register
public void genericCommand(PlayerChatter sender, int replayId, @StaticValue(value = {"", "-a"}, allowISE = true) @OptionalValue("") boolean isAdmin, @OptionalValue("") String optionalMap) {
Fight fight = Fight.getById(replayId);
@@ -109,4 +116,5 @@ public class ReplayCommand extends SWCommand {
private Message parseLeader(SteamwarUser leader, int players, boolean winner) {
return new Message("REPLAY_" + (players > 1 ? "" : "SOLO_") + (winner ? "WINNER" : "LOSER"), leader.getUserName(), players - 1);
}
*/
}
@@ -30,6 +30,7 @@ import de.steamwar.messages.PlayerChatter;
import de.steamwar.sql.*;
import de.steamwar.velocitycore.VelocityCore;
import de.steamwar.velocitycore.discord.DiscordBot;
import de.steamwar.velocitycore.inventory.SWInventory;
import de.steamwar.velocitycore.inventory.SWItem;
import de.steamwar.velocitycore.inventory.SWListInv;
import de.steamwar.velocitycore.util.SteamwarPrefix;
@@ -437,6 +438,117 @@ public class TeamCommand extends SWCommand {
}
}
@Register("worlds")
@Register("world")
public void worlds(@Validator("isInTeam") PlayerChatter sender) {
List<SWListInv.SWListEntry<SteamwarWorld>> entries = SteamwarWorld.getTeamWorlds(Team.byId(sender.user().getTeam())).stream()
.map(world -> new SWListInv.SWListEntry<>(
new SWItem("GRASS_BLOCK", new Message("PLAIN_STRING", "§e" + world.getName()))
.addLore(new Message("PLAIN_STRING", "§7Version: §e" + world.getVersion()))
.addLore(new Message("PLAIN_STRING", "§7Left: start, right: manage")),
world
))
.toList();
SWListInv<SteamwarWorld> inv = new SWListInv<>(sender, new Message("TEAM_WORLD_GUI_TITLE"), entries, (click, world) -> {
if (click.isRightClick() && sender.user().isLeader()) {
openTeamWorldManagement(sender, world);
} else {
startTeamWorld(sender, world);
}
});
inv.open();
}
@Register("world")
public void world(@Validator("isInTeam") PlayerChatter sender, @Mapper("teamWorld") SteamwarWorld world) {
startTeamWorld(sender, world);
}
@Register(value = "world create", description = "TEAM_WORLD_CREATE_USAGE")
public void createWorld(@Validator("isLeader") Chatter sender, String name, @ErrorMessage("BAU_WORLD_VERSION") ServerVersion version, @Mapper("templateWorld") SteamwarWorld template) {
Team team = Team.byId(sender.user().getTeam());
if (template == null || template.getVersion() != version.getVersionSuffix()) {
sender.system("BAU_WORLD_UNKNOWN");
return;
}
if (SteamwarWorld.getTeamWorld(team, name, version.getVersionSuffix()) != null) {
sender.system("TEAM_WORLD_EXISTS");
return;
}
long count = SteamwarWorld.countTeamWorlds(team);
if (count > SteamwarWorld.TEAM_WORLD_LIMIT) {
sender.system("TEAM_WORLD_OVER_LIMIT_DELETE", SteamwarWorld.TEAM_WORLD_LIMIT);
return;
}
if (count >= SteamwarWorld.TEAM_WORLD_LIMIT) {
sender.system("TEAM_WORLD_LIMIT", SteamwarWorld.TEAM_WORLD_LIMIT);
return;
}
SteamwarWorld.getOrCreateTeamWorld(team, name, version.getVersionSuffix(), template.getStorageDirectory());
sender.system("TEAM_WORLD_CREATED");
}
@Register(value = "world rename", description = "TEAM_WORLD_RENAME_USAGE")
public void renameWorld(@Validator("isLeader") Chatter sender, @Mapper("teamWorld") SteamwarWorld world, String newName) {
Team team = Team.byId(sender.user().getTeam());
if (hasTooManyTeamWorlds(sender, team)) return;
if (SteamwarWorld.getTeamWorld(team, newName, world.getVersion()) != null) {
sender.system("TEAM_WORLD_EXISTS");
return;
}
world.rename(newName);
sender.system("TEAM_WORLD_RENAMED");
}
@Register(value = "world upgrade", description = "TEAM_WORLD_UPGRADE_USAGE")
public void upgradeWorld(@Validator("isLeader") Chatter sender, @Mapper("teamWorld") SteamwarWorld world, @ErrorMessage("BAU_WORLD_VERSION") ServerVersion version) {
if (hasTooManyTeamWorlds(sender, Team.byId(sender.user().getTeam()))) return;
world.changeVersion(version.getVersionSuffix());
sender.system("TEAM_WORLD_UPGRADED");
}
@Register(value = "world delete", description = "TEAM_WORLD_DELETE_USAGE")
public void deleteWorld(@Validator("isLeader") PlayerChatter sender, @Mapper("teamWorld") SteamwarWorld world) {
SWInventory inventory = new SWInventory(sender, 9, new Message("BAU_DELETE_GUI_NAME"));
inventory.addItem(0, new SWItem("LIME_DYE", new Message("BAU_DELETE_GUI_DELETE")), click -> {
VelocityCore.schedule(() -> {
Bauserver subserver = Bauserver.getByWorld(world.getUuid());
if (subserver != null) {
subserver.stop();
}
world.markDeleted();
SubserverSystem.deleteFolder(VelocityCore.local, world.getStorageDirectory().getPath());
sender.system("TEAM_WORLD_DELETED");
}).schedule();
inventory.close();
});
inventory.addItem(8, new SWItem("RED_DYE", new Message("BAU_DELETE_GUI_CANCEL")), click -> inventory.close());
inventory.open();
}
private void startTeamWorld(PlayerChatter sender, SteamwarWorld world) {
new ServerStarter().build(ServerVersion.get(world.getVersion()), world).send(sender.getPlayer()).start();
}
private void openTeamWorldManagement(PlayerChatter sender, SteamwarWorld world) {
SWInventory inventory = new SWInventory(sender, 9, new Message("PLAIN_STRING", "§e" + world.getName()));
inventory.addItem(0, new SWItem("ENDER_PEARL", new Message("PLAIN_STRING", "§aStart")), click -> {
inventory.close();
startTeamWorld(sender, world);
});
inventory.addItem(3, new SWItem("EXPERIENCE_BOTTLE", new Message("PLAIN_STRING", "§eUpgrade to latest")), click -> {
if (hasTooManyTeamWorlds(sender, Team.byId(sender.user().getTeam()))) {
inventory.close();
return;
}
world.changeVersion(ServerVersion.PAPER_21.getVersionSuffix());
sender.system("TEAM_WORLD_UPGRADED");
inventory.close();
});
inventory.addItem(8, new SWItem("BARRIER", new Message("PLAIN_STRING", "§cDelete")), click -> deleteWorld(sender, world));
inventory.open();
}
@Register("event")
public void event(@Validator("isLeader") Chatter sender, Event event) {
Team team = Team.byId(sender.user().getTeam());
@@ -585,6 +697,56 @@ public class TeamCommand extends SWCommand {
};
}
@Mapper(value = "teamWorld", local = true)
public TypeMapper<SteamwarWorld> teamWorldMapper() {
return new TypeMapper<>() {
@Override
public SteamwarWorld map(Chatter sender, PreviousArguments previousArguments, String s) {
Team team = Team.byId(sender.user().getTeam());
return SteamwarWorld.getTeamWorlds(team).stream()
.filter(world -> world.getName().equalsIgnoreCase(s) || world.getUuid().toString().equalsIgnoreCase(s))
.findFirst()
.orElse(null);
}
@Override
public Collection<String> tabCompletes(Chatter sender, PreviousArguments previousArguments, String s) {
if (sender.user().getTeam() == 0) return List.of();
return SteamwarWorld.getTeamWorlds(Team.byId(sender.user().getTeam())).stream()
.map(SteamwarWorld::getName)
.toList();
}
};
}
@Mapper(value = "templateWorld", local = true)
public TypeMapper<SteamwarWorld> templateWorldMapper() {
return new TypeMapper<>() {
@Override
public SteamwarWorld map(Chatter sender, PreviousArguments previousArguments, String s) {
ServerVersion version = previousArguments.getAll(ServerVersion.class).stream().findFirst().orElse(null);
if (version == null) return null;
return SteamwarWorld.getTemplateWorld(s, version.getVersionSuffix());
}
@Override
public Collection<String> tabCompletes(Chatter sender, PreviousArguments previousArguments, String s) {
ServerVersion version = previousArguments.getAll(ServerVersion.class).stream().findFirst().orElse(null);
if (version == null) return List.of();
return SteamwarWorld.getTemplateWorlds(version.getVersionSuffix()).stream().map(SteamwarWorld::getName).toList();
}
};
}
private boolean hasTooManyTeamWorlds(Chatter sender, Team team) {
if (SteamwarWorld.countTeamWorlds(team) <= SteamwarWorld.TEAM_WORLD_LIMIT) {
return false;
}
sender.system("TEAM_WORLD_OVER_LIMIT_DELETE", SteamwarWorld.TEAM_WORLD_LIMIT);
return true;
}
private boolean checkTeamName(Chatter sender, Team team, String arg) {
Team t = Team.get(arg);
if (t != null && t.getTeamId() != team.getTeamId()) {
@@ -120,17 +120,26 @@ public class TpCommand extends SWCommand {
}
} else if (Subserver.isBuild(subserver)) {
Bauserver bauserver = (Bauserver) subserver;
SteamwarWorld world = SteamwarWorld.getWorld(bauserver.getWorld());
if (world != null && world.getType() == WorldType.TEAM) {
if (sender.user().getTeam() != world.getTeam().getValue()) {
sender.system("JOIN_PLAYER_BLOCK");
return;
}
SubserverSystem.sendPlayer(subserver, sender.getPlayer());
return;
}
Player checker = VelocityCore.getProxy().getPlayer(bauserver.getOwner()).orElse(null);
if (checker != null && CheckCommand.isChecking(checker)) {
if (!sender.user().hasPerm(UserPerm.CHECK) && CheckCommand.getCheckingSchem(checker).getOwner() != sender.user().getId()) {
sender.system("JOIN_PLAYER_BLOCK");
return;
}
} else if (BauLock.isLocked(SteamwarUser.get(bauserver.getOwner()), sender.user())) {
} else if (world != null && BauLock.isLocked(world, sender.user())) {
sender.system("BAU_LOCKED_NOALLOWED");
Chatter.of(bauserver.getOwner()).system("BAU_LOCK_BLOCKED", sender);
return;
} else if (!bauserver.getOwner().equals(sender.user().getUUID()) && BauweltMember.getBauMember(bauserver.getOwner(), sender.user().getUUID()) == null) {
} else if (!bauserver.getOwner().equals(sender.user().getUUID()) && BauweltMember.getBauMember(bauserver.getWorld(), sender.user().getUUID()) == null) {
SubserverSystem.sendDeniedMessage(sender, bauserver.getOwner());
sender.system("JOIN_PLAYER_BLOCK");
return;
@@ -25,37 +25,39 @@ import de.steamwar.network.packets.server.BaulockUpdatePacket;
import de.steamwar.persistent.Bauserver;
import de.steamwar.sql.BauweltMember;
import de.steamwar.sql.SteamwarUser;
import de.steamwar.sql.UserConfig;
import de.steamwar.sql.SteamwarWorld;
import de.steamwar.sql.UserPerm;
import de.steamwar.sql.WorldType;
import de.steamwar.velocitycore.network.NetworkSender;
import lombok.experimental.UtilityClass;
@UtilityClass
public class BauLock {
private static final String BAU_LOCK_CONFIG_NAME = "baulockstate";
public static void setLocked(Chatter owner, BauLockState state) {
UserConfig.updatePlayerConfig(owner.user().getId(), BAU_LOCK_CONFIG_NAME, state == BauLockState.OPEN ? null : state.name());
public static void setLocked(Chatter owner, SteamwarWorld world, BauLockState state) {
world.changeLockState(state == BauLockState.OPEN ? null : state.name());
owner.system("BAU_LOCKED_" + state.name());
Bauserver bauserver = Bauserver.get(owner.user().getUUID());
Bauserver bauserver = Bauserver.getByWorld(world.getUuid());
if (bauserver != null) {
bauserver.getRegisteredServer().getPlayersConnected().stream().findAny().ifPresent(player -> NetworkSender.send(player, new BaulockUpdatePacket()));
}
}
public static boolean isLocked(SteamwarUser owner, SteamwarUser target) {
if (owner.getId() == target.getId()) return false;
public static boolean isLocked(SteamwarWorld world, SteamwarUser target) {
if (world.getType() != WorldType.BAU || world.getOwner() == null) return false;
SteamwarUser owner = SteamwarUser.byId(world.getOwner().getValue());
if (owner == null || owner.getId() == target.getId()) return false;
boolean locked;
String state = UserConfig.getConfig(owner.getId(), BAU_LOCK_CONFIG_NAME);
String state = world.getLockState();
switch (state == null ? BauLockState.OPEN : BauLockState.valueOf(state)) {
case NOBODY:
locked = true;
break;
case SUPERVISOR:
BauweltMember member = BauweltMember.getBauMember(owner.getId(), target.getId());
BauweltMember member = BauweltMember.getBauMember(world.getUuid(), target.getId());
locked = member == null || !member.isSupervisor();
break;
case SERVERTEAM: