Compare commits

...

55 Commits

Author SHA1 Message Date
Chaoscaot 1cb2e70ede Revert "Remove deprecated world management commands and associated SQL logic"
Pull Request Build / Build (pull_request) Failing after 58s
This reverts commit a7adfe37

Signed-off-by: Chaoscaot <max@maxsp.de>
2026-05-31 20:21:30 +02:00
Chaoscaot 8ce25ec19f Merge branch 'main' into world-system
Pull Request Build / Build (pull_request) Successful in 1m9s
2026-05-31 20:16:17 +02:00
YoyoNow a9fb982143 Merge pull request 'Fix WorldDir of Event servers' (#405) from VelocityCore/FixEventWorldDir into main
Deploy / Build (push) Successful in 1m56s
Deploy / Deploy (push) Successful in 11s
Reviewed-on: #405
Reviewed-by: D4rkr34lm <dark@steamwar.de>
2026-05-30 18:33:17 +02:00
YoyoNow 5b8d881e01 Fix WorldDir of Event servers
Pull Request Build / Build (pull_request) Successful in 1m7s
Backport CommonCore / Create CommonCore backport PRs (pull_request) Successful in 7s
2026-05-30 18:30:51 +02:00
YoyoNow eb55f4b395 Merge pull request 'Remove global Entity Interact callback' (#404) from SpigotCore/RemoveGlobalEntityServerCallback into main
Deploy / Build (push) Successful in 1m56s
Deploy / Deploy (push) Successful in 10s
Reviewed-on: #404
Reviewed-by: D4rkr34lm <dark@steamwar.de>
2026-05-30 15:27:45 +02:00
YoyoNow 273db91d06 Fix compile
Pull Request Build / Build (pull_request) Successful in 1m6s
Backport CommonCore / Create CommonCore backport PRs (pull_request) Successful in 10s
2026-05-30 15:24:20 +02:00
YoyoNow 983ad544c1 Remove global Entity Interact callback
Pull Request Build / Build (pull_request) Failing after 55s
2026-05-30 15:18:37 +02:00
YoyoNow a6a34b2221 Fix Schem add on WGS Schems
Deploy / Build (push) Successful in 1m52s
Deploy / Deploy (push) Successful in 11s
2026-05-30 14:13:08 +02:00
YoyoNow 6c6bd19038 Merge pull request 'fix(BauSystem): interaction with trace entity not showing details text' (#398) from BauSystem/fix-interaction-with-trace-entity into main
Deploy / Build (push) Successful in 1m53s
Deploy / Deploy (push) Successful in 11s
Reviewed-on: #398
Reviewed-by: YoyoNow <4+yoyonow@noreply.localhost>
2026-05-30 13:35:35 +02:00
D4rkr34lm 89e05cd109 Remove remnatn of old impl
Pull Request Build / Build (pull_request) Successful in 1m11s
Backport CommonCore / Create CommonCore backport PRs (pull_request) Successful in 7s
2026-05-30 13:30:47 +02:00
D4rkr34lm ea9d7ac584 Refactor to use better impl of interaction entity in REntity system
Pull Request Build / Build (pull_request) Failing after 57s
2026-05-30 13:29:50 +02:00
D4rkr34lm 17e1cf53b0 Merge branch 'main' into BauSystem/fix-interaction-with-trace-entity
Pull Request Build / Build (pull_request) Successful in 1m9s
2026-05-30 13:24:43 +02:00
YoyoNow f64f337f17 Merge pull request 'Add RInteraction Entity' (#403) from SpigotCore/RInteraction into main
Deploy / Build (push) Successful in 1m59s
Deploy / Deploy (push) Successful in 11s
Reviewed-on: #403
Reviewed-by: D4rkr34lm <dark@steamwar.de>
2026-05-30 13:22:35 +02:00
YoyoNow c5f5be7d58 Add RInteraction Entity
Pull Request Build / Build (pull_request) Successful in 1m8s
Backport CommonCore / Create CommonCore backport PRs (pull_request) Successful in 8s
2026-05-30 13:17:49 +02:00
Chaoscaot 9f18644763 Merge pull request 'fix(FightSystem): arrow stopper removing entites not shot by player (#399)' (#402) from wip/399-fix-error-stopper-for-dispensers into main
Deploy / Build (push) Successful in 1m58s
Deploy / Deploy (push) Successful in 12s
Reviewed-on: #402
Reviewed-by: Chaoscaot <max@chaoscaot.de>
2026-05-30 12:11:12 +02:00
D4rkr34lm 1de17d27f4 Fix arrow stopper
Pull Request Build / Build (pull_request) Successful in 1m15s
Backport CommonCore / Create CommonCore backport PRs (pull_request) Successful in 7s
2026-05-30 11:55:59 +02:00
D4rkr34lm 3cd0db9bdf Merge branch 'main' into BauSystem/fix-interaction-with-trace-entity
Pull Request Build / Build (pull_request) Successful in 1m6s
2026-05-25 21:32:12 +02:00
D4rkr34lm 9711b48f2f Fix interaction with trace entity not showing detail text
Pull Request Build / Build (pull_request) Failing after 1m3s
2026-05-25 18:20:42 +02:00
Chaoscaot 9934b8bbb2 Fix TNTLeague InviteCommand.kt
Deploy / Build (push) Successful in 1m55s
Deploy / Deploy (push) Successful in 12s
Signed-off-by: Chaoscaot <max@maxsp.de>
2026-05-25 01:55:20 +02:00
Chaoscaot 6447265b90 Merge pull request 'feat(BauSystem): use display entity to render trace' (#397) from BauSystem/use-display-entities-for-trace-render into main
Deploy / Build (push) Successful in 1m57s
Deploy / Deploy (push) Successful in 12s
Reviewed-on: #397
Reviewed-by: Chaoscaot <max@chaoscaot.de>
2026-05-25 01:40:30 +02:00
D4rkr34lm 71258318db Replace trace rendering by fallingblockentity with display entity for better accuracy
Pull Request Build / Build (pull_request) Successful in 1m8s
Backport CommonCore / Create CommonCore backport PRs (pull_request) Successful in 34s
2026-05-24 23:39:50 +02:00
Chaoscaot 52e95e2649 Merge pull request 'fix(VelocityCore): disable replays' (#395) from disable-replays into main
Deploy / Build (push) Successful in 2m10s
Deploy / Deploy (push) Successful in 11s
Reviewed-on: #395
Reviewed-by: Chaoscaot <max@chaoscaot.de>
2026-05-24 13:45:32 +02:00
D4rkr34lm de6ac2cf20 fix
Backport CommonCore / Create CommonCore backport PRs (pull_request) Successful in 10s
Pull Request Build / Build (pull_request) Successful in 1m20s
2026-05-24 13:44:57 +02:00
D4rkr34lm 628001e693 Disable Replays
Pull Request Build / Build (pull_request) Successful in 1m8s
2026-05-24 13:42:08 +02:00
Chaoscaot 47c148d64a Merge pull request 'fix(SpigotCore): techhider not hiding waterlogged blocks if neccecary' (#393) from fix-techhider-not-hidding-waterlogged-blocks into main
Deploy / Build (push) Successful in 1m56s
Deploy / Deploy (push) Successful in 11s
Reviewed-on: #393
Reviewed-by: Chaoscaot <max@chaoscaot.de>
2026-05-23 16:33:11 +02:00
D4rkr34lm 78617d5a98 another fix
Pull Request Build / Build (pull_request) Successful in 1m9s
Backport CommonCore / Create CommonCore backport PRs (pull_request) Successful in 9s
2026-05-23 16:15:31 +02:00
D4rkr34lm 69b924ded6 fix techhider not hiding waterlogged blocks if neccecary
Pull Request Build / Build (pull_request) Successful in 1m7s
2026-05-23 12:29:19 +02:00
YoyoNow 002f7e5542 Merge pull request 'fix(FightSystem): techhider being active in check and test state' (#392) from fix-techhider-being-active-in-check-n-test-arena into main
Deploy / Build (push) Successful in 2m1s
Deploy / Deploy (push) Successful in 10s
Reviewed-on: #392
Reviewed-by: YoyoNow <4+yoyonow@noreply.localhost>
2026-05-23 06:23:16 +02:00
D4rkr34lm 5d70f75ac9 Move techhider init into enable to prevent always active even in check and test state
Pull Request Build / Build (pull_request) Successful in 1m6s
Backport CommonCore / Create CommonCore backport PRs (pull_request) Successful in 7s
2026-05-22 23:12:39 +02:00
YoyoNow 7f21a31ec9 Merge pull request 'refactor(SpigotCore): Techhider to ensure safety and improve reviewablity' (#338) from FightSystem/fix-tech-and-hull-hider into main
Deploy / Build (push) Successful in 2m3s
Deploy / Deploy (push) Successful in 11s
Reviewed-on: #338
2026-05-22 21:37:03 +02:00
YoyoNow 23b5ab3e82 Fix build
Pull Request Build / Build (pull_request) Successful in 1m15s
Backport CommonCore / Create CommonCore backport PRs (pull_request) Successful in 9s
2026-05-22 21:35:01 +02:00
YoyoNow 4f9fe07951 Fix final formatting stuff
Pull Request Build / Build (pull_request) Failing after 1m7s
2026-05-22 21:29:58 +02:00
YoyoNow 3bcff4c5ce Merge pull request 'perf(FightSystem): increase chunk hiding performance by skipping irrelavant chunks' (#385) from FightSystem/Optimize-new-techhider into FightSystem/fix-tech-and-hull-hider
Pull Request Build / Build (pull_request) Successful in 1m25s
Reviewed-on: #385
Reviewed-by: YoyoNow <4+yoyonow@noreply.localhost>
2026-05-22 20:52:11 +02:00
D4rkr34lm 1810cb7546 Fix inlineing
Pull Request Build / Build (pull_request) Successful in 1m21s
2026-05-22 20:51:24 +02:00
D4rkr34lm e55ca911c4 Inline get all regions
Pull Request Build / Build (pull_request) Successful in 1m12s
2026-05-22 20:41:05 +02:00
D4rkr34lm 793f2de6c3 Sync with base branch
Pull Request Build / Build (pull_request) Successful in 1m13s
2026-05-22 19:42:36 +02:00
D4rkr34lm 54fa47bd99 Ensure parity with old techhider by suppressing select packets
Pull Request Build / Build (pull_request) Successful in 1m11s
2026-05-22 19:39:57 +02:00
YoyoNow 14a770b207 Merge pull request 'fix(BauSystem): tick step bossbar remaining visable' (#391) from fix-390-fix-tickstep-bosbar-not-disapearing into main
Deploy / Build (push) Successful in 1m53s
Deploy / Deploy (push) Successful in 12s
Reviewed-on: #391
2026-05-22 18:56:30 +02:00
D4rkr34lm 7b3a04f4eb Remove unused custom event
Pull Request Build / Build (pull_request) Successful in 1m6s
Backport CommonCore / Create CommonCore backport PRs (pull_request) Successful in 7s
2026-05-22 18:49:51 +02:00
D4rkr34lm f2a06057a8 Fix tick step bossbar remaining visable
Pull Request Build / Build (pull_request) Successful in 1m9s
2026-05-22 18:46:36 +02:00
D4rkr34lm e11f3f7cbc Fix preschem state and techider not being always active
Pull Request Build / Build (pull_request) Successful in 1m12s
2026-05-22 18:27:53 +02:00
D4rkr34lm ebc10c1ce4 Fix constructor visiblity
Pull Request Build / Build (pull_request) Successful in 1m13s
2026-05-22 17:57:15 +02:00
D4rkr34lm d682e35159 Resolve open comments
Pull Request Build / Build (pull_request) Successful in 1m12s
2026-05-22 17:55:12 +02:00
D4rkr34lm 7d74eb0c09 add old techhider to skip supporting bausystem for now
Pull Request Build / Build (pull_request) Successful in 1m11s
2026-05-22 17:40:33 +02:00
D4rkr34lm 7e18207b87 fix entities not despawning
Pull Request Build / Build (pull_request) Failing after 1m5s
2026-05-22 16:32:49 +02:00
YoyoNow 72b23ad116 Add filling off Hull for reset and paste with either visible blocks or occluding blocks
Pull Request Build / Build (pull_request) Failing after 1m6s
2026-05-22 16:24:34 +02:00
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
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
Chaoscaot a7adfe378f Remove deprecated world management commands and associated SQL logic
Signed-off-by: Chaoscaot <max@maxsp.de>
2026-05-20 16:44:32 +02:00
Chaoscaot 9aa363de6f Add world-based permissions and migration commands
Pull Request Build / Build (pull_request) Successful in 1m7s
- Switch bau logic from owner-scoped to world-scoped data
- Add CLI commands for world migration, templating, and archiving
- Extend world storage with team worlds and lock state support
2026-05-19 20:53:32 +02:00
Chaoscaot 5a5090c74d Remove unused import and fix incorrect import path in BuilderCloudCommand and World classes.
Pull Request Build / Build (pull_request) Successful in 1m8s
Signed-off-by: Chaoscaot <max@maxsp.de>
2026-05-19 19:36:43 +02:00
Chaoscaot 99ca7205fb Merge branch 'main' into world-system
# Conflicts:
#	VelocityCore/src/de/steamwar/velocitycore/ServerStarter.java
#	VelocityCore/src/de/steamwar/velocitycore/commands/BauCommand.java
#	VelocityCore/src/de/steamwar/velocitycore/commands/BuilderCloudCommand.java
2026-05-19 19:30:30 +02:00
Chaoscaot e85604fdc4 Merge branch 'main' into world-system
Pull Request Build / Build (pull_request) Failing after 42s
2026-05-16 14:49:18 +02:00
Chaoscaot 936ee38503 Migrate builder and bau worlds to shared world records
SteamWarCI Build successful
- add SQL-backed world entities with archive and rename support
- route builder, bau, deploy, and GDPR flows through world storage
- keep legacy folder support for existing worlds
2026-05-09 16:38:13 +02:00
75 changed files with 2769 additions and 635 deletions
@@ -133,7 +133,12 @@ public class BauSystem extends JavaPlugin implements Listener {
Bukkit.getWorlds().get(0).setGameRule(GameRule.SEND_COMMAND_FEEDBACK, false);
String identifier = BauServerInfo.getOwnerUser().getUUID().toString().replace("-", "");
String identifier;
if (BauServerInfo.getWorldId() != null) {
identifier = BauServerInfo.getWorldId().toString().replace("-", "");
} else {
identifier = BauServerInfo.getOwnerUser().getUUID().toString().replace("-", "");
}
WorldIdentifier.set("bau/" + Core.getVersion() + "/" + identifier);
}
@@ -224,4 +229,4 @@ public class BauSystem extends JavaPlugin implements Listener {
AtomicReference<BukkitTask> task = new AtomicReference<>();
task.set(runTaskTimer(plugin, () -> consumer.accept(task.get()), delay, period));
}
}
}
@@ -60,11 +60,16 @@ public enum Permission {
}
public boolean hasPermission(Player member) {
if (SteamwarUser.get(member.getUniqueId()).getId() == BauServer.getInstance().getOwnerID()) {
SteamwarUser steamwarUser = SteamwarUser.get(member.getUniqueId());
BauServer server = BauServer.getInstance();
if (!server.isTeamWorld() && steamwarUser.getId() == server.getOwnerID()) {
return this != SPECTATOR;
}
BauweltMember bauweltMember = BauweltMember.getBauMember(BauServer.getInstance().getOwner(), member.getUniqueId());
if (server.isTeamWorld() && steamwarUser.getTeam() == server.getTeamID()) {
return this != SPECTATOR;
}
BauweltMember bauweltMember = BauweltMember.getBauMember(server.getWorldID(), steamwarUser.getId());
if (bauweltMember == null) return this == SPECTATOR;
return permissionPredicate.test(bauweltMember);
}
}
}
@@ -34,16 +34,41 @@ public class BauServer {
}
private Integer owner;
private UUID world;
private Integer team;
public UUID getOwner() {
return SteamwarUser.byId(getOwnerID()).getUUID();
Integer ownerId = getOwnerID();
return ownerId == null ? null : SteamwarUser.byId(ownerId).getUUID();
}
public int getOwnerID() {
public Integer getOwnerID() {
//Lazy loading to improve startup time of the server in 1.15
if (owner == null) {
owner = BauServerInfo.getOwnerId();
}
return owner;
}
}
public boolean hasOwner() {
return getOwnerID() != null;
}
public UUID getWorldID() {
if (world == null) {
world = BauServerInfo.getWorldId();
}
return world;
}
public Integer getTeamID() {
if (team == null) {
team = BauServerInfo.getTeamId();
}
return team;
}
public boolean isTeamWorld() {
return getTeamID() != null;
}
}
@@ -47,7 +47,7 @@ public class BauInfoBauGuiItem extends BauGuiItem {
@Override
public ItemStack getItem(Player player) {
SWItem itemStack;
if (!player.getName().endsWith("")) {
if (!player.getName().endsWith("") && !BauServer.getInstance().isTeamWorld()) {
itemStack = SWItem.getPlayerSkull(SteamwarUser.get(BauServer.getInstance().getOwner()).getUserName());
} else {
itemStack = new SWItem(Material.PLAYER_HEAD, "");
@@ -47,7 +47,9 @@ public class InfoCommand extends SWCommand {
@Register(description = "BAU_INFO_COMMAND_HELP")
public void genericCommand(Player p) {
BauSystem.MESSAGE.send("BAU_INFO_COMMAND_OWNER", p, SteamwarUser.byId(bauServer.getOwnerID()).getUserName());
if (!bauServer.isTeamWorld()) {
BauSystem.MESSAGE.send("BAU_INFO_COMMAND_OWNER", p, SteamwarUser.byId(bauServer.getOwnerID()).getUserName());
}
Region region = Region.getRegion(p.getLocation());
for (Flag flag : Flag.getFlags()) {
if (!region.getRegionData().has(flag).isApplicable()) continue;
@@ -58,7 +60,7 @@ public class InfoCommand extends SWCommand {
}
if (Permission.BUILD.hasPermission(p)) {
List<BauweltMember> members = BauweltMember.getMembers(bauServer.getOwnerID());
List<BauweltMember> members = BauweltMember.getWorldMembers(bauServer.getWorldID());
Map<Permission, List<BauweltMember>> memberByPermission = new HashMap<>();
members.forEach(member -> {
if (Permission.SUPERVISOR.hasPermission(member)) {
@@ -21,9 +21,7 @@ package de.steamwar.bausystem.features.design.endstone;
import de.steamwar.bausystem.BauSystem;
import de.steamwar.bausystem.region.Region;
import de.steamwar.entity.REntity;
import de.steamwar.entity.REntityServer;
import de.steamwar.entity.RFallingBlockEntity;
import de.steamwar.entity.*;
import net.md_5.bungee.api.ChatMessageType;
import org.bukkit.Bukkit;
import org.bukkit.Location;
@@ -59,15 +57,15 @@ public class DesignEndStone {
.filter(material -> material.getBlastResistance() > region.getGameModeConfig().Schematic.MaxDesignBlastResistance)
.collect(Collectors.toSet());
calculateFromBottom = region.getGameModeConfig().Arena.NoFloor;
}
entityServer.setCallback((player, rEntity, entityAction) -> {
if (entityAction != REntityServer.EntityAction.ATTACK) return;
Location location = new Location(WORLD, rEntity.getX(), rEntity.getY(), rEntity.getZ());
Bukkit.getScheduler().runTaskLater(BauSystem.getInstance(), () -> {
location.getBlock().breakNaturally();
calc();
}, 1);
});
private void interact(Player player, RInteraction entity, REntityAction action) {
if (action != REntityAction.ATTACK) return;
Location location = new Location(WORLD, entity.getX(), entity.getY(), entity.getZ());
Bukkit.getScheduler().runTaskLater(BauSystem.getInstance(), () -> {
location.getBlock().breakNaturally();
calc();
}, 1);
}
public void calc() {
@@ -110,12 +108,15 @@ public class DesignEndStone {
Material material = WORLD.getBlockAt(cx, cy, cz).getType();
if (material != Material.WATER && material != Material.LAVA && limited.contains(material)) {
Location location = new Location(WORLD, cx + 0.5, cy, cz + 0.5);
Location location = new Location(WORLD, cx, cy, cz);
if (!locations.add(location)) break;
RFallingBlockEntity entity = new RFallingBlockEntity(entityServer, location, Material.RED_STAINED_GLASS);
entity.setNoGravity(true);
RBlockDisplay entity = new RBlockDisplay(entityServer, location);
entity.setBlock(Material.RED_STAINED_GLASS.createBlockData());
entity.setGlowing(true);
entities.add(entity);
RInteraction interaction = new RInteraction(entityServer, location);
interaction.setCallback(this::interact);
entities.add(interaction);
break;
} else if (!material.isAir() && material != Material.WATER && material != Material.LAVA) {
break;
@@ -26,8 +26,9 @@ import de.steamwar.bausystem.features.autostart.AutostartListener;
import de.steamwar.bausystem.features.detonator.storage.DetonatorStorage;
import de.steamwar.bausystem.features.detonator.storage.ItemStorage;
import de.steamwar.core.SWPlayer;
import de.steamwar.entity.RBlockDisplay;
import de.steamwar.entity.REntityServer;
import de.steamwar.entity.RFallingBlockEntity;
import de.steamwar.entity.RInteraction;
import lombok.Getter;
import lombok.Setter;
import lombok.experimental.UtilityClass;
@@ -58,10 +59,6 @@ public class Detonator {
@Override
public void onMount(SWPlayer player) {
entities.addPlayer(player.getPlayer());
entities.setCallback((player1, entity, action) -> {
Vector vector = new Vector(entity.getX(), entity.getY(), entity.getZ());
DetonatorListener.addLocationToDetonator(vector.toLocation(player.getWorld()).getBlock().getLocation(), player1);
});
}
@Override
@@ -70,8 +67,6 @@ public class Detonator {
}
}
private static final Vector HALF = new Vector(0.5, 0, 0.5);
public static boolean isDetonator(ItemStack itemStack) {
return ItemStorage.isDetonator(itemStack);
}
@@ -79,8 +74,14 @@ public class Detonator {
public static void showDetonator(Player p, List<Location> locs) {
DetonatorComponent detonatorComponent = SWPlayer.of(p).getComponentOrDefault(DetonatorComponent.class, DetonatorComponent::new);
locs.forEach(location -> {
RFallingBlockEntity entity = new RFallingBlockEntity(detonatorComponent.entities, location.clone().add(HALF), Material.RED_STAINED_GLASS);
entity.setNoGravity(true);
RBlockDisplay blockDisplay = new RBlockDisplay(detonatorComponent.entities, location);
blockDisplay.setBlock(Material.RED_STAINED_GLASS.createBlockData());
RInteraction interaction = new RInteraction(detonatorComponent.entities, location);
interaction.setCallback((player, entity, action) -> {
Vector vector = new Vector(entity.getX(), entity.getY(), entity.getZ());
DetonatorListener.addLocationToDetonator(vector.toLocation(player.getWorld()).getBlock().getLocation(), player);
});
});
}
@@ -24,9 +24,9 @@ import de.steamwar.bausystem.region.Point;
import de.steamwar.bausystem.region.Region;
import de.steamwar.bausystem.utils.bossbar.BauSystemBossbar;
import de.steamwar.bausystem.utils.bossbar.BossBarService;
import de.steamwar.entity.RBlockDisplay;
import de.steamwar.entity.REntity;
import de.steamwar.entity.REntityServer;
import de.steamwar.entity.RFallingBlockEntity;
import org.bukkit.Bukkit;
import org.bukkit.Material;
import org.bukkit.World;
@@ -290,8 +290,8 @@ public class KillcheckerVisualizer {
}
rEntities.get(point).die();
}
RFallingBlockEntity entity = new RFallingBlockEntity(outlinePoints.contains(point) ? outline : inner, point.toLocation(WORLD, 0.5, 0, 0.5), MATERIALS[Math.min(count - 1, MATERIALS.length) - 1]);
entity.setNoGravity(true);
RBlockDisplay entity = new RBlockDisplay(outlinePoints.contains(point) ? outline : inner, point.toLocation(WORLD, 0.5, 0, 0.5));
entity.setBlock(MATERIALS[Math.min(count - 1, MATERIALS.length) - 1].createBlockData());
rEntities.put(point, entity);
if (outlinePoints.contains(point)) outlinePointsCache.add(point);
killCount.put(point, count);
@@ -20,6 +20,7 @@
package de.steamwar.bausystem.features.region;
import de.steamwar.bausystem.BauSystem;
import de.steamwar.bausystem.Permission;
import de.steamwar.bausystem.config.BauServer;
import de.steamwar.bausystem.region.Region;
import de.steamwar.bausystem.region.RegionUtils;
@@ -83,6 +84,9 @@ public class ColorCommand extends SWCommand {
@ClassValidator(value = Player.class, local = true)
public TypeValidator<Player> validator() {
return (commandSender, player, messageSender) -> {
if (bauServer.isTeamWorld()) {
return !messageSender.send(!Permission.SUPERVISOR.hasPermission(player), "NO_PERMISSION");
}
return !messageSender.send(!bauServer.getOwner().equals(player.getUniqueId()), "NO_PERMISSION");
};
}
@@ -68,7 +68,7 @@ public class ResetCommand extends SWCommand {
Region region = regionCheck(p);
if (region == null) return;
if (!p.getUniqueId().equals(bauServer.getOwner())) {
if (bauServer.hasOwner() && !p.getUniqueId().equals(bauServer.getOwner())) {
if (Punishment.isPunished(SteamwarUser.get(bauServer.getOwner()), Punishment.PunishmentType.NoSchemReceiving, punishment -> BauSystem.MESSAGE.send("REGION_TB_NO_SCHEMRECEIVING", p, punishment.getEndTime()))) {
return;
}
@@ -91,7 +91,7 @@ public class TestblockCommand extends SWCommand {
}
}
if (!p.getUniqueId().equals(bauServer.getOwner())) {
if (bauServer.hasOwner() && !p.getUniqueId().equals(bauServer.getOwner())) {
if (Punishment.isPunished(SteamwarUser.get(bauServer.getOwner()), Punishment.PunishmentType.NoSchemReceiving, punishment -> BauSystem.MESSAGE.send("REGION_TB_NO_SCHEMRECEIVING", p, punishment.getEndTime()))) {
return;
}
@@ -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());
}
}
}
@@ -20,6 +20,7 @@
package de.steamwar.bausystem.features.world;
import de.steamwar.bausystem.BauSystem;
import de.steamwar.bausystem.Permission;
import de.steamwar.bausystem.config.BauServer;
import de.steamwar.core.CRIUWakeupEvent;
import de.steamwar.linkage.Linked;
@@ -36,12 +37,13 @@ public class AntiBauAddMemberFix implements Listener {
@EventHandler(priority = EventPriority.LOW)
public void onPlayerJoin(PlayerJoinEvent event) {
if (BauSystem.DEV_SERVER) return;
if (event.getPlayer().getUniqueId().equals(BauServer.getInstance().getOwner())) {
if (Permission.SUPERVISOR.hasPermission(event.getPlayer())) {
return;
}
if (BauweltMember.getBauMember(BauServer.getInstance().getOwner(), event.getPlayer().getUniqueId()) == null) {
if (BauweltMember.getBauMember(BauServer.getInstance().getWorldID(), event.getPlayer().getUniqueId()) == null) {
event.getPlayer().kickPlayer("");
throw new SecurityException("The player " + event.getPlayer().getName() + " joined on the server of " + SteamwarUser.byId(BauServer.getInstance().getOwnerID()).getUserName() + " without being added!");
String owner = BauServer.getInstance().isTeamWorld() ? "team " + BauServer.getInstance().getTeamID() : SteamwarUser.byId(BauServer.getInstance().getOwnerID()).getUserName();
throw new SecurityException("The player " + event.getPlayer().getName() + " joined on the server of " + owner + " without being added!");
}
}
@@ -33,7 +33,7 @@ public class AxiomPermissionCheck implements Listener {
@EventHandler
public void onAxiomHandshake(AxiomHandshakeEvent event) {
if (Permission.SUPERVISOR.hasPermission(event.getPlayer()) || BauServer.getInstance().getOwner().equals(event.getPlayer().getUniqueId())) {
if (Permission.SUPERVISOR.hasPermission(event.getPlayer()) || (BauServer.getInstance().hasOwner() && BauServer.getInstance().getOwner().equals(event.getPlayer().getUniqueId()))) {
event.setMaxBufferSize(Short.MAX_VALUE);
return;
}
@@ -28,7 +28,7 @@ import de.steamwar.data.BauLockState;
import de.steamwar.linkage.Linked;
import de.steamwar.network.packets.PacketHandler;
import de.steamwar.network.packets.server.BaulockUpdatePacket;
import de.steamwar.sql.UserConfig;
import de.steamwar.sql.SteamwarWorld;
import lombok.Getter;
import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
@@ -37,13 +37,13 @@ import org.bukkit.event.Listener;
@Linked
public class BauLockStateScoreboard extends PacketHandler implements ScoreboardElement, Listener {
private static final String BAU_LOCK_CONFIG_NAME = "baulockstate";
@Getter
private BauLockState lockState = loadLockState();
private BauLockState loadLockState() {
String state = UserConfig.getConfig(BauServer.getInstance().getOwner(), BAU_LOCK_CONFIG_NAME);
if (BauServer.getInstance().isTeamWorld()) return BauLockState.OPEN;
SteamwarWorld world = SteamwarWorld.getWorld(BauServer.getInstance().getWorldID());
String state = world == null ? null : world.getLockState();
return state == null ? BauLockState.OPEN : BauLockState.valueOf(state);
}
@@ -69,6 +69,7 @@ public class BauLockStateScoreboard extends PacketHandler implements ScoreboardE
@Override
public String get(Region region, Player p) {
if (BauServer.getInstance().isTeamWorld()) return null;
if (!BauServer.getInstance().getOwner().equals(p.getUniqueId())) {
return null;
}
@@ -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);
}
}
}
@@ -42,7 +42,11 @@ public class KickallCommand extends SWCommand {
if (!Permission.OWNER.hasPermission(player)) return;
Bukkit.getOnlinePlayers().forEach(p -> {
if (!bauServer.getOwner().equals(p.getUniqueId())) p.kickPlayer("");
if (bauServer.isTeamWorld()) {
if (!Permission.SUPERVISOR.hasPermission(p)) p.kickPlayer("");
} else if (!bauServer.getOwner().equals(p.getUniqueId())) {
p.kickPlayer("");
}
});
}
}
@@ -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;
@@ -142,17 +143,14 @@ public class SpectatorListener implements Listener {
public void onPlayerJoin(PlayerJoinEvent event) {
enableOrDisableTechhider();
if (BauSystem.DEV_SERVER) return;
if (event.getPlayer().getUniqueId().equals(BauServer.getInstance().getOwner())) {
if (Permission.SUPERVISOR.hasPermission(event.getPlayer())) {
return;
}
BauweltMember bauweltMember = BauweltMember.getBauMember(BauServer.getInstance().getOwner(), event.getPlayer().getUniqueId());
BauweltMember bauweltMember = BauweltMember.getBauMember(BauServer.getInstance().getWorldID(), event.getPlayer().getUniqueId());
if (bauweltMember == null) {
event.getPlayer().kickPlayer("");
return;
}
if (Permission.SUPERVISOR.hasPermission(event.getPlayer())) {
return;
}
if (!anySupervisorOnline(null)) {
Bukkit.getScheduler().runTaskLater(BauSystem.getInstance(), () -> {
event.getPlayer().kickPlayer("");
@@ -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;
+11 -1
View File
@@ -11,13 +11,23 @@ import de.steamwar.commands.profiler.ProfilerCommand
import de.steamwar.commands.user.UserCommand
import de.steamwar.commands.user.UserInfoCommand
import de.steamwar.commands.user.UserSearchCommand
import de.steamwar.commands.world.MigrationCommand
import de.steamwar.commands.world.SaveToStorageCommand
import de.steamwar.commands.world.TemplateCommand
import de.steamwar.commands.world.TemplateCreateCommand
import de.steamwar.commands.world.WorldCommand
fun main(args: Array<String>) =
SteamWar()
.subcommands(
DatabaseCommand().subcommands(InfoCommand(), ResetCommand()),
UserCommand().subcommands(UserInfoCommand(), UserSearchCommand()),
WorldCommand().subcommands(
MigrationCommand(),
SaveToStorageCommand(),
TemplateCommand().subcommands(TemplateCreateCommand())
),
DevCommand(),
ProfilerCommand()
)
.main(args)
.main(args)
+243
View File
@@ -0,0 +1,243 @@
package de.steamwar.commands.world
import com.github.ajalt.clikt.core.CliktCommand
import de.steamwar.db.Database
import de.steamwar.sql.SteamwarUser
import de.steamwar.sql.SteamwarWorld
import de.steamwar.sql.UserConfig
import org.jetbrains.exposed.v1.jdbc.transactions.transaction
import java.io.File
import java.sql.SQLException
import java.util.UUID
class MigrationCommand : CliktCommand(name = "migrate") {
private val userWorldPattern = Regex("""userworlds(\d+)""")
private val builderWorldPattern = Regex("""builder(\d+)""")
private val trailingVersionPattern = Regex("""(\d+)$""")
override fun run() {
Database.ensureConnected()
val bau = migrateBauWorlds()
val builder = migrateBuilderWorlds()
val arena = migrateArenaWorlds()
migrateBauLockConfig()
migrateBauweltMembers()
echo("Imported ${bau.imported} build worlds, skipped ${bau.skipped}, missing users ${bau.missingOwner}.")
echo("Imported ${builder.imported} builder worlds, skipped ${builder.skipped}.")
echo("Imported ${arena.imported} arena worlds, skipped ${arena.skipped}.")
echo("World migration completed.")
}
private fun migrateBauWorlds(): MigrationStats {
val stats = MigrationStats()
val worldsRoot = File("/worlds")
worldsRoot.listFiles { file -> file.isDirectory && userWorldPattern.matches(file.name) }
?.forEach { versionFolder ->
val version = userWorldPattern.matchEntire(versionFolder.name)!!.groupValues[1].toInt()
versionFolder.listFiles { file -> file.isDirectory && isWorldDirectory(file) }
?.sortedBy { it.name }
?.forEach { legacyWorld ->
val owner = legacyWorld.ownerUser()
if (owner == null) {
stats.missingOwner++
echo("Skipped build world ${legacyWorld.path}: no matching user.")
return@forEach
}
val lockState = UserConfig.getConfig(owner.id.value, BAU_LOCK_CONFIG_NAME)
val world = SteamwarWorld.getOrCreateBauWorld(owner, owner.userName, version, legacyWorld)
if (world.lockState == null && lockState != null) {
world.changeLockState(lockState)
}
stats.imported++
}
} ?: run {
stats.skipped++
echo("Skipped build worlds: /worlds does not exist.")
}
return stats
}
private fun migrateBuilderWorlds(): MigrationStats {
val stats = MigrationStats()
val worldsRoot = File("/worlds")
worldsRoot.listFiles { file -> file.isDirectory && builderWorldPattern.matches(file.name) }
?.forEach { versionFolder ->
val version = builderWorldPattern.matchEntire(versionFolder.name)!!.groupValues[1].toInt()
versionFolder.listFiles { file -> file.isDirectory && isWorldDirectory(file) }
?.sortedBy { it.name }
?.forEach { legacyWorld ->
SteamwarWorld.getOrCreateBuilderWorld(legacyWorld.name, version, legacyWorld)
stats.imported++
}
} ?: run {
stats.skipped++
echo("Skipped builder worlds: /worlds does not exist.")
}
return stats
}
private fun migrateArenaWorlds(): MigrationStats {
val stats = MigrationStats()
val configs = parseGameModeConfigs(File("/configs/GameModes"))
if (configs.isEmpty()) {
stats.skipped++
echo("Skipped arena worlds: no game mode configs found in /configs/GameModes.")
return stats
}
for (config in configs) {
val version = trailingVersionPattern.find(config.serverFolder)?.value?.toIntOrNull()
if (version == null) {
stats.skipped++
echo("Skipped arena mode ${config.modeName}: server folder '${config.serverFolder}' has no version suffix.")
continue
}
val arenaRoot = File("/servers/${config.serverFolder}/arenas")
for (map in config.maps) {
val legacyWorld = File(arenaRoot, map)
if (!isWorldDirectory(legacyWorld)) {
stats.skipped++
continue
}
SteamwarWorld.getOrCreateArenaWorld(config.modeName, map, version, legacyWorld)
stats.imported++
}
}
return stats
}
private fun migrateBauLockConfig() {
transaction(Database.db) {
exec(
"""
UPDATE `world` w
JOIN `UserConfig` uc ON uc.`User` = w.`Owner` AND uc.`Config` = 'baulockstate'
SET w.`LockState` = uc.`Value`
WHERE w.`Owner` IS NOT NULL AND w.`LockState` IS NULL
""".trimIndent()
)
}
}
private fun migrateBauweltMembers() {
if (!columnExists("BauweltMember", "WorldID") || !columnExists("BauweltMember", "BauweltID")) {
echo("Skipped BauweltMember data migration: expected WorldID and BauweltID columns are not both present.")
return
}
execIgnore(
"""
INSERT IGNORE INTO `BauweltMember` (`WorldID`, `MemberID`, `Build`, `WorldEdit`, `World`)
SELECT w.`id`, bm.`MemberID`, bm.`Build`, bm.`WorldEdit`, bm.`World`
FROM `BauweltMember` bm
JOIN `world` w ON w.`Owner` = bm.`BauweltID` AND w.`Deleted` = 0
""".trimIndent(),
"Skipped BauweltMember data migration"
)
}
private fun parseGameModeConfigs(configRoot: File): List<ArenaMigrationConfig> {
if (!configRoot.isDirectory) return emptyList()
return configRoot.listFiles { file -> file.isFile && file.extension == "yml" && !file.name.endsWith(".kits.yml") }
?.mapNotNull { file ->
val server = parseServerBlock(file)
val folder = server.folder ?: return@mapNotNull null
ArenaMigrationConfig(file.nameWithoutExtension, folder, server.maps)
}
?: emptyList()
}
private fun parseServerBlock(file: File): ParsedServerBlock {
var inServer = false
var inMaps = false
var folder: String? = null
val maps = mutableListOf<String>()
file.readLines().forEach { line ->
val trimmed = line.trim()
if (trimmed.isEmpty() || trimmed.startsWith("#")) return@forEach
val indent = line.indexOfFirst { !it.isWhitespace() }.let { if (it == -1) 0 else it }
if (indent == 0) {
inServer = trimmed == "Server:"
inMaps = false
return@forEach
}
if (!inServer) return@forEach
when {
indent <= 2 && trimmed.startsWith("Folder:") -> folder = trimmed.substringAfter(":").cleanYamlValue()
indent <= 2 && trimmed.startsWith("Maps:") -> {
val value = trimmed.substringAfter(":").trim()
inMaps = true
if (value.startsWith("[") && value.endsWith("]")) {
maps += value.removePrefix("[").removeSuffix("]")
.split(",")
.map { it.cleanYamlValue() }
.filter { it.isNotEmpty() }
inMaps = false
}
}
inMaps && trimmed.startsWith("-") -> maps += trimmed.removePrefix("-").cleanYamlValue()
inMaps && indent <= 2 -> inMaps = false
}
}
return ParsedServerBlock(folder, maps)
}
private fun File.ownerUser(): SteamwarUser? {
name.toIntOrNull()?.let { return SteamwarUser.byId(it) }
return runCatching { SteamwarUser.get(UUID.fromString(name)) }.getOrNull()
}
private fun isWorldDirectory(file: File): Boolean =
file.isDirectory && (File(file, "level.dat").isFile || File(file, "region").isDirectory)
private fun String.cleanYamlValue(): String =
trim().trim('"').trim('\'')
private fun columnExists(table: String, column: String): Boolean =
transaction(Database.db) {
exec("SHOW COLUMNS FROM `$table` LIKE '$column'") { result -> result.next() } ?: false
}
private fun execIgnore(sql: String, prefix: String) {
try {
transaction(Database.db) {
exec(sql)
}
} catch (e: SQLException) {
echo("$prefix: ${e.message}")
}
}
private data class MigrationStats(
var imported: Int = 0,
var skipped: Int = 0,
var missingOwner: Int = 0,
)
private data class ArenaMigrationConfig(
val modeName: String,
val serverFolder: String,
val maps: List<String>,
)
private data class ParsedServerBlock(
val folder: String?,
val maps: List<String>,
)
private companion object {
private const val BAU_LOCK_CONFIG_NAME = "baulockstate"
}
}
@@ -0,0 +1,30 @@
package de.steamwar.commands.world
import com.github.ajalt.clikt.core.CliktCommand
import de.steamwar.db.Database
import de.steamwar.sql.SteamwarWorld
import java.time.LocalTime
class SaveToStorageCommand : CliktCommand(name = "saveToStorage") {
override fun run() {
Database.ensureConnected()
var archived = 0
for (world in SteamwarWorld.getWorldsToArchive()) {
if (!beforeCutoff()) {
echo("Stopping before 04:00. Archived $archived worlds.")
return
}
if (world.archiveIfNeeded()) {
archived++
echo("Archived ${world.uuid} (${world.type}/${world.name}).")
}
}
echo("Storage save completed. Archived $archived worlds.")
}
private fun beforeCutoff(): Boolean =
LocalTime.now().isBefore(LocalTime.of(4, 0))
}
+24
View File
@@ -0,0 +1,24 @@
package de.steamwar.commands.world
import com.github.ajalt.clikt.core.CliktCommand
import com.github.ajalt.clikt.parameters.arguments.argument
import com.github.ajalt.clikt.parameters.types.int
import com.github.ajalt.clikt.parameters.types.path
import de.steamwar.db.Database
import de.steamwar.sql.SteamwarWorld
class TemplateCommand : CliktCommand(name = "template") {
override fun run() = Unit
}
class TemplateCreateCommand : CliktCommand(name = "create") {
private val name by argument()
private val version by argument().int()
private val source by argument().path(canBeFile = false, mustExist = true)
override fun run() {
Database.ensureConnected()
SteamwarWorld.getOrCreateTemplateWorld(name, version, source.toFile())
echo("Template world '$name' created.")
}
}
+7
View File
@@ -0,0 +1,7 @@
package de.steamwar.commands.world
import com.github.ajalt.clikt.core.CliktCommand
class WorldCommand : CliktCommand(name = "world") {
override fun run() = Unit
}
@@ -19,7 +19,6 @@
package de.steamwar.sql
import de.steamwar.sql.BauweltMemberTable.bauweltId
import de.steamwar.sql.internal.useDb
import org.jetbrains.exposed.v1.core.and
import org.jetbrains.exposed.v1.core.dao.id.CompositeID
@@ -32,45 +31,37 @@ import org.jetbrains.exposed.v1.jdbc.insertIgnore
import java.util.*
object BauweltMemberTable : CompositeIdTable("BauweltMember") {
val bauweltId = reference("BauweltID", SteamwarUserTable).index()
val worldId = reference("WorldID", WorldTable).index()
val memberId = reference("MemberID", SteamwarUserTable).index()
val build = bool("Build")
val worldEdit = bool("WorldEdit")
val world = bool("World")
override val primaryKey = PrimaryKey(bauweltId, memberId)
override val primaryKey = PrimaryKey(worldId, memberId)
init {
addIdColumn(bauweltId)
addIdColumn(worldId)
addIdColumn(memberId)
}
}
class BauweltMember(id: EntityID<CompositeID>) : CompositeEntity(id) {
companion object : CompositeEntityClass<BauweltMember>(BauweltMemberTable) {
private val cache = mutableMapOf<Int, BauweltMember>()
private val cache = mutableMapOf<Pair<UUID, Int>, BauweltMember>()
private fun cache(member: BauweltMember) =
cache.put(member.memberID, member)
cache.put(member.worldID to member.memberID, member)
@JvmStatic
fun clear() =
cache.clear()
@JvmStatic
@Deprecated("Use addMember(ownerId: Int, newMemberId: Int)")
fun addMember(ownerId: UUID, newMemberId: UUID) =
addMember(SteamwarUser.get(ownerId)!!.id, SteamwarUser.get(newMemberId)!!.id)
@JvmStatic
fun addMember(ownerId: Int, newMemberId: Int) =
addMember(EntityID(ownerId, SteamwarUserTable), EntityID(newMemberId, SteamwarUserTable))
fun addMember(ownerId: EntityID<Int>, newMemberId: EntityID<Int>) =
fun addMember(worldId: UUID, newMemberId: Int) =
useDb {
BauweltMemberTable.insertIgnore {
it[bauweltId] = ownerId
it[memberId] = newMemberId
it[BauweltMemberTable.worldId] = EntityID(worldId, WorldTable)
it[memberId] = EntityID(newMemberId, SteamwarUserTable)
it[build] = false
it[worldEdit] = false
it[world] = false
@@ -78,31 +69,32 @@ class BauweltMember(id: EntityID<CompositeID>) : CompositeEntity(id) {
}
@JvmStatic
@Deprecated("Use getBauMember(bauwelt: Int, member: Int)")
fun getBauMember(bauwelt: UUID, member: UUID) =
fun addMember(worldId: UUID, newMemberId: UUID) =
addMember(worldId, SteamwarUser.get(newMemberId)!!.id.value)
@JvmStatic
fun getBauMember(world: UUID, member: Int) =
cache[world to member]
?: useDb {
find { (BauweltMemberTable.worldId eq world) and (BauweltMemberTable.memberId eq member) }.firstOrNull()?.also { cache(it) }
}
@JvmStatic
fun getBauMember(world: UUID, member: UUID) =
getBauMember(world, SteamwarUser.get(member)!!.id.value)
@JvmStatic
fun getWorldMembers(world: UUID) =
useDb {
find { (bauweltId eq SteamwarUser.get(bauwelt)!!.id) and (BauweltMemberTable.memberId eq SteamwarUser.get(member)!!.id) }.firstOrNull()?.also { cache(it) }
find { BauweltMemberTable.worldId eq world }.toList().also { it.forEach { cache(it) } }
}
@JvmStatic
fun getBauMember(bauwelt: Int, member: Int) =
useDb {
find { (bauweltId eq bauwelt) and (BauweltMemberTable.memberId eq member) }.firstOrNull()?.also { cache(it) }
}
@JvmStatic
@Deprecated("Use getMembers(bauwelt: Int)")
fun getMembers(bauwelt: UUID) =
getMembers(SteamwarUser.get(bauwelt)!!.id.value)
@JvmStatic
fun getMembers(bauwelt: Int) =
useDb {
find { bauweltId eq bauwelt }.toList().also { it.forEach { cache(it) } }
}
fun getMembers(world: UUID) =
getWorldMembers(world)
}
val bauweltID by BauweltMemberTable.bauweltId.transform({ EntityID(it, SteamwarUserTable) }, { it.value })
val worldID by BauweltMemberTable.worldId.transform({ EntityID(it, WorldTable) }, { it.value })
val memberID by BauweltMemberTable.memberId.transform({ EntityID(it, SteamwarUserTable) }, { it.value })
private var worldEditInternal by BauweltMemberTable.worldEdit
var worldEdit: Boolean
@@ -144,4 +136,4 @@ class BauweltMember(id: EntityID<CompositeID>) : CompositeEntity(id) {
useDb {
delete()
}
}
}
@@ -50,6 +50,8 @@ enum class UserPerm {
PUNISHMENTS,
TICKET_LOG,
BUILD,
BUILD_EXTRA_WORLDS,
BUILD_UNLIMITED_WORLDS,
CHECK,
MODERATION,
ADMINISTRATION;
@@ -95,4 +97,4 @@ enum class UserPerm {
}
data class Prefix(val colorCode: String, val chatPrefix: String, val teamPrefix: Boolean = false)
}
}
+345
View File
@@ -0,0 +1,345 @@
/*
* This file is a part of the SteamWar software.
*
* Copyright (C) 2026 SteamWar.de-Serverteam
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package de.steamwar.sql
import de.steamwar.sql.internal.useDb
import org.jetbrains.exposed.v1.core.and
import org.jetbrains.exposed.v1.core.dao.id.EntityID
import org.jetbrains.exposed.v1.core.dao.id.java.UUIDTable
import org.jetbrains.exposed.v1.core.eq
import org.jetbrains.exposed.v1.core.not
import org.jetbrains.exposed.v1.core.or
import org.jetbrains.exposed.v1.core.SortOrder
import org.jetbrains.exposed.v1.dao.Entity
import org.jetbrains.exposed.v1.dao.EntityClass
import org.jetbrains.exposed.v1.javatime.CurrentTimestamp
import org.jetbrains.exposed.v1.javatime.timestamp
import java.io.File
import java.time.Instant
import java.time.temporal.ChronoUnit
import java.util.UUID
object WorldTable : UUIDTable("world") {
val name = varchar("Name", 512)
val version = integer("Version")
val type = enumeration<WorldType>("Type")
val owner = reference("Owner", SteamwarUserTable).nullable()
val team = reference("Team", TeamTable).nullable()
val lockState = varchar("LockState", 32).nullable()
val deleted = bool("Deleted").default(false)
val created = timestamp("Created").defaultExpression(CurrentTimestamp)
val lastUsed = timestamp("LastUsed").defaultExpression(CurrentTimestamp)
}
enum class WorldType {
BAU,
BUILDER,
TEAM,
ARENA,
TEMPLATE,
}
class SteamwarWorld(id: EntityID<UUID>) : Entity<UUID>(id) {
var name by WorldTable.name
private set
var version by WorldTable.version
private set
var type by WorldTable.type
private set
var owner by WorldTable.owner
private set
var team by WorldTable.team
private set
var lockState by WorldTable.lockState
private set
var deleted by WorldTable.deleted
private set
val created by WorldTable.created
var lastUsed by WorldTable.lastUsed
private set
val archived: Boolean
get() = !storageDirectory.exists() && archiveFile.exists()
val shouldArchive: Boolean
get() = !archived && (deleted || lastUsed.plus(7, ChronoUnit.DAYS).isBefore(Instant.now()))
val uuid: UUID
get() = id.value
val storageDirectory: File
get() = File(WORLD_STORAGE, id.value.toString())
private val archiveFile: File
get() = File(ARCHIVE_WORLD_STORAGE, "${id.value}.zip")
@JvmOverloads
fun setupAndGetStoragePath(prototype: File? = null): String {
if (archived) {
loadFromArchive()
}
val needsInitialization = !storageDirectory.exists() || storageDirectory.list()?.isEmpty() == true
if (needsInitialization) {
if (prototype != null && prototype.exists()) {
prototype.copyRecursively(storageDirectory, overwrite = true)
} else {
storageDirectory.mkdirs()
}
}
useDb {
lastUsed = Instant.now()
}
return storageDirectory.path
}
fun markDeleted() = useDb {
deleted = true
}
fun rename(newName: String) = useDb {
name = newName
}
fun archiveIfNeeded(): Boolean {
if (!shouldArchive) return false
File(ARCHIVE_WORLD_STORAGE).mkdirs()
return archiveWorld() == 0
}
fun changeVersion(newVersion: Int) = useDb {
version = newVersion
}
fun changeLockState(newLockState: String?) = useDb {
lockState = newLockState
}
private fun archiveWorld() = ProcessBuilder("zip", "-u9oymrqq", "$ARCHIVE_WORLD_STORAGE/${id.value}.zip", id.value.toString())
.directory(File(WORLD_STORAGE))
.inheritIO()
.start()
.waitFor()
private fun loadFromArchive() = ProcessBuilder("unzip", "-qq", "-o", "$ARCHIVE_WORLD_STORAGE/${id.value}.zip", "-d", WORLD_STORAGE)
.inheritIO()
.start()
.waitFor()
companion object : EntityClass<UUID, SteamwarWorld>(WorldTable) {
const val WORLD_STORAGE = "/worlds/storage"
const val ARCHIVE_WORLD_STORAGE = "/mnt/storage/worlds/storage"
const val DEFAULT_BAU_WORLD_LIMIT = 3
const val EXTRA_BAU_WORLD_LIMIT = 10
const val TEAM_WORLD_LIMIT = 2
@JvmStatic
fun getWorld(uuid: UUID) = useDb {
findById(uuid)
}
@JvmStatic
fun getBauWorld(user: SteamwarUser, version: Int) = useDb {
find {
(WorldTable.owner eq user.id) and
(WorldTable.version eq version) and
(WorldTable.type eq WorldType.BAU) and
not(WorldTable.deleted)
}.firstOrNull()
}
@JvmStatic
fun getBauWorld(user: SteamwarUser, name: String, version: Int) = useDb {
find {
(WorldTable.owner eq user.id) and
(WorldTable.name eq name) and
(WorldTable.version eq version) and
(WorldTable.type eq WorldType.BAU) and
not(WorldTable.deleted)
}.firstOrNull()
}
@JvmStatic
fun getBauWorlds(user: SteamwarUser) = useDb {
find {
(WorldTable.owner eq user.id) and
(WorldTable.type eq WorldType.BAU) and
not(WorldTable.deleted)
}.orderBy(WorldTable.created to SortOrder.ASC).toList()
}
@JvmStatic
fun countBauWorlds(user: SteamwarUser) = useDb {
find {
(WorldTable.owner eq user.id) and
(WorldTable.type eq WorldType.BAU) and
not(WorldTable.deleted)
}.count()
}
@JvmStatic
@JvmOverloads
fun getOrCreateBauWorld(user: SteamwarUser, version: Int, prototype: File? = null): SteamwarWorld =
getBauWorld(user, version) ?: createWorld(user, user.userName, version, WorldType.BAU, prototype)
@JvmStatic
@JvmOverloads
fun getOrCreateBauWorld(user: SteamwarUser, name: String, version: Int, prototype: File? = null): SteamwarWorld =
getBauWorld(user, name, version) ?: createWorld(user, name, version, WorldType.BAU, prototype)
@JvmStatic
fun getBuilderWorld(name: String, version: Int) = useDb {
find {
(WorldTable.name eq name) and
(WorldTable.version eq version) and
(WorldTable.type eq WorldType.BUILDER) and
not(WorldTable.deleted)
}.firstOrNull()
}
@JvmStatic
fun getBuilderWorlds(version: Int) = useDb {
find {
(WorldTable.version eq version) and
(WorldTable.type eq WorldType.BUILDER) and
not(WorldTable.deleted)
}.toList()
}
@JvmStatic
@JvmOverloads
fun getOrCreateBuilderWorld(name: String, version: Int, prototype: File? = null): SteamwarWorld =
getBuilderWorld(name, version) ?: createWorld(null, name, version, WorldType.BUILDER, prototype)
@JvmStatic
fun getTeamWorld(team: Team, name: String, version: Int) = useDb {
find {
(WorldTable.team eq team.id) and
(WorldTable.name eq name) and
(WorldTable.version eq version) and
(WorldTable.type eq WorldType.TEAM) and
not(WorldTable.deleted)
}.firstOrNull()
}
@JvmStatic
fun getTeamWorlds(team: Team) = useDb {
find {
(WorldTable.team eq team.id) and
(WorldTable.type eq WorldType.TEAM) and
not(WorldTable.deleted)
}.orderBy(WorldTable.created to SortOrder.ASC).toList()
}
@JvmStatic
@JvmOverloads
fun getOrCreateTeamWorld(team: Team, name: String, version: Int, prototype: File? = null): SteamwarWorld =
getTeamWorld(team, name, version) ?: createWorld(null, name, version, WorldType.TEAM, prototype, team)
@JvmStatic
fun countTeamWorlds(team: Team) = useDb {
find {
(WorldTable.team eq team.id) and
(WorldTable.type eq WorldType.TEAM) and
not(WorldTable.deleted)
}.count()
}
@JvmStatic
fun getArenaWorld(mode: String, map: String, version: Int) = useDb {
find {
(WorldTable.name eq arenaWorldName(mode, map)) and
(WorldTable.version eq version) and
(WorldTable.type eq WorldType.ARENA) and
not(WorldTable.deleted)
}.firstOrNull()
}
@JvmStatic
@JvmOverloads
fun getOrCreateArenaWorld(mode: String, map: String, version: Int, prototype: File? = null): SteamwarWorld =
getArenaWorld(mode, map, version) ?: createWorld(null, arenaWorldName(mode, map), version, WorldType.ARENA, prototype)
@JvmStatic
fun getTemplateWorld(name: String, version: Int) = useDb {
find {
(WorldTable.name eq name) and
(WorldTable.version eq version) and
(WorldTable.type eq WorldType.TEMPLATE) and
not(WorldTable.deleted)
}.firstOrNull()
}
@JvmStatic
fun getTemplateWorlds() = useDb {
find {
(WorldTable.type eq WorldType.TEMPLATE) and
not(WorldTable.deleted)
}.orderBy(WorldTable.name to SortOrder.ASC).toList()
}
@JvmStatic
fun getTemplateWorlds(version: Int) = useDb {
find {
(WorldTable.version eq version) and
(WorldTable.type eq WorldType.TEMPLATE) and
not(WorldTable.deleted)
}.orderBy(WorldTable.name to SortOrder.ASC).toList()
}
@JvmStatic
@JvmOverloads
fun getOrCreateTemplateWorld(name: String, version: Int, prototype: File? = null): SteamwarWorld =
getTemplateWorld(name, version) ?: createWorld(null, name, version, WorldType.TEMPLATE, prototype)
@JvmStatic
fun getAllActiveWorlds() = useDb {
find { not(WorldTable.deleted) }.toList()
}
@JvmStatic
fun getWorldsToArchive() = useDb {
find {
not(WorldTable.deleted) or (WorldTable.deleted eq true)
}.toList().filter { it.shouldArchive }
}
private fun arenaWorldName(mode: String, map: String) = "$mode/$map"
@JvmStatic
@JvmOverloads
fun createWorld(user: SteamwarUser?, name: String, version: Int, type: WorldType, prototype: File? = null, team: Team? = null): SteamwarWorld {
val world = useDb { new {
this.name = name
this.version = version
this.type = type
this.owner = user?.id
this.team = team?.id
} }
world.setupAndGetStoragePath(prototype)
return world
}
}
}
@@ -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);
}
@@ -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) {
@@ -22,7 +22,6 @@ package de.steamwar.fightsystem.utils;
import de.steamwar.Reflection;
import de.steamwar.core.CraftbukkitWrapper;
import de.steamwar.fightsystem.Config;
import de.steamwar.fightsystem.FightSystem;
import de.steamwar.fightsystem.events.BoardingEvent;
import de.steamwar.fightsystem.events.TeamLeaveEvent;
import de.steamwar.fightsystem.events.TeamSpawnEvent;
@@ -31,6 +30,7 @@ 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;
@@ -38,6 +38,11 @@ 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;
@@ -45,10 +50,12 @@ 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 {
@@ -56,11 +63,21 @@ 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());
@@ -72,14 +89,19 @@ public class TechHiderWrapper extends StateDependent implements Listener {
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()
Set<BlockEntityType<?>> blockEntityTypeToObfuscate = Config.GameModeConfig.Techhider.HiddenBlockEntities.stream()
.map((id) -> {
ResourceLocation loc = ResourceLocation.parse(id);
return ((Optional<Holder.Reference<BlockEntityType<?>>>) method.invoke(blockEntityType, loc)).get().value();
})
.collect(Collectors.toUnmodifiableSet());
techHider = new TechHider(CraftMagicNumbers.getBlock(Config.GameModeConfig.Techhider.ObfuscateWith)) {
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);
@@ -87,41 +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) {
net.minecraft.world.entity.Entity nmsEntity = ((CraftWorld) p.getWorld()).getHandle().moonrise$getEntityLookup().get(entityId);
net.minecraft.world.entity.Entity nmsEntity = ((CraftWorld) p.getWorld()).getHandle().moonrise$getEntityLookup().get(entityId);
if(nmsEntity != null) {
if (nmsEntity != null) {
return !hullHider.isBlockHidden(p, nmsEntity.getBlockX(), nmsEntity.getBlockY(), nmsEntity.getBlockZ());
}
else {
return false;
} 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);
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 isEveryonePrivilegedToAccessAllDataWithinChunk(int chunkX, int chunkZ) {
return getHiddenRegions().stream().allMatch(region -> region.chunkOutside(chunkX, chunkZ));
public boolean isPlayerPrivilegedToPerformAction(Player p) {
return p.getGameMode() != GameMode.SPECTATOR;
}
};
new StateDependentListener(ENABLED, FightState.Schem, this);
register();
}
@Override
public void enable() {
});
}
@Override
@@ -164,10 +181,6 @@ public class TechHiderWrapper extends StateDependent implements Listener {
});
}
private Set<Region> getHiddenRegions() {
return Fight.teams().stream().map(FightTeam::getExtendRegion).collect(Collectors.toSet());
}
private Region getHiddenRegion(Player player) {
if (Config.isReferee(player)) return Region.EMPTY;
@@ -178,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);
}
@@ -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;
@@ -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;
@@ -20,14 +20,54 @@
package de.steamwar.providers;
import de.steamwar.sql.SteamwarUser;
import de.steamwar.sql.WorldType;
import org.bukkit.Bukkit;
import java.util.UUID;
public class BauServerInfo {
private static Integer bauOwner = null;
private static Integer bauTeam = null;
private static UUID bauWorld = null;
private static WorldType bauType = null;
static {
String ownerProperty = System.getProperty("bauOwner");
if (ownerProperty != null && !ownerProperty.isBlank()) {
try {
bauOwner = Integer.parseInt(ownerProperty);
} catch (NumberFormatException ignored) {
}
}
String teamProperty = System.getProperty("bauTeam");
if (teamProperty != null && !teamProperty.isBlank()) {
try {
bauTeam = Integer.parseInt(teamProperty);
} catch (NumberFormatException ignored) {
}
}
String worldProperty = System.getProperty("bauWorld");
if (worldProperty != null && !worldProperty.isBlank()) {
try {
bauWorld = UUID.fromString(worldProperty);
} catch (IllegalArgumentException ignored) {
}
}
String typeProperty = System.getProperty("bauType");
if (typeProperty != null && !typeProperty.isBlank()) {
try {
bauType = WorldType.valueOf(typeProperty);
} catch (IllegalArgumentException ignored) {
}
}
try {
bauOwner = Integer.parseInt(Bukkit.getWorlds().get(0).getName());
if (bauOwner == null) {
bauOwner = Integer.parseInt(Bukkit.getWorlds().get(0).getName());
}
} catch (NumberFormatException ignored) {
}
}
@@ -37,11 +77,23 @@ public class BauServerInfo {
}
public static boolean isBauServer() {
return bauOwner != null;
return bauOwner != null || bauWorld != null || bauTeam != null;
}
public static SteamwarUser getOwnerUser() {
if (bauOwner == null) return null;
return SteamwarUser.byId(bauOwner);
}
public static UUID getWorldId() {
return bauWorld;
}
public static Integer getTeamId() {
return bauTeam;
}
public static WorldType getWorldType() {
return bauType;
}
}
@@ -0,0 +1,41 @@
/*
* This file is a part of the SteamWar software.
*
* Copyright (C) 2026 SteamWar.de-Serverteam
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package de.steamwar.techhider;
import net.minecraft.world.level.block.Block;
import net.minecraft.world.level.block.entity.BlockEntityType;
import net.minecraft.world.level.block.state.BlockState;
import org.bukkit.entity.Player;
public interface AccessPrivilegeProvider {
boolean isEveryonePrivilegedToAccessAllDataWithinChunk(int chunkX, int chunkZ);
boolean isPlayerPrivilegedToAccessPosition(Player p, int blockX, int blockY, int blockZ);
boolean isPlayerPrivilegedToAccessBlock(Player p, int blockX, int blockY, int blockZ, Block block);
boolean isPlayerPrivilegedToAccessBlockState(Player p, int blockX, int blockY, int blockZ, BlockState blockState);
boolean isPlayerPrivilegedToAccessEntity(Player p, int entityId);
boolean isPlayerPrivilegedToAccessBlockEntity(Player p, int blockX, int blockY, int blockZ, BlockEntityType<?> type);
boolean isPlayerPrivilegedToPerformAction(Player p);
}
@@ -19,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,9 +22,6 @@ package de.steamwar.techhider;
import de.steamwar.Reflection;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import it.unimi.dsi.fastutil.ints.Int2IntMap;
import it.unimi.dsi.fastutil.ints.Int2IntOpenHashMap;
import it.unimi.dsi.fastutil.ints.IntArrayList;
import net.minecraft.core.SectionPos;
import net.minecraft.network.protocol.game.ClientboundLevelChunkPacketData;
import net.minecraft.network.protocol.game.ClientboundLevelChunkWithLightPacket;
@@ -34,11 +31,10 @@ import net.minecraft.world.level.block.entity.BlockEntityType;
import net.minecraft.world.level.block.state.BlockState;
import org.bukkit.entity.Player;
import java.util.*;
import java.util.List;
import java.util.function.UnaryOperator;
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);
@@ -54,16 +50,16 @@ 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;
public ChunkHider(Block blockUsedForObfuscation) {
blockIdUsedForHiding = Block.BLOCK_STATE_REGISTRY.getId(blockUsedForObfuscation.defaultBlockState());
public ChunkHider(Block blockUsedForObfuscation, AccessPrivilegeProvider accessPrivilegeProvider) {
blockIdUsedForHiding = Block.BLOCK_STATE_REGISTRY.getId(blockUsedForObfuscation.defaultBlockState());
this.accessPrivilegeProvider = accessPrivilegeProvider;
}
private int getLongsRequiredToEncodeEntries(int bitsPerEntry, int entryCount) {
int entriesPerLong = BITS_PER_LONG / bitsPerEntry;
int entriesPerLong = Long.SIZE / bitsPerEntry;
int dataLengthAsLongCount = (entryCount + entriesPerLong - 1) / entriesPerLong;
return dataLengthAsLongCount;
@@ -73,7 +69,7 @@ public abstract class ChunkHider {
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();
}
@@ -96,22 +92,21 @@ 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);
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];
}
@@ -123,7 +118,7 @@ public abstract class ChunkHider {
out.writeShort(blockCount);
out.writeByte(15);
for(long rawDataSegment : reEncodedData) {
for (long rawDataSegment : reEncodedData) {
out.writeLong(rawDataSegment);
}
/*
@@ -156,9 +151,8 @@ public abstract class ChunkHider {
out.writeLong(rawDataSegment);
}*/
}
else {
long[] rawData = readSectionDataFromBuffer(in, bitsPerBlock, BLOCKS_PER_SECTION);
} else {
long[] rawData = readSectionDataFromBuffer(in, bitsPerBlock, BLOCKS_PER_SECTION);
int[] blockData = resolveDirectBlockDataArray(rawData, bitsPerBlock);
@@ -169,7 +163,7 @@ public abstract class ChunkHider {
out.writeShort(blockCount);
out.writeByte(15);
for(long rawDataSegment : reEncodedData) {
for (long rawDataSegment : reEncodedData) {
out.writeLong(rawDataSegment);
}
}
@@ -185,7 +179,7 @@ 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);
@@ -203,15 +197,13 @@ public abstract class ChunkHider {
int worldZ = sectionZ + (SECTION_SPAN_SIZE * chunkZ);
int blockId = blockDataArray[blockDataIndex];
BlockState blockState = Block.BLOCK_STATE_REGISTRY.byId(blockId);
BlockState blockState = Block.BLOCK_STATE_REGISTRY.byId(blockId);
Block block = blockState.getBlock();
if(isPlayerPrivilegedToAccessPosition(player, worldX, worldY, worldZ) && isPlayerPrivilegedToAccessBlock(player, worldX, worldY, worldZ, block)) {
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 {
} else {
obfuscatedData[blockDataIndex] = blockIdUsedForHiding;
}
}
@@ -225,7 +217,7 @@ public abstract class ChunkHider {
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++){
for (int i = 0; i < BLOCKS_PER_SECTION; i++) {
resolvedData[i] = data.get(i);
}
@@ -236,7 +228,7 @@ public abstract class ChunkHider {
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++) {
for (int i = 0; i < blockDataArray.length; i++) {
int blockId = blockDataArray[i];
reEncodedData.set(i, blockId);
@@ -245,10 +237,6 @@ public abstract class ChunkHider {
return reEncodedData.getRaw();
}
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 ClientboundLevelChunkWithLightPacket buildNewChunkPacket(ClientboundLevelChunkWithLightPacket originalPacket, byte[] newBlockDataBuffer, List<Object> newBlockEntities) {
ClientboundLevelChunkWithLightPacket clonedPacket = (ClientboundLevelChunkWithLightPacket) chunkPacketShallowCloner.apply(originalPacket);
ClientboundLevelChunkPacketData clonedPacketChunkData = (ClientboundLevelChunkPacketData) chunkDataShallowCloner.apply(originalPacket.getChunkData());
@@ -258,7 +246,7 @@ public abstract class ChunkHider {
levelChunkPacketDataField.set(clonedPacket, clonedPacketChunkData);
return clonedPacket;
return clonedPacket;
}
@@ -267,48 +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){
private void copyOverSectionBiomeData(ByteBuf oldData, ByteBuf newData) {
short bitsPerBiome = oldData.readByte();
newData.writeByte(bitsPerBiome);
if(bitsPerBiome == 0) {
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,3 +1,22 @@
/*
* 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 com.comphenix.tinyprotocol.TinyProtocol;
@@ -6,11 +25,6 @@ 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.protocol.game.*;
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 net.minecraft.core.BlockPos;
import net.minecraft.core.SectionPos;
import net.minecraft.network.PacketListener;
@@ -23,13 +37,11 @@ import net.minecraft.network.protocol.configuration.ClientboundUpdateEnabledFeat
import net.minecraft.network.protocol.cookie.ClientboundCookieRequestPacket;
import net.minecraft.network.protocol.game.*;
import net.minecraft.network.protocol.login.*;
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.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;
@@ -41,35 +53,21 @@ import java.util.function.ToIntFunction;
* 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 ---
@@ -81,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
@@ -95,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 ---
@@ -190,6 +187,8 @@ public abstract class TechHider {
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
@@ -232,7 +231,7 @@ 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.
@@ -274,10 +273,6 @@ 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);
// --- Sound packets ---
// 7.1.115 Entity Sound Effect: entity id and sound.
processors.put(ClientboundSoundEntityPacket.class, this.buildEntityPacketProcessor(ClientboundSoundEntityPacket::getId));
@@ -304,48 +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, rawPacket) -> {
if(rawPacket instanceof ClientboundBundlePacket bundlePacket) {
List<Packet<? super ClientGamePacketListener>> processedPackets = new ArrayList<>();
try {
if (rawPacket instanceof ClientboundBundlePacket bundlePacket) {
List<Packet<? super ClientGamePacketListener>> processedPackets = new ArrayList<>();
for(Packet<? super ClientGamePacketListener> packet : bundlePacket.subPackets()) {
Packet<? super ClientGamePacketListener> processedPacket = (Packet<? super ClientGamePacketListener>) processPacket(player, packet);
for (Packet<? super ClientGamePacketListener> packet : bundlePacket.subPackets()) {
Packet<? super ClientGamePacketListener> processedPacket = (Packet<? super ClientGamePacketListener>) processPacket(player, packet);
if(processedPacket != null) {
processedPackets.add(processedPacket);
if (processedPacket != null) {
processedPackets.add(processedPacket);
}
}
}
return new ClientboundBundlePacket(processedPackets);
}
else if(rawPacket instanceof Packet) {
return processPacket(player, (Packet<?>) rawPacket);
}
else {
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())) {
if (bypassingPackets.contains(packet.getClass())) {
return packet;
}
else if(packetProcessors.containsKey(packet.getClass())) {
return packetProcessors.get(packet.getClass()).apply(player, (Packet<? extends PacketListener>) packet);
}
else {
} 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;
@@ -353,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);
@@ -367,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;
}
}
@@ -393,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);
}
@@ -404,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;
}
}
@@ -419,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;
@@ -428,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;
@@ -449,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;
@@ -460,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;
}
}
@@ -471,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);
@@ -489,12 +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)) {
// TODO statefull !!!
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)){
modified = true;
} else if (privilegeProvider.isPlayerPrivilegedToAccessPosition(p, worldX, worldY, worldZ)) {
filteredPos.add(posShort);
filteredStates.add(blockStateUsedForObfuscation);
}
@@ -503,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++) {
@@ -514,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) {
@@ -526,30 +512,29 @@ public abstract class TechHider {
int blockY = (int) packet.getY();
int blockZ = (int) packet.getZ();
if(isPlayerPrivilegedToAccessPosition(player, blockX, blockY, blockZ)) {
return packet;
}
else {
return null;
}
}
private ClientboundLevelChunkWithLightPacket processChunkWithLight(Player p, ClientboundLevelChunkWithLightPacket packet) {
if(isEveryonePrivilegedToAccessAllDataWithinChunk(packet.getX(), packet.getZ())) {
return packet;
}
else {
return chunkHider.processLevelChunkWithLightPacket(p, packet);
}
}
private Packet<? extends PacketListener> proccessPositionBasedPacket(Player player, int blockX, int blockY, int blockZ, Packet<? extends PacketListener> packet) {
if(isPlayerPrivilegedToAccessPosition(player, blockX, blockY, blockZ)) {
if (privilegeProvider.isPlayerPrivilegedToAccessPosition(player, blockX, blockY, blockZ)) {
return packet;
} else {
return null;
}
}
private ClientboundLevelChunkWithLightPacket processChunkWithLight(Player p, ClientboundLevelChunkWithLightPacket packet) {
if (privilegeProvider.isEveryonePrivilegedToAccessAllDataWithinChunk(packet.getX(), packet.getZ())) {
return packet;
} else {
return chunkHider.processLevelChunkWithLightPacket(p, packet);
}
}
private Packet<? extends PacketListener> proccessPositionBasedPacket(Player player, int blockX, int blockY, int blockZ, Packet<? extends PacketListener> packet) {
if (privilegeProvider.isPlayerPrivilegedToAccessPosition(player, blockX, blockY, blockZ)) {
return packet;
} else {
return null;
}
}
private <TTargetPacket extends Packet<?>> BiFunction<Player, Packet<?>, Packet<?>> buildPositionBasedPacketProcessor(Function<TTargetPacket, BlockPos> positionExtractor) {
return (p, rawPacket) -> {
TTargetPacket packet = (TTargetPacket) rawPacket;
@@ -561,10 +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 isEveryonePrivilegedToAccessAllDataWithinChunk(int chunkX, int chunkZ);
}
@@ -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) {
@@ -29,6 +29,7 @@ import java.util.function.Consumer;
@Getter
public class Bauserver extends Subserver {
private static final Map<UUID, Bauserver> servers = new HashMap<>();
private static final Map<UUID, Bauserver> serversByWorld = new HashMap<>();
public static Bauserver get(UUID owner) {
synchronized (servers) {
@@ -36,25 +37,43 @@ public class Bauserver extends Subserver {
}
}
public static Bauserver getByWorld(UUID world) {
synchronized (serversByWorld) {
return serversByWorld.get(world);
}
}
private final UUID owner;
private final UUID world;
public Bauserver(String serverName, UUID owner, int port, ProcessBuilder processBuilder, Runnable shutdownCallback) {
this(serverName, owner, port, processBuilder, shutdownCallback, null);
this(serverName, owner, owner, port, processBuilder, shutdownCallback, null);
}
public Bauserver(String serverName, UUID owner, int port, ProcessBuilder processBuilder, Runnable shutdownCallback, Consumer<Exception> failureCallback) {
this(serverName, owner, owner, port, processBuilder, shutdownCallback, failureCallback);
}
public Bauserver(String serverName, UUID owner, UUID world, int port, ProcessBuilder processBuilder, Runnable shutdownCallback, Consumer<Exception> failureCallback) {
super(serverName, port, processBuilder, shutdownCallback, failureCallback);
this.owner = owner;
this.world = world;
synchronized (servers) {
servers.put(owner, this);
servers.putIfAbsent(owner, this);
}
synchronized (serversByWorld) {
serversByWorld.put(world, this);
}
}
@Override
protected void unregister() {
synchronized (servers) {
servers.remove(owner);
servers.remove(owner, this);
}
synchronized (serversByWorld) {
serversByWorld.remove(world);
}
super.unregister();
}
+3 -3
View File
@@ -37,8 +37,8 @@ if __name__ == "__main__":
with open(configfile, 'r') as file:
gamemode = yaml.load(file)
builderworld = path.expanduser(f'/worlds/builder{version}/{worldname}')
arenaworld = f'/servers/{gamemode["Server"]["Folder"]}/arenas/{worldname}'
builderworld = sys.argv[4] if len(sys.argv) > 4 else path.expanduser(f'/worlds/builder{version}/{worldname}')
arenaworld = sys.argv[5] if len(sys.argv) > 5 else f'/servers/{gamemode["Server"]["Folder"]}/arenas/{worldname}'
if path.exists(arenaworld):
backupworld = path.expanduser(f'/mnt/storage/backup/arenas/{datetime.datetime.now()}-{worldname}-{version}.tar.xz')
@@ -91,7 +91,7 @@ if __name__ == "__main__":
if path.exists(arenaworld):
shutil.rmtree(arenaworld)
os.makedirs(f'{arenaworld}/backup')
os.makedirs(f'{arenaworld}/backup', exist_ok=True)
shutil.copy2(f'{builderworld}/level.dat', f'{arenaworld}/backup/level.dat')
shutil.copytree(f'{builderworld}/region', f'{arenaworld}/backup/region')
@@ -219,22 +219,22 @@ IGNORE_ALREADY = §cYou are already ignoring this player.
IGNORE_MESSAGE = §7You are now ignoring §e{0}§8.
#BauCommand
BAU_ADDMEMBER_USAGE = §8/§7build addmember §8[§eplayer§8]
BAU_ADDMEMBER_USAGE = §8/§7build addmember §8[§eworld/all§8] §8[§eplayer§8]
BAU_ADDMEMBER_SELFADD = §cYou don't have to add yourself!
BAU_ADDMEMBER_ISADDED = §cThis player is already a member of your world.
BAU_ADDMEMBER_ADDED = §aThe player was added to your world.
BAU_ADDMEMBER_ADDED_TARGET = §aYou have been added to the world of §e{0}§a.
BAU_TP_USAGE = §8/§7build tp §8[§eplayer§8]
BAU_TP_USAGE = §8/§7build tp §8[§eplayer§8] §8<§eworld§8>
BAU_TP_NOALLOWED = §cYou are not allowed to teleport to this player's world.
BAU_LOCKED_NOALLOWED = §cThe build server is currently locked.
BAU_LOCK_BLOCKED = §cYour build lock has prevented §e{0} §cfrom joining.
BAU_LOCKED_OPTIONS = §7Build server lock options§8: §cnobody§8, §eserverteam§8, §eteam_and_serverteam§8, §eteam§8, §aopen
BAU_LOCKED_OPTIONS = §8/§7build lock §8[§eworld§8] §8[§eoption§8] §7Options§8: §cnobody§8, §eserverteam§8, §eteam_and_serverteam§8, §eteam§8, §aopen
BAU_LOCKED_NOBODY = §7You have locked your build server for all players.
BAU_LOCKED_SERVERTEAM = §7You have locked your build server for all players except added server team members.
BAU_LOCKED_TEAM_AND_SERVERTEAM = §7You have locked your build server for all players except added team members and server team members.
BAU_LOCKED_TEAM = §7You have locked your build server for all players except added team members.
BAU_LOCKED_OPEN = §7You have opened your build server for all added players.
BAU_DELMEMBER_USAGE = §8/§7build delmember §8[§eplayer§8]
BAU_DELMEMBER_USAGE = §8/§7build delmember §8[§eworld/all§8] §8[§eplayer§8]
BAU_DELMEMBER_SELFDEL = §cYou cannot remove yourself!
BAU_DELMEMBER_DELETED = §cPlayer was removed.
BAU_DELMEMBER_DELETED_TARGET = §cYou were removed from the world of §e{0}.
@@ -244,9 +244,23 @@ BAU_DELETE_DELETED = §aYour world is being reset.
BAU_DELETE_GUI_NAME = §eDo you really want to delete the world?
BAU_DELETE_GUI_CANCEL = §cCancel
BAU_DELETE_GUI_DELETE = §aDelete
BAU_WORLD_GUI_TITLE = §eYour worlds
BAU_WORLD_CREATE_USAGE = §8/§7build world create §8[§ename§8] §8[§eversion§8] §8[§etemplate§8]
BAU_WORLD_START_USAGE = §8/§7build world start §8[§eworld§8]
BAU_WORLD_RENAME_USAGE = §8/§7build world rename §8[§eworld§8] §8[§enew name§8]
BAU_WORLD_DELETE_USAGE = §8/§7build world delete §8[§eworld§8]
BAU_WORLD_UPGRADE_USAGE = §8/§7build world upgrade §8[§eworld§8] §8[§eversion§8]
BAU_WORLD_VERSION = §cUnknown version.
BAU_WORLD_UNKNOWN = §cUnknown world.
BAU_WORLD_EXISTS = §cA world with this name already exists for this version.
BAU_WORLD_LIMIT = §cYou have reached your build world limit of §e{0}§c. Delete one first.
BAU_WORLD_OVER_LIMIT_DELETE = §cYou currently have more than your allowed §e{0}§c build worlds. Delete one before creating, renaming, or upgrading worlds.
BAU_WORLD_CREATED = §aWorld created.
BAU_WORLD_RENAMED = §aWorld renamed.
BAU_WORLD_UPGRADED = §aWorld version updated.
BAU_START_ALREADY = §cThis server is already starting.
BAU_MEMBER_NOMEMBER = §cThis player is no member of your world!
BAU_MEMBER_SET_USAGE = §8/§7build {0} §8[§eplayer§8]
BAU_MEMBER_SET_USAGE = §8/§7build {0} §8[§eworld/all§8] §8[§eplayer§8]
BAU_MEMBER_SET_TARGET = §7You are now a §e{1}§7 on the world of §e{0}§7.
BAU_MEMBER_SET = §7The player is now a §e{0}§7.
BAU_MEMBER_SET_SPECTATOR = spectator
@@ -524,6 +538,20 @@ TEAM_EVENT_LEFT = §7Your team no longer takes part in this event
TEAM_EVENT_JOINED = §7Your team now takes part in the event §e{0}§7!
TEAM_EVENT_HOW_TO_LEAVE = §7To cancel the participation repeat the command.
#Team Worlds
TEAM_WORLD_GUI_TITLE = §eTeam worlds
TEAM_WORLD_CREATE_USAGE = §8/§7team world create §8[§ename§8] §8[§eversion§8] §8[§etemplate§8]
TEAM_WORLD_RENAME_USAGE = §8/§7team world rename §8[§eworld§8] §8[§enew name§8]
TEAM_WORLD_UPGRADE_USAGE = §8/§7team world upgrade §8[§eworld§8] §8[§eversion§8]
TEAM_WORLD_DELETE_USAGE = §8/§7team world delete §8[§eworld§8]
TEAM_WORLD_EXISTS = §cA team world with this name already exists for this version.
TEAM_WORLD_LIMIT = §cYour team already has the maximum of §e{0}§c worlds. Delete one first.
TEAM_WORLD_OVER_LIMIT_DELETE = §cYour team currently has more than the allowed §e{0}§c worlds. Delete one before creating, renaming, or upgrading worlds.
TEAM_WORLD_CREATED = §aTeam world created.
TEAM_WORLD_RENAMED = §aTeam world renamed.
TEAM_WORLD_UPGRADED = §aTeam world version updated.
TEAM_WORLD_DELETED = §aTeam world deleted.
#Team Color
TEAM_COLOR_TITLE = Choose color
@@ -682,7 +710,7 @@ LOCK_LOCALE_CHANGED = §aLanguage saved
#Builder Cloud
BUILDERCLOUD_USAGE = §8/§7buildercloud §8[§eversion§8] §8[§emap§8]
BUILDERCLOUD_CREATE_USAGE = §8/§7buildercloud create §8[§eversion§8] §8[§emap§8] §8<§7generator§8>
BUILDERCLOUD_CREATE_USAGE = §8/§7buildercloud create §8[§eversion§8] §8[§emap§8] §8[§etemplate§8]
BUILDERCLOUD_RENAME_USAGE = §8/§7buildercloud rename §8[§eversion§8] §8[§emap§8] §8[§enew name§8]
BUILDERCLOUD_DEPLOY_USAGE = §8/§7deployarena §8[§egamemode§8] §8[§eversion§8] §8[§emap§8]
BUILDERCLOUD_DEPLOY_FINISHED = §7Map deployment finished.
@@ -201,22 +201,22 @@ IGNORE_ALREADY = §cDu ignorierst diesen Spieler bereits.
IGNORE_MESSAGE = §7Du ignorierst nun §e{0}§8.
#BauCommand
BAU_ADDMEMBER_USAGE = §8/§7bau addmember §8[§eSpieler§8]
BAU_ADDMEMBER_USAGE = §8/§7bau addmember §8[§eWelt/all§8] §8[§eSpieler§8]
BAU_ADDMEMBER_SELFADD = §cDu brauchst dich nicht selbst hinzufügen!
BAU_ADDMEMBER_ISADDED = §cDieser Spieler ist bereits Mitglied auf deiner Welt.
BAU_ADDMEMBER_ADDED = §aDer Spieler wurde zu deiner Welt hinzugefügt.
BAU_ADDMEMBER_ADDED_TARGET = §aDu wurdest zu der Welt von §e{0} §ahinzugefügt.
BAU_TP_USAGE = §8/§7bau tp §8[§eSpieler§8]
BAU_TP_USAGE = §8/§7bau tp §8[§eSpieler§8] §8<§eWelt§8>
BAU_TP_NOALLOWED = §cDu darfst dich nicht auf diese Welt teleportieren.
BAU_LOCKED_NOALLOWED = §cDer Bauserver ist momentan gesperrt.
BAU_LOCK_BLOCKED = §cDeine Bausperre hat den Beitritt von §e{0} §cverhindert.
BAU_LOCKED_OPTIONS = §7Bauserver-Sperroptionen§8: §cnobody§8, §eserverteam§8, §eteam_and_serverteam§8, §eteam§8, §aopen
BAU_LOCKED_OPTIONS = §8/§7bau lock §8[§eWelt§8] §8[§eOption§8] §7Optionen§8: §cnobody§8, §eserverteam§8, §eteam_and_serverteam§8, §eteam§8, §aopen
BAU_LOCKED_NOBODY = §7Du hast deinen Bau für alle Spieler geschlossen.
BAU_LOCKED_SERVERTEAM = §7Du hast deinen Bau für alle außer hinzugefügte Serverteammitglieder gesperrt.
BAU_LOCKED_TEAM_AND_SERVERTEAM = §7Du hast deinen Bau für alle außer hinzugefügte Teammitglieder und Serverteammitglieder gesperrt.
BAU_LOCKED_TEAM = §7Du hast deinen Bau für alle außer hinzugefügte Teammitglieder gesperrt.
BAU_LOCKED_OPEN = §7Du hast deinen Bau für alle hinzugefügten Spieler geöffnet.
BAU_DELMEMBER_USAGE = §8/§7bau delmember §8[§eSpieler§8]
BAU_DELMEMBER_USAGE = §8/§7bau delmember §8[§eWelt/all§8] §8[§eSpieler§8]
BAU_DELMEMBER_SELFDEL = §cDu kannst dich nicht selbst entfernen!
BAU_DELMEMBER_DELETED = §cDer Spieler wurde entfernt.
BAU_DELMEMBER_DELETED_TARGET = §cDu wurdest von der Welt von §e{0} §centfernt.
@@ -226,9 +226,23 @@ BAU_DELETE_DELETED = §aDeine Welt wird zurückgesetzt.
BAU_DELETE_GUI_NAME = §eWirklich Welt löschen?
BAU_DELETE_GUI_CANCEL = §cAbbrechen
BAU_DELETE_GUI_DELETE = §aLöschen
BAU_WORLD_GUI_TITLE = §eDeine Welten
BAU_WORLD_CREATE_USAGE = §8/§7bau world create §8[§eName§8] §8[§eVersion§8] §8[§eTemplate§8]
BAU_WORLD_START_USAGE = §8/§7bau world start §8[§eWelt§8]
BAU_WORLD_RENAME_USAGE = §8/§7bau world rename §8[§eWelt§8] §8[§eNeuer Name§8]
BAU_WORLD_DELETE_USAGE = §8/§7bau world delete §8[§eWelt§8]
BAU_WORLD_UPGRADE_USAGE = §8/§7bau world upgrade §8[§eWelt§8] §8[§eVersion§8]
BAU_WORLD_VERSION = §cUnbekannte Version.
BAU_WORLD_UNKNOWN = §cUnbekannte Welt.
BAU_WORLD_EXISTS = §cEine Welt mit diesem Namen existiert bereits in dieser Version.
BAU_WORLD_LIMIT = §cDu hast dein Bauwelt-Limit von §e{0}§c erreicht. Lösche zuerst eine Welt.
BAU_WORLD_OVER_LIMIT_DELETE = §cDu hast aktuell mehr als die erlaubten §e{0}§c Bauwelten. Lösche eine Welt, bevor du Welten erstellst, umbenennst oder upgradest.
BAU_WORLD_CREATED = §aWelt erstellt.
BAU_WORLD_RENAMED = §aWelt umbenannt.
BAU_WORLD_UPGRADED = §aWelt-Version aktualisiert.
BAU_START_ALREADY = §cDer Server startet bereits.
BAU_MEMBER_NOMEMBER = §cDer Spieler ist kein Mitglied deiner Welt!
BAU_MEMBER_SET_USAGE = §8/§7bau {0} §8[§eSpieler§8]
BAU_MEMBER_SET_USAGE = §8/§7bau {0} §8[§eWelt/all§8] §8[§eSpieler§8]
BAU_MEMBER_SET_TARGET = §7Du bist nun ein §e{1}§7 auf der Welt von §e{0}§7.
BAU_MEMBER_SET = §7Der Spieler ist nun §e{0}§7.
BAU_MEMBER_SET_SPECTATOR = Zuschauer
@@ -491,6 +505,20 @@ TEAM_EVENT_LEFT = §7Dein Team nimmt nicht mehr am Event teil
TEAM_EVENT_JOINED = §7Dein Team nimmt nun am Event §e{0} §7 teil!
TEAM_EVENT_HOW_TO_LEAVE = §7Um die Teilnahme abzusagen, wiederhole den Befehl.
#Team Worlds
TEAM_WORLD_GUI_TITLE = §eTeamwelten
TEAM_WORLD_CREATE_USAGE = §8/§7team world create §8[§eName§8] §8[§eVersion§8] §8[§eTemplate§8]
TEAM_WORLD_RENAME_USAGE = §8/§7team world rename §8[§eWelt§8] §8[§eNeuer Name§8]
TEAM_WORLD_UPGRADE_USAGE = §8/§7team world upgrade §8[§eWelt§8] §8[§eVersion§8]
TEAM_WORLD_DELETE_USAGE = §8/§7team world delete §8[§eWelt§8]
TEAM_WORLD_EXISTS = §cEine Teamwelt mit diesem Namen existiert bereits in dieser Version.
TEAM_WORLD_LIMIT = §cDein Team hat bereits das Maximum von §e{0}§c Welten. Lösche zuerst eine Welt.
TEAM_WORLD_OVER_LIMIT_DELETE = §cDein Team hat aktuell mehr als die erlaubten §e{0}§c Welten. Lösche eine Welt, bevor du Welten erstellst, umbenennst oder upgradest.
TEAM_WORLD_CREATED = §aTeamwelt erstellt.
TEAM_WORLD_RENAMED = §aTeamwelt umbenannt.
TEAM_WORLD_UPGRADED = §aTeamwelt-Version aktualisiert.
TEAM_WORLD_DELETED = §aTeamwelt gelöscht.
#Team Color
TEAM_COLOR_TITLE = Farbe wählen
@@ -644,7 +672,7 @@ LOCK_LOCALE_CHANGED = §aSprache gespeichert
#Builder Cloud
BUILDERCLOUD_USAGE = §8/§7buildercloud §8[§eVersion§8] §8[§eWelt§8]
BUILDERCLOUD_CREATE_USAGE = §8/§7buildercloud create §8[§eVersion§8] §8[§eWelt§8] §8<§7Generator§8>
BUILDERCLOUD_CREATE_USAGE = §8/§7buildercloud create §8[§eVersion§8] §8[§eWelt§8] §8[§eTemplate§8]
BUILDERCLOUD_RENAME_USAGE = §8/§7buildercloud rename §8[§eVersion§8] §8[§eWElt§8] §8[§eNeuer Name§8]
BUILDERCLOUD_VERSION = §cUnbekannte Version.
BUILDERCLOUD_EXISTING_MAP = §cWelt existiert bereits.
@@ -51,11 +51,9 @@ public class ServerStarter {
private static final String EVENT_PATH = TMP_DATA + "event/";
public static final String TEMP_WORLD_PATH = TMP_DATA + "arenaserver/";
private static final String WORLDS_FOLDER = "/worlds";
public static final String WORLDS_BASE_PATH = WORLDS_FOLDER + "/userworlds";
public static final String BUILDER_BASE_PATH = WORLDS_FOLDER + "/builder";
private static final String WORLDS_STORAGE_BASE_PATH = "/mnt/storage/worlds/userworlds";
public static final String WORLDS_BASE_PATH = SteamwarWorld.WORLD_STORAGE + "/";
public static final String LEGACY_WORLDS_BASE_PATH = "/worlds/userworlds";
public static final String LEGACY_BUILDER_BASE_PATH = "/worlds/builder";
private File directory = null;
private String worldDir = null;
@@ -91,7 +89,8 @@ public class ServerStarter {
gameMode = mode.configFile.getName().replace(".yml", "");
directory = new File(SERVER_PATH, mode.Server.Folder);
arguments.put("config", mode.configFile.getName());
tempWorld(SERVER_PATH + mode.Server.Folder + "/arenas/" + map);
SteamwarWorld arenaWorld = SteamwarWorld.getOrCreateArenaWorld(gameMode, map, version.getVersionSuffix());
tempWorld(arenaWorld.setupAndGetStoragePath());
startCondition = () -> {
if (playersToSend.stream().anyMatch(player -> Subserver.isArena(Subserver.getSubserver(player)))) {
playersToSend.forEach(player -> Chatter.of(player).system("FIGHT_IN_ARENA"));
@@ -106,7 +105,7 @@ public class ServerStarter {
public ServerStarter event(EventFight eventFight) {
arena(ArenaMode.getByInternal(eventFight.getSpielmodus()), eventFight.getMap());
node = VelocityCore.local;
worldDir = EVENT_PATH;
worldDir = EVENT_PATH + "/" + serverToWorldName(Event.byId(eventFight.getEventID()).getEventName()) + "/" + eventFight.getStartTime().toLocalDateTime().format(DateTimeFormatter.ISO_LOCAL_DATE);
worldCleanup = () -> {
};
arguments.put("fightID", String.valueOf(eventFight.getFightID()));
@@ -153,30 +152,36 @@ public class ServerStarter {
}
public ServerStarter build(ServerVersion version, UUID owner) {
this.version = version;
SteamwarUser user = SteamwarUser.get(owner);
return build(version, SteamwarWorld.getOrCreateBauWorld(user, version.getVersionSuffix()));
}
public ServerStarter build(ServerVersion version, SteamwarWorld world) {
this.version = version;
directory = version.getServerDirectory("Bau");
worldDir = version.getWorldFolder(WORLDS_BASE_PATH);
worldName = String.valueOf(SteamwarUser.get(owner).getId());
SteamwarUser user = world.getOwner() == null ? null : SteamwarUser.byId(world.getOwner().getValue());
worldDir = WORLDS_BASE_PATH;
worldName = world.getUuid().toString();
checkpoint = true;
build(owner);
if (world.getType() == WorldType.TEAM) {
build(world.getUuid());
Team team = Team.byId(world.getTeam().getValue());
serverNameProvider = port -> team.getTeamKuerzel() + "s Bau";
arguments.put("bauTeam", String.valueOf(world.getTeam().getValue()));
} else {
build(user.getUUID(), world.getUuid());
arguments.put("bauOwner", String.valueOf(user.getId()));
}
arguments.put("bauWorld", world.getUuid().toString());
arguments.put("bauType", world.getType().name());
worldSetup = () -> {
File world = new File(worldDir, worldName);
if (!world.exists()) {
File storage = new File(version.getWorldFolder(WORLDS_STORAGE_BASE_PATH), worldName);
if (storage.exists()) {
node.execute("mv", storage.getPath(), world.getPath());
} else {
copyWorld(node, new File(directory, "Bauwelt").getPath(), world.getPath());
}
}
};
worldSetup = () -> world.setupAndGetStoragePath(new File(directory, "Bauwelt"));
// Send players to existing server
startCondition = () -> {
Bauserver subserver = Bauserver.get(owner);
Bauserver subserver = Bauserver.getByWorld(world.getUuid());
if (subserver != null) {
for (Player p : playersToSend) {
SubserverSystem.sendPlayer(subserver, p);
@@ -184,9 +189,13 @@ public class ServerStarter {
return false;
}
boolean atLeastOneSupervisor = playersToSend.stream().anyMatch(player -> {
if (player.getUniqueId().equals(owner)) return true;
BauweltMember bauweltMember = BauweltMember.getBauMember(owner, player.getUniqueId());
return bauweltMember.isSupervisor();
SteamwarUser playerUser = SteamwarUser.get(player.getUniqueId());
if (world.getType() == WorldType.TEAM) {
return playerUser.getTeam() == world.getTeam().getValue();
}
if (user != null && playerUser.getId() == user.getId()) return true;
BauweltMember bauweltMember = BauweltMember.getBauMember(world.getUuid(), playerUser.getId());
return bauweltMember != null && bauweltMember.isSupervisor();
});
if (!atLeastOneSupervisor) {
for (Player p : playersToSend) {
@@ -220,15 +229,20 @@ public class ServerStarter {
}
private void build(UUID owner) {
constructor = (serverName, port, builder, shutdownCallback, failureCallback) -> new Bauserver(serverName, owner, port, builder, shutdownCallback, failureCallback);
build(owner, owner);
}
private void build(UUID owner, UUID world) {
constructor = (serverName, port, builder, shutdownCallback, failureCallback) -> new Bauserver(serverName, owner, world, port, builder, shutdownCallback, failureCallback);
serverNameProvider = port -> bauServerName(SteamwarUser.get(owner));
}
public ServerStarter builder(ServerVersion version, String map, File generator) {
public ServerStarter builder(ServerVersion version, String map, File prototype) {
this.version = version;
directory = version.getServerDirectory("Builder");
worldDir = version.getWorldFolder(BUILDER_BASE_PATH);
worldName = map;
SteamwarWorld world = SteamwarWorld.getOrCreateBuilderWorld(map, version.getVersionSuffix());
worldDir = WORLDS_BASE_PATH;
worldName = world.getUuid().toString();
serverNameProvider = port -> "*" + map;
checkpoint = true;
constructor = (serverName, port, builder, shutdownCallback, failureCallback) -> new Builderserver(serverName, worldName, port, builder, shutdownCallback, failureCallback);
@@ -245,16 +259,7 @@ public class ServerStarter {
return true;
};
if (generator != null) {
worldSetup = () -> {
File leveldat = new File(new File(worldDir, worldName), "level.dat");
try {
Files.copy(generator.toPath(), leveldat.toPath());
} catch (IOException e) {
throw new SecurityException(e);
}
};
}
worldSetup = () -> world.setupAndGetStoragePath(prototype != null ? prototype : legacyBuilderWorld(version, map));
return this;
}
@@ -351,13 +356,29 @@ public class ServerStarter {
}
public static String serverToWorldName(String serverName) {
return serverName.replace(' ', '_').replace("[", "").replace("]", "");
return serverName.replace(' ', '_').replace("[", "").replace("]", "").replace(".", "");
}
public static void copyWorld(Node node, String template, String target) {
node.execute("cp", "-r", template, target);
}
private static File legacyBauWorld(ServerVersion version, SteamwarUser user) {
File legacyIdWorld = new File(version.getWorldFolder(LEGACY_WORLDS_BASE_PATH), String.valueOf(user.getId()));
if(legacyIdWorld.exists())
return legacyIdWorld;
File legacyUuidWorld = new File(version.getWorldFolder(LEGACY_WORLDS_BASE_PATH), user.getUUID().toString());
if(legacyUuidWorld.exists())
return legacyUuidWorld;
return new File(version.getServerDirectory("Bau"), "Bauwelt");
}
public static File legacyBuilderWorld(ServerVersion version, String map) {
return new File(version.getWorldFolder(LEGACY_BUILDER_BASE_PATH), map);
}
private interface ServerConstructor {
Subserver construct(String serverName, int port, ProcessBuilder builder, Runnable shutdownCallback, Consumer<Exception> failureCallback);
}
@@ -396,4 +417,4 @@ public class ServerStarter {
}
}
}
}
@@ -35,16 +35,19 @@ import de.steamwar.persistent.Bauserver;
import de.steamwar.sql.BauweltMember;
import de.steamwar.sql.GameModeConfig;
import de.steamwar.sql.SteamwarUser;
import de.steamwar.velocitycore.ServerStarter;
import de.steamwar.velocitycore.ServerVersion;
import de.steamwar.velocitycore.SubserverSystem;
import de.steamwar.velocitycore.VelocityCore;
import de.steamwar.sql.SteamwarWorld;
import de.steamwar.sql.UserPerm;
import de.steamwar.velocitycore.*;
import de.steamwar.velocitycore.inventory.SWInventory;
import de.steamwar.velocitycore.inventory.SWItem;
import de.steamwar.velocitycore.inventory.SWListInv;
import de.steamwar.velocitycore.network.NetworkSender;
import de.steamwar.velocitycore.util.BauLock;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Locale;
import java.util.function.Consumer;
@Linked
@@ -64,13 +67,13 @@ public class BauCommand extends SWCommand {
}
@Register
public void toBau(PlayerChatter sender, @OptionalValue(value = "", onlyUINIG = true) ServerVersion version) {
new ServerStarter().build(version, sender.user().getUUID()).send(sender.getPlayer()).start();
public void toBau(PlayerChatter sender, @Mapper("ownBauWorld") @OptionalValue(value = "", onlyUINIG = true) @AllowNull SteamwarWorld world) {
startPersonalWorld(sender, world);
}
@Register(value = "addmember", description = "BAU_ADDMEMBER_USAGE")
public void addmember(Chatter sender, @Validator("addMemberTarget") SteamwarUser target) {
BauweltMember.addMember(sender.user().getUUID(), target.getUUID());
public void addmember(Chatter sender, @Mapper("bauWorldSelector") String world, @Validator("addMemberTarget") SteamwarUser target) {
if (!forEachSelectedWorld(sender, world, bauworld -> BauweltMember.addMember(bauworld.getUuid(), target.getId()))) return;
sender.system("BAU_ADDMEMBER_ADDED");
Chatter.of(target.getUUID()).system("BAU_ADDMEMBER_ADDED_TARGET", sender);
}
@@ -86,18 +89,31 @@ public class BauCommand extends SWCommand {
messageSender.send("BAU_ADDMEMBER_SELFADD");
return false;
}
if (BauweltMember.getBauMember(sender.user().getId(), value.getId()) != null) {
messageSender.send("BAU_ADDMEMBER_ISADDED");
return false;
}
return true;
};
}
@Register(value = "tp", description = "BAU_TP_USAGE")
@Register("teleport")
public void teleport(PlayerChatter sender, @Validator("teleportTarget") SteamwarUser worldOwner, @OptionalValue(value = "", onlyUINIG = true) ServerVersion version) {
new ServerStarter().build(version, worldOwner.getUUID()).send(sender.getPlayer()).start();
public void teleport(PlayerChatter sender, @Validator("teleportTarget") SteamwarUser worldOwner, @Mapper("targetBauWorld") @OptionalValue(value = "", onlyUINIG = true) @AllowNull SteamwarWorld world) {
if (world == null) {
world = defaultPersonalWorld(worldOwner);
}
if (world == null) {
sender.system("BAU_WORLD_UNKNOWN");
return;
}
if (sender.user().getId() != worldOwner.getId() && BauLock.isLocked(world, sender.user())) {
sender.system("BAU_LOCKED_NOALLOWED");
Chatter.of(worldOwner.getUUID()).system("BAU_LOCK_BLOCKED", sender);
return;
}
if (sender.user().getId() != worldOwner.getId() && BauweltMember.getBauMember(world.getUuid(), sender.user().getId()) == null) {
SubserverSystem.sendDeniedMessage(sender, worldOwner.getUUID());
sender.system("BAU_TP_NOALLOWED");
return;
}
new ServerStarter().build(ServerVersion.get(world.getVersion()), world).send(sender.getPlayer()).start();
}
@Validator(value = "teleportTarget", local = true)
@@ -107,17 +123,6 @@ public class BauCommand extends SWCommand {
messageSender.send("UNKNOWN_PLAYER");
return false;
}
if (sender.user().getId() != owner.getId() && BauweltMember.getBauMember(owner.getId(), sender.user().getId()) == null) {
SubserverSystem.sendDeniedMessage(sender, owner.getUUID());
messageSender.send("BAU_TP_NOALLOWED");
return false;
}
if (BauLock.isLocked(owner, sender.user())) {
messageSender.send("BAU_LOCKED_NOALLOWED");
Chatter.of(owner.getUUID()).system("BAU_LOCK_BLOCKED", sender);
return false;
}
return true;
};
}
@@ -128,39 +133,39 @@ public class BauCommand extends SWCommand {
}
@Register("setspectator")
public void setSpectator(Chatter sender, @Mapper("addedUsers") @AllowNull @OptionalValue("") SteamwarUser user) {
setPerms(sender, user, "setspectator", "BAU_MEMBER_SET_SPECTATOR", member -> {
public void setSpectator(Chatter sender, @Mapper("bauWorldSelector") String world, @Mapper("addedUsers") @AllowNull @OptionalValue("") SteamwarUser user) {
setPerms(sender, world, user, "setspectator", "BAU_MEMBER_SET_SPECTATOR", member -> {
member.setBuild(false);
member.setSupervisor(false);
});
}
@Register("setbuilder")
public void setBuilder(Chatter sender, @Mapper("addedUsers") @AllowNull @OptionalValue("") SteamwarUser user) {
setPerms(sender, user, "setbuild", "BAU_MEMBER_SET_BUILDER", member -> {
public void setBuilder(Chatter sender, @Mapper("bauWorldSelector") String world, @Mapper("addedUsers") @AllowNull @OptionalValue("") SteamwarUser user) {
setPerms(sender, world, user, "setbuild", "BAU_MEMBER_SET_BUILDER", member -> {
member.setBuild(true);
member.setSupervisor(false);
});
}
@Register("setsupervisor")
public void setSupervisor(Chatter sender, @Mapper("addedUsers") @AllowNull @OptionalValue("") SteamwarUser user) {
setPerms(sender, user, "setsupervisor", "BAU_MEMBER_SET_SUPERVISOR", member -> {
public void setSupervisor(Chatter sender, @Mapper("bauWorldSelector") String world, @Mapper("addedUsers") @AllowNull @OptionalValue("") SteamwarUser user) {
setPerms(sender, world, user, "setsupervisor", "BAU_MEMBER_SET_SUPERVISOR", member -> {
member.setBuild(true);
member.setSupervisor(true);
});
}
private void setPerms(Chatter owner, SteamwarUser user, String name, String permName, Consumer<BauweltMember> setter) {
private void setPerms(Chatter owner, String world, SteamwarUser user, String name, String permName, Consumer<BauweltMember> setter) {
if (user == null) {
owner.system("BAU_MEMBER_SET_USAGE", name);
return;
}
withMember(owner, user, target -> {
withMembers(owner, world, user, target -> {
setter.accept(target);
Bauserver bauserver = Bauserver.get(owner.user().getUUID());
Bauserver bauserver = Bauserver.getByWorld(target.getWorldID());
if (bauserver != null) {
bauserver.getRegisteredServer().getPlayersConnected().stream().findAny().ifPresent(player -> NetworkSender.send(player, new BaumemberUpdatePacket()));
}
@@ -171,11 +176,11 @@ public class BauCommand extends SWCommand {
}
@Register(value = "delmember", description = "BAU_DELMEMBER_USAGE")
public void delmember(Chatter owner, @Mapper("addedUsers") SteamwarUser user) {
withMember(owner, user, target -> {
public void delmember(Chatter owner, @Mapper("bauWorldSelector") String world, @Mapper("addedUsers") SteamwarUser user) {
withMembers(owner, world, user, target -> {
target.remove();
Bauserver bauserver = Bauserver.get(owner.user().getUUID());
Bauserver bauserver = Bauserver.getByWorld(target.getWorldID());
Chatter member = Chatter.of(user.getUUID());
member.withPlayer(player -> {
if (bauserver != null && bauserver.getRegisteredServer().getPlayersConnected().contains(player)) {
@@ -192,6 +197,29 @@ public class BauCommand extends SWCommand {
});
}
@Mapper(value = "bauWorldSelector", local = true)
public TypeMapper<String> bauWorldSelector() {
return new TypeMapper<>() {
@Override
public String map(Chatter sender, PreviousArguments previousArguments, String s) {
if ("all".equalsIgnoreCase(s)) return "all";
return SteamwarWorld.getBauWorlds(sender.user()).stream()
.map(SteamwarWorld::getName)
.filter(name -> name.equalsIgnoreCase(s))
.findFirst()
.orElse(null);
}
@Override
public Collection<String> tabCompletes(Chatter sender, PreviousArguments previousArguments, String s) {
List<String> worlds = new ArrayList<>();
worlds.add("all");
SteamwarWorld.getBauWorlds(sender.user()).stream().map(SteamwarWorld::getName).forEach(worlds::add);
return worlds;
}
};
}
@Mapper(value = "addedUsers", local = true)
public TypeMapper<SteamwarUser> addedUsers() {
return new TypeMapper<SteamwarUser>() {
@@ -202,8 +230,10 @@ public class BauCommand extends SWCommand {
@Override
public Collection<String> tabCompletes(Chatter sender, PreviousArguments previousArguments, String s) {
return BauweltMember.getMembers(sender.user().getId()).stream()
return selectedWorlds(sender, previousArguments.userArgs.length == 0 ? "all" : previousArguments.userArgs[previousArguments.userArgs.length - 1]).stream()
.flatMap(world -> BauweltMember.getWorldMembers(world.getUuid()).stream())
.map(bauweltMember -> SteamwarUser.byId(bauweltMember.getMemberID()).getUserName())
.distinct()
.toList();
}
};
@@ -213,9 +243,11 @@ public class BauCommand extends SWCommand {
public void stop(PlayerChatter sender) {
VelocityCore.schedule(() -> {
sender.system("BAU_STOPPING");
Bauserver subserver = Bauserver.get(sender.user().getUUID());
if (subserver != null) {
subserver.stop();
for (SteamwarWorld world : SteamwarWorld.getBauWorlds(sender.user())) {
Bauserver subserver = Bauserver.getByWorld(world.getUuid());
if (subserver != null) {
subserver.stop();
}
}
sender.system("BAU_STOPPED");
@@ -224,25 +256,50 @@ public class BauCommand extends SWCommand {
@Register("resetall")
@Register("delete")
public void delete(PlayerChatter sender, ServerVersion version) {
SWInventory inventory = new SWInventory(sender, 9, new Message("BAU_DELETE_GUI_NAME"));
inventory.addItem(0, new SWItem("LIME_DYE", new Message("BAU_DELETE_GUI_DELETE")), click -> {
String world = version.getWorldFolder(ServerStarter.WORLDS_BASE_PATH) + sender.user().getId();
public void delete(PlayerChatter sender, @Mapper("ownBauWorld") SteamwarWorld world) {
openDeleteWorld(sender, world);
}
VelocityCore.schedule(() -> {
Bauserver subserver = Bauserver.get(sender.user().getUUID());
if (subserver != null) {
subserver.stop();
}
@Register("worlds")
@Register("world")
public void worlds(PlayerChatter sender) {
openWorldList(sender);
}
SubserverSystem.deleteFolder(VelocityCore.local, world);
sender.system("BAU_DELETE_DELETED");
}).schedule();
@Register(value = "world create", description = "BAU_WORLD_CREATE_USAGE")
public void createWorld(PlayerChatter sender, String name, @ErrorMessage("BAU_WORLD_VERSION") ServerVersion version, @Mapper("templateWorld") SteamwarWorld template) {
SteamwarWorld world = createPersonalWorld(sender, name, version, template);
if (world != null) {
sender.system("BAU_WORLD_CREATED");
}
}
inventory.close();
});
inventory.addItem(8, new SWItem("RED_DYE", new Message("BAU_DELETE_GUI_CANCEL")), click -> inventory.close());
inventory.open();
@Register(value = "world start", description = "BAU_WORLD_START_USAGE")
public void startWorld(PlayerChatter sender, @Mapper("ownBauWorld") SteamwarWorld world) {
new ServerStarter().build(ServerVersion.get(world.getVersion()), world).send(sender.getPlayer()).start();
}
@Register(value = "world rename", description = "BAU_WORLD_RENAME_USAGE")
public void renameWorld(Chatter sender, @Mapper("ownBauWorld") SteamwarWorld world, String newName) {
if (hasTooManyPersonalWorlds(sender)) return;
if (SteamwarWorld.getBauWorld(sender.user(), newName, world.getVersion()) != null) {
sender.system("BAU_WORLD_EXISTS");
return;
}
world.rename(newName);
sender.system("BAU_WORLD_RENAMED");
}
@Register(value = "world delete", description = "BAU_WORLD_DELETE_USAGE")
public void deleteWorld(PlayerChatter sender, @Mapper("ownBauWorld") SteamwarWorld world) {
openDeleteWorld(sender, world);
}
@Register(value = "world upgrade", description = "BAU_WORLD_UPGRADE_USAGE")
public void upgradeWorld(Chatter sender, @Mapper("ownBauWorld") SteamwarWorld world, @ErrorMessage("BAU_WORLD_VERSION") ServerVersion version) {
if (hasTooManyPersonalWorlds(sender)) return;
world.changeVersion(version.getVersionSuffix());
sender.system("BAU_WORLD_UPGRADED");
}
@Register("test")
@@ -254,27 +311,244 @@ public class BauCommand extends SWCommand {
}
@Register(value = "lock", description = "BAU_LOCKED_OPTIONS")
public void lock(Chatter sender, BauLockState bauLockState) {
BauLock.setLocked(sender, bauLockState);
public void lock(Chatter sender, @Mapper("ownBauWorld") SteamwarWorld world, BauLockState bauLockState) {
BauLock.setLocked(sender, world, bauLockState);
}
@Register("unlock")
public void unlock(Chatter sender) {
BauLock.setLocked(sender, BauLockState.OPEN);
public void unlock(Chatter sender, @Mapper("ownBauWorld") SteamwarWorld world) {
BauLock.setLocked(sender, world, BauLockState.OPEN);
}
private static void withMember(Chatter owner, SteamwarUser member, Consumer<BauweltMember> function) {
private static void withMembers(Chatter owner, String worldSelector, SteamwarUser member, Consumer<BauweltMember> function) {
if (member == null) {
owner.system("UNKNOWN_PLAYER");
return;
}
BauweltMember target = BauweltMember.getBauMember(owner.user().getId(), member.getId());
if (target == null) {
owner.system("BAU_MEMBER_NOMEMBER");
List<SteamwarWorld> worlds = selectedWorlds(owner, worldSelector);
if (worlds.isEmpty()) {
owner.system("BAU_WORLD_UNKNOWN");
return;
}
function.accept(target);
boolean found = false;
for (SteamwarWorld world : worlds) {
BauweltMember target = BauweltMember.getBauMember(world.getUuid(), member.getId());
if (target == null) continue;
found = true;
function.accept(target);
}
if (!found) {
owner.system("BAU_MEMBER_NOMEMBER");
}
}
@Mapper(value = "ownBauWorld", local = true)
private TypeMapper<SteamwarWorld> ownBauWorldMapper() {
return new TypeMapper<SteamwarWorld>() {
@Override
public SteamwarWorld map(Chatter sender, PreviousArguments previousArguments, String s) {
if (s == null || s.isEmpty()) return defaultPersonalWorld(sender);
return SteamwarWorld.getBauWorlds(sender.user()).stream()
.filter(world -> world.getName().equalsIgnoreCase(s) || world.getUuid().toString().equalsIgnoreCase(s))
.findFirst()
.orElse(null);
}
@Override
public Collection<String> tabCompletes(Chatter sender, PreviousArguments previousArguments, String s) {
return SteamwarWorld.getBauWorlds(sender.user()).stream().map(SteamwarWorld::getName).toList();
}
};
}
@Mapper(value = "targetBauWorld", local = true)
private TypeMapper<SteamwarWorld> targetBauWorldMapper() {
return new TypeMapper<>() {
@Override
public SteamwarWorld map(Chatter sender, PreviousArguments previousArguments, String s) {
SteamwarUser owner = previousArguments.getAll(SteamwarUser.class).stream().findFirst().orElse(null);
if (owner == null) return null;
if (s == null || s.isEmpty()) return defaultPersonalWorld(owner);
return SteamwarWorld.getBauWorlds(owner).stream()
.filter(world -> world.getName().equalsIgnoreCase(s) || world.getUuid().toString().equalsIgnoreCase(s))
.findFirst()
.orElse(null);
}
@Override
public Collection<String> tabCompletes(Chatter sender, PreviousArguments previousArguments, String s) {
SteamwarUser owner = previousArguments.getAll(SteamwarUser.class).stream().findFirst().orElse(null);
if (owner == null) return List.of();
return SteamwarWorld.getBauWorlds(owner).stream().map(SteamwarWorld::getName).toList();
}
};
}
@Mapper(value = "templateWorld", local = true)
private TypeMapper<SteamwarWorld> templateWorldMapper() {
return new TypeMapper<>() {
@Override
public SteamwarWorld map(Chatter sender, PreviousArguments previousArguments, String s) {
ServerVersion version = previousArguments.getAll(ServerVersion.class).stream().findFirst().orElse(null);
if (version == null) return null;
return SteamwarWorld.getTemplateWorld(s, version.getVersionSuffix());
}
@Override
public Collection<String> tabCompletes(Chatter sender, PreviousArguments previousArguments, String s) {
ServerVersion version = previousArguments.getAll(ServerVersion.class).stream().findFirst().orElse(null);
if (version == null) return List.of();
return SteamwarWorld.getTemplateWorlds(version.getVersionSuffix()).stream().map(SteamwarWorld::getName).toList();
}
};
}
private void openWorldList(PlayerChatter sender) {
List<SWListInv.SWListEntry<SteamwarWorld>> entries = new ArrayList<>();
for (SteamwarWorld world : SteamwarWorld.getBauWorlds(sender.user())) {
SWItem item = new SWItem("GRASS_BLOCK", new Message("PLAIN_STRING", "§e" + world.getName()))
.addLore(new Message("PLAIN_STRING", "§7Version: §e" + world.getVersion()))
.addLore(new Message("PLAIN_STRING", "§7Left: start, right: manage"));
entries.add(new SWListInv.SWListEntry<>(item, world));
}
SWListInv<SteamwarWorld> inventory = new SWListInv<>(sender, new Message("BAU_WORLD_GUI_TITLE"), entries, (click, world) -> {
if (click.isRightClick()) {
openWorldManagement(sender, world);
} else {
new ServerStarter().build(ServerVersion.get(world.getVersion()), world).send(sender.getPlayer()).start();
}
});
inventory.open();
}
private void openWorldManagement(PlayerChatter sender, SteamwarWorld world) {
SWInventory inventory = new SWInventory(sender, 9, new Message("PLAIN_STRING", "§e" + world.getName()));
inventory.addItem(0, new SWItem("ENDER_PEARL", new Message("PLAIN_STRING", "§aStart")), click -> {
inventory.close();
new ServerStarter().build(ServerVersion.get(world.getVersion()), world).send(sender.getPlayer()).start();
});
inventory.addItem(3, new SWItem("EXPERIENCE_BOTTLE", new Message("PLAIN_STRING", "§eUpgrade to latest")), click -> {
if (hasTooManyPersonalWorlds(sender)) {
inventory.close();
return;
}
world.changeVersion(ServerVersion.PAPER_21.getVersionSuffix());
sender.system("BAU_WORLD_UPGRADED");
inventory.close();
});
inventory.addItem(8, new SWItem("BARRIER", new Message("PLAIN_STRING", "§cDelete")), click -> openDeleteWorld(sender, world));
inventory.open();
}
private void openDeleteWorld(PlayerChatter sender, SteamwarWorld world) {
SWInventory inventory = new SWInventory(sender, 9, new Message("BAU_DELETE_GUI_NAME"));
inventory.addItem(0, new SWItem("LIME_DYE", new Message("BAU_DELETE_GUI_DELETE")), click -> {
VelocityCore.schedule(() -> {
Bauserver subserver = Bauserver.getByWorld(world.getUuid());
if (subserver != null) {
subserver.stop();
}
world.markDeleted();
SubserverSystem.deleteFolder(VelocityCore.local, world.getStorageDirectory().getPath());
sender.system("BAU_DELETE_DELETED");
}).schedule();
inventory.close();
});
inventory.addItem(8, new SWItem("RED_DYE", new Message("BAU_DELETE_GUI_CANCEL")), click -> inventory.close());
inventory.open();
}
private void startPersonalWorld(PlayerChatter sender, SteamwarWorld world) {
if (world == null) {
world = defaultPersonalWorld(sender);
}
if (world == null) {
sender.system("BAU_WORLD_UNKNOWN");
return;
}
new ServerStarter().build(ServerVersion.get(world.getVersion()), world).send(sender.getPlayer()).start();
}
private static SteamwarWorld defaultPersonalWorld(Chatter sender) {
return defaultPersonalWorld(sender.user());
}
private static SteamwarWorld defaultPersonalWorld(SteamwarUser user) {
return SteamwarWorld.getBauWorlds(user).stream().findFirst().orElse(null);
}
private static List<SteamwarWorld> selectedWorlds(Chatter sender, String selector) {
List<SteamwarWorld> worlds = SteamwarWorld.getBauWorlds(sender.user());
if ("all".equalsIgnoreCase(selector)) {
return worlds;
}
String normalized = selector.toLowerCase(Locale.ROOT);
return worlds.stream()
.filter(world -> world.getName().toLowerCase(Locale.ROOT).equals(normalized))
.toList();
}
private static boolean forEachSelectedWorld(Chatter sender, String selector, Consumer<SteamwarWorld> consumer) {
List<SteamwarWorld> worlds = selectedWorlds(sender, selector);
if (worlds.isEmpty()) {
sender.system("BAU_WORLD_UNKNOWN");
return false;
}
worlds.forEach(consumer);
return true;
}
private SteamwarWorld createPersonalWorld(Chatter sender, String name, ServerVersion version, SteamwarWorld template) {
if (template == null || template.getVersion() != version.getVersionSuffix()) {
sender.system("BAU_WORLD_UNKNOWN");
return null;
}
if (sender.user().hasPerm(UserPerm.BUILD_UNLIMITED_WORLDS)) {
if (SteamwarWorld.getBauWorld(sender.user(), name, version.getVersionSuffix()) != null) {
sender.system("BAU_WORLD_EXISTS");
return null;
}
return SteamwarWorld.getOrCreateBauWorld(sender.user(), name, version.getVersionSuffix(), template.getStorageDirectory());
}
long limit = personalWorldLimit(sender);
long count = SteamwarWorld.countBauWorlds(sender.user());
if (count > limit) {
sender.system("BAU_WORLD_OVER_LIMIT_DELETE", limit);
return null;
}
if (count >= limit) {
sender.system("BAU_WORLD_LIMIT", limit);
return null;
}
if (SteamwarWorld.getBauWorld(sender.user(), name, version.getVersionSuffix()) != null) {
sender.system("BAU_WORLD_EXISTS");
return null;
}
return SteamwarWorld.getOrCreateBauWorld(sender.user(), name, version.getVersionSuffix(), template.getStorageDirectory());
}
private boolean hasTooManyPersonalWorlds(Chatter sender) {
if (sender.user().hasPerm(UserPerm.BUILD_UNLIMITED_WORLDS)) {
return false;
}
long limit = personalWorldLimit(sender);
if (SteamwarWorld.countBauWorlds(sender.user()) <= limit) {
return false;
}
sender.system("BAU_WORLD_OVER_LIMIT_DELETE", limit);
return true;
}
private long personalWorldLimit(Chatter sender) {
return sender.user().hasPerm(UserPerm.BUILD_EXTRA_WORLDS) ? SteamwarWorld.EXTRA_BAU_WORLD_LIMIT : SteamwarWorld.DEFAULT_BAU_WORLD_LIMIT;
}
}
@@ -26,6 +26,7 @@ import de.steamwar.linkage.Linked;
import de.steamwar.messages.Chatter;
import de.steamwar.messages.PlayerChatter;
import de.steamwar.sql.GameModeConfig;
import de.steamwar.sql.SteamwarWorld;
import de.steamwar.sql.UserPerm;
import de.steamwar.velocitycore.ArenaMode;
import de.steamwar.velocitycore.ServerStarter;
@@ -33,11 +34,11 @@ import de.steamwar.velocitycore.ServerVersion;
import de.steamwar.velocitycore.VelocityCore;
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.LinkedHashSet;
import java.util.Set;
@Linked
public class BuilderCloudCommand extends SWCommand {
@@ -47,14 +48,23 @@ public class BuilderCloudCommand extends SWCommand {
}
@Register(value = "create", description = "BUILDERCLOUD_CREATE_USAGE")
public void create(Chatter sender, @ErrorMessage("BUILDERCLOUD_VERSION") ServerVersion version, @Mapper("map") String map, @OptionalValue("") @Mapper("generator") @AllowNull File generator) {
mapFile(version, map).mkdir();
sender.withPlayer(p -> new ServerStarter().builder(version, map, generator).send(p).start());
public void create(Chatter sender, @ErrorMessage("BUILDERCLOUD_VERSION") ServerVersion version, @Mapper("map") String map, @Mapper("templateWorld") SteamwarWorld template) {
if(mapExists(version, map)) {
sender.system("BUILDERCLOUD_EXISTING_MAP");
return;
}
if (template == null || template.getVersion() != version.getVersionSuffix()) {
sender.system("BAU_WORLD_UNKNOWN");
return;
}
SteamwarWorld.getOrCreateBuilderWorld(map, version.getVersionSuffix(), template.getStorageDirectory());
sender.withPlayer(p -> new ServerStarter().builder(version, map, template.getStorageDirectory()).send(p).start());
}
@Register(description = "BUILDERCLOUD_USAGE")
public void start(PlayerChatter sender, @ErrorMessage("BUILDERCLOUD_VERSION") ServerVersion version, @Mapper("map") String map) {
if (!mapFile(version, map).exists()) {
if (!mapExists(version, map)) {
sender.system("BUILDERCLOUD_UNKNOWN_MAP");
return;
}
@@ -64,36 +74,34 @@ public class BuilderCloudCommand extends SWCommand {
@Register(value = "rename", description = "BUILDERCLOUD_RENAME_USAGE")
public void rename(Chatter sender, @ErrorMessage("BUILDERCLOUD_VERSION") ServerVersion version, @Mapper("map") String oldName, String newName) {
File oldMap = mapFile(version, oldName);
if (!oldMap.exists()) {
SteamwarWorld oldMap = builderWorld(version, oldName);
if (oldMap == null) {
sender.system("BUILDERCLOUD_UNKNOWN_MAP");
return;
}
File newMap = mapFile(version, newName);
if (newMap.exists()) {
if (mapExists(version, newName)) {
sender.system("BUILDERCLOUD_EXISTING_MAP");
return;
}
try {
Files.move(oldMap.toPath(), newMap.toPath());
} catch (IOException e) {
throw new SecurityException(e);
}
oldMap.rename(newName);
sender.system("BUILDERCLOUD_RENAMED");
}
@Register(value = "deploy", description = "BUILDERCLOUD_DEPLOY_USAGE")
public void deploy(Chatter sender, @Mapper("nonHistoricArenaMode") GameModeConfig<String, String> arenaMode, @ErrorMessage("BUILDERCLOUD_VERSION") ServerVersion version, @Mapper("map") String map) {
if (!mapFile(version, map).exists()) {
SteamwarWorld builderWorld = builderWorld(version, map);
if (builderWorld == null) {
sender.system("BUILDERCLOUD_UNKNOWN_MAP");
return;
}
VelocityCore.schedule(() -> {
VelocityCore.local.execute("deployarena.py", arenaMode.configFile.getName(), Integer.toString(version.getVersionSuffix()), map);
String modeName = arenaMode.configFile.getName().replace(".yml", "");
SteamwarWorld arenaWorld = SteamwarWorld.getOrCreateArenaWorld(modeName, map, version.getVersionSuffix());
VelocityCore.local.execute("deployarena.py", arenaMode.configFile.getName(), Integer.toString(version.getVersionSuffix()), map, builderWorld.setupAndGetStoragePath(), arenaWorld.setupAndGetStoragePath());
ArenaMode.init();
sender.system("BUILDERCLOUD_DEPLOY_FINISHED");
}).schedule();
@@ -111,60 +119,78 @@ public class BuilderCloudCommand extends SWCommand {
@Override
public Collection<String> tabCompletes(Chatter sender, PreviousArguments previousArguments, String s) {
File folder = getWorldFolder(previousArguments, 1);
String[] files;
if (folder == null || (files = folder.list()) == null) {
ServerVersion version = getVersion(previousArguments, 1);
if (version == null) {
return Collections.emptyList();
}
return Arrays.stream(files).filter(file -> new File(folder, file).isDirectory()).filter(file -> s.startsWith(".") || !file.startsWith(".")).toList();
Set<String> maps = new LinkedHashSet<>();
SteamwarWorld.getBuilderWorlds(version.getVersionSuffix()).stream()
.map(SteamwarWorld::getName)
.forEach(maps::add);
File legacyFolder = new File(version.getWorldFolder(ServerStarter.LEGACY_BUILDER_BASE_PATH));
String[] files = legacyFolder.list();
if(files != null)
Arrays.stream(files).filter(file -> new File(legacyFolder, file).isDirectory()).forEach(maps::add);
return maps.stream()
.filter(file -> s.startsWith(".") || !file.startsWith("."))
.toList();
}
};
}
@Cached(global = true)
@Mapper(value = "generator", local = true)
private TypeMapper<File> generatorTypeMapper() {
@Mapper(value = "templateWorld", local = true)
private TypeMapper<SteamwarWorld> templateWorldMapper() {
return new TypeMapper<File>() {
return new TypeMapper<>() {
@Override
public File map(Chatter sender, PreviousArguments previousArguments, String s) {
if (s.isEmpty()) return null;
File folder = getWorldFolder(previousArguments, 2);
if (folder == null) throw new SecurityException();
File generator = new File(folder, s + ".dat");
if (!generator.exists() || !generator.isFile()) {
throw new SecurityException();
}
return generator;
public SteamwarWorld map(Chatter sender, PreviousArguments previousArguments, String s) {
ServerVersion version = previousArguments.getAll(ServerVersion.class).stream().findFirst().orElse(null);
if (version == null) return null;
return SteamwarWorld.getTemplateWorld(s, version.getVersionSuffix());
}
@Override
public Collection<String> tabCompletes(Chatter sender, PreviousArguments previousArguments, String s) {
File folder = getWorldFolder(previousArguments, 2);
String[] files;
if (folder == null || (files = folder.list()) == null) {
ServerVersion version = previousArguments.getAll(ServerVersion.class).stream().findFirst().orElse(null);
if (version == null) {
return Collections.emptyList();
}
return Arrays.stream(files).filter(file -> new File(folder, file).isFile()).filter(file -> file.endsWith(".dat")).map(file -> file.substring(0, file.length() - 4)).toList();
return SteamwarWorld.getTemplateWorlds(version.getVersionSuffix()).stream()
.map(SteamwarWorld::getName)
.toList();
}
};
}
private File mapFile(ServerVersion version, String map) {
return new File(version.getWorldFolder(ServerStarter.BUILDER_BASE_PATH), map);
private SteamwarWorld builderWorld(ServerVersion version, String map) {
SteamwarWorld world = SteamwarWorld.getBuilderWorld(map, version.getVersionSuffix());
if(world != null)
return world;
File legacyWorld = mapFile(version, map);
if(!legacyWorld.exists())
return null;
return SteamwarWorld.getOrCreateBuilderWorld(map, version.getVersionSuffix(), legacyWorld);
}
private File getWorldFolder(PreviousArguments previousArguments, int offset) {
ServerVersion v = ServerVersion.get(previousArguments.userArgs[previousArguments.userArgs.length - offset]);
if (v == null) return null;
return new File(v.getWorldFolder(ServerStarter.BUILDER_BASE_PATH));
private boolean mapExists(ServerVersion version, String map) {
return SteamwarWorld.getBuilderWorld(map, version.getVersionSuffix()) != null || mapFile(version, map).exists();
}
private File mapFile(ServerVersion version, String map) {
return new File(version.getWorldFolder(ServerStarter.LEGACY_BUILDER_BASE_PATH), map);
}
private ServerVersion getVersion(PreviousArguments previousArguments, int offset) {
if (previousArguments.userArgs.length < offset) {
return null;
}
return ServerVersion.get(previousArguments.userArgs[previousArguments.userArgs.length - offset]);
}
}
@@ -307,6 +307,13 @@ public class CheckCommand extends SWCommand {
return;
}
// Remove any added players from the schematic in the folder
for (SchematicNode schematicNode : SchematicNode.getSchematicNodeInNode(teamFolder.getNodeId())) {
for (NodeMember nodeMember : NodeMember.getNodeMembers(schematicNode.getNodeId())) {
nodeMember.delete();
}
}
// Copy Schem into team folder of -1 user
String name = DateTimeFormatter.ofPattern("yyyy.MM.dd_HH:mm:ss").format(schematic.getLastUpdate().toLocalDateTime());
NodeData data = NodeData.getLatest(schematic);
@@ -316,14 +323,8 @@ public class CheckCommand extends SWCommand {
// Accept the team folder schematic and set other to Normal as well as adding the original owner on the schematic
node.setSchemtype(GameModeConfig.getBySchematicType(schematic.getSchemtype()).Schematic.Type);
NodeMember.createNodeMember(node.getNodeId(), schematic.getOwner());
// Remove any added players from the schematic in the folder
for (SchematicNode schematicNode : SchematicNode.getSchematicNodeInNode(teamFolder.getNodeId())) {
if (schematicNode.getNodeId() == node.getNodeId()) continue;
for (NodeMember nodeMember : NodeMember.getNodeMembers(schematicNode.getNodeId())) {
NodeMember.createNodeMember(node.getNodeId(), nodeMember.getMember());
nodeMember.delete();
}
for (NodeMember nodeMember : NodeMember.getNodeMembers(schematic.getId())) {
NodeMember.createNodeMember(node.getNodeId(), nodeMember.getMember());
}
// Conclude by setting send in schematic to normal and broadcast
@@ -24,11 +24,16 @@ import de.steamwar.linkage.Linked;
import de.steamwar.messages.Chatter;
import de.steamwar.messages.PlayerChatter;
import de.steamwar.sql.SteamwarUser;
import de.steamwar.sql.SteamwarWorld;
import de.steamwar.sql.UserPerm;
import de.steamwar.sql.internal.Statement;
import de.steamwar.velocitycore.ServerStarter;
import de.steamwar.velocitycore.ServerVersion;
import de.steamwar.velocitycore.VelocityCore;
import java.io.*;
import java.util.LinkedHashSet;
import java.util.Set;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;
@@ -64,12 +69,21 @@ public class GDPRQuery extends SWCommand {
copy(getClass().getClassLoader().getResourceAsStream("GDPRQueryREADME.md"), out, "README.txt");
sender.system("GDPR_STATUS_WORLD");
copyBauwelt(user, out, "/home/minecraft/userworlds/" + user.getUUID().toString(), "BuildWorld12");
copyBauwelt(user, out, "/home/minecraft/userworlds15/" + user.getId(), "BuildWorld15");
Set<Integer> exportedVersions = new LinkedHashSet<>();
for(ServerVersion version : ServerVersion.values()) {
int versionSuffix = version.getVersionSuffix();
if(!exportedVersions.add(versionSuffix))
continue;
SteamwarWorld world = SteamwarWorld.getBauWorld(user, versionSuffix);
if(world != null)
copyBauwelt(user, out, world.setupAndGetStoragePath(), "BuildWorld" + versionSuffix);
else
copyLegacyBauwelt(user, out, version);
}
sender.system("GDPR_STATUS_INVENTORIES");
copyPlayerdata(user, out, "/home/minecraft/userworlds", "BuildInventories12");
copyPlayerdata(user, out, "/home/minecraft/userworlds15", "BuildInventories15");
copyPlayerdata(user, out, SteamwarWorld.WORLD_STORAGE, "BuildInventories");
sender.system("GDPR_STATUS_DATABASE");
sqlCSV(user, out, bannedIPs, "BannedIPs.csv");
@@ -99,8 +113,8 @@ public class GDPRQuery extends SWCommand {
}
private static final Statement bannedIPs = new Statement("SELECT Timestamp, IP FROM BannedUserIPs WHERE UserID = ?");
private static final Statement bauweltMember = new Statement("SELECT BauweltID AS Bauwelt, WorldEdit, World FROM BauweltMember WHERE MemberID = ?");
private static final Statement bauweltMembers = new Statement("SELECT u.UserName AS 'User', m.WorldEdit AS WorldEdit, m.World AS World FROM BauweltMember m INNER JOIN UserData u ON m.MemberID = u.id WHERE m.BauweltID = ?");
private static final Statement bauweltMember = new Statement("SELECT WorldID AS Bauwelt, WorldEdit, World FROM BauweltMember WHERE MemberID = ?");
private static final Statement bauweltMembers = new Statement("SELECT w.Name AS World, u.UserName AS 'User', m.WorldEdit AS WorldEdit, m.World AS World FROM BauweltMember m INNER JOIN UserData u ON m.MemberID = u.id INNER JOIN world w ON m.WorldID = w.id WHERE w.Owner = ?");
private static final Statement checkedSchems = new Statement("SELECT NodeName AS Schematic, StartTime, EndTime, DeclineReason AS Result FROM CheckedSchematic WHERE NodeOwner = ? ORDER BY StartTime ASC");
private static final Statement fights = new Statement("SELECT p.Team AS Team, p.Kit AS Kit, p.Kills AS Kills, p.IsOut AS Died, f.GameMode AS GameMode, f.Server AS Server, f.StartTime AS StartTime, f.Duration AS Duration, (f.BlueLeader = p.UserID) AS IsBlueLeader, (f.RedLeader = p.UserID) AS IsRedLeader, f.Win AS Winner, f.WinCondition AS WinCondition FROM Fight f INNER JOIN FightPlayer p ON f.FightID = p.FightID WHERE p.UserID = ? ORDER BY StartTime ASC");
private static final Statement ignoredPlayers = new Statement("SELECT u.UserName AS IgnoredPlayer FROM IgnoredPlayers i INNER JOIN UserData u ON i.Ignored = u.id WHERE Ignorer = ?");
@@ -230,6 +244,18 @@ public class GDPRQuery extends SWCommand {
}
}
private void copyLegacyBauwelt(SteamwarUser user, ZipOutputStream out, ServerVersion version) throws IOException {
File legacyIdWorld = new File(version.getWorldFolder(ServerStarter.LEGACY_WORLDS_BASE_PATH), String.valueOf(user.getId()));
if(legacyIdWorld.exists()) {
copyBauwelt(user, out, legacyIdWorld.getPath(), "BuildWorld" + version.getVersionSuffix());
return;
}
File legacyUuidWorld = new File(version.getWorldFolder(ServerStarter.LEGACY_WORLDS_BASE_PATH), user.getUUID().toString());
if(legacyUuidWorld.exists())
copyBauwelt(user, out, legacyUuidWorld.getPath(), "BuildWorld" + version.getVersionSuffix());
}
private void copyPlayerdata(SteamwarUser user, ZipOutputStream out, String inDir, String outDir) throws IOException {
File worlds = new File(inDir);
String path = "playerdata/" + user.getUUID().toString() + ".dat";
@@ -42,6 +42,13 @@ public class ReplayCommand extends SWCommand {
super("replay");
}
@Register
public void genericCommand(PlayerChatter sender) {
sender.system("REPLAY_UNAVAILABLE");
return;
}
/*
@Register
public void genericCommand(PlayerChatter sender, int replayId, @StaticValue(value = {"", "-a"}, allowISE = true) @OptionalValue("") boolean isAdmin, @OptionalValue("") String optionalMap) {
Fight fight = Fight.getById(replayId);
@@ -109,4 +116,5 @@ public class ReplayCommand extends SWCommand {
private Message parseLeader(SteamwarUser leader, int players, boolean winner) {
return new Message("REPLAY_" + (players > 1 ? "" : "SOLO_") + (winner ? "WINNER" : "LOSER"), leader.getUserName(), players - 1);
}
*/
}
@@ -30,6 +30,7 @@ import de.steamwar.messages.PlayerChatter;
import de.steamwar.sql.*;
import de.steamwar.velocitycore.VelocityCore;
import de.steamwar.velocitycore.discord.DiscordBot;
import de.steamwar.velocitycore.inventory.SWInventory;
import de.steamwar.velocitycore.inventory.SWItem;
import de.steamwar.velocitycore.inventory.SWListInv;
import de.steamwar.velocitycore.util.SteamwarPrefix;
@@ -437,6 +438,117 @@ public class TeamCommand extends SWCommand {
}
}
@Register("worlds")
@Register("world")
public void worlds(@Validator("isInTeam") PlayerChatter sender) {
List<SWListInv.SWListEntry<SteamwarWorld>> entries = SteamwarWorld.getTeamWorlds(Team.byId(sender.user().getTeam())).stream()
.map(world -> new SWListInv.SWListEntry<>(
new SWItem("GRASS_BLOCK", new Message("PLAIN_STRING", "§e" + world.getName()))
.addLore(new Message("PLAIN_STRING", "§7Version: §e" + world.getVersion()))
.addLore(new Message("PLAIN_STRING", "§7Left: start, right: manage")),
world
))
.toList();
SWListInv<SteamwarWorld> inv = new SWListInv<>(sender, new Message("TEAM_WORLD_GUI_TITLE"), entries, (click, world) -> {
if (click.isRightClick() && sender.user().isLeader()) {
openTeamWorldManagement(sender, world);
} else {
startTeamWorld(sender, world);
}
});
inv.open();
}
@Register("world")
public void world(@Validator("isInTeam") PlayerChatter sender, @Mapper("teamWorld") SteamwarWorld world) {
startTeamWorld(sender, world);
}
@Register(value = "world create", description = "TEAM_WORLD_CREATE_USAGE")
public void createWorld(@Validator("isLeader") Chatter sender, String name, @ErrorMessage("BAU_WORLD_VERSION") ServerVersion version, @Mapper("templateWorld") SteamwarWorld template) {
Team team = Team.byId(sender.user().getTeam());
if (template == null || template.getVersion() != version.getVersionSuffix()) {
sender.system("BAU_WORLD_UNKNOWN");
return;
}
if (SteamwarWorld.getTeamWorld(team, name, version.getVersionSuffix()) != null) {
sender.system("TEAM_WORLD_EXISTS");
return;
}
long count = SteamwarWorld.countTeamWorlds(team);
if (count > SteamwarWorld.TEAM_WORLD_LIMIT) {
sender.system("TEAM_WORLD_OVER_LIMIT_DELETE", SteamwarWorld.TEAM_WORLD_LIMIT);
return;
}
if (count >= SteamwarWorld.TEAM_WORLD_LIMIT) {
sender.system("TEAM_WORLD_LIMIT", SteamwarWorld.TEAM_WORLD_LIMIT);
return;
}
SteamwarWorld.getOrCreateTeamWorld(team, name, version.getVersionSuffix(), template.getStorageDirectory());
sender.system("TEAM_WORLD_CREATED");
}
@Register(value = "world rename", description = "TEAM_WORLD_RENAME_USAGE")
public void renameWorld(@Validator("isLeader") Chatter sender, @Mapper("teamWorld") SteamwarWorld world, String newName) {
Team team = Team.byId(sender.user().getTeam());
if (hasTooManyTeamWorlds(sender, team)) return;
if (SteamwarWorld.getTeamWorld(team, newName, world.getVersion()) != null) {
sender.system("TEAM_WORLD_EXISTS");
return;
}
world.rename(newName);
sender.system("TEAM_WORLD_RENAMED");
}
@Register(value = "world upgrade", description = "TEAM_WORLD_UPGRADE_USAGE")
public void upgradeWorld(@Validator("isLeader") Chatter sender, @Mapper("teamWorld") SteamwarWorld world, @ErrorMessage("BAU_WORLD_VERSION") ServerVersion version) {
if (hasTooManyTeamWorlds(sender, Team.byId(sender.user().getTeam()))) return;
world.changeVersion(version.getVersionSuffix());
sender.system("TEAM_WORLD_UPGRADED");
}
@Register(value = "world delete", description = "TEAM_WORLD_DELETE_USAGE")
public void deleteWorld(@Validator("isLeader") PlayerChatter sender, @Mapper("teamWorld") SteamwarWorld world) {
SWInventory inventory = new SWInventory(sender, 9, new Message("BAU_DELETE_GUI_NAME"));
inventory.addItem(0, new SWItem("LIME_DYE", new Message("BAU_DELETE_GUI_DELETE")), click -> {
VelocityCore.schedule(() -> {
Bauserver subserver = Bauserver.getByWorld(world.getUuid());
if (subserver != null) {
subserver.stop();
}
world.markDeleted();
SubserverSystem.deleteFolder(VelocityCore.local, world.getStorageDirectory().getPath());
sender.system("TEAM_WORLD_DELETED");
}).schedule();
inventory.close();
});
inventory.addItem(8, new SWItem("RED_DYE", new Message("BAU_DELETE_GUI_CANCEL")), click -> inventory.close());
inventory.open();
}
private void startTeamWorld(PlayerChatter sender, SteamwarWorld world) {
new ServerStarter().build(ServerVersion.get(world.getVersion()), world).send(sender.getPlayer()).start();
}
private void openTeamWorldManagement(PlayerChatter sender, SteamwarWorld world) {
SWInventory inventory = new SWInventory(sender, 9, new Message("PLAIN_STRING", "§e" + world.getName()));
inventory.addItem(0, new SWItem("ENDER_PEARL", new Message("PLAIN_STRING", "§aStart")), click -> {
inventory.close();
startTeamWorld(sender, world);
});
inventory.addItem(3, new SWItem("EXPERIENCE_BOTTLE", new Message("PLAIN_STRING", "§eUpgrade to latest")), click -> {
if (hasTooManyTeamWorlds(sender, Team.byId(sender.user().getTeam()))) {
inventory.close();
return;
}
world.changeVersion(ServerVersion.PAPER_21.getVersionSuffix());
sender.system("TEAM_WORLD_UPGRADED");
inventory.close();
});
inventory.addItem(8, new SWItem("BARRIER", new Message("PLAIN_STRING", "§cDelete")), click -> deleteWorld(sender, world));
inventory.open();
}
@Register("event")
public void event(@Validator("isLeader") Chatter sender, Event event) {
Team team = Team.byId(sender.user().getTeam());
@@ -585,6 +697,56 @@ public class TeamCommand extends SWCommand {
};
}
@Mapper(value = "teamWorld", local = true)
public TypeMapper<SteamwarWorld> teamWorldMapper() {
return new TypeMapper<>() {
@Override
public SteamwarWorld map(Chatter sender, PreviousArguments previousArguments, String s) {
Team team = Team.byId(sender.user().getTeam());
return SteamwarWorld.getTeamWorlds(team).stream()
.filter(world -> world.getName().equalsIgnoreCase(s) || world.getUuid().toString().equalsIgnoreCase(s))
.findFirst()
.orElse(null);
}
@Override
public Collection<String> tabCompletes(Chatter sender, PreviousArguments previousArguments, String s) {
if (sender.user().getTeam() == 0) return List.of();
return SteamwarWorld.getTeamWorlds(Team.byId(sender.user().getTeam())).stream()
.map(SteamwarWorld::getName)
.toList();
}
};
}
@Mapper(value = "templateWorld", local = true)
public TypeMapper<SteamwarWorld> templateWorldMapper() {
return new TypeMapper<>() {
@Override
public SteamwarWorld map(Chatter sender, PreviousArguments previousArguments, String s) {
ServerVersion version = previousArguments.getAll(ServerVersion.class).stream().findFirst().orElse(null);
if (version == null) return null;
return SteamwarWorld.getTemplateWorld(s, version.getVersionSuffix());
}
@Override
public Collection<String> tabCompletes(Chatter sender, PreviousArguments previousArguments, String s) {
ServerVersion version = previousArguments.getAll(ServerVersion.class).stream().findFirst().orElse(null);
if (version == null) return List.of();
return SteamwarWorld.getTemplateWorlds(version.getVersionSuffix()).stream().map(SteamwarWorld::getName).toList();
}
};
}
private boolean hasTooManyTeamWorlds(Chatter sender, Team team) {
if (SteamwarWorld.countTeamWorlds(team) <= SteamwarWorld.TEAM_WORLD_LIMIT) {
return false;
}
sender.system("TEAM_WORLD_OVER_LIMIT_DELETE", SteamwarWorld.TEAM_WORLD_LIMIT);
return true;
}
private boolean checkTeamName(Chatter sender, Team team, String arg) {
Team t = Team.get(arg);
if (t != null && t.getTeamId() != team.getTeamId()) {
@@ -120,17 +120,26 @@ public class TpCommand extends SWCommand {
}
} else if (Subserver.isBuild(subserver)) {
Bauserver bauserver = (Bauserver) subserver;
SteamwarWorld world = SteamwarWorld.getWorld(bauserver.getWorld());
if (world != null && world.getType() == WorldType.TEAM) {
if (sender.user().getTeam() != world.getTeam().getValue()) {
sender.system("JOIN_PLAYER_BLOCK");
return;
}
SubserverSystem.sendPlayer(subserver, sender.getPlayer());
return;
}
Player checker = VelocityCore.getProxy().getPlayer(bauserver.getOwner()).orElse(null);
if (checker != null && CheckCommand.isChecking(checker)) {
if (!sender.user().hasPerm(UserPerm.CHECK) && CheckCommand.getCheckingSchem(checker).getOwner() != sender.user().getId()) {
sender.system("JOIN_PLAYER_BLOCK");
return;
}
} else if (BauLock.isLocked(SteamwarUser.get(bauserver.getOwner()), sender.user())) {
} else if (world != null && BauLock.isLocked(world, sender.user())) {
sender.system("BAU_LOCKED_NOALLOWED");
Chatter.of(bauserver.getOwner()).system("BAU_LOCK_BLOCKED", sender);
return;
} else if (!bauserver.getOwner().equals(sender.user().getUUID()) && BauweltMember.getBauMember(bauserver.getOwner(), sender.user().getUUID()) == null) {
} else if (!bauserver.getOwner().equals(sender.user().getUUID()) && BauweltMember.getBauMember(bauserver.getWorld(), sender.user().getUUID()) == null) {
SubserverSystem.sendDeniedMessage(sender, bauserver.getOwner());
sender.system("JOIN_PLAYER_BLOCK");
return;
@@ -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())
@@ -25,37 +25,39 @@ import de.steamwar.network.packets.server.BaulockUpdatePacket;
import de.steamwar.persistent.Bauserver;
import de.steamwar.sql.BauweltMember;
import de.steamwar.sql.SteamwarUser;
import de.steamwar.sql.UserConfig;
import de.steamwar.sql.SteamwarWorld;
import de.steamwar.sql.UserPerm;
import de.steamwar.sql.WorldType;
import de.steamwar.velocitycore.network.NetworkSender;
import lombok.experimental.UtilityClass;
@UtilityClass
public class BauLock {
private static final String BAU_LOCK_CONFIG_NAME = "baulockstate";
public static void setLocked(Chatter owner, BauLockState state) {
UserConfig.updatePlayerConfig(owner.user().getId(), BAU_LOCK_CONFIG_NAME, state == BauLockState.OPEN ? null : state.name());
public static void setLocked(Chatter owner, SteamwarWorld world, BauLockState state) {
world.changeLockState(state == BauLockState.OPEN ? null : state.name());
owner.system("BAU_LOCKED_" + state.name());
Bauserver bauserver = Bauserver.get(owner.user().getUUID());
Bauserver bauserver = Bauserver.getByWorld(world.getUuid());
if (bauserver != null) {
bauserver.getRegisteredServer().getPlayersConnected().stream().findAny().ifPresent(player -> NetworkSender.send(player, new BaulockUpdatePacket()));
}
}
public static boolean isLocked(SteamwarUser owner, SteamwarUser target) {
if (owner.getId() == target.getId()) return false;
public static boolean isLocked(SteamwarWorld world, SteamwarUser target) {
if (world.getType() != WorldType.BAU || world.getOwner() == null) return false;
SteamwarUser owner = SteamwarUser.byId(world.getOwner().getValue());
if (owner == null || owner.getId() == target.getId()) return false;
boolean locked;
String state = UserConfig.getConfig(owner.getId(), BAU_LOCK_CONFIG_NAME);
String state = world.getLockState();
switch (state == null ? BauLockState.OPEN : BauLockState.valueOf(state)) {
case NOBODY:
locked = true;
break;
case SUPERVISOR:
BauweltMember member = BauweltMember.getBauMember(owner.getId(), target.getId());
BauweltMember member = BauweltMember.getBauMember(world.getUuid(), target.getId());
locked = member == null || !member.isSupervisor();
break;
case SERVERTEAM: