Compare commits

...

71 Commits

Author SHA1 Message Date
D4rkr34lm 04519d4cf2 Move a lot more logic into component
Pull Request Build / Build (pull_request) Successful in 1m8s
2026-06-03 21:13:44 +02:00
D4rkr34lm 88de8c81e7 Remove old cursor
Pull Request Build / Build (pull_request) Successful in 1m22s
2026-06-03 20:48:50 +02:00
D4rkr34lm db655bf9db Refactor to player components
Pull Request Build / Build (pull_request) Successful in 1m16s
2026-06-03 20:46:51 +02:00
YoyoNow aa807060f4 Merge pull request 'fix(SpigotCore): small collection of rendering bugs in new cursor' (#413) from BauSystem/fix-small-sim-cursor-bug into main
Deploy / Build (push) Successful in 1m58s
Deploy / Deploy (push) Successful in 11s
Reviewed-on: #413
Reviewed-by: YoyoNow <4+yoyonow@noreply.localhost>
2026-06-02 21:39:13 +02:00
D4rkr34lm 9bbbab9d4b Fix collection of rendering issues
Pull Request Build / Build (pull_request) Successful in 1m8s
Backport CommonCore / Create CommonCore backport PRs (pull_request) Successful in 7s
2026-06-02 21:33:31 +02:00
YoyoNow 7cca4ada10 Merge pull request 'refactor(BauSystem): Extract sim cursor into generic curser and refactor sim cursor' (#410) from BauSystem/refactor-sim-cursor-no2 into main
Deploy / Build (push) Successful in 2m21s
Deploy / Deploy (push) Successful in 11s
Reviewed-on: #410
Reviewed-by: YoyoNow <4+yoyonow@noreply.localhost>
2026-06-02 20:22:00 +02:00
YoyoNow 725e6df047 Merge pull request 'fix(SpigotCore): versions above 1.21.6 being kicked from arena' (#412) from SpigotCore/fix-newer-version-being-kicked-from-arena into main
Deploy / Build (push) Successful in 2m52s
Deploy / Deploy (push) Successful in 11s
Reviewed-on: #412
Reviewed-by: YoyoNow <4+yoyonow@noreply.localhost>
2026-06-02 20:19:57 +02:00
D4rkr34lm f3ce124a23 Make dev velocity easier to debug and fix version issue
Backport CommonCore / Create CommonCore backport PRs (pull_request) Successful in 12s
Pull Request Build / Build (pull_request) Successful in 1m42s
2026-06-02 20:18:13 +02:00
D4rkr34lm e83f72a53e Further simplification
Pull Request Build / Build (pull_request) Successful in 1m11s
Backport CommonCore / Create CommonCore backport PRs (pull_request) Successful in 12s
2026-06-01 22:01:52 +02:00
D4rkr34lm def0c19e43 Refactor simulation cursor to use new generic cursor
Pull Request Build / Build (pull_request) Successful in 1m9s
2026-06-01 21:51:54 +02:00
D4rkr34lm 3810ccd63d Add proper onclick handleing 2026-06-01 21:27:34 +02:00
D4rkr34lm a018af1c8a Refactor to use Player components for better usability 2026-05-31 13:20:24 +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
YoyoNow c508627d92 Merge pull request 'fix(BauSystem): techhider bypass on bau by middleclick pick' (#389) from fix-387-bau-techhider-bypass-by-middleclick into main
Deploy / Build (push) Successful in 1m56s
Deploy / Deploy (push) Successful in 11s
Reviewed-on: #389
Reviewed-by: Chaoscaot <max@chaoscaot.de>
2026-05-22 10:17:19 +02:00
D4rkr34lm 93ff982649 fix techhider bypass on bau by middleclick pick
Pull Request Build / Build (pull_request) Successful in 1m6s
Backport CommonCore / Create CommonCore backport PRs (pull_request) Successful in 8s
2026-05-21 23:57:38 +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 e49cfa9495 Fix duplicates in CouncilChannel
Deploy / Build (push) Successful in 1m52s
Deploy / Deploy (push) Successful in 11s
2026-05-21 13:19:27 +02:00
58 changed files with 2058 additions and 1667 deletions
@@ -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);
@@ -38,7 +38,7 @@ import java.util.Collection;
public class SimulatorCommand extends SWCommand {
@LinkedInstance
public SimulatorCursor simulatorCursor;
public SimulatorCursorManager simulatorCursorManager;
public SimulatorCommand() {
super("sim", "simulator");
@@ -47,12 +47,12 @@ public class SimulatorCommand extends SWCommand {
@Register(description = "SIMULATOR_HELP")
public void genericCommand(@Validator Player p) {
SWUtils.giveItemToPlayer(p, SimulatorStorage.getWand(p));
simulatorCursor.calcCursor(p);
simulatorCursorManager.calcCursor(p);
}
@Register(value = "change", description = "SIMULATOR_CHANGE_HELP")
public void change(@Validator Player p) {
if (!SimulatorCursor.isSimulatorItem(p.getInventory().getItemInMainHand()) && !SimulatorCursor.isSimulatorItem(p.getInventory().getItemInOffHand())) {
if (!SimulatorCursorManager.isSimulatorItem(p.getInventory().getItemInMainHand()) && !SimulatorCursorManager.isSimulatorItem(p.getInventory().getItemInOffHand())) {
BauSystem.MESSAGE.send("SIMULATOR_NO_SIM_IN_HAND", p);
return;
}
@@ -1,468 +0,0 @@
/*
* This file is a part of the SteamWar software.
*
* Copyright (C) 2025 SteamWar.de-Serverteam
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package de.steamwar.bausystem.features.simulator;
import com.comphenix.tinyprotocol.TinyProtocol;
import de.steamwar.bausystem.BauSystem;
import de.steamwar.bausystem.Permission;
import de.steamwar.bausystem.SWUtils;
import de.steamwar.bausystem.features.simulator.data.Simulator;
import de.steamwar.bausystem.features.simulator.data.SimulatorElement;
import de.steamwar.bausystem.features.simulator.data.SimulatorGroup;
import de.steamwar.bausystem.features.simulator.data.observer.ObserverElement;
import de.steamwar.bausystem.features.simulator.data.observer.ObserverPhase;
import de.steamwar.bausystem.features.simulator.data.redstone.RedstoneElement;
import de.steamwar.bausystem.features.simulator.data.redstone.RedstonePhase;
import de.steamwar.bausystem.features.simulator.data.tnt.TNTElement;
import de.steamwar.bausystem.features.simulator.data.tnt.TNTPhase;
import de.steamwar.bausystem.features.simulator.execute.SimulatorExecutor;
import de.steamwar.bausystem.features.simulator.gui.SimulatorGroupGui;
import de.steamwar.bausystem.features.simulator.gui.SimulatorGui;
import de.steamwar.bausystem.features.simulator.gui.base.SimulatorBaseGui;
import de.steamwar.bausystem.utils.BauMemberUpdateEvent;
import de.steamwar.bausystem.utils.ItemUtils;
import de.steamwar.bausystem.utils.RayTraceUtils;
import de.steamwar.entity.REntity;
import de.steamwar.entity.REntityServer;
import de.steamwar.entity.RFallingBlockEntity;
import de.steamwar.inventory.SWAnvilInv;
import de.steamwar.linkage.Linked;
import lombok.AllArgsConstructor;
import lombok.Getter;
import net.minecraft.network.protocol.game.ServerboundMovePlayerPacket;
import org.bukkit.Bukkit;
import org.bukkit.Location;
import org.bukkit.Material;
import org.bukkit.World;
import org.bukkit.block.BlockFace;
import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
import org.bukkit.event.EventPriority;
import org.bukkit.event.Listener;
import org.bukkit.event.block.Action;
import org.bukkit.event.player.*;
import org.bukkit.inventory.ItemStack;
import org.bukkit.util.Vector;
import java.util.*;
import java.util.function.BiFunction;
import java.util.function.Function;
import java.util.stream.Collectors;
@Linked
public class SimulatorCursor implements Listener {
private static final World WORLD = Bukkit.getWorlds().get(0);
private static Map<Player, CursorType> cursorType = Collections.synchronizedMap(new HashMap<>());
private static Map<Player, REntityServer> cursors = Collections.synchronizedMap(new HashMap<>());
private static final Set<Player> calculating = new HashSet<>();
public static boolean isSimulatorItem(ItemStack itemStack) {
return ItemUtils.isItem(itemStack, "simulator");
}
public SimulatorCursor() {
BiFunction<Player, ServerboundMovePlayerPacket, Object> function = (player, object) -> {
calcCursor(player);
return object;
};
TinyProtocol.instance.addFilter(ServerboundMovePlayerPacket.Pos.class, function);
TinyProtocol.instance.addFilter(ServerboundMovePlayerPacket.Rot.class, function);
TinyProtocol.instance.addFilter(ServerboundMovePlayerPacket.PosRot.class, function);
}
@EventHandler
public void onPlayerJoin(PlayerJoinEvent event) {
if (!Permission.BUILD.hasPermission(event.getPlayer())) return;
Bukkit.getScheduler().runTaskLater(BauSystem.getInstance(), () -> {
calcCursor(event.getPlayer());
}, 0);
}
@EventHandler
public void onPlayerDropItem(PlayerDropItemEvent event) {
if (!Permission.BUILD.hasPermission(event.getPlayer())) return;
calcCursor(event.getPlayer());
}
@EventHandler
public void onPlayerItemHeld(PlayerItemHeldEvent event) {
if (!Permission.BUILD.hasPermission(event.getPlayer())) return;
Bukkit.getScheduler().runTaskLater(BauSystem.getInstance(), () -> {
calcCursor(event.getPlayer());
}, 1);
}
@EventHandler
public void onBauMemberUpdate(BauMemberUpdateEvent event) {
event.getChanged().forEach(SimulatorCursor::calcCursor);
}
@EventHandler
public void onPlayerQuit(PlayerQuitEvent event) {
cursorType.remove(event.getPlayer());
cursors.remove(event.getPlayer());
synchronized (calculating) {
calculating.remove(event.getPlayer());
}
}
private static final Map<Player, Long> LAST_SNEAKS = new HashMap<>();
static {
Bukkit.getScheduler().runTaskTimer(BauSystem.getInstance(), () -> {
long millis = System.currentTimeMillis();
LAST_SNEAKS.entrySet().removeIf(entry -> millis - entry.getValue() > 200);
}, 1, 1);
}
@EventHandler(priority = EventPriority.HIGH)
public void onPlayerToggleSneak(PlayerToggleSneakEvent event) {
if (!event.isSneaking()) return;
Player player = event.getPlayer();
if (!isSimulatorItem(player.getInventory().getItemInMainHand()) && !isSimulatorItem(player.getInventory().getItemInOffHand())) {
return;
}
if (LAST_SNEAKS.containsKey(player)) {
CursorType currentType = cursorType.getOrDefault(player, CursorType.TNT);
if (currentType == CursorType.TNT) {
cursorType.put(player, CursorType.REDSTONE_BLOCK);
} else {
cursorType.put(player, CursorType.TNT);
}
calcCursor(player);
} else {
LAST_SNEAKS.put(player, System.currentTimeMillis());
}
}
public static CursorType getCursorType(Player player) {
return cursorType.getOrDefault(player, CursorType.TNT);
}
public static void setCursorType(Player player, CursorType cursorType) {
SimulatorCursor.cursorType.put(player, cursorType);
calcCursor(player);
}
public static void calcCursor(Player player) {
synchronized (calculating) {
if (calculating.contains(player)) return;
calculating.add(player);
}
if (!Permission.BUILD.hasPermission(player) || (!isSimulatorItem(player.getInventory().getItemInMainHand()) && !isSimulatorItem(player.getInventory().getItemInOffHand()))) {
if (removeCursor(player) || SimulatorWatcher.show(null, player)) {
SWUtils.sendToActionbar(player, "");
}
synchronized (calculating) {
calculating.remove(player);
}
return;
}
Simulator simulator = SimulatorStorage.getSimulator(player);
if (simulator != null && simulator.getStabGenerator() != null) {
removeCursor(player);
SimulatorWatcher.show(null, player);
SWUtils.sendToActionbar(player, "§cGenerating Stab");
synchronized (calculating) {
calculating.remove(player);
}
return;
}
SimulatorWatcher.show(simulator, player);
List<REntity> entities = SimulatorWatcher.getEntitiesOfSimulator(simulator);
RayTraceUtils.RRayTraceResult rayTraceResult = RayTraceUtils.traceREntity(player, player.getLocation(), entities);
if (rayTraceResult == null) {
removeCursor(player);
if (simulator == null) {
SWUtils.sendToActionbar(player, "§eSelect Simulator");
} else {
SWUtils.sendToActionbar(player, "§eOpen Simulator");
}
synchronized (calculating) {
calculating.remove(player);
}
return;
}
showCursor(player, rayTraceResult, simulator != null);
synchronized (calculating) {
calculating.remove(player);
}
}
private static synchronized boolean removeCursor(Player player) {
REntityServer entityServer = cursors.get(player);
boolean hadCursor = entityServer != null && !entityServer.getEntities().isEmpty();
if (entityServer != null) {
entityServer.getEntities().forEach(REntity::die);
}
return hadCursor;
}
private static synchronized void showCursor(Player player, RayTraceUtils.RRayTraceResult rayTraceResult, boolean hasSimulatorSelected) {
REntityServer entityServer = cursors.computeIfAbsent(player, __ -> {
REntityServer rEntityServer = new REntityServer();
rEntityServer.addPlayer(player);
return rEntityServer;
});
CursorType type = cursorType.getOrDefault(player, CursorType.TNT);
REntity hitEntity = rayTraceResult.getHitEntity();
Location location = hitEntity != null ? new Vector(hitEntity.getX(), hitEntity.getY(), hitEntity.getZ()).toLocation(WORLD) :
type.position.apply(player, rayTraceResult).toLocation(WORLD);
Material material = hitEntity != null ? Material.GLASS : type.getMaterial();
List<RFallingBlockEntity> entities = entityServer.getEntitiesByType(RFallingBlockEntity.class);
entities.removeIf(rFallingBlockEntity -> {
if (rFallingBlockEntity.getMaterial() != material) {
rFallingBlockEntity.die();
return true;
}
rFallingBlockEntity.move(location);
return false;
});
if (entities.isEmpty()) {
RFallingBlockEntity rFallingBlockEntity = new RFallingBlockEntity(entityServer, location, material);
rFallingBlockEntity.setNoGravity(true);
if (material == Material.GLASS) {
rFallingBlockEntity.setGlowing(true);
}
}
if (hasSimulatorSelected) {
if (hitEntity != null) {
SWUtils.sendToActionbar(player, "§eEdit Position");
} else {
SWUtils.sendToActionbar(player, "§eAdd new " + type.name);
}
} else {
SWUtils.sendToActionbar(player, "§eCreate new Simulator");
}
}
public static Vector getPosFree(Player player, RayTraceUtils.RRayTraceResult result) {
Vector pos = result.getHitPosition();
BlockFace face = result.getHitBlockFace();
if (face != null) {
switch (face) {
case DOWN:
pos.setY(pos.getY() - 0.98);
break;
case EAST:
pos.setX(pos.getX() + 0.49);
break;
case WEST:
pos.setX(pos.getX() - 0.49);
break;
case NORTH:
pos.setZ(pos.getZ() - 0.49);
break;
case SOUTH:
pos.setZ(pos.getZ() + 0.49);
break;
default:
break;
}
if (face.getModY() == 0 && player.isSneaking()) {
pos.setY(pos.getY() - 0.49);
}
}
if (!player.isSneaking()) {
pos.setX(pos.getBlockX() + 0.5);
if (face == null || face.getModY() == 0) {
pos.setY(pos.getBlockY() + 0.0);
}
pos.setZ(pos.getBlockZ() + 0.5);
}
return pos;
}
private static Vector getPosBlockAligned(Player player, RayTraceUtils.RRayTraceResult result) {
Vector pos = result.getHitPosition();
BlockFace face = result.getHitBlockFace();
if (face != null) {
switch (face) {
case DOWN:
pos.setY(pos.getY() - 0.98);
break;
case EAST:
pos.setX(pos.getX() + 0.49);
break;
case WEST:
pos.setX(pos.getX() - 0.49);
break;
case NORTH:
pos.setZ(pos.getZ() - 0.49);
break;
case SOUTH:
pos.setZ(pos.getZ() + 0.49);
break;
default:
break;
}
}
pos.setX(pos.getBlockX() + 0.5);
if (pos.getY() - pos.getBlockY() != 0 && face == BlockFace.UP) {
pos.setY(pos.getBlockY() + 1.0);
} else {
pos.setY(pos.getBlockY());
}
pos.setZ(pos.getBlockZ() + 0.5);
return pos;
}
@Getter
@AllArgsConstructor
public enum CursorType {
TNT(Material.TNT, Material.GUNPOWDER, SimulatorCursor::getPosFree, "TNT", vector -> new TNTElement(vector).add(new TNTPhase())),
REDSTONE_BLOCK(Material.REDSTONE_BLOCK, Material.REDSTONE, SimulatorCursor::getPosBlockAligned, "Redstone Block", vector -> new RedstoneElement(vector).add(new RedstonePhase())),
OBSERVER(Material.OBSERVER, Material.QUARTZ, SimulatorCursor::getPosBlockAligned, "Observer", vector -> new ObserverElement(vector).add(new ObserverPhase())),
;
public final Material material;
public final Material nonSelectedMaterial;
public final BiFunction<Player, RayTraceUtils.RRayTraceResult, Vector> position;
public final String name;
public final Function<Vector, SimulatorElement<?>> elementFunction;
}
@EventHandler
public void onPlayerInteract(PlayerInteractEvent event) {
if (!Permission.BUILD.hasPermission(event.getPlayer())) return;
if (!ItemUtils.isItem(event.getItem(), "simulator")) {
return;
}
event.setCancelled(true);
Player player = event.getPlayer();
Simulator simulator = SimulatorStorage.getSimulator(player);
if (event.getAction() == Action.LEFT_CLICK_BLOCK || event.getAction() == Action.LEFT_CLICK_AIR) {
if (simulator == null) {
return;
}
SimulatorExecutor.run(event.getPlayer(), simulator, null);
return;
}
if (event.getAction() != Action.RIGHT_CLICK_BLOCK && event.getAction() != Action.RIGHT_CLICK_AIR) {
return;
}
RayTraceUtils.RRayTraceResult rayTraceResult = RayTraceUtils.traceREntity(player, player.getLocation(), SimulatorWatcher.getEntitiesOfSimulator(simulator));
if (simulator == null) {
if (rayTraceResult == null) {
SimulatorStorage.openSimulatorSelector(player);
} else {
SWAnvilInv anvilInv = new SWAnvilInv(player, "Name");
anvilInv.setCallback(s -> {
Simulator sim = SimulatorStorage.getSimulator(s);
if (sim != null) {
BauSystem.MESSAGE.send("SIMULATOR_NAME_ALREADY_EXISTS", player);
return;
}
if (!s.matches("[a-zA-Z_0-9-]+")) {
BauSystem.MESSAGE.send("SIMULATOR_NAME_INVALID", player);
return;
}
sim = new Simulator(s);
SimulatorStorage.addSimulator(s, sim);
createElement(player, rayTraceResult, sim);
SimulatorStorage.setSimulator(player, sim);
});
anvilInv.open();
}
return;
}
if (rayTraceResult == null) {
new SimulatorGui(player, simulator).open();
return;
}
if (rayTraceResult.getHitEntity() != null) {
REntity hitEntity = rayTraceResult.getHitEntity();
Vector vector = new Vector(hitEntity.getX(), hitEntity.getY(), hitEntity.getZ());
List<SimulatorElement<?>> elements = simulator.getGroups().stream().map(SimulatorGroup::getElements).flatMap(List::stream).filter(element -> {
return element.getWorldPos().distanceSquared(vector) < (1 / 16.0) * (1 / 16.0);
}).collect(Collectors.toList());
switch (elements.size()) {
case 0:
return;
case 1:
// Open single element present in Simulator
SimulatorElement<?> element = elements.get(0);
SimulatorGroup group1 = element.getGroup(simulator);
SimulatorBaseGui back = new SimulatorGui(player, simulator);
if (group1.getElements().size() > 1) {
back = new SimulatorGroupGui(player, simulator, group1, back);
}
element.open(player, simulator, group1, back);
break;
default:
List<SimulatorGroup> parents = elements.stream().map(e -> e.getGroup(simulator)).distinct().collect(Collectors.toList());
if (parents.size() == 1) {
// Open multi element present in Simulator in existing group
SimulatorGui simulatorGui = new SimulatorGui(player, simulator);
new SimulatorGroupGui(player, simulator, parents.get(0), simulatorGui).open();
} else {
// Open multi element present in Simulator in implicit group
SimulatorGroup group2 = new SimulatorGroup();
group2.setMaterial(null);
group2.getElements().addAll(elements);
SimulatorGui simulatorGui = new SimulatorGui(player, simulator);
new SimulatorGroupGui(player, simulator, group2, simulatorGui).open();
}
break;
}
return;
}
// Add new Element to current simulator
createElement(player, rayTraceResult, simulator);
}
private void createElement(Player player, RayTraceUtils.RRayTraceResult rayTraceResult, Simulator simulator) {
CursorType type = cursorType.getOrDefault(player, CursorType.TNT);
Vector vector = type.position.apply(player, rayTraceResult);
if (type == CursorType.REDSTONE_BLOCK) {
vector.subtract(new Vector(0.5, 0, 0.5));
}
SimulatorElement<?> element = type.elementFunction.apply(vector);
SimulatorGroup group = new SimulatorGroup().add(element);
simulator.getGroups().add(group);
SimulatorGui simulatorGui = new SimulatorGui(player, simulator);
element.open(player, simulator, group, simulatorGui);
SimulatorWatcher.update(simulator);
calcCursor(player);
}
}
@@ -0,0 +1,440 @@
/*
* This file is a part of the SteamWar software.
*
* Copyright (C) 2025 SteamWar.de-Serverteam
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package de.steamwar.bausystem.features.simulator;
import de.steamwar.bausystem.BauSystem;
import de.steamwar.bausystem.Permission;
import de.steamwar.bausystem.SWUtils;
import de.steamwar.bausystem.features.simulator.data.Simulator;
import de.steamwar.bausystem.features.simulator.data.SimulatorElement;
import de.steamwar.bausystem.features.simulator.data.SimulatorGroup;
import de.steamwar.bausystem.features.simulator.data.observer.ObserverElement;
import de.steamwar.bausystem.features.simulator.data.observer.ObserverPhase;
import de.steamwar.bausystem.features.simulator.data.redstone.RedstoneElement;
import de.steamwar.bausystem.features.simulator.data.redstone.RedstonePhase;
import de.steamwar.bausystem.features.simulator.data.tnt.TNTElement;
import de.steamwar.bausystem.features.simulator.data.tnt.TNTPhase;
import de.steamwar.bausystem.features.simulator.execute.SimulatorExecutor;
import de.steamwar.bausystem.features.simulator.gui.SimulatorGroupGui;
import de.steamwar.bausystem.features.simulator.gui.SimulatorGui;
import de.steamwar.bausystem.features.simulator.gui.base.SimulatorBaseGui;
import de.steamwar.bausystem.utils.BauMemberUpdateEvent;
import de.steamwar.bausystem.utils.ItemUtils;
import de.steamwar.core.SWPlayer;
import de.steamwar.cursor.Cursor;
import de.steamwar.entity.REntity;
import de.steamwar.entity.REntityServer;
import de.steamwar.inventory.SWAnvilInv;
import de.steamwar.linkage.Linked;
import lombok.AllArgsConstructor;
import lombok.Getter;
import org.bukkit.Bukkit;
import org.bukkit.Location;
import org.bukkit.Material;
import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
import org.bukkit.event.EventPriority;
import org.bukkit.event.Listener;
import org.bukkit.event.block.Action;
import org.bukkit.event.player.PlayerDropItemEvent;
import org.bukkit.event.player.PlayerItemHeldEvent;
import org.bukkit.event.player.PlayerJoinEvent;
import org.bukkit.event.player.PlayerQuitEvent;
import org.bukkit.event.player.PlayerToggleSneakEvent;
import org.bukkit.inventory.ItemStack;
import org.bukkit.util.Vector;
import java.util.List;
import java.util.Optional;
import java.util.function.Function;
import java.util.stream.Collectors;
@Linked
public class SimulatorCursorManager implements Listener {
private static class SimulatorCursorComponent implements SWPlayer.Component {
private Player player;
private Cursor cursor;
private CursorType cursorType = CursorType.TNT;
private REntityServer emptyTargetServer;
private REntityServer currentTargetServer;
private long lastSneakMillis;
private SimulatorCursorComponent() {
}
private SimulatorCursorComponent(CursorType cursorType) {
this.cursorType = cursorType;
}
private REntityServer getOrCreateEmptyTargetServer() {
if (emptyTargetServer == null) {
emptyTargetServer = new REntityServer();
}
return emptyTargetServer;
}
private boolean handleSneak(long now) {
boolean doubleSneak = now - lastSneakMillis <= 200;
lastSneakMillis = doubleSneak ? 0 : now;
return doubleSneak;
}
private CursorType getCursorType() {
return cursorType;
}
private void setCursorType(CursorType cursorType) {
this.cursorType = cursorType;
refresh();
}
private void switchCursorMode() {
cursorType = cursorType == CursorType.TNT ? CursorType.REDSTONE_BLOCK : CursorType.TNT;
refresh();
}
private void refresh() {
if (!Permission.BUILD.hasPermission(player) || !hasSimulatorItem(player)) {
deactivateCursor(player);
return;
}
Simulator simulator = SimulatorStorage.getSimulator(player);
if (simulator != null && simulator.getStabGenerator() != null) {
removeGenericCursor();
boolean watcherRemoved = SimulatorWatcher.show(null, player);
if (watcherRemoved) {
SWUtils.sendToActionbar(player, "");
}
SWUtils.sendToActionbar(player, "§cGenerating Stab");
return;
}
SimulatorWatcher.show(simulator, player);
REntityServer targetServer = simulator == null ? getOrCreateEmptyTargetServer() : SimulatorWatcher.getEntityServerOfSimulator(simulator);
if (cursor == null || currentTargetServer != targetServer) {
removeGenericCursor();
currentTargetServer = targetServer;
cursor = new Cursor(
targetServer,
player,
Material.GLASS,
cursorType.material,
cursorType.cursorModes,
this::handlePlayerClick,
(location, hitEntity) -> sendCursorActionbar(simulator, location != null, hitEntity.isPresent())
);
} else {
cursor.setCursorMaterial(cursorType.material);
cursor.setAllowedCursorModes(cursorType.cursorModes);
}
cursor.renderDeduplicated();
}
private void removeGenericCursor() {
if (cursor == null && !SWPlayer.of(player).hasComponent(Cursor.class)) {
currentTargetServer = null;
return;
}
SWPlayer.of(player).removeComponent(Cursor.class);
cursor = null;
currentTargetServer = null;
}
private void closeEmptyTargetServer() {
if (emptyTargetServer == null) {
return;
}
emptyTargetServer.close();
emptyTargetServer = null;
}
private void sendCursorActionbar(Simulator simulator, boolean hasCursorLocation, boolean hasHitEntity) {
if (!hasCursorLocation) {
SWUtils.sendToActionbar(player, simulator == null ? "§eSelect Simulator" : "§eOpen Simulator");
} else if (simulator == null) {
SWUtils.sendToActionbar(player, "§eCreate new Simulator");
} else if (hasHitEntity) {
SWUtils.sendToActionbar(player, "§eEdit Position");
} else {
SWUtils.sendToActionbar(player, "§eAdd new " + cursorType.name);
}
}
private void handlePlayerClick(Location cursorLocation, Optional<REntity> hitEntity, Action action) {
if (!Permission.BUILD.hasPermission(player)) return;
if (!hasSimulatorItem(player)) {
return;
}
Simulator simulator = SimulatorStorage.getSimulator(player);
if (action == Action.LEFT_CLICK_BLOCK || action == Action.LEFT_CLICK_AIR) {
if (simulator == null) {
return;
}
SimulatorExecutor.run(player, simulator, null);
return;
}
if (action != Action.RIGHT_CLICK_BLOCK && action != Action.RIGHT_CLICK_AIR) {
return;
}
if (simulator == null) {
if (cursorLocation == null) {
SimulatorStorage.openSimulatorSelector(player);
} else {
SWAnvilInv anvilInv = new SWAnvilInv(player, "Name");
anvilInv.setCallback(s -> {
Simulator sim = SimulatorStorage.getSimulator(s);
if (sim != null) {
BauSystem.MESSAGE.send("SIMULATOR_NAME_ALREADY_EXISTS", player);
return;
}
if (!s.matches("[a-zA-Z_0-9-]+")) {
BauSystem.MESSAGE.send("SIMULATOR_NAME_INVALID", player);
return;
}
sim = new Simulator(s);
SimulatorStorage.addSimulator(s, sim);
createElement(player, cursorLocation, sim);
SimulatorStorage.setSimulator(player, sim);
});
anvilInv.open();
}
return;
}
if (cursorLocation == null) {
new SimulatorGui(player, simulator).open();
return;
}
if (hitEntity.isPresent()) {
openElement(player, simulator, hitEntity.get());
return;
}
createElement(player, cursorLocation, simulator);
}
@Override
public void onMount(SWPlayer player) {
this.player = player.getPlayer();
refresh();
}
@Override
public void onUnmount(SWPlayer player) {
boolean hadCursor = cursor != null || player.hasComponent(Cursor.class);
removeGenericCursor();
closeEmptyTargetServer();
boolean watcherRemoved = SimulatorWatcher.show(null, this.player);
if (hadCursor || watcherRemoved) {
SWUtils.sendToActionbar(this.player, "");
}
}
}
public static boolean isSimulatorItem(ItemStack itemStack) {
return ItemUtils.isItem(itemStack, "simulator");
}
private static boolean hasSimulatorItem(Player player) {
return isSimulatorItem(player.getInventory().getItemInMainHand()) || isSimulatorItem(player.getInventory().getItemInOffHand());
}
@EventHandler
public void onPlayerDropItem(PlayerDropItemEvent event) {
if (!Permission.BUILD.hasPermission(event.getPlayer())) return;
calcCursor(event.getPlayer());
}
@EventHandler
public void onPlayerItemHeld(PlayerItemHeldEvent event) {
Player player = event.getPlayer();
if (!Permission.BUILD.hasPermission(player)) return;
boolean hasSimulatorInNewMainHand = isSimulatorItem(player.getInventory().getItem(event.getNewSlot()));
boolean hasSimulatorInOffHand = isSimulatorItem(player.getInventory().getItemInOffHand());
if (!hasSimulatorInNewMainHand && !hasSimulatorInOffHand) {
boolean cursorRemoved = deactivateCursor(player);
boolean watcherRemoved = SimulatorWatcher.show(null, player);
if (cursorRemoved || watcherRemoved) {
SWUtils.sendToActionbar(player, "");
}
return;
}
Bukkit.getScheduler().runTaskLater(BauSystem.getInstance(), () -> calcCursor(player), 1);
}
@EventHandler
public void onBauMemberUpdate(BauMemberUpdateEvent event) {
event.getChanged().forEach(SimulatorCursorManager::calcCursor);
}
@EventHandler
public void onPlayerQuit(PlayerQuitEvent event) {
deactivateCursor(event.getPlayer());
}
@EventHandler(priority = EventPriority.HIGH)
public void onPlayerToggleSneak(PlayerToggleSneakEvent event) {
if (!event.isSneaking()) return;
Player player = event.getPlayer();
if (!hasSimulatorItem(player)) {
return;
}
SimulatorCursorComponent component = activateOrRefresh(player);
if (component == null) {
return;
}
boolean shouldSwitch = component.handleSneak(System.currentTimeMillis());
if (shouldSwitch) {
component.switchCursorMode();
}
}
public static CursorType getCursorType(Player player) {
return SWPlayer.of(player).getComponent(SimulatorCursorComponent.class)
.map(SimulatorCursorComponent::getCursorType)
.orElse(CursorType.TNT);
}
public static void setCursorType(Player player, CursorType cursorType) {
Optional<SimulatorCursorComponent> component = SWPlayer.of(player).getComponent(SimulatorCursorComponent.class);
if (component.isPresent()) {
component.get().setCursorType(cursorType);
return;
}
if (!Permission.BUILD.hasPermission(player) || !hasSimulatorItem(player)) {
return;
}
SWPlayer.of(player).setComponent(new SimulatorCursorComponent(cursorType));
}
public static void calcCursor(Player player) {
activateOrRefresh(player);
}
private static SimulatorCursorComponent activateOrRefresh(Player player) {
if (!Permission.BUILD.hasPermission(player) || !hasSimulatorItem(player)) {
boolean cursorRemoved = deactivateCursor(player);
boolean watcherRemoved = SimulatorWatcher.show(null, player);
if (cursorRemoved || watcherRemoved) {
SWUtils.sendToActionbar(player, "");
}
return null;
}
SWPlayer swPlayer = SWPlayer.of(player);
Optional<SimulatorCursorComponent> existingComponent = swPlayer.getComponent(SimulatorCursorComponent.class);
if (existingComponent.isPresent()) {
SimulatorCursorComponent component = existingComponent.get();
component.refresh();
return component;
}
SimulatorCursorComponent component = new SimulatorCursorComponent();
swPlayer.setComponent(component);
return component;
}
private static synchronized boolean deactivateCursor(Player player) {
SWPlayer swPlayer = SWPlayer.of(player);
boolean hadSimulatorCursor = swPlayer.hasComponent(SimulatorCursorComponent.class);
boolean hadCursor = swPlayer.hasComponent(Cursor.class);
swPlayer.removeComponent(SimulatorCursorComponent.class);
if (!hadSimulatorCursor) {
swPlayer.removeComponent(Cursor.class);
}
return hadSimulatorCursor || hadCursor;
}
@Getter
@AllArgsConstructor
public enum CursorType {
TNT(Material.TNT, Material.GUNPOWDER, List.of(Cursor.CursorMode.SURFACE_ALIGNED, Cursor.CursorMode.FREE), "TNT", vector -> new TNTElement(vector).add(new TNTPhase())),
REDSTONE_BLOCK(Material.REDSTONE_BLOCK, Material.REDSTONE, List.of(Cursor.CursorMode.BLOCK_ALIGNED), "Redstone Block", vector -> new RedstoneElement(vector).add(new RedstonePhase())),
OBSERVER(Material.OBSERVER, Material.QUARTZ, List.of(Cursor.CursorMode.BLOCK_ALIGNED), "Observer", vector -> new ObserverElement(vector).add(new ObserverPhase())),
;
public final Material material;
public final Material nonSelectedMaterial;
public final List<Cursor.CursorMode> cursorModes;
public final String name;
public final Function<Vector, SimulatorElement<?>> elementFunction;
}
private static void openElement(Player player, Simulator simulator, REntity hitEntity) {
Vector vector = new Vector(hitEntity.getX(), hitEntity.getY(), hitEntity.getZ());
List<SimulatorElement<?>> elements = simulator.getGroups().stream().map(SimulatorGroup::getElements).flatMap(List::stream).filter(element -> {
return element.getWorldPos().distanceSquared(vector) < (1 / 16.0) * (1 / 16.0);
}).collect(Collectors.toList());
switch (elements.size()) {
case 0:
return;
case 1:
SimulatorElement<?> element = elements.get(0);
SimulatorGroup group1 = element.getGroup(simulator);
SimulatorBaseGui back = new SimulatorGui(player, simulator);
if (group1.getElements().size() > 1) {
back = new SimulatorGroupGui(player, simulator, group1, back);
}
element.open(player, simulator, group1, back);
break;
default:
List<SimulatorGroup> parents = elements.stream().map(e -> e.getGroup(simulator)).distinct().collect(Collectors.toList());
if (parents.size() == 1) {
SimulatorGui simulatorGui = new SimulatorGui(player, simulator);
new SimulatorGroupGui(player, simulator, parents.get(0), simulatorGui).open();
} else {
SimulatorGroup group2 = new SimulatorGroup();
group2.setMaterial(null);
group2.getElements().addAll(elements);
SimulatorGui simulatorGui = new SimulatorGui(player, simulator);
new SimulatorGroupGui(player, simulator, group2, simulatorGui).open();
}
break;
}
}
private static void createElement(Player player, Location cursorLocation, Simulator simulator) {
CursorType type = getCursorType(player);
Vector vector = cursorLocation.toVector();
if (type == CursorType.REDSTONE_BLOCK) {
vector.subtract(new Vector(0.5, 0, 0.5));
}
SimulatorElement<?> element = type.elementFunction.apply(vector);
SimulatorGroup group = new SimulatorGroup().add(element);
simulator.getGroups().add(group);
SimulatorGui simulatorGui = new SimulatorGui(player, simulator);
element.open(player, simulator, group, simulatorGui);
SimulatorWatcher.update(simulator);
calcCursor(player);
}
}
@@ -59,7 +59,7 @@ public class SimulatorStorage implements Enable {
}
public static Simulator getSimulator(ItemStack itemStack) {
if (!SimulatorCursor.isSimulatorItem(itemStack)) {
if (!SimulatorCursorManager.isSimulatorItem(itemStack)) {
return null;
}
String selection = ItemUtils.getTag(itemStack, simulatorSelection);
@@ -181,9 +181,9 @@ public class SimulatorStorage implements Enable {
ItemStack mainHand = player.getInventory().getItemInMainHand();
ItemStack offHand = player.getInventory().getItemInOffHand();
ItemStack itemStack;
if (SimulatorCursor.isSimulatorItem(mainHand)) {
if (SimulatorCursorManager.isSimulatorItem(mainHand)) {
itemStack = mainHand;
} else if (SimulatorCursor.isSimulatorItem(offHand)) {
} else if (SimulatorCursorManager.isSimulatorItem(offHand)) {
itemStack = offHand;
} else {
itemStack = null;
@@ -124,4 +124,11 @@ public class SimulatorWatcher {
}
return entityServer.getEntities();
}
synchronized REntityServer getEntityServerOfSimulator(Simulator simulator) {
if (simulator == null) {
return null;
}
return entityServers.computeIfAbsent(simulator, __ -> createSim(new REntityServer(), simulator));
}
}
@@ -19,7 +19,7 @@
package de.steamwar.bausystem.features.simulator.gui;
import de.steamwar.bausystem.features.simulator.SimulatorCursor;
import de.steamwar.bausystem.features.simulator.SimulatorCursorManager;
import de.steamwar.bausystem.features.simulator.data.Simulator;
import de.steamwar.bausystem.features.simulator.gui.base.SimulatorBaseGui;
import de.steamwar.data.CMDs;
@@ -50,14 +50,14 @@ public class SimulatorCursorSwitcherGui extends SimulatorBaseGui {
}).setCustomModelData(CMDs.BACK));
int slot = 2;
SimulatorCursor.CursorType currentType = SimulatorCursor.getCursorType(player);
for (SimulatorCursor.CursorType type : SimulatorCursor.CursorType.values()) {
SimulatorCursorManager.CursorType currentType = SimulatorCursorManager.getCursorType(player);
for (SimulatorCursorManager.CursorType type : SimulatorCursorManager.CursorType.values()) {
boolean selected = type == currentType;
SWItem swItem = new SWItem(selected ? type.material : type.nonSelectedMaterial, "§e" + type.name)
.setCustomModelData(selected ? 0 : CMDs.Simulator.NEW_PHASE)
.setLore(Collections.singletonList("§eClick to select"))
.setCallback(click -> {
SimulatorCursor.setCursorType(player, type);
SimulatorCursorManager.setCursorType(player, type);
player.closeInventory();
});
inventory.setItem(slot, swItem);
@@ -19,7 +19,7 @@
package de.steamwar.bausystem.features.simulator.gui;
import de.steamwar.bausystem.features.simulator.SimulatorCursor;
import de.steamwar.bausystem.features.simulator.SimulatorCursorManager;
import de.steamwar.bausystem.features.simulator.SimulatorWatcher;
import de.steamwar.bausystem.features.simulator.data.Simulator;
import de.steamwar.bausystem.features.simulator.data.SimulatorElement;
@@ -50,7 +50,7 @@ public class SimulatorGui extends SimulatorPageGui<SimulatorGroup> {
inventory.setItem(4, simulator.toItem(player, clickType -> {
new SimulatorMaterialGui(player, simulator, simulator::getMaterial, simulator::setMaterial, this).open();
}));
SimulatorCursor.CursorType cursorType = SimulatorCursor.getCursorType(player);
SimulatorCursorManager.CursorType cursorType = SimulatorCursorManager.getCursorType(player);
inventory.setItem(48, new SWItem(cursorType.material, "§7Placing §8-§e " + cursorType.name, clickType -> {
new SimulatorCursorSwitcherGui(player, simulator, this).open();
}));
@@ -38,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());
}
}
}
@@ -28,6 +28,7 @@ import de.steamwar.bausystem.utils.bossbar.BossBarService;
import de.steamwar.linkage.Linked;
import de.steamwar.network.packets.PacketHandler;
import de.steamwar.network.packets.server.BaumemberUpdatePacket;
import io.papermc.paper.event.player.PlayerPickBlockEvent;
import org.bukkit.Bukkit;
import org.bukkit.boss.BarColor;
import org.bukkit.boss.BarStyle;
@@ -35,6 +36,8 @@ import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
import org.bukkit.event.Listener;
import org.bukkit.event.entity.PlayerDeathEvent;
import org.bukkit.event.inventory.ClickType;
import org.bukkit.event.inventory.InventoryClickEvent;
import org.bukkit.event.player.PlayerItemConsumeEvent;
import org.bukkit.event.player.PlayerJoinEvent;
import org.bukkit.event.player.PlayerQuitEvent;
@@ -127,4 +130,11 @@ public class BauMemberUpdate extends PacketHandler implements Listener {
}
}, 1);
}
@EventHandler
public void onPlayerClick(PlayerPickBlockEvent event) {
if(SPECTATORS.contains(event.getPlayer())) {
event.setCancelled(true);
}
}
}
@@ -39,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;
@@ -40,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;
@@ -110,6 +110,7 @@ public class FightSystem extends JavaPlugin {
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,17 +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 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;
@@ -54,8 +44,6 @@ 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 {
@@ -79,10 +67,13 @@ public class HullHider implements Listener {
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) {
@@ -140,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) {
@@ -19,6 +19,7 @@
package de.steamwar.fightsystem.utils;
import de.steamwar.Reflection;
import de.steamwar.core.CraftbukkitWrapper;
import de.steamwar.fightsystem.Config;
import de.steamwar.fightsystem.events.BoardingEvent;
@@ -29,21 +30,32 @@ 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.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 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 Listener {
@@ -51,21 +63,45 @@ public class TechHiderWrapper extends StateDependent implements Listener {
@Getter
private final ConcurrentHashMap<Player, Region> hiddenRegion = new ConcurrentHashMap<>();
private final TechHider techHider;
private final HullHider hullHider;
public TechHiderWrapper(HullHider hullHider) {
super(ENABLED, FightState.Schem);
super(ENABLED, FightState.All);
this.hullHider = hullHider;
new StateDependentListener(ENABLED, FightState.All, this);
register();
}
@Override
public void enable() {
Set<BlockState> blockStatesToObfuscate = getBlockStatesToHideFromMaterials(Config.GameModeConfig.Techhider.HiddenBlocks);
Set<Block> blocksToObfuscate = Config.GameModeConfig.Techhider.HiddenBlocks.stream()
.map(CraftMagicNumbers::getBlock)
.collect(Collectors.toUnmodifiableSet());
/* Set<BlockEntityType<?>> blockEntityTypeToObfuscate = Config.GameModeConfig.Techhider.HiddenBlockEntities.stream()
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 BuiltInRegistries.BLOCK_ENTITY_TYPE.get(loc).get().value();
return ((Optional<Holder.Reference<BlockEntityType<?>>>) method.invoke(blockEntityType, loc)).get().value();
})
.collect(Collectors.toUnmodifiableSet()); */
techHider = new TechHider(CraftMagicNumbers.getBlock(Config.GameModeConfig.Techhider.ObfuscateWith)) {
.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);
@@ -73,40 +109,36 @@ public class TechHiderWrapper extends StateDependent implements Listener {
@Override
public boolean isPlayerPrivilegedToAccessBlock(Player p, int blockX, int blockY, int blockZ, Block block) {
Region hiddenRegion = getHiddenRegion(p);
return !hiddenRegion.inRegion(blockX, blockY, blockZ) || !blocksToObfuscate.contains(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) {
return false;
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 isPlayerPrivilegedToAccessBlocEntity(Player p, int blockX, int blockY, int blockZ, BlockEntityType<?> type) {
//Region hiddenRegion = getHiddenRegion(p);
//return !hiddenRegion.inRegion(blockX, blockY, blockZ) || !blockEntityTypeToObfuscate.contains(type);
return false;
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 isPlayerPrivilegedToAccessContainer(Player p, int containerId) {
return false;
public boolean isPlayerPrivilegedToPerformAction(Player p) {
return p.getGameMode() != GameMode.SPECTATOR;
}
@Override
public boolean isPlayerPrivilegedToAccessSound(Player p, ResourceLocation soundId) {
return false;
}
};
new StateDependentListener(ENABLED, FightState.Schem, this);
register();
}
@Override
public void enable() {
});
}
@Override
@@ -159,4 +191,27 @@ public class TechHiderWrapper extends StateDependent implements Listener {
return Fight.getOpposite(team).getExtendRegion();
}
private Stream<BlockState> getWaterloggedBlockStates() {
FluidState waterFluidState = Fluids.WATER.getSource(false);
return BuiltInRegistries.BLOCK.stream()
.map((block) -> block.getStateDefinition().getPossibleStates())
.flatMap(Collection::stream)
.filter((blockState -> blockState.getFluidState() == waterFluidState));
}
private Set<BlockState> getBlockStatesToHideFromMaterials(Set<Material> materials) {
Stream<BlockState> allStatesFromMaterials = materials.stream()
.map(CraftMagicNumbers::getBlock)
.map((block) -> block.getStateDefinition().getPossibleStates())
.flatMap(Collection::stream);
if(materials.contains(Material.WATER)) {
return Stream.concat(allStatesFromMaterials, getWaterloggedBlockStates()).collect(Collectors.toUnmodifiableSet());
}
else {
return allStatesFromMaterials.collect(Collectors.toUnmodifiableSet());
}
}
}
+1 -1
View File
@@ -60,7 +60,7 @@ tasks.register<FightServer>("WarGear21") {
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,502 +0,0 @@
package com.comphenix.tinyprotocol;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.Arrays;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.bukkit.Bukkit;
/**
* An utility class that simplifies reflection in Bukkit plugins.
*
* @author Kristian
*/
public final class Reflection {
/**
* An interface for invoking a specific constructor.
*/
public interface ConstructorInvoker {
/**
* Invoke a constructor for a specific class.
*
* @param arguments - the arguments to pass to the constructor.
* @return The constructed object.
*/
public Object invoke(Object... arguments);
}
/**
* An interface for invoking a specific method.
*/
public interface MethodInvoker {
/**
* Invoke a method on a specific target object.
*
* @param target - the target object, or NULL for a static method.
* @param arguments - the arguments to pass to the method.
* @return The return value, or NULL if is void.
*/
public Object invoke(Object target, Object... arguments);
}
/**
* An interface for retrieving the field content.
*
* @param <T> - field type.
*/
public interface FieldAccessor<T> {
/**
* Retrieve the content of a field.
*
* @param target - the target object, or NULL for a static field.
* @return The value of the field.
*/
public T get(Object target);
/**
* Set the content of a field.
*
* @param target - the target object, or NULL for a static field.
* @param value - the new value of the field.
*/
public void set(Object target, Object value);
/**
* Determine if the given object has this field.
*
* @param target - the object to test.
* @return TRUE if it does, FALSE otherwise.
*/
public boolean hasField(Object target);
}
// Deduce the net.minecraft.server.v* package
private static String OBC_PREFIX = Bukkit.getServer().getClass().getPackage().getName();
private static String NMS_PREFIX = OBC_PREFIX.replace("org.bukkit.craftbukkit", "net.minecraft.server");
private static String VERSION = OBC_PREFIX.replace("org.bukkit.craftbukkit", "").replace(".", "");
// Variable replacement
private static Pattern MATCH_VARIABLE = Pattern.compile("\\{([^\\}]+)\\}");
private Reflection() {
// Seal class
}
/**
* Retrieve a field accessor for a specific field type and name.
*
* @param target - the target type.
* @param name - the name of the field, or NULL to ignore.
* @param fieldType - a compatible field type.
* @return The field accessor.
*/
public static <T> FieldAccessor<T> getField(Class<?> target, String name, Class<T> fieldType) {
return getField(target, name, fieldType, 0);
}
/**
* Retrieve a field accessor for a specific field type and name.
*
* @param className - lookup name of the class, see {@link #getClass(String)}.
* @param name - the name of the field, or NULL to ignore.
* @param fieldType - a compatible field type.
* @return The field accessor.
*/
public static <T> FieldAccessor<T> getField(String className, String name, Class<T> fieldType) {
return getField(getClass(className), name, fieldType, 0);
}
/**
* Retrieve a field accessor for a specific field type and name.
*
* @param target - the target type.
* @param fieldType - a compatible field type.
* @param index - the number of compatible fields to skip.
* @return The field accessor.
*/
public static <T> FieldAccessor<T> getField(Class<?> target, Class<T> fieldType, int index) {
return getField(target, null, fieldType, index);
}
/**
* Retrieve a field accessor for a specific field type and name.
*
* @param className - lookup name of the class, see {@link #getClass(String)}.
* @param fieldType - a compatible field type.
* @param index - the number of compatible fields to skip.
* @return The field accessor.
*/
public static <T> FieldAccessor<T> getField(String className, Class<T> fieldType, int index) {
return getField(getClass(className), fieldType, index);
}
// Common method
private static <T> FieldAccessor<T> getField(Class<?> target, String name, Class<T> fieldType, int index) {
for (final Field field : target.getDeclaredFields()) {
if ((name == null || field.getName().equals(name)) && fieldType.isAssignableFrom(field.getType()) && index-- <= 0) {
field.setAccessible(true);
// A function for retrieving a specific field value
return new FieldAccessor<T>() {
@Override
@SuppressWarnings("unchecked")
public T get(Object target) {
try {
return (T) field.get(target);
} catch (IllegalAccessException e) {
throw new RuntimeException("Cannot access reflection.", e);
}
}
@Override
public void set(Object target, Object value) {
try {
field.set(target, value);
} catch (IllegalAccessException e) {
throw new RuntimeException("Cannot access reflection.", e);
}
}
@Override
public boolean hasField(Object target) {
// target instanceof DeclaringClass
return field.getDeclaringClass().isAssignableFrom(target.getClass());
}
};
}
}
// Search in parent classes
if (target.getSuperclass() != null)
return getField(target.getSuperclass(), name, fieldType, index);
throw new IllegalArgumentException("Cannot find field with type " + fieldType);
}
/**
* Retrieves a field with a given type and parameters. This is most useful
* when dealing with Collections.
*
* @param target the target class.
* @param fieldType Type of the field
* @param params Variable length array of type parameters
* @return The field
*
* @throws IllegalArgumentException If the field cannot be found
*/
public static Field getParameterizedField(Class<?> target, Class<?> fieldType, Class<?>... params) {
for (Field field : target.getDeclaredFields()) {
if (field.getType().equals(fieldType)) {
Type type = field.getGenericType();
if (type instanceof ParameterizedType) {
if (Arrays.equals(((ParameterizedType) type).getActualTypeArguments(), params))
return field;
}
}
}
throw new IllegalArgumentException("Unable to find a field with type " + fieldType + " and params " + Arrays.toString(params));
}
/**
* Search for the first publicly and privately defined method of the given name and parameter count.
*
* @param className - lookup name of the class, see {@link #getClass(String)}.
* @param methodName - the method name, or NULL to skip.
* @param params - the expected parameters.
* @return An object that invokes this specific method.
* @throws IllegalStateException If we cannot find this method.
*/
public static MethodInvoker getMethod(String className, String methodName, Class<?>... params) {
return getTypedMethod(getClass(className), methodName, null, params);
}
/**
* Search for the first publicly and privately defined method of the given name and parameter count.
*
* @param clazz - a class to start with.
* @param methodName - the method name, or NULL to skip.
* @param params - the expected parameters.
* @return An object that invokes this specific method.
* @throws IllegalStateException If we cannot find this method.
*/
public static MethodInvoker getMethod(Class<?> clazz, String methodName, Class<?>... params) {
return getTypedMethod(clazz, methodName, null, params);
}
/**
* Search for the first publicly and privately defined method of the given name and parameter count.
*
* @param clazz - a class to start with.
* @param methodName - the method name, or NULL to skip.
* @param returnType - the expected return type, or NULL to ignore.
* @param params - the expected parameters.
* @return An object that invokes this specific method.
* @throws IllegalStateException If we cannot find this method.
*/
public static MethodInvoker getTypedMethod(Class<?> clazz, String methodName, Class<?> returnType, Class<?>... params) {
for (final Method method : clazz.getDeclaredMethods()) {
if ((methodName == null || method.getName().equals(methodName))
&& (returnType == null || method.getReturnType().equals(returnType))
&& Arrays.equals(method.getParameterTypes(), params)) {
method.setAccessible(true);
return new MethodInvoker() {
@Override
public Object invoke(Object target, Object... arguments) {
try {
return method.invoke(target, arguments);
} catch (Exception e) {
throw new RuntimeException("Cannot invoke method " + method, e);
}
}
};
}
}
// Search in every superclass
if (clazz.getSuperclass() != null)
return getMethod(clazz.getSuperclass(), methodName, params);
throw new IllegalStateException(String.format("Unable to find method %s (%s).", methodName, Arrays.asList(params)));
}
/**
* Search for the first publically and privately defined constructor of the given name and parameter count.
*
* @param className - lookup name of the class, see {@link #getClass(String)}.
* @param params - the expected parameters.
* @return An object that invokes this constructor.
* @throws IllegalStateException If we cannot find this method.
*/
public static ConstructorInvoker getConstructor(String className, Class<?>... params) {
return getConstructor(getClass(className), params);
}
/**
* Search for the first publically and privately defined constructor of the given name and parameter count.
*
* @param clazz - a class to start with.
* @param params - the expected parameters.
* @return An object that invokes this constructor.
* @throws IllegalStateException If we cannot find this method.
*/
public static ConstructorInvoker getConstructor(Class<?> clazz, Class<?>... params) {
for (final Constructor<?> constructor : clazz.getDeclaredConstructors()) {
if (Arrays.equals(constructor.getParameterTypes(), params)) {
constructor.setAccessible(true);
return new ConstructorInvoker() {
@Override
public Object invoke(Object... arguments) {
try {
return constructor.newInstance(arguments);
} catch (Exception e) {
throw new RuntimeException("Cannot invoke constructor " + constructor, e);
}
}
};
}
}
throw new IllegalStateException(String.format("Unable to find constructor for %s (%s).", clazz, Arrays.asList(params)));
}
/**
* Retrieve a class from its full name, without knowing its type on compile time.
* <p>
* This is useful when looking up fields by a NMS or OBC type.
* <p>
*
* @see {@link #getClass()} for more information.
* @param lookupName - the class name with variables.
* @return The class.
*/
public static Class<Object> getUntypedClass(String lookupName) {
@SuppressWarnings({ "rawtypes", "unchecked" })
Class<Object> clazz = (Class) getClass(lookupName);
return clazz;
}
/**
* Retrieve a class from its full name with alternatives, without knowing its type on compile time.
* <p>
* This is useful when looking up fields by a NMS or OBC type.
* <p>
*
* @see {@link #getClass()} for more information.
* @param lookupName - the class name with variables.
* @param aliases - alternative names for this class.
* @return The class.
*/
public static Class<Object> getUntypedClass(String lookupName, String... aliases) {
@SuppressWarnings({ "rawtypes", "unchecked" })
Class<Object> clazz = (Class) getClass(lookupName, aliases);
return clazz;
}
/**
* Retrieve a class from its full name.
* <p>
* Strings enclosed with curly brackets - such as {TEXT} - will be replaced according to the following table:
* <p>
* <table border="1">
* <tr>
* <th>Variable</th>
* <th>Content</th>
* </tr>
* <tr>
* <td>{nms}</td>
* <td>Actual package name of net.minecraft.server.VERSION</td>
* </tr>
* <tr>
* <td>{obc}</td>
* <td>Actual pacakge name of org.bukkit.craftbukkit.VERSION</td>
* </tr>
* <tr>
* <td>{version}</td>
* <td>The current Minecraft package VERSION, if any.</td>
* </tr>
* </table>
*
* @param lookupName - the class name with variables.
* @return The looked up class.
* @throws IllegalArgumentException If a variable or class could not be found.
*/
public static Class<?> getClass(String lookupName) {
return getCanonicalClass(expandVariables(lookupName));
}
/**
* Retrieve the first class that matches the full class name.
* <p>
* Strings enclosed with curly brackets - such as {TEXT} - will be replaced according to the following table:
* <p>
* <table border="1">
* <tr>
* <th>Variable</th>
* <th>Content</th>
* </tr>
* <tr>
* <td>{nms}</td>
* <td>Actual package name of net.minecraft.server.VERSION</td>
* </tr>
* <tr>
* <td>{obc}</td>
* <td>Actual pacakge name of org.bukkit.craftbukkit.VERSION</td>
* </tr>
* <tr>
* <td>{version}</td>
* <td>The current Minecraft package VERSION, if any.</td>
* </tr>
* </table>
*
* @param lookupName - the class name with variables.
* @param aliases - alternative names for this class.
* @return Class object.
* @throws RuntimeException If we are unable to find any of the given classes.
*/
public static Class<?> getClass(String lookupName, String... aliases) {
try {
// Try the main class first
return getClass(lookupName);
} catch (RuntimeException e) {
Class<?> success = null;
// Try every alias too
for (String alias : aliases) {
try {
success = getClass(alias);
break;
} catch (RuntimeException e1) {
// e1.printStackTrace();
}
}
if (success != null) {
return success;
} else {
// Hack failed
throw new RuntimeException(String.format("Unable to find %s (%s)", lookupName, String.join(",", aliases)));
}
}
}
/**
* Retrieve a class in the net.minecraft.server.VERSION.* package.
*
* @param name - the name of the class, excluding the package.
* @throws IllegalArgumentException If the class doesn't exist.
*/
public static Class<?> getMinecraftClass(String name) {
return getCanonicalClass(NMS_PREFIX + "." + name);
}
/**
* Retrieve a class in the org.bukkit.craftbukkit.VERSION.* package.
*
* @param name - the name of the class, excluding the package.
* @throws IllegalArgumentException If the class doesn't exist.
*/
public static Class<?> getCraftBukkitClass(String name) {
return getCanonicalClass(OBC_PREFIX + "." + name);
}
/**
* Retrieve a class by its canonical name.
*
* @param canonicalName - the canonical name.
* @return The class.
*/
private static Class<?> getCanonicalClass(String canonicalName) {
try {
return Class.forName(canonicalName);
} catch (ClassNotFoundException e) {
throw new IllegalArgumentException("Cannot find " + canonicalName, e);
}
}
/**
* Expand variables such as "{nms}" and "{obc}" to their corresponding packages.
*
* @param name - the full name of the class.
* @return The expanded string.
*/
private static String expandVariables(String name) {
StringBuffer output = new StringBuffer();
Matcher matcher = MATCH_VARIABLE.matcher(name);
while (matcher.find()) {
String variable = matcher.group(1);
String replacement = "";
// Expand all detected variables
if ("nms".equalsIgnoreCase(variable))
replacement = NMS_PREFIX;
else if ("obc".equalsIgnoreCase(variable))
replacement = OBC_PREFIX;
else if ("version".equalsIgnoreCase(variable))
replacement = VERSION;
else
throw new IllegalArgumentException("Unknown variable: " + variable);
// Assume the expanded variables are all packages, and append a dot
if (replacement.length() > 0 && matcher.end() < name.length() && name.charAt(matcher.end()) != '.')
replacement += ".";
matcher.appendReplacement(output, Matcher.quoteReplacement(replacement));
}
matcher.appendTail(output);
return output.toString();
}
}
@@ -227,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);
}
@@ -235,8 +235,8 @@ public class TinyProtocol {
packetFilters.getOrDefault(packetType, Collections.emptyList()).remove(filter);
}
public void addGlobalClientboundFilter(BiFunction<Player, Object, Object> filter) {
globalClientboundFilters.add(filter);
public void addGlobalClientboundFilter(BiFunction<Player, Packet<?>, Object> filter) {
globalClientboundFilters.add((BiFunction) filter);
}
/**
@@ -453,7 +453,7 @@ public class TinyProtocol {
try {
msg = filterPacket(player, msg);
if(msg instanceof Packet<?>) {
if (msg instanceof Packet<?>) {
for (BiFunction<Player, Object, Object> filter : globalClientboundFilters) {
msg = filter.apply(player, msg);
if (msg == null) break;
@@ -0,0 +1,240 @@
package de.steamwar.cursor;
import de.steamwar.core.SWPlayer;
import de.steamwar.entity.REntity;
import de.steamwar.entity.REntityServer;
import de.steamwar.entity.RFallingBlockEntity;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.Setter;
import org.bukkit.Bukkit;
import org.bukkit.Location;
import org.bukkit.Material;
import org.bukkit.World;
import org.bukkit.block.BlockFace;
import org.bukkit.entity.Player;
import org.bukkit.event.block.Action;
import org.bukkit.util.Vector;
import java.util.Comparator;
import java.util.List;
import java.util.Optional;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.function.BiConsumer;
import java.util.function.BiFunction;
import java.util.function.Predicate;
@Getter
public class Cursor implements SWPlayer.Component {
private final World WORLD = Bukkit.getWorlds().get(0);
private final REntityServer targetServer;
private final Player owner;
private final AtomicBoolean isRendering = new AtomicBoolean(false);
private RFallingBlockEntity cursorEntity;
private final REntityServer cursorServer;
private Location cursorLocation;
private REntity hitEntity;
@Setter
private Material cursorMaterial;
@Setter
private List<CursorMode> allowedCursorModes;
private final Material highlightMaterial;
private final ClickHandler onClick;
private final BiConsumer<Location, Optional<REntity>> onRender;
public Cursor(REntityServer targetServer, Player owner, Material highlightMaterial, Material cursorMaterial, List<CursorMode> allowedModes, ClickHandler onClick) {
this(targetServer, owner, highlightMaterial, cursorMaterial, allowedModes, onClick, (location, hitEntity) -> {
});
}
public Cursor(REntityServer targetServer, Player owner, Material highlightMaterial, Material cursorMaterial, List<CursorMode> allowedModes, ClickHandler onClick, BiConsumer<Location, Optional<REntity>> onRender) {
this.targetServer = targetServer;
this.owner = owner;
this.highlightMaterial = highlightMaterial;
this.cursorMaterial = cursorMaterial;
this.allowedCursorModes = allowedModes;
this.onClick = onClick;
this.onRender = onRender;
cursorServer = new REntityServer();
cursorServer.addPlayer(owner);
SWPlayer.of(owner).setComponent(this);
}
public void renderDeduplicated() {
if (!isRendering.getAndSet(true)) {
render();
isRendering.set(false);
}
}
private void render() {
RayTraceUtils.RRayTraceResult rayTraceResult = RayTraceUtils.traceREntity(owner, owner.getLocation(), targetServer.getEntities());
if (rayTraceResult == null) {
if (cursorEntity != null)
cursorEntity.die();
cursorEntity = null;
cursorLocation = null;
hitEntity = null;
onRender.accept(null, Optional.empty());
return;
}
REntity hitEntity = rayTraceResult.getHitEntity() == cursorEntity ? null : rayTraceResult.getHitEntity();
Material activeCursorMaterial = hitEntity == null ? cursorMaterial : highlightMaterial;
CursorMode activeCursorMode = allowedCursorModes.stream().filter((mode) -> mode.isActive.test(owner)).min(Comparator.comparingInt(a -> a.priority))
.orElse(CursorMode.BLOCK_ALIGNED);
Location activeCursorLocation = hitEntity == null ? activeCursorMode.positionTransform.apply(owner, rayTraceResult).toLocation(WORLD)
: new Vector(hitEntity.getX(), hitEntity.getY(), hitEntity.getZ()).toLocation(WORLD);
cursorLocation = activeCursorLocation;
this.hitEntity = hitEntity;
if (cursorEntity == null) {
cursorEntity = new RFallingBlockEntity(cursorServer, activeCursorLocation, activeCursorMaterial);
cursorEntity.setNoGravity(true);
} else if (cursorEntity.getMaterial() == activeCursorMaterial) {
cursorEntity.move(activeCursorLocation);
} else {
cursorEntity.die();
cursorEntity = new RFallingBlockEntity(cursorServer, activeCursorLocation, activeCursorMaterial);
cursorEntity.setNoGravity(true);
if (activeCursorMaterial == highlightMaterial) {
cursorEntity.setGlowing(true);
}
}
onRender.accept(cursorLocation, Optional.ofNullable(hitEntity));
}
protected void handlePlayerClick(Action clickAction) {
renderDeduplicated();
onClick.onClick(this.cursorLocation, Optional.ofNullable(this.hitEntity), clickAction);
}
@Override
public void onUnmount(SWPlayer player) {
cursorServer.close();
}
@AllArgsConstructor
public enum CursorMode {
FREE(1, (player, rayTraceResult) -> {
Vector pos = rayTraceResult.getHitPosition();
BlockFace face = rayTraceResult.getHitBlockFace();
if (face != null) {
switch (face) {
case DOWN:
pos.setY(pos.getY() - 0.98);
break;
case EAST:
pos.setX(pos.getX() + 0.49);
break;
case WEST:
pos.setX(pos.getX() - 0.49);
break;
case NORTH:
pos.setZ(pos.getZ() - 0.49);
break;
case SOUTH:
pos.setZ(pos.getZ() + 0.49);
break;
default:
break;
}
if (face.getModY() == 0 && player.isSneaking()) {
pos.setY(pos.getY() - 0.49);
}
}
return pos;
}, Player::isSneaking),
SURFACE_ALIGNED(2, (player, rayTraceResult) -> {
Vector hitPosition = rayTraceResult.getHitPosition().clone();
Vector pos = blockAlignedPosition(rayTraceResult);
BlockFace face = rayTraceResult.getHitBlockFace();
if (face != null && face != BlockFace.SELF) {
switch (face) {
case UP:
pos.setY(hitPosition.getY());
break;
case DOWN:
pos.setY(hitPosition.getY() + face.getModY());
break;
case EAST:
case WEST:
pos.setX(hitPosition.getX() + face.getModX() * 0.5);
break;
case NORTH:
case SOUTH:
pos.setZ(hitPosition.getZ() + face.getModZ() * 0.5);
break;
default:
break;
}
}
return pos;
}, (player) -> true),
BLOCK_ALIGNED(0, (player, rayTraceResult) -> blockAlignedPosition(rayTraceResult), (player) -> true);
private final int priority;
private final BiFunction<Player, RayTraceUtils.RRayTraceResult, Vector> positionTransform;
private final Predicate<Player> isActive;
private static Vector blockAlignedPosition(RayTraceUtils.RRayTraceResult rayTraceResult) {
Vector pos = rayTraceResult.getHitPosition();
BlockFace face = rayTraceResult.getHitBlockFace();
if (face != null) {
switch (face) {
case DOWN:
pos.setY(pos.getY() - 0.98);
break;
case EAST:
pos.setX(pos.getX() + 0.49);
break;
case WEST:
pos.setX(pos.getX() - 0.49);
break;
case NORTH:
pos.setZ(pos.getZ() - 0.49);
break;
case SOUTH:
pos.setZ(pos.getZ() + 0.49);
break;
default:
break;
}
}
pos.setX(pos.getBlockX() + 0.5);
if (pos.getY() - pos.getBlockY() != 0 && face == BlockFace.UP) {
pos.setY(pos.getBlockY() + 1.0);
} else {
pos.setY(pos.getBlockY());
}
pos.setZ(pos.getBlockZ() + 0.5);
return pos;
}
}
@FunctionalInterface
public interface ClickHandler {
void onClick(Location location, Optional<REntity> hitEntity, Action action);
}
}
@@ -0,0 +1,56 @@
package de.steamwar.cursor;
import com.comphenix.tinyprotocol.TinyProtocol;
import de.steamwar.core.Core;
import de.steamwar.core.SWPlayer;
import de.steamwar.linkage.Linked;
import lombok.Getter;
import net.minecraft.network.protocol.Packet;
import net.minecraft.network.protocol.game.ServerboundMovePlayerPacket;
import org.bukkit.Bukkit;
import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
import org.bukkit.event.Listener;
import org.bukkit.event.player.PlayerInteractEvent;
import java.util.*;
@Linked
public class CursorListener implements Listener {
@Getter
private static CursorListener instance;
public CursorListener() {
if (instance == null) {
instance = this;
}
TinyProtocol.instance.addFilter(ServerboundMovePlayerPacket.Pos.class, this::updateCursorFromPacket);
TinyProtocol.instance.addFilter(ServerboundMovePlayerPacket.Rot.class, this::updateCursorFromPacket);
TinyProtocol.instance.addFilter(ServerboundMovePlayerPacket.PosRot.class, this::updateCursorFromPacket);
Bukkit.getScheduler().runTaskTimer(Core.getInstance(), () -> {
SWPlayer.allWithSingleComponent(Cursor.class)
.map(SWPlayer.SWPlayerWithComponent::getComponent)
.forEach(Cursor::renderDeduplicated);
}, 1, 1);
}
public Packet<?> updateCursorFromPacket(Player player, Packet<?> packet) {
SWPlayer swPlayer = SWPlayer.of(player);
Optional<Cursor> activeCursor = swPlayer.getComponent(Cursor.class);
activeCursor.ifPresent(Cursor::renderDeduplicated);
return packet;
}
@EventHandler
public void onPlayerInteract(PlayerInteractEvent event) {
SWPlayer.of(event.getPlayer()).getComponent(Cursor.class).ifPresent(cursor -> {
event.setCancelled(true);
cursor.handlePlayerClick(event.getAction());
});
}
}
@@ -1,7 +1,7 @@
/*
* This file is a part of the SteamWar software.
*
* Copyright (C) 2025 SteamWar.de-Serverteam
* Copyright (C) 2026 SteamWar.de-Serverteam
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
@@ -17,7 +17,7 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package de.steamwar.bausystem.utils;
package de.steamwar.cursor;
import de.steamwar.entity.REntity;
import lombok.Data;
@@ -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;
@@ -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,18 +17,18 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package de.steamwar.techhider;
package de.steamwar.entity;
import de.steamwar.Reflection;
import net.minecraft.core.SectionPos;
import net.minecraft.world.level.block.entity.BlockEntityType;
import net.minecraft.world.level.block.entity.SignBlockEntity;
import net.minecraft.world.level.block.state.BlockState;
import org.bukkit.entity.Player;
import java.util.ArrayList;
import java.util.function.BiFunction;
import java.util.function.BiConsumer;
public class ProtocolWrapper {
public static final ProtocolWrapper impl = new ProtocolWrapper();
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;
@@ -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,17 +19,12 @@
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;
import net.minecraft.world.level.material.Fluids;
import org.bukkit.Material;
import org.bukkit.Registry;
import org.bukkit.block.data.BlockData;
import org.bukkit.block.data.Waterlogged;
import org.bukkit.craftbukkit.util.CraftMagicNumbers;
import java.util.HashSet;
@@ -22,30 +22,19 @@ package de.steamwar.techhider;
import de.steamwar.Reflection;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import it.unimi.dsi.fastutil.ints.Int2IntArrayMap;
import it.unimi.dsi.fastutil.ints.Int2IntMap;
import it.unimi.dsi.fastutil.ints.Int2IntOpenHashMap;
import lombok.Getter;
import net.minecraft.core.Registry;
import net.minecraft.core.SectionPos;
import net.minecraft.core.registries.BuiltInRegistries;
import net.minecraft.core.registries.Registries;
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 org.bukkit.craftbukkit.util.CraftMagicNumbers;
import net.minecraft.world.level.block.state.BlockState;
import org.bukkit.entity.Player;
import java.util.*;
import java.util.function.BiFunction;
import java.util.List;
import java.util.function.UnaryOperator;
import java.util.stream.Collectors;
public abstract class ChunkHider {
public class ChunkHider {
private static final UnaryOperator<Object> chunkPacketShallowCloner = ProtocolUtils.shallowCloneGenerator(ClientboundLevelChunkWithLightPacket.class);
private static final UnaryOperator<Object> chunkDataShallowCloner = ProtocolUtils.shallowCloneGenerator(ClientboundLevelChunkPacketData.class);
@@ -61,21 +50,26 @@ public abstract class ChunkHider {
private final int BLOCKS_PER_SECTION = 4096;
private final int BIOMES_PER_SECTION = 64;
private final byte BITS_PER_LONG = 64;
private final int blockIdUsedForHiding;
private final AccessPrivilegeProvider accessPrivilegeProvider;
private final int blockIdUsedForHiding = 121;
public ChunkHider(Block blockUsedForObfuscation, AccessPrivilegeProvider accessPrivilegeProvider) {
blockIdUsedForHiding = Block.BLOCK_STATE_REGISTRY.getId(blockUsedForObfuscation.defaultBlockState());
this.accessPrivilegeProvider = accessPrivilegeProvider;
}
public ChunkHider(Block blockUsedForObfuscation) {
// blockIdUsedForHiding = Block.BLOCK_STATE_REGISTRY.getId(blockUsedForObfuscation.defaultBlockState());
private int getLongsRequiredToEncodeEntries(int bitsPerEntry, int entryCount) {
int entriesPerLong = Long.SIZE / bitsPerEntry;
int dataLengthAsLongCount = (entryCount + entriesPerLong - 1) / entriesPerLong;
return dataLengthAsLongCount;
}
private long[] readSectionDataFromBuffer(ByteBuf dataSource, short bitsPerEntry, int entryCount) {
int entriesPerLong = BITS_PER_LONG / bitsPerEntry;
int dataLengthAsLongCount = (entryCount + entriesPerLong - 1) / entriesPerLong;
int dataLengthAsLongCount = getLongsRequiredToEncodeEntries(bitsPerEntry, entryCount);
long[] dataArray = new long[dataLengthAsLongCount];
for(int i = 0; i < dataLengthAsLongCount; i++){
for (int i = 0; i < dataLengthAsLongCount; i++) {
dataArray[i] = dataSource.readLong();
}
@@ -98,64 +92,49 @@ public abstract class ChunkHider {
byte bitsPerBlock = in.readByte();
if(bitsPerBlock == 0) {
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) {
} else if (bitsPerBlock <= BIT_PER_BLOCK_INDIRECTION_LIMIT) {
int palletLength = ProtocolUtils.readVarInt(in);
int[] pallet = ProtocolUtils.readVarIntArray(in, palletLength);
System.out.println(Arrays.toString(pallet));
long[] rawData = readSectionDataFromBuffer(in, bitsPerBlock, BLOCKS_PER_SECTION);
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++){
for (int i = 0; i < BLOCKS_PER_SECTION; i++) {
int palletReference = data.get(i);
resolvedData[i] = pallet[palletReference];
}
int[] obfuscatedData = new int[BLOCKS_PER_SECTION];
for (int sectionY = 0; sectionY < 16; sectionY++) {
for (int sectionZ = 0; sectionZ < 16; sectionZ++) {
for (int sectionX = 0; sectionX < 16; sectionX++) {
int blockDataIndex = (((sectionY * 16) + sectionZ) * 16) + sectionX;
int[] obfuscatedBlockData = obfuscateBlockDataArray(player, chunkX, chunkZ, yOffset, resolvedData);
int worldX = sectionX * chunkX;
int worldY = sectionY * yOffset;
int worldZ = sectionZ * chunkZ;
long[] reEncodedData = encodeDirectBlockDataArray(obfuscatedBlockData);
int blockId = resolvedData[blockDataIndex];
System.out.println("DEV - " + blockId);
Block block = BuiltInRegistries.BLOCK.get(blockId).get().value();
if(isPlayerPrivilegedToAccessPosition(player, worldX, worldY, worldZ) && isPlayerPrivilegedToAccessBlock(player, worldX, worldY, worldZ, block)) {
obfuscatedData[blockDataIndex] = blockId;
}
else {
obfuscatedData[blockDataIndex] = blockIdUsedForHiding;
}
}
}
out.writeShort(blockCount);
out.writeByte(15);
for (long rawDataSegment : reEncodedData) {
out.writeLong(rawDataSegment);
}
/*
Int2IntMap blockIdToPalletIndex = new Int2IntOpenHashMap();
int[] newPallet = new int[palletLength];
int runningIndex = 0;
IntArrayList newPallet = new IntArrayList();
for(int blockId : obfuscatedData) {
if(!blockIdToPalletIndex.containsKey(blockId)) {
newPallet[runningIndex] = blockId;
blockIdToPalletIndex.put(blockId, runningIndex);
runningIndex++;
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];
@@ -164,56 +143,27 @@ public abstract class ChunkHider {
reEncodedData.set(i, palletReference);
}
out.writeByte(blockCount);
out.writeByte(bitsPerBlock);
out.writeShort(blockCount);
out.writeByte(newBitsPerBlock);
ProtocolUtils.writeVarInt(out, palletLength);
ProtocolUtils.writeVarIntArray(out, pallet);
ProtocolUtils.writeVarIntArray(out, newPalletRaw);
for(long rawDataSegment : reEncodedData.getRaw()) {
out.writeLong(rawDataSegment);
}
}
else {
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++){
resolvedData[i] = data.get(i);
}
} else {
long[] rawData = readSectionDataFromBuffer(in, bitsPerBlock, BLOCKS_PER_SECTION);
int[] obfuscatedData = new int[BLOCKS_PER_SECTION];
for (int sectionY = 0; sectionY < 16; sectionY++) {
for (int sectionZ = 0; sectionZ < 16; sectionZ++) {
for (int sectionX = 0; sectionX < 16; sectionX++) {
int blockDataIndex = (((sectionY * 16) + sectionZ) * 16) + sectionX;
int[] blockData = resolveDirectBlockDataArray(rawData, bitsPerBlock);
int worldX = sectionX * chunkX;
int worldY = sectionY * yOffset;
int worldZ = sectionZ * chunkZ;
int[] obfuscatedBlockData = obfuscateBlockDataArray(player, chunkX, chunkZ, yOffset, blockData);
int blockId = resolvedData[blockDataIndex];
Block block = BuiltInRegistries.BLOCK.get(blockId).get().value();
long[] reEncodedData = encodeDirectBlockDataArray(obfuscatedBlockData);
if(isPlayerPrivilegedToAccessPosition(player, worldX, worldY, worldZ) && isPlayerPrivilegedToAccessBlock(player, worldX, worldY, worldZ, block)) {
obfuscatedData[blockDataIndex] = blockId;
}
else {
obfuscatedData[blockDataIndex] = blockIdUsedForHiding;
}
}
}
}
SimpleBitStorage reEncodedData = new SimpleBitStorage(bitsPerBlock, BLOCKS_PER_SECTION, new long[rawData.length]);
for(int i = 0; i < obfuscatedData.length; i++) {
int blockId = obfuscatedData[i];
reEncodedData.set(i, blockId);
}
out.writeByte(blockCount);
out.writeByte(bitsPerBlock);
for(long rawDataSegment : reEncodedData.getRaw()) {
out.writeShort(blockCount);
out.writeByte(15);
for (long rawDataSegment : reEncodedData) {
out.writeLong(rawDataSegment);
}
}
@@ -229,15 +179,63 @@ public abstract class ChunkHider {
out.readBytes(data);
List<Object> blockEntities = chunkBlockEntitiesDataField.get(chunkData);
List<Object> filteredBlockEntities = filterBlockEntities(player, blockEntities);
List<Object> filteredBlockEntities = filterBlockEntities(player, blockEntities, chunkX, chunkZ);
return buildNewChunkPacket(packet, data, filteredBlockEntities);
}
public abstract boolean isPlayerPrivilegedToAccessPosition(Player p, int blockX, int blockY, int blockZ);
public abstract boolean isPlayerPrivilegedToAccessBlock(Player p, int blockX, int blockY, int blockZ, Block block);
public abstract boolean isPlayerPrivilegedToAccessBlockEntity(Player p, int blockX, int blockY, int blockZ, BlockEntityType<?> type);
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;
}
}
}
}
return obfuscatedData;
}
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);
@@ -248,7 +246,7 @@ public abstract class ChunkHider {
levelChunkPacketDataField.set(clonedPacket, clonedPacketChunkData);
return clonedPacket;
return clonedPacket;
}
@@ -257,49 +255,52 @@ public abstract class ChunkHider {
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) {
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 y = yField.get(blockEntityInfo);
int x = SectionPos.sectionRelativeX((short) packedXZ);
int z = SectionPos.sectionRelativeZ((short) packedXZ);
int localX = (packedXZ >> 4) & fourBitBitmask;
int localZ = packedXZ & fourBitBitmask;
return isPlayerPrivilegedToAccessPosition(player, x, y, z) && isPlayerPrivilegedToAccessBlockEntity(player, x, y, z, type);
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.readShort();
newData.writeShort(bitsPerBiome);
System.out.println(bitsPerBiome);
if(bitsPerBiome == 0) {
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) {
} else if (bitsPerBiome <= BIT_PER_BIOME_INDIRECTION_LIMIT) {
int palletLength = ProtocolUtils.readVarInt(oldData);
ProtocolUtils.writeVarInt(newData, palletLength);
for(int i = 0; i < palletLength; i++) {
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 ) {
long[] rawData = readSectionDataFromBuffer(oldData, bitsPerBiome, BIOMES_PER_SECTION);
for (long rawDataSegment : rawData) {
newData.writeLong(rawDataSegment);
}
} else {
long[] rawData = readSectionDataFromBuffer(oldData, bitsPerBiome, BIOMES_PER_SECTION);
for (long rawDataSegment : rawData) {
newData.writeLong(rawDataSegment);
}
}
else {
long[] rawData = readSectionDataFromBuffer(oldData, bitsPerBiome, BIOMES_PER_SECTION);
for(long rawDataSegment : rawData ) {
newData.writeLong(rawDataSegment);
}
}
}
}
@@ -143,7 +143,7 @@ public class ProtocolUtils {
public static int[] readVarIntArray(ByteBuf buffer, int length) {
int[] array = new int[length];
for(int i = 0; i < length; i++) {
for (int i = 0; i < length; i++) {
array[i] = readVarInt(buffer);
}
@@ -162,8 +162,8 @@ public class ProtocolUtils {
} while (value != 0);
}
public static void writeVarIntArray(ByteBuf buf, int[] values){
for(int varInt : values) {
public static void writeVarIntArray(ByteBuf buf, int[] values) {
for (int varInt : values) {
writeVarInt(buf, varInt);
}
}
@@ -1,207 +1,73 @@
/*
* This file is a part of the SteamWar software.
*
* Copyright (C) 2026 SteamWar.de-Serverteam
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package de.steamwar.techhider;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.function.BiFunction;
import java.util.function.Function;
import java.util.function.ToIntFunction;
import com.comphenix.tinyprotocol.TinyProtocol;
import de.steamwar.Reflection;
import it.unimi.dsi.fastutil.ints.IntArrayList;
import it.unimi.dsi.fastutil.ints.IntList;
import it.unimi.dsi.fastutil.shorts.ShortArraySet;
import it.unimi.dsi.fastutil.shorts.ShortSets;
import net.minecraft.network.UnconfiguredPipelineHandler;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.world.level.block.entity.BlockEntityType;
import net.minecraft.world.phys.Vec3;
import org.bukkit.entity.Player;
import com.comphenix.tinyprotocol.TinyProtocol;
import de.steamwar.Reflection;
import net.minecraft.core.BlockPos;
import net.minecraft.core.SectionPos;
import net.minecraft.network.PacketListener;
import net.minecraft.network.protocol.Packet;
import net.minecraft.network.protocol.handshake.ClientIntentionPacket;
import net.minecraft.network.protocol.common.ClientboundClearDialogPacket;
import net.minecraft.network.protocol.common.ClientboundCustomPayloadPacket;
import net.minecraft.network.protocol.common.ClientboundCustomReportDetailsPacket;
import net.minecraft.network.protocol.common.ClientboundDisconnectPacket;
import net.minecraft.network.protocol.common.ClientboundKeepAlivePacket;
import net.minecraft.network.protocol.common.ClientboundPingPacket;
import net.minecraft.network.protocol.common.ClientboundResourcePackPopPacket;
import net.minecraft.network.protocol.common.ClientboundResourcePackPushPacket;
import net.minecraft.network.protocol.common.ClientboundServerLinksPacket;
import net.minecraft.network.protocol.common.ClientboundShowDialogPacket;
import net.minecraft.network.protocol.common.ClientboundStoreCookiePacket;
import net.minecraft.network.protocol.common.ClientboundTransferPacket;
import net.minecraft.network.protocol.common.ClientboundUpdateTagsPacket;
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.ClientboundAddEntityPacket;
import net.minecraft.network.protocol.game.ClientboundAnimatePacket;
import net.minecraft.network.protocol.game.ClientboundBlockChangedAckPacket;
import net.minecraft.network.protocol.game.ClientboundBlockDestructionPacket;
import net.minecraft.network.protocol.game.ClientboundBlockEntityDataPacket;
import net.minecraft.network.protocol.game.ClientboundBlockEventPacket;
import net.minecraft.network.protocol.game.ClientboundBlockUpdatePacket;
import net.minecraft.network.protocol.game.ClientboundBossEventPacket;
import net.minecraft.network.protocol.game.ClientboundBundleDelimiterPacket;
import net.minecraft.network.protocol.game.ClientboundBundlePacket;
import net.minecraft.network.protocol.game.ClientboundChangeDifficultyPacket;
import net.minecraft.network.protocol.game.ClientboundChunkBatchFinishedPacket;
import net.minecraft.network.protocol.game.ClientboundChunkBatchStartPacket;
import net.minecraft.network.protocol.game.ClientboundClearTitlesPacket;
import net.minecraft.network.protocol.game.ClientboundCommandSuggestionsPacket;
import net.minecraft.network.protocol.game.ClientboundCommandsPacket;
import net.minecraft.network.protocol.game.ClientboundContainerClosePacket;
import net.minecraft.network.protocol.game.ClientboundContainerSetContentPacket;
import net.minecraft.network.protocol.game.ClientboundContainerSetDataPacket;
import net.minecraft.network.protocol.game.ClientboundContainerSetSlotPacket;
import net.minecraft.network.protocol.game.ClientboundCooldownPacket;
import net.minecraft.network.protocol.game.ClientboundCustomChatCompletionsPacket;
import net.minecraft.network.protocol.game.ClientboundDamageEventPacket;
import net.minecraft.network.protocol.game.ClientboundDeleteChatPacket;
import net.minecraft.network.protocol.game.ClientboundDisguisedChatPacket;
import net.minecraft.network.protocol.game.ClientboundEntityEventPacket;
import net.minecraft.network.protocol.game.ClientboundEntityPositionSyncPacket;
import net.minecraft.network.protocol.game.ClientboundExplodePacket;
import net.minecraft.network.protocol.game.ClientboundForgetLevelChunkPacket;
import net.minecraft.network.protocol.game.ClientboundGameEventPacket;
import net.minecraft.network.protocol.game.ClientboundHorseScreenOpenPacket;
import net.minecraft.network.protocol.game.ClientboundHurtAnimationPacket;
import net.minecraft.network.protocol.game.ClientboundInitializeBorderPacket;
import net.minecraft.network.protocol.game.ClientboundLevelChunkWithLightPacket;
import net.minecraft.network.protocol.game.ClientboundLevelEventPacket;
import net.minecraft.network.protocol.game.ClientboundLevelParticlesPacket;
import net.minecraft.network.protocol.game.ClientboundLightUpdatePacket;
import net.minecraft.network.protocol.game.ClientboundLoginPacket;
import net.minecraft.network.protocol.game.ClientboundMapItemDataPacket;
import net.minecraft.network.protocol.game.ClientboundMerchantOffersPacket;
import net.minecraft.network.protocol.game.ClientboundMoveEntityPacket;
import net.minecraft.network.protocol.game.ClientboundMoveMinecartPacket;
import net.minecraft.network.protocol.game.ClientboundMoveVehiclePacket;
import net.minecraft.network.protocol.game.ClientboundOpenBookPacket;
import net.minecraft.network.protocol.game.ClientboundOpenScreenPacket;
import net.minecraft.network.protocol.game.ClientboundOpenSignEditorPacket;
import net.minecraft.network.protocol.game.ClientboundPlaceGhostRecipePacket;
import net.minecraft.network.protocol.game.ClientboundPlayerAbilitiesPacket;
import net.minecraft.network.protocol.game.ClientboundPlayerChatPacket;
import net.minecraft.network.protocol.game.ClientboundPlayerCombatEndPacket;
import net.minecraft.network.protocol.game.ClientboundPlayerCombatEnterPacket;
import net.minecraft.network.protocol.game.ClientboundPlayerCombatKillPacket;
import net.minecraft.network.protocol.game.ClientboundPlayerInfoRemovePacket;
import net.minecraft.network.protocol.game.ClientboundPlayerInfoUpdatePacket;
import net.minecraft.network.protocol.game.ClientboundPlayerLookAtPacket;
import net.minecraft.network.protocol.game.ClientboundPlayerPositionPacket;
import net.minecraft.network.protocol.game.ClientboundPlayerRotationPacket;
import net.minecraft.network.protocol.game.ClientboundProjectilePowerPacket;
import net.minecraft.network.protocol.game.ClientboundRecipeBookAddPacket;
import net.minecraft.network.protocol.game.ClientboundRecipeBookRemovePacket;
import net.minecraft.network.protocol.game.ClientboundRecipeBookSettingsPacket;
import net.minecraft.network.protocol.game.ClientboundRemoveEntitiesPacket;
import net.minecraft.network.protocol.game.ClientboundRemoveMobEffectPacket;
import net.minecraft.network.protocol.game.ClientboundResetScorePacket;
import net.minecraft.network.protocol.game.ClientboundRespawnPacket;
import net.minecraft.network.protocol.game.ClientboundRotateHeadPacket;
import net.minecraft.network.protocol.game.ClientboundSectionBlocksUpdatePacket;
import net.minecraft.network.protocol.game.ClientboundSelectAdvancementsTabPacket;
import net.minecraft.network.protocol.game.ClientboundSetActionBarTextPacket;
import net.minecraft.network.protocol.game.ClientboundSetBorderCenterPacket;
import net.minecraft.network.protocol.game.ClientboundSetBorderLerpSizePacket;
import net.minecraft.network.protocol.game.ClientboundSetBorderSizePacket;
import net.minecraft.network.protocol.game.ClientboundSetBorderWarningDelayPacket;
import net.minecraft.network.protocol.game.ClientboundSetBorderWarningDistancePacket;
import net.minecraft.network.protocol.game.ClientboundSetCameraPacket;
import net.minecraft.network.protocol.game.ClientboundSetChunkCacheCenterPacket;
import net.minecraft.network.protocol.game.ClientboundSetChunkCacheRadiusPacket;
import net.minecraft.network.protocol.game.ClientboundSetCursorItemPacket;
import net.minecraft.network.protocol.game.ClientboundServerDataPacket;
import net.minecraft.network.protocol.game.ClientboundSetDefaultSpawnPositionPacket;
import net.minecraft.network.protocol.game.ClientboundSetDisplayObjectivePacket;
import net.minecraft.network.protocol.game.ClientboundSetEntityDataPacket;
import net.minecraft.network.protocol.game.ClientboundSetEntityLinkPacket;
import net.minecraft.network.protocol.game.ClientboundSetEntityMotionPacket;
import net.minecraft.network.protocol.game.ClientboundSetEquipmentPacket;
import net.minecraft.network.protocol.game.ClientboundSetExperiencePacket;
import net.minecraft.network.protocol.game.ClientboundSetHeldSlotPacket;
import net.minecraft.network.protocol.game.ClientboundSetHealthPacket;
import net.minecraft.network.protocol.game.ClientboundSetObjectivePacket;
import net.minecraft.network.protocol.game.ClientboundSetPassengersPacket;
import net.minecraft.network.protocol.game.ClientboundSetPlayerInventoryPacket;
import net.minecraft.network.protocol.game.ClientboundSetPlayerTeamPacket;
import net.minecraft.network.protocol.game.ClientboundSetScorePacket;
import net.minecraft.network.protocol.game.ClientboundSetSimulationDistancePacket;
import net.minecraft.network.protocol.game.ClientboundSetSubtitleTextPacket;
import net.minecraft.network.protocol.game.ClientboundSetTimePacket;
import net.minecraft.network.protocol.game.ClientboundSetTitleTextPacket;
import net.minecraft.network.protocol.game.ClientboundSetTitlesAnimationPacket;
import net.minecraft.network.protocol.game.ClientboundSoundEntityPacket;
import net.minecraft.network.protocol.game.ClientboundSoundPacket;
import net.minecraft.network.protocol.game.ClientboundStartConfigurationPacket;
import net.minecraft.network.protocol.game.ClientboundStopSoundPacket;
import net.minecraft.network.protocol.game.ClientboundSystemChatPacket;
import net.minecraft.network.protocol.game.ClientboundTabListPacket;
import net.minecraft.network.protocol.game.ClientboundTagQueryPacket;
import net.minecraft.network.protocol.game.ClientboundTakeItemEntityPacket;
import net.minecraft.network.protocol.game.ClientboundTeleportEntityPacket;
import net.minecraft.network.protocol.game.ClientboundTickingStatePacket;
import net.minecraft.network.protocol.game.ClientboundTickingStepPacket;
import net.minecraft.network.protocol.game.ClientboundTrackedWaypointPacket;
import net.minecraft.network.protocol.game.ClientboundUpdateAdvancementsPacket;
import net.minecraft.network.protocol.game.ClientboundUpdateAttributesPacket;
import net.minecraft.network.protocol.game.ClientboundUpdateMobEffectPacket;
import net.minecraft.network.protocol.game.ClientboundUpdateRecipesPacket;
import net.minecraft.network.protocol.login.ClientboundCustomQueryPacket;
import net.minecraft.network.protocol.login.ClientboundHelloPacket;
import net.minecraft.network.protocol.login.ClientboundLoginCompressionPacket;
import net.minecraft.network.protocol.login.ClientboundLoginDisconnectPacket;
import net.minecraft.network.protocol.login.ClientboundLoginFinishedPacket;
import net.minecraft.network.protocol.configuration.ClientboundSelectKnownPacks;
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 net.minecraft.world.phys.Vec3;
import org.bukkit.entity.Player;
import java.util.*;
import java.util.function.BiFunction;
import java.util.function.Function;
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 abstract class TechHider {
public class TechHider {
private final Set<Class<?>> bypassingPackets;
private final Map<Class<? extends Packet<? extends PacketListener>>, BiFunction<Player, Packet<? extends PacketListener>, Packet<? extends PacketListener>>> packetProcessors;
private final Block blockUsedForObfuscation;
private final BlockState blockStateUsedForObfuscation;
private ChunkHider chunkHider;
private final ChunkHider chunkHider;
private final AccessPrivilegeProvider privilegeProvider;
// TODO handle packet bundle
public TechHider(Block blockUsedForObfuscation) {
this.blockUsedForObfuscation = blockUsedForObfuscation;
public TechHider(Block blockUsedForObfuscation, AccessPrivilegeProvider privilegeProvider) {
this.blockStateUsedForObfuscation = blockUsedForObfuscation.defaultBlockState();
this.chunkHider = new ChunkHider(blockUsedForObfuscation) {
@Override
public boolean isPlayerPrivilegedToAccessBlock(Player p, int blockX, int blockY, int blockZ, Block block) {
return TechHider.this.isPlayerPrivilegedToAccessBlock(p, blockX, blockY, blockZ, block);
}
@Override
public boolean isPlayerPrivilegedToAccessBlockEntity(Player p, int blockX, int blockY, int blockZ, BlockEntityType<?> type) {
return TechHider.this.isPlayerPrivilegedToAccessBlocEntity(p, blockX, blockY, blockZ, type);
}
@Override
public boolean isPlayerPrivilegedToAccessPosition(Player p, int blockX, int blockY, int blockZ) {
return TechHider.this.isPlayerPrivilegedToAccessPosition(p, blockX, blockY, blockZ);
}
};
this.chunkHider = new ChunkHider(blockUsedForObfuscation, privilegeProvider);
this.privilegeProvider = privilegeProvider;
this.bypassingPackets = new HashSet<>(List.of(
// --- 5.1.x Login Protocol ---
@@ -213,8 +79,7 @@ public abstract class TechHider {
ClientboundCookieRequestPacket.class, // 5.1.6 Cookie Request
// --- 6.1.x Configuration Protocol ---
ClientboundSelectKnownPacks.class,
ClientboundCustomPayloadPacket.class, // 6.1.2 Clientbound Plugin Message
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
@@ -227,7 +92,7 @@ public abstract class TechHider {
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
// Payloads
ClientboundServerDataPacket.class, // 6.1.20 Code of Conduct is usually in Server Data
// --- 7.1.x Play Protocol ---
@@ -287,7 +152,7 @@ public abstract class TechHider {
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
// 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
@@ -312,23 +177,26 @@ public abstract class TechHider {
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
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)
ClientboundMoveVehiclePacket.class, // 7.1.56 Move Vehicle (vehicle the player is in)
ClientboundStopSoundPacket.class, // 7.1.118 Stop Sound: sound state side channel
ClientboundLightUpdatePacket.class, // 7.1.48 Update Light
ClientboundContainerSetContentPacket.class, // 7.1.19 Set Container Content
ClientboundContainerSetDataPacket.class, // 7.1.20 Set Container Property
ClientboundContainerSetSlotPacket.class // 7.1.21 Set Container Slot
));
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<>();
// 7.1.x Bundle Packet: packet container; blocked until nested packets are
// safely unbundled/processed.
processors.put(ClientboundBundlePacket.class, tossPacket);
// --- 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));
@@ -363,11 +231,15 @@ public abstract class TechHider {
// 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));
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.
@@ -401,25 +273,13 @@ public abstract class TechHider {
// 7.1.47 Particle: particle type and position can reveal hidden machinery.
processors.put(ClientboundLevelParticlesPacket.class, (p, packet) -> processLevelParticlesPacket(p, (ClientboundLevelParticlesPacket) packet));
// --- Lighting packets ---
// 7.1.48 Update Light: lighting can reveal hidden blocks/operations.
processors.put(ClientboundLightUpdatePacket.class, tossPacket);
// --- Container packets ---
// 7.1.19 Set Container Content
processors.put(ClientboundContainerSetContentPacket.class, buildContainerPacketProcessor(ClientboundContainerSetContentPacket::containerId));
// 7.1.20 Set Container Property
processors.put(ClientboundContainerSetDataPacket.class, buildContainerPacketProcessor(ClientboundContainerSetDataPacket::getContainerId));
// 7.1.21 Set Container Slot
processors.put(ClientboundContainerSetSlotPacket.class, buildContainerPacketProcessor(ClientboundContainerSetSlotPacket::getContainerId));
// --- Sound packets ---
// 7.1.115 Entity Sound Effect: entity id and sound.
processors.put(ClientboundSoundEntityPacket.class, tossPacket);
processors.put(ClientboundSoundEntityPacket.class, this.buildEntityPacketProcessor(ClientboundSoundEntityPacket::getId));
// 7.1.116 Sound Effect: sound type and position.
processors.put(ClientboundSoundPacket.class, tossPacket);
// 7.1.118 Stop Sound: sound state side channel.
processors.put(ClientboundStopSoundPacket.class, tossPacket);
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.
@@ -429,7 +289,7 @@ public abstract class TechHider {
// 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(ClientboundLevelEventPacket::getPos));
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;
@@ -439,31 +299,50 @@ public abstract class TechHider {
int blockZ = (int) pos.z;
return proccessPositionBasedPacket(p, blockX, blockY, blockZ, packet);
return proccessPositionBasedPacket(p, blockX, blockY, blockZ, packet);
});
this.packetProcessors = processors;
TinyProtocol.instance.addGlobalClientboundFilter((player, packet) -> {
System.out.println(packet.getClass().toString());
TinyProtocol.instance.addGlobalClientboundFilter((player, rawPacket) -> {
try {
if (rawPacket instanceof ClientboundBundlePacket bundlePacket) {
List<Packet<? super ClientGamePacketListener>> processedPackets = new ArrayList<>();
if(bypassingPackets.contains(packet.getClass())) {
System.out.println("bypassing");
return packet;
}
else if(packetProcessors.containsKey(packet.getClass())) {
System.out.println("processing");
return packetProcessors.get(packet.getClass()).apply(player, (Packet<? extends PacketListener>) packet);
}
else {
System.out.println("dropping");
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);
}
} 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;
}
}
private Packet<? extends PacketListener> processAddEntityPacket(Player player, ClientboundAddEntityPacket packet) {
if(isPlayerPrivilegedToAccessEntity(player, packet.getId()) && isPlayerPrivilegedToAccessPosition(player, (int) packet.getX(), (int) packet.getY(), (int) packet.getZ())) {
if (privilegeProvider.isPlayerPrivilegedToAccessEntity(player, packet.getId()) && privilegeProvider.isPlayerPrivilegedToAccessPosition(player, (int) packet.getX(), (int) packet.getY(), (int) packet.getZ())) {
return packet;
} else {
return null;
@@ -471,13 +350,14 @@ public abstract class TechHider {
}
private Packet<? extends PacketListener> processEntityPacket(Player player, int entityId, Packet<? extends PacketListener> packet) {
if(isPlayerPrivilegedToAccessEntity(player, entityId)) {
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) {
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);
@@ -485,25 +365,25 @@ public abstract class TechHider {
}
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(isPlayerPrivilegedToAccessEntity(player, entityId)) {
if (privilegeProvider.isPlayerPrivilegedToAccessEntity(player, entityId)) {
return packet;
}
else {
} else {
return null;
}
}
private final Reflection.Field<Integer> rotateHeadPacketEntityIdField = Reflection.getField(ClientboundRotateHeadPacket.class, int.class, 0);
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(isPlayerPrivilegedToAccessEntity(player, entityId)) {
if (privilegeProvider.isPlayerPrivilegedToAccessEntity(player, entityId)) {
return packet;
}
else {
} else {
return null;
}
}
@@ -511,9 +391,7 @@ public abstract class TechHider {
private Packet<?> processRemoveEntitiesPacket(Player player, ClientboundRemoveEntitiesPacket packet) {
IntList entityIdsToRemove = packet.getEntityIds();
IntList filteredEntitiesToRemove = entityIdsToRemove.intStream()
.filter((id) -> isPlayerPrivilegedToAccessEntity(player, id))
.collect(IntArrayList::new, IntList::add, IntList::addAll);
IntList filteredEntitiesToRemove = entityIdsToRemove.intStream().filter((id) -> privilegeProvider.isPlayerPrivilegedToAccessEntity(player, id)).collect(IntArrayList::new, IntList::add, IntList::addAll);
return new ClientboundRemoveEntitiesPacket(filteredEntitiesToRemove);
}
@@ -522,10 +400,9 @@ public abstract class TechHider {
int fromEntityId = packet.getSourceId();
int toEntityId = packet.getDestId();
if(isPlayerPrivilegedToAccessEntity(player, fromEntityId) && isPlayerPrivilegedToAccessEntity(player, toEntityId)) {
if (privilegeProvider.isPlayerPrivilegedToAccessEntity(player, fromEntityId) && privilegeProvider.isPlayerPrivilegedToAccessEntity(player, toEntityId)) {
return packet;
}
else {
} else {
return null;
}
}
@@ -537,7 +414,7 @@ public abstract class TechHider {
int blockY = blockPos.getY();
int blockZ = blockPos.getZ();
if (isPlayerPrivilegedToAccessPosition(player, blockX, blockY, blockZ) && isPlayerPrivilegedToAccessBlock(player, blockX, blockY, blockZ, block)) {
if (privilegeProvider.isPlayerPrivilegedToAccessPosition(player, blockX, blockY, blockZ) && privilegeProvider.isPlayerPrivilegedToAccessBlock(player, blockX, blockY, blockZ, block)) {
return packet;
} else {
return null;
@@ -546,14 +423,15 @@ public abstract class TechHider {
private Packet<?> processBlockUpdatePacket(Player player, ClientboundBlockUpdatePacket packet) {
BlockPos blockPos = packet.getPos();
Block block = packet.getBlockState().getBlock();
BlockState blockState = packet.getBlockState();
Block block = blockState.getBlock();
int blockX = blockPos.getX();
int blockY = blockPos.getY();
int blockZ = blockPos.getZ();
if (isPlayerPrivilegedToAccessPosition(player, blockX, blockY, blockZ) && isPlayerPrivilegedToAccessBlock(player, blockX, blockY, blockZ, block)) {
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(isPlayerPrivilegedToAccessPosition(player, blockX, blockY, blockZ)) {
} else if (privilegeProvider.isPlayerPrivilegedToAccessPosition(player, blockX, blockY, blockZ)) {
return new ClientboundBlockUpdatePacket(blockPos, blockStateUsedForObfuscation);
} else {
return null;
@@ -567,7 +445,7 @@ public abstract class TechHider {
int blockY = blockPos.getY();
int blockZ = blockPos.getZ();
if (isPlayerPrivilegedToAccessPosition(player, blockX, blockY, blockZ) && isPlayerPrivilegedToAccessBlocEntity(player, blockX, blockY, blockZ, blockEntityType)) {
if (privilegeProvider.isPlayerPrivilegedToAccessPosition(player, blockX, blockY, blockZ) && privilegeProvider.isPlayerPrivilegedToAccessBlockEntity(player, blockX, blockY, blockZ, blockEntityType)) {
return packet;
} else {
return null;
@@ -578,10 +456,9 @@ public abstract class TechHider {
BlockPos blockPos = packet.getPos();
int entityBreakingBlockId = packet.getId();
if(isPlayerPrivilegedToAccessEntity(player, entityBreakingBlockId) && isPlayerPrivilegedToAccessPosition(player, blockPos.getX(), blockPos.getY(), blockPos.getZ())) {
if (privilegeProvider.isPlayerPrivilegedToAccessEntity(player, entityBreakingBlockId) && privilegeProvider.isPlayerPrivilegedToAccessPosition(player, blockPos.getX(), blockPos.getY(), blockPos.getZ())) {
return packet;
}
else {
} else {
return null;
}
}
@@ -589,12 +466,12 @@ public abstract class TechHider {
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);
boolean modified = false;
List<Short> filteredPos = new ArrayList<>(oldPos.length);
List<BlockState> filteredStates = new ArrayList<>(oldStates.length);
@@ -607,10 +484,10 @@ public abstract class TechHider {
int worldY = sectionPos.relativeToBlockY(posShort);
int worldZ = sectionPos.relativeToBlockZ(posShort);
if (isPlayerPrivilegedToAccessPosition(p, worldX, worldY, worldZ) && isPlayerPrivilegedToAccessBlock(p, worldX, worldY, worldZ, block)) {
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(isPlayerPrivilegedToAccessPosition(p, worldX, worldY, worldZ)){
} else if (privilegeProvider.isPlayerPrivilegedToAccessPosition(p, worldX, worldY, worldZ)) {
filteredPos.add(posShort);
filteredStates.add(blockStateUsedForObfuscation);
}
@@ -619,9 +496,6 @@ public abstract class TechHider {
if (filteredStates.isEmpty()) {
return null;
}
if (!modified) {
return packet;
}
short[] newPos = new short[filteredPos.size()];
for (int i = 0; i < newPos.length; i++) {
@@ -630,11 +504,7 @@ public abstract class TechHider {
BlockState[] newStates = filteredStates.toArray(new BlockState[0]);
return new ClientboundSectionBlocksUpdatePacket(
sectionPos,
ShortSets.unmodifiable(new ShortArraySet(newPos)),
newStates
);
return new ClientboundSectionBlocksUpdatePacket(sectionPos, ShortSets.unmodifiable(new ShortArraySet(newPos)), newStates);
}
private Packet<?> processLevelParticlesPacket(Player player, ClientboundLevelParticlesPacket packet) {
@@ -642,40 +512,29 @@ public abstract class TechHider {
int blockY = (int) packet.getY();
int blockZ = (int) packet.getZ();
if(isPlayerPrivilegedToAccessPosition(player, blockX, blockY, blockZ)) {
if (privilegeProvider.isPlayerPrivilegedToAccessPosition(player, blockX, blockY, blockZ)) {
return packet;
}
else {
} else {
return null;
}
}
private ClientboundLevelChunkWithLightPacket processChunkWithLight(Player p, ClientboundLevelChunkWithLightPacket packet) {
return chunkHider.processLevelChunkWithLightPacket(p, packet);
}
private Packet<? extends PacketListener> processContainerPacket(Player player, int containerId, Packet<? extends PacketListener> packet) {
if(isPlayerPrivilegedToAccessContainer(player, containerId)) {
if (privilegeProvider.isEveryonePrivilegedToAccessAllDataWithinChunk(packet.getX(), packet.getZ())) {
return packet;
} else {
return null;
return chunkHider.processLevelChunkWithLightPacket(p, packet);
}
}
private <TTargetPacket extends Packet<?>> BiFunction<Player, Packet<?>, Packet<?>> buildContainerPacketProcessor(ToIntFunction<TTargetPacket> containerIdExtractor) {
return (p, rawPacket) -> {
TTargetPacket packet = (TTargetPacket) rawPacket;
return processContainerPacket(p, containerIdExtractor.applyAsInt(packet), packet);
};
}
private Packet<? extends PacketListener> proccessPositionBasedPacket(Player player, int blockX, int blockY, int blockZ, Packet<? extends PacketListener> packet) {
if(isPlayerPrivilegedToAccessPosition(player, blockX, blockY, blockZ)) {
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;
@@ -687,11 +546,4 @@ public abstract class TechHider {
return proccessPositionBasedPacket(p, blockX, blockY, blockZ, packet);
};
}
public abstract boolean isPlayerPrivilegedToAccessPosition(Player p, int blockX, int blockY, int blockZ);
public abstract boolean isPlayerPrivilegedToAccessBlock(Player p, int blockX, int blockY, int blockZ, Block block);
public abstract boolean isPlayerPrivilegedToAccessEntity(Player p, int entityId);
public abstract boolean isPlayerPrivilegedToAccessBlocEntity(Player p, int blockX, int blockY, int blockZ, BlockEntityType<?> type);
public abstract boolean isPlayerPrivilegedToAccessContainer(Player p, int containerId);
public abstract boolean isPlayerPrivilegedToAccessSound(Player p, ResourceLocation soundId);
}
@@ -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);
}
}
}
}
@@ -0,0 +1,92 @@
/*
* 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 net.minecraft.core.SectionPos;
import net.minecraft.world.level.block.entity.BlockEntityType;
import net.minecraft.world.level.block.entity.SignBlockEntity;
import net.minecraft.world.level.block.state.BlockState;
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();
private static final Reflection.Field<SectionPos> multiBlockChangeChunk = Reflection.getField(TechHider.multiBlockChangePacket, SectionPos.class, 0);
private static final Reflection.Field<short[]> multiBlockChangePos = Reflection.getField(TechHider.multiBlockChangePacket, short[].class, 0);
private static final Reflection.Field<BlockState[]> multiBlockChangeBlocks = Reflection.getField(TechHider.multiBlockChangePacket, BlockState[].class, 0);
public BiFunction<Player, Object, Object> multiBlockChangeGenerator(TechHider techHider) {
return (p, packet) -> {
TechHider.LocationEvaluator locationEvaluator = techHider.getLocationEvaluator();
Object chunkCoords = multiBlockChangeChunk.get(packet);
int chunkX = TechHider.blockPositionX.get(chunkCoords);
int chunkY = TechHider.blockPositionY.get(chunkCoords);
int chunkZ = TechHider.blockPositionZ.get(chunkCoords);
if (locationEvaluator.skipChunkSection(p, chunkX, chunkY, chunkZ)) {
return packet;
}
packet = TechHider.multiBlockChangeCloner.apply(packet);
final short[] oldPos = multiBlockChangePos.get(packet);
final BlockState[] oldBlocks = multiBlockChangeBlocks.get(packet);
ArrayList<Short> poss = new ArrayList<>(oldPos.length);
ArrayList<BlockState> blocks = new ArrayList<>(oldPos.length);
for (int i = 0; i < oldPos.length; i++) {
short pos = oldPos[i];
BlockState block = oldBlocks[i];
switch (locationEvaluator.check(p, 16 * chunkX + (pos >> 8 & 0xF), 16 * chunkY + (pos & 0xF), 16 * chunkZ + (pos >> 4 & 0xF))) {
case SKIP:
poss.add(pos);
blocks.add(block);
break;
case CHECK:
poss.add(pos);
blocks.add(techHider.iBlockDataHidden(block) ? (BlockState) techHider.getObfuscationTarget() : block);
break;
default:
break;
}
}
if (blocks.isEmpty()) return null;
short[] newPos = new short[poss.size()];
for (int i = 0; i < newPos.length; i++) {
newPos[i] = poss.get(i);
}
multiBlockChangePos.set(packet, newPos);
multiBlockChangeBlocks.set(packet, blocks.toArray(new BlockState[0]));
return packet;
};
}
private static final Reflection.Field<BlockEntityType> tileEntityType = Reflection.getField(TechHider.tileEntityDataPacket, BlockEntityType.class, 0);
private static final BlockEntityType<?> signType = Reflection.getField(BlockEntityType.class, BlockEntityType.class, 0, SignBlockEntity.class).get(null);
public boolean unfilteredTileEntityDataAction(Object packet) {
return tileEntityType.get(packet) != signType;
}
}
@@ -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) {
+2 -1
View File
@@ -42,11 +42,12 @@ dependencies {
implementation(project(":CommandFramework"))
}
tasks.register<DevServer>("DevVelocity") {
tasks.register<VelocityServer>("DevVelocity") {
group = "run"
description = "Run a Dev Velocity"
dependsOn(":VelocityCore:shadowJar")
dependsOn(":VelocityCore:Persistent:jar")
dependsOn(":VelocityCore:Dependencies:shadowJar")
template = "DevVelocity"
packetDecodeLogging=true
}
@@ -106,7 +106,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()));
@@ -351,7 +351,7 @@ 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) {
@@ -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
@@ -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);
}
*/
}
@@ -95,6 +95,7 @@ public class CouncilChannel extends StaticMessageChannel {
super(threadChannel, () -> {
MessageCreateBuilder messageCreateBuilder = new MessageCreateBuilder();
messageCreateBuilder.setContent("# Ratsmitglieder");
Set<String> uniqueNames = new HashSet<>();
membersByRole.get(role)
.stream()
.map(member -> {
@@ -105,6 +106,7 @@ public class CouncilChannel extends StaticMessageChannel {
})
.sorted(Map.Entry.comparingByKey())
.forEach(entry -> {
if (!uniqueNames.add(entry.getKey())) return;
messageCreateBuilder.addEmbeds(new EmbedBuilder()
.setTitle(entry.getKey())
.setImage(entry.getValue() == null ? null : "https://api.steamwar.de/data/skin/" + entry.getValue())
+13
View File
@@ -278,6 +278,19 @@ class DevServer extends DefaultTask {
}
}
class VelocityServer extends DevServer {
@Input
@Optional
Boolean packetDecodeLogging = false
VelocityServer() {
super()
doFirst {
if(packetDecodeLogging) dParams.put("velocity.packet-decode-logging", "true")
}
}
}
class FightServer extends DevServer {
@Input