forked from SteamWar/SteamWar
Move Hull and generic HullHider into HiderCore
Hull no longer depends on FightSystem: plugin, world, region, ground visibility and hidden blocks are passed in. HullRegion is the common block box hulls work on. The generic HullHider<K> manages one hull per key and does the block, entity and REntity tracking; FightHullHider keeps the fight specific parts (team hulls, viewer rules, state dependent registration). Entities are now checked against the hull with block coordinates, so entities in the bottom layer and at the max z edge are handled correctly.
This commit is contained in:
@@ -32,8 +32,8 @@ import de.steamwar.fightsystem.record.GlobalRecorder;
|
||||
import de.steamwar.fightsystem.states.FightState;
|
||||
import de.steamwar.fightsystem.states.OneShotStateDependent;
|
||||
import de.steamwar.fightsystem.states.StateDependentListener;
|
||||
import de.steamwar.fightsystem.utils.FightHullHider;
|
||||
import de.steamwar.fightsystem.utils.FightUI;
|
||||
import de.steamwar.fightsystem.utils.HullHider;
|
||||
import de.steamwar.fightsystem.utils.SWSound;
|
||||
import de.steamwar.fightsystem.utils.TechHiderWrapper;
|
||||
import de.steamwar.linkage.AbstractLinker;
|
||||
@@ -58,7 +58,7 @@ public class FightSystem extends JavaPlugin {
|
||||
private FightTeam lastWinner;
|
||||
private String lastWinreason;
|
||||
private TechHiderWrapper techHider;
|
||||
private HullHider hullHider;
|
||||
private FightHullHider hullHider;
|
||||
|
||||
private SpigotLinker spigotLinker;
|
||||
|
||||
@@ -111,9 +111,9 @@ public class FightSystem extends JavaPlugin {
|
||||
new OneShotStateDependent(ArenaMode.Test, FightState.All, SteamEdit::init);
|
||||
Config.world.setGameRule(GameRule.REDUCED_DEBUG_INFO, ArenaMode.AntiTest.contains(Config.mode));
|
||||
|
||||
hullHider = new HullHider();
|
||||
hullHider = new FightHullHider();
|
||||
techHider = new TechHiderWrapper(hullHider);
|
||||
FightSystem.getHullHider().getHullMap().values().forEach(hull -> hull.fill(true));
|
||||
hullHider.fillAll(true);
|
||||
|
||||
|
||||
FileSource.startReplay();
|
||||
@@ -165,7 +165,7 @@ public class FightSystem extends JavaPlugin {
|
||||
return plugin.techHider;
|
||||
}
|
||||
|
||||
public static HullHider getHullHider() {
|
||||
public static FightHullHider getHullHider() {
|
||||
return plugin.hullHider;
|
||||
}
|
||||
|
||||
|
||||
+1
-1
@@ -56,7 +56,7 @@ public class TechhiderbugCommand extends FightSWCommand {
|
||||
writer.append('\n');
|
||||
|
||||
writer.append("HullHider regions: ");
|
||||
FightSystem.getHullHider().getHullMap().forEach((t, h) -> writer.append(t.getName()).append(':').append(h.getRegion().toString()).append(' '));
|
||||
FightSystem.getHullHider().forEachHull((t, h) -> writer.append(t.getName()).append(':').append(h.getRegion().toString()).append(' '));
|
||||
writer.append('\n');
|
||||
|
||||
writer.append("Hidden regions: ");
|
||||
|
||||
@@ -84,7 +84,7 @@ public class FightWorld extends StateDependent {
|
||||
entities.forEach(Entity::remove);
|
||||
entities.clear();
|
||||
|
||||
FightSystem.getHullHider().getHullMap().values().forEach(hull -> hull.fill(true));
|
||||
FightSystem.getHullHider().fillAll(true);
|
||||
|
||||
Config.ArenaRegion.forEachChunk((x, z) -> {
|
||||
for (Player p : Bukkit.getOnlinePlayers()) {
|
||||
|
||||
+34
-85
@@ -30,7 +30,8 @@ import de.steamwar.fightsystem.listener.Recording;
|
||||
import de.steamwar.fightsystem.states.FightState;
|
||||
import de.steamwar.fightsystem.states.StateDependentListener;
|
||||
import de.steamwar.fightsystem.states.StateDependentTask;
|
||||
import lombok.Getter;
|
||||
import de.steamwar.hullhider.Hull;
|
||||
import de.steamwar.hullhider.HullHider;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.Material;
|
||||
import org.bukkit.block.Block;
|
||||
@@ -38,43 +39,38 @@ import org.bukkit.entity.Player;
|
||||
import org.bukkit.event.EventHandler;
|
||||
import org.bukkit.event.EventPriority;
|
||||
import org.bukkit.event.Listener;
|
||||
import org.bukkit.event.block.BlockPhysicsEvent;
|
||||
import org.bukkit.event.entity.EntityDeathEvent;
|
||||
import org.bukkit.event.entity.EntitySpawnEvent;
|
||||
import org.bukkit.event.player.PlayerJoinEvent;
|
||||
import org.bukkit.event.player.PlayerQuitEvent;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.function.BiConsumer;
|
||||
|
||||
public class HullHider implements Listener {
|
||||
/**
|
||||
* Fight specific part of the hull hider: one hull per team schematic region, and who may view which hull.
|
||||
*/
|
||||
public class FightHullHider {
|
||||
|
||||
@Getter
|
||||
private final Map<FightTeam, Hull> hullMap = new HashMap<>();
|
||||
private final Hull[] hulls;
|
||||
private final HullHider<FightTeam> hullHider = new HullHider<>();
|
||||
|
||||
public HullHider() {
|
||||
if (!TechHiderWrapper.ENABLED) {
|
||||
hulls = new Hull[0];
|
||||
return;
|
||||
}
|
||||
|
||||
Fight.teams().forEach(team -> hullMap.put(team, new Hull(team)));
|
||||
hulls = hullMap.values().toArray(new Hull[0]);
|
||||
public FightHullHider() {
|
||||
if (!TechHiderWrapper.ENABLED) return;
|
||||
|
||||
Fight.teams().forEach(team -> {
|
||||
Region region = team.getSchemRegion();
|
||||
hullHider.addHull(team, new Hull(FightSystem.getPlugin(), Config.world, region.toHullRegion(), region.getMinY() != Config.PlayerRegion.getMinY(), Config.GameModeConfig.Techhider.HiddenBlocks));
|
||||
});
|
||||
|
||||
new StateDependentListener(TechHiderWrapper.ENABLED, FightState.All, new MembershipListener());
|
||||
new StateDependentListener(TechHiderWrapper.ENABLED, FightState.Schem, this);
|
||||
new StateDependentListener(TechHiderWrapper.ENABLED, FightState.Schem, hullHider);
|
||||
new StateDependentTask(TechHiderWrapper.ENABLED, FightState.Schem, this::onTick, 0, 1);
|
||||
}
|
||||
|
||||
public void initialize(FightTeam team) {
|
||||
if (!TechHiderWrapper.ENABLED) return;
|
||||
Hull hull = hullMap.get(team);
|
||||
hull.initialize();
|
||||
hullHider.initialize(team);
|
||||
|
||||
// Chunks encoded while the hull was filled as hidden contain obfuscated blocks that are visible now, but no block update is generated for them
|
||||
Hull hull = hullHider.getHull(team);
|
||||
for (Player player : Bukkit.getOnlinePlayers()) {
|
||||
if (!hull.isViewer(player)) {
|
||||
FightSystem.getTechHider().reloadChunks(player, team.getExtendRegion(), Region.EMPTY);
|
||||
@@ -84,7 +80,15 @@ public class HullHider implements Listener {
|
||||
|
||||
public void fill(FightTeam team, boolean visible) {
|
||||
if (!TechHiderWrapper.ENABLED) return;
|
||||
hullMap.get(team).fill(visible);
|
||||
hullHider.fill(team, visible);
|
||||
}
|
||||
|
||||
public void fillAll(boolean visible) {
|
||||
hullHider.fillAll(visible);
|
||||
}
|
||||
|
||||
public void forEachHull(BiConsumer<FightTeam, Hull> consumer) {
|
||||
hullHider.forEach(consumer);
|
||||
}
|
||||
|
||||
// Registered in every state: players must be hidden from the hulls by default, no matter when they join.
|
||||
@@ -96,7 +100,7 @@ public class HullHider implements Listener {
|
||||
|
||||
@EventHandler
|
||||
public void onLeave(PlayerQuitEvent e) {
|
||||
forgetViewer(e.getPlayer());
|
||||
hullHider.forgetViewer(e.getPlayer());
|
||||
}
|
||||
|
||||
// A dead player loses the right to see the enemy hull again
|
||||
@@ -114,82 +118,27 @@ public class HullHider implements Listener {
|
||||
|
||||
FightTeam team = Fight.getPlayerTeam(player);
|
||||
FightPlayer fp = Fight.getFightPlayer(player);
|
||||
for (Map.Entry<FightTeam, Hull> hull : hullMap.entrySet()) {
|
||||
if (Config.isReferee(player) || hull.getKey() == team || (fp != null && fp.canEntern())) {
|
||||
hull.getValue().addViewer(player);
|
||||
} else {
|
||||
hull.getValue().removeViewer(player);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void forgetViewer(Player player) {
|
||||
for (Hull hull : hulls) {
|
||||
hull.forgetViewer(player);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
|
||||
public void onBlockPhysic(BlockPhysicsEvent e) {
|
||||
if (e.getBlock() == e.getSourceBlock() || e.getChangedType() == Material.AIR) {
|
||||
blockUpdate(e.getBlock(), e.getChangedType());
|
||||
}
|
||||
hullHider.forEach((hullTeam, hull) -> hullHider.setViewer(player, hullTeam, Config.isReferee(player) || hullTeam == team || (fp != null && fp.canEntern())));
|
||||
}
|
||||
|
||||
public void blockUpdate(Block block, Material changedType) {
|
||||
for (Hull hull : hulls) {
|
||||
hull.updateBlockVisibility(block, changedType);
|
||||
}
|
||||
hullHider.blockUpdate(block, changedType);
|
||||
}
|
||||
|
||||
public boolean isBlockHidden(Player player, int x, int y, int z) {
|
||||
if (!TechHiderWrapper.ENABLED) return false;
|
||||
|
||||
for (Hull hull : hulls) {
|
||||
if (hull.isBlockHidden(player, x, y, z)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
|
||||
public void onSpawn(EntitySpawnEvent e) {
|
||||
for (Hull hull : hulls) {
|
||||
hull.checkEntity(e.getEntity());
|
||||
}
|
||||
return hullHider.isBlockHidden(player, x, y, z);
|
||||
}
|
||||
|
||||
private void onTick() {
|
||||
Recording.iterateOverEntities(Objects::nonNull, entity -> {
|
||||
for (Hull hull : hulls) {
|
||||
hull.checkEntity(entity);
|
||||
}
|
||||
});
|
||||
|
||||
for (Hull hull : hulls) {
|
||||
hull.sendUncoveredBlocks();
|
||||
}
|
||||
}
|
||||
|
||||
@EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
|
||||
public void onDeath(EntityDeathEvent e) {
|
||||
for (Hull hull : hulls) {
|
||||
hull.removeEntity(e.getEntity());
|
||||
}
|
||||
Recording.iterateOverEntities(Objects::nonNull, hullHider::checkEntity);
|
||||
hullHider.sendUncoveredBlocks();
|
||||
}
|
||||
|
||||
public void updateREntity(REntity e) {
|
||||
for (Hull hull : hulls) {
|
||||
hull.checkREntity(e);
|
||||
}
|
||||
hullHider.updateREntity(e);
|
||||
}
|
||||
|
||||
public void despawnREntity(REntity e) {
|
||||
for (Hull hull : hulls) {
|
||||
hull.removeREntity(e);
|
||||
}
|
||||
hullHider.despawnREntity(e);
|
||||
}
|
||||
}
|
||||
@@ -20,6 +20,7 @@
|
||||
package de.steamwar.fightsystem.utils;
|
||||
|
||||
import de.steamwar.core.ProtocolUtils;
|
||||
import de.steamwar.hullhider.HullRegion;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
import org.bukkit.Location;
|
||||
@@ -155,6 +156,10 @@ public class Region {
|
||||
return in2dRegion(x, z) && minY <= y && y < maxY;
|
||||
}
|
||||
|
||||
public HullRegion toHullRegion() {
|
||||
return new HullRegion(minX, minY, minZ, maxX, maxY, maxZ);
|
||||
}
|
||||
|
||||
public Region intersection(Region other) {
|
||||
int x = Math.max(minX, other.minX);
|
||||
int y = Math.max(minY, other.minY);
|
||||
|
||||
+2
-2
@@ -67,9 +67,9 @@ public class TechHiderWrapper extends StateDependent implements Listener {
|
||||
|
||||
@Getter
|
||||
private final ConcurrentHashMap<Player, Region[]> hiddenRegion = new ConcurrentHashMap<>();
|
||||
private final HullHider hullHider;
|
||||
private final FightHullHider hullHider;
|
||||
|
||||
public TechHiderWrapper(HullHider hullHider) {
|
||||
public TechHiderWrapper(FightHullHider hullHider) {
|
||||
super(ENABLED, FightState.All);
|
||||
|
||||
this.hullHider = hullHider;
|
||||
|
||||
@@ -34,6 +34,7 @@ dependencies {
|
||||
compileOnly(libs.nms)
|
||||
compileOnly(libs.netty)
|
||||
compileOnly(libs.fastutil)
|
||||
compileOnly(libs.authlib)
|
||||
}
|
||||
|
||||
widener {
|
||||
|
||||
+34
-25
@@ -17,15 +17,12 @@
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package de.steamwar.fightsystem.utils;
|
||||
package de.steamwar.hullhider;
|
||||
|
||||
import com.comphenix.tinyprotocol.TinyProtocol;
|
||||
import com.mojang.authlib.GameProfile;
|
||||
import de.steamwar.core.ProtocolWrapper;
|
||||
import de.steamwar.entity.REntity;
|
||||
import de.steamwar.fightsystem.Config;
|
||||
import de.steamwar.fightsystem.FightSystem;
|
||||
import de.steamwar.fightsystem.fight.FightTeam;
|
||||
import it.unimi.dsi.fastutil.shorts.Short2ObjectArrayMap;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
@@ -38,10 +35,12 @@ import org.bukkit.Bukkit;
|
||||
import org.bukkit.GameMode;
|
||||
import org.bukkit.Location;
|
||||
import org.bukkit.Material;
|
||||
import org.bukkit.World;
|
||||
import org.bukkit.block.Block;
|
||||
import org.bukkit.craftbukkit.block.data.CraftBlockData;
|
||||
import org.bukkit.entity.Entity;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.plugin.Plugin;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
@@ -53,13 +52,12 @@ public class Hull {
|
||||
|
||||
private static final IntVector[] NO_BRANCHES = new IntVector[0];
|
||||
|
||||
private static boolean isOccluding(Material material) {
|
||||
return material.isOccluding() || Config.GameModeConfig.Techhider.HiddenBlocks.contains(material);
|
||||
}
|
||||
|
||||
private final Plugin plugin;
|
||||
private final World world;
|
||||
@Getter
|
||||
private final Region region;
|
||||
private final HullRegion region;
|
||||
private final boolean groundVisible;
|
||||
private final Set<Material> hiddenBlocks;
|
||||
private final IntVector[] directions;
|
||||
|
||||
private final BitSet occluding;
|
||||
@@ -72,9 +70,16 @@ public class Hull {
|
||||
private final HashSet<Entity> entities = new HashSet<>();
|
||||
private final HashSet<REntity> rentities = new HashSet<>();
|
||||
|
||||
public Hull(FightTeam team) {
|
||||
this.region = team.getSchemRegion();
|
||||
this.groundVisible = region.getMinY() != Config.PlayerRegion.getMinY();
|
||||
/**
|
||||
* @param groundVisible whether the bottom layer of the region is uncovered (if the region does not reach the ground)
|
||||
* @param hiddenBlocks blocks that occlude like solid blocks, as the techhider replaces them by an occluding block
|
||||
*/
|
||||
public Hull(Plugin plugin, World world, HullRegion region, boolean groundVisible, Set<Material> hiddenBlocks) {
|
||||
this.plugin = plugin;
|
||||
this.world = world;
|
||||
this.region = region;
|
||||
this.groundVisible = groundVisible;
|
||||
this.hiddenBlocks = hiddenBlocks;
|
||||
this.occluding = new BitSet(region.volume());
|
||||
this.visibility = new BitSet(region.volume());
|
||||
|
||||
@@ -87,8 +92,12 @@ public class Hull {
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isOccluding(Material material) {
|
||||
return material.isOccluding() || hiddenBlocks.contains(material);
|
||||
}
|
||||
|
||||
public boolean isBlockHidden(Player player, int x, int y, int z) {
|
||||
return region.inRegion(x, y, z) && !isViewer(player) && !visibility.get(((y - region.getMinY()) * region.getSizeZ() + (z - region.getMinZ())) * region.getSizeX() + (x - region.getMinX()));
|
||||
return region.contains(x, y, z) && !isViewer(player) && !visibility.get(region.toIndex(x, y, z));
|
||||
}
|
||||
|
||||
public boolean isViewer(Player player) {
|
||||
@@ -123,7 +132,7 @@ public class Hull {
|
||||
|
||||
public void checkEntity(Entity entity) {
|
||||
Location location = entity.getLocation();
|
||||
if (region.inRegion(location) && !visibility.get(new IntVector(location).toId(region))) {
|
||||
if (region.contains(location) && !visibility.get(new IntVector(location).toId(region))) {
|
||||
if (entities.add(entity)) {
|
||||
for (Player player : Bukkit.getOnlinePlayers()) {
|
||||
if (!isViewer(player) && !player.equals(entity)) untrackEntity(player, entity);
|
||||
@@ -143,11 +152,11 @@ public class Hull {
|
||||
TinyProtocol.instance.sendPacket(player, ProtocolWrapper.playerInfoPacketConstructor(ProtocolWrapper.PlayerInfoAction.REMOVE, new GameProfile(entity.getUniqueId(), entity.getName()), GameMode.CREATIVE));
|
||||
}
|
||||
|
||||
player.showEntity(FightSystem.getPlugin(), entity);
|
||||
player.showEntity(plugin, entity);
|
||||
}
|
||||
|
||||
public void untrackEntity(Player player, Entity entity) {
|
||||
player.hideEntity(FightSystem.getPlugin(), entity);
|
||||
player.hideEntity(plugin, entity);
|
||||
|
||||
if (entity instanceof Player) {
|
||||
TinyProtocol.instance.sendPacket(player, ProtocolWrapper.playerInfoPacketConstructor(ProtocolWrapper.PlayerInfoAction.ADD, new GameProfile(entity.getUniqueId(), entity.getName()), GameMode.CREATIVE));
|
||||
@@ -159,8 +168,8 @@ public class Hull {
|
||||
}
|
||||
|
||||
public void checkREntity(REntity entity) {
|
||||
Location location = new Location(Config.world, entity.getX(), entity.getY(), entity.getZ());
|
||||
if (region.inRegion(location) && !visibility.get(new IntVector(location).toId(region))) {
|
||||
Location location = new Location(world, entity.getX(), entity.getY(), entity.getZ());
|
||||
if (region.contains(location) && !visibility.get(new IntVector(location).toId(region))) {
|
||||
if (rentities.add(entity)) entity.hide(true);
|
||||
} else {
|
||||
if (rentities.remove(entity)) entity.hide(false);
|
||||
@@ -191,12 +200,12 @@ public class Hull {
|
||||
long start = System.currentTimeMillis();
|
||||
region.forEach((x, y, z) -> {
|
||||
IntVector block = new IntVector(x, y, z);
|
||||
if (isOccluding(Config.world.getBlockAt(x, y, z).getType())) {
|
||||
if (isOccluding(world.getBlockAt(x, y, z).getType())) {
|
||||
occluding.set(block.toId(region));
|
||||
}
|
||||
});
|
||||
forEachBorder((root, direction) -> uncoverBlocks(new NullList<>(), root, direction));
|
||||
FightSystem.getPlugin().getLogger().log(Level.INFO, () -> "[HullHider] initialisation finished: " + (System.currentTimeMillis() - start) + " ms, visible blocks: " + visibility.cardinality());
|
||||
plugin.getLogger().log(Level.INFO, () -> "[HullHider] initialisation finished: " + (System.currentTimeMillis() - start) + " ms, visible blocks: " + visibility.cardinality());
|
||||
}
|
||||
|
||||
public void updateBlockVisibility(Block b, Material changedType) {
|
||||
@@ -235,7 +244,7 @@ public class Hull {
|
||||
Object[] blockdata = new Object[changes.size()];
|
||||
for (int i = 0; i < blockdata.length; i++) {
|
||||
IntVector change = changes.get(i);
|
||||
blockdata[i] = ((CraftBlockData) Config.world.getBlockData(change.getX(), change.getY(), change.getZ())).getState();
|
||||
blockdata[i] = ((CraftBlockData) world.getBlockData(change.getX(), change.getY(), change.getZ())).getState();
|
||||
}
|
||||
|
||||
if (changes.size() > 1) {
|
||||
@@ -332,12 +341,12 @@ public class Hull {
|
||||
this.z = location.getBlockZ();
|
||||
}
|
||||
|
||||
public boolean notInRegion(Region region) {
|
||||
return !region.inRegion(x, y, z);
|
||||
public boolean notInRegion(HullRegion region) {
|
||||
return !region.contains(x, y, z);
|
||||
}
|
||||
|
||||
public int toId(Region region) {
|
||||
return ((y - region.getMinY()) * region.getSizeZ() + (z - region.getMinZ())) * region.getSizeX() + (x - region.getMinX());
|
||||
public int toId(HullRegion region) {
|
||||
return region.toIndex(x, y, z);
|
||||
}
|
||||
|
||||
public IntVector add(int x, int y, int z) {
|
||||
@@ -0,0 +1,154 @@
|
||||
/*
|
||||
* 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.hullhider;
|
||||
|
||||
import de.steamwar.entity.REntity;
|
||||
import org.bukkit.Material;
|
||||
import org.bukkit.block.Block;
|
||||
import org.bukkit.entity.Entity;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.event.EventHandler;
|
||||
import org.bukkit.event.EventPriority;
|
||||
import org.bukkit.event.Listener;
|
||||
import org.bukkit.event.block.BlockPhysicsEvent;
|
||||
import org.bukkit.event.entity.EntityDeathEvent;
|
||||
import org.bukkit.event.entity.EntitySpawnEvent;
|
||||
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
import java.util.function.BiConsumer;
|
||||
|
||||
/**
|
||||
* Manages one {@link Hull} per key (e.g. a team or a region).
|
||||
* Which players may view which hull, when hulls are initialised and when this listener is registered is up to the user.
|
||||
*/
|
||||
public class HullHider<K> implements Listener {
|
||||
|
||||
private final Map<K, Hull> hullMap = new LinkedHashMap<>();
|
||||
// Read on netty threads, replaced on every change
|
||||
private volatile Hull[] hulls = new Hull[0];
|
||||
|
||||
public void addHull(K key, Hull hull) {
|
||||
hullMap.put(key, hull);
|
||||
hulls = hullMap.values().toArray(new Hull[0]);
|
||||
}
|
||||
|
||||
public Hull getHull(K key) {
|
||||
return hullMap.get(key);
|
||||
}
|
||||
|
||||
public void forEach(BiConsumer<K, Hull> consumer) {
|
||||
hullMap.forEach(consumer);
|
||||
}
|
||||
|
||||
public void initialize(K key) {
|
||||
hullMap.get(key).initialize();
|
||||
}
|
||||
|
||||
public void fill(K key, boolean visible) {
|
||||
hullMap.get(key).fill(visible);
|
||||
}
|
||||
|
||||
public void fillAll(boolean visible) {
|
||||
for (Hull hull : hulls) {
|
||||
hull.fill(visible);
|
||||
}
|
||||
}
|
||||
|
||||
public void setViewer(Player player, K key, boolean viewer) {
|
||||
Hull hull = hullMap.get(key);
|
||||
if (viewer) {
|
||||
hull.addViewer(player);
|
||||
} else {
|
||||
hull.removeViewer(player);
|
||||
}
|
||||
}
|
||||
|
||||
public void forgetViewer(Player player) {
|
||||
for (Hull hull : hulls) {
|
||||
hull.forgetViewer(player);
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isBlockHidden(Player player, int x, int y, int z) {
|
||||
for (Hull hull : hulls) {
|
||||
if (hull.isBlockHidden(player, x, y, z)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
|
||||
public void onBlockPhysic(BlockPhysicsEvent e) {
|
||||
if (e.getBlock() == e.getSourceBlock() || e.getChangedType() == Material.AIR) {
|
||||
blockUpdate(e.getBlock(), e.getChangedType());
|
||||
}
|
||||
}
|
||||
|
||||
public void blockUpdate(Block block, Material changedType) {
|
||||
for (Hull hull : hulls) {
|
||||
hull.updateBlockVisibility(block, changedType);
|
||||
}
|
||||
}
|
||||
|
||||
@EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
|
||||
public void onSpawn(EntitySpawnEvent e) {
|
||||
checkEntity(e.getEntity());
|
||||
}
|
||||
|
||||
@EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
|
||||
public void onDeath(EntityDeathEvent e) {
|
||||
for (Hull hull : hulls) {
|
||||
hull.removeEntity(e.getEntity());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Has to be called every tick for every entity, as entities can move into or out of the hidden part of a hull.
|
||||
*/
|
||||
public void checkEntity(Entity entity) {
|
||||
for (Hull hull : hulls) {
|
||||
hull.checkEntity(entity);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Has to be called every tick after the entities have been checked.
|
||||
*/
|
||||
public void sendUncoveredBlocks() {
|
||||
for (Hull hull : hulls) {
|
||||
hull.sendUncoveredBlocks();
|
||||
}
|
||||
}
|
||||
|
||||
public void updateREntity(REntity e) {
|
||||
for (Hull hull : hulls) {
|
||||
hull.checkREntity(e);
|
||||
}
|
||||
}
|
||||
|
||||
public void despawnREntity(REntity e) {
|
||||
for (Hull hull : hulls) {
|
||||
hull.removeREntity(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,97 @@
|
||||
/*
|
||||
* 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.hullhider;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
import org.bukkit.Location;
|
||||
|
||||
/**
|
||||
* Immutable block box a {@link Hull} covers. The minimum is inclusive, the maximum exclusive.
|
||||
*/
|
||||
@Getter
|
||||
@AllArgsConstructor
|
||||
public final class HullRegion {
|
||||
|
||||
public static HullRegion fromSize(int minX, int minY, int minZ, int sizeX, int sizeY, int sizeZ) {
|
||||
return new HullRegion(minX, minY, minZ, minX + sizeX, minY + sizeY, minZ + sizeZ);
|
||||
}
|
||||
|
||||
public static HullRegion fromInclusive(int minX, int minY, int minZ, int maxX, int maxY, int maxZ) {
|
||||
return new HullRegion(minX, minY, minZ, maxX + 1, maxY + 1, maxZ + 1);
|
||||
}
|
||||
|
||||
private final int minX;
|
||||
private final int minY;
|
||||
private final int minZ;
|
||||
private final int maxX;
|
||||
private final int maxY;
|
||||
private final int maxZ;
|
||||
|
||||
public int getSizeX() {
|
||||
return maxX - minX;
|
||||
}
|
||||
|
||||
public int getSizeY() {
|
||||
return maxY - minY;
|
||||
}
|
||||
|
||||
public int getSizeZ() {
|
||||
return maxZ - minZ;
|
||||
}
|
||||
|
||||
public int volume() {
|
||||
return getSizeX() * getSizeY() * getSizeZ();
|
||||
}
|
||||
|
||||
public boolean contains(int x, int y, int z) {
|
||||
return minX <= x && x < maxX && minY <= y && y < maxY && minZ <= z && z < maxZ;
|
||||
}
|
||||
|
||||
public boolean contains(Location location) {
|
||||
return contains(location.getBlockX(), location.getBlockY(), location.getBlockZ());
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the index of the block within a bitset of {@link #volume()} bits, only valid for contained blocks
|
||||
*/
|
||||
public int toIndex(int x, int y, int z) {
|
||||
return ((y - minY) * getSizeZ() + (z - minZ)) * getSizeX() + (x - minX);
|
||||
}
|
||||
|
||||
public void forEach(BlockConsumer executor) {
|
||||
for (int x = minX; x < maxX; x++) {
|
||||
for (int y = minY; y < maxY; y++) {
|
||||
for (int z = minZ; z < maxZ; z++) {
|
||||
executor.accept(x, y, z);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public interface BlockConsumer {
|
||||
void accept(int x, int y, int z);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return minX + "," + minY + "," + minZ + "->" + maxX + "," + maxY + "," + maxZ;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user