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:
@@ -34,6 +34,7 @@ dependencies {
|
||||
compileOnly(libs.nms)
|
||||
compileOnly(libs.netty)
|
||||
compileOnly(libs.fastutil)
|
||||
compileOnly(libs.authlib)
|
||||
}
|
||||
|
||||
widener {
|
||||
|
||||
@@ -0,0 +1,401 @@
|
||||
/*
|
||||
* 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.hullhider;
|
||||
|
||||
import com.comphenix.tinyprotocol.TinyProtocol;
|
||||
import com.mojang.authlib.GameProfile;
|
||||
import de.steamwar.core.ProtocolWrapper;
|
||||
import de.steamwar.entity.REntity;
|
||||
import it.unimi.dsi.fastutil.shorts.Short2ObjectArrayMap;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
import net.minecraft.core.BlockPos;
|
||||
import net.minecraft.core.SectionPos;
|
||||
import net.minecraft.network.protocol.game.ClientboundBlockUpdatePacket;
|
||||
import net.minecraft.network.protocol.game.ClientboundSectionBlocksUpdatePacket;
|
||||
import net.minecraft.world.level.block.state.BlockState;
|
||||
import org.bukkit.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;
|
||||
import java.util.function.BiConsumer;
|
||||
import java.util.logging.Level;
|
||||
import java.util.stream.IntStream;
|
||||
|
||||
public class Hull {
|
||||
|
||||
private static final IntVector[] NO_BRANCHES = new IntVector[0];
|
||||
|
||||
private final Plugin plugin;
|
||||
private final World world;
|
||||
@Getter
|
||||
private final HullRegion region;
|
||||
private final boolean groundVisible;
|
||||
private final Set<Material> hiddenBlocks;
|
||||
private final IntVector[] directions;
|
||||
|
||||
private final BitSet occluding;
|
||||
private final BitSet visibility;
|
||||
private final HashMap<IntVector, BitSet> visibilityDirections = new HashMap<>(); // Contains the visible directions of each occluding visible block
|
||||
private final HashSet<IntVector> uncoveredSurface = new HashSet<>();
|
||||
|
||||
// Players that may see the hull contents (viewers). Everybody else is hidden from it (default deny). Concurrent as it is read on netty threads.
|
||||
private final Set<UUID> viewers = ConcurrentHashMap.newKeySet();
|
||||
private final HashSet<Entity> entities = new HashSet<>();
|
||||
private final HashSet<REntity> rentities = new HashSet<>();
|
||||
|
||||
/**
|
||||
* @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());
|
||||
|
||||
directions = IntStream.range(0, 27)
|
||||
.mapToObj(v -> new IntVector(v % 3 - 1, (v / 3) % 3 - 1, v / 9 - 1))
|
||||
.toArray(IntVector[]::new);
|
||||
|
||||
for (IntVector direction : directions) {
|
||||
visibilityDirections.put(direction, new BitSet(region.volume()));
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isOccluding(Material material) {
|
||||
return material.isOccluding() || hiddenBlocks.contains(material);
|
||||
}
|
||||
|
||||
public boolean isBlockHidden(Player player, int x, int y, int z) {
|
||||
return region.contains(x, y, z) && !isViewer(player) && !visibility.get(region.toIndex(x, y, z));
|
||||
}
|
||||
|
||||
public boolean isViewer(Player player) {
|
||||
return player != null && viewers.contains(player.getUniqueId());
|
||||
}
|
||||
|
||||
/**
|
||||
* Hides the hull from this player (removes the viewer), also hiding all entities currently inside it.
|
||||
*/
|
||||
public void removeViewer(Player player) {
|
||||
viewers.remove(player.getUniqueId());
|
||||
for (Entity entity : entities) {
|
||||
if (!player.equals(entity)) untrackEntity(player, entity);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Allows this player to see the hull (adds a viewer).
|
||||
*/
|
||||
public void addViewer(Player player) {
|
||||
if (viewers.add(player.getUniqueId())) {
|
||||
for (Entity entity : entities) {
|
||||
trackEntity(player, entity);
|
||||
}
|
||||
// techhider triggers block change sending
|
||||
}
|
||||
}
|
||||
|
||||
public void forgetViewer(Player player) {
|
||||
viewers.remove(player.getUniqueId());
|
||||
}
|
||||
|
||||
public void checkEntity(Entity entity) {
|
||||
Location location = entity.getLocation();
|
||||
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);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (entities.remove(entity)) {
|
||||
for (Player player : Bukkit.getOnlinePlayers()) {
|
||||
if (!isViewer(player) && !player.equals(entity)) trackEntity(player, entity);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void trackEntity(Player player, Entity entity) {
|
||||
if (entity instanceof Player) {
|
||||
TinyProtocol.instance.sendPacket(player, ProtocolWrapper.playerInfoPacketConstructor(ProtocolWrapper.PlayerInfoAction.REMOVE, new GameProfile(entity.getUniqueId(), entity.getName()), GameMode.CREATIVE));
|
||||
}
|
||||
|
||||
player.showEntity(plugin, entity);
|
||||
}
|
||||
|
||||
public void untrackEntity(Player player, Entity 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));
|
||||
}
|
||||
}
|
||||
|
||||
public void removeEntity(Entity entity) {
|
||||
entities.remove(entity);
|
||||
}
|
||||
|
||||
public void checkREntity(REntity entity) {
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
public void removeREntity(REntity entity) {
|
||||
rentities.remove(entity);
|
||||
}
|
||||
|
||||
public void fill(boolean visible) {
|
||||
visibility.set(0, visibility.size(), visible);
|
||||
occluding.set(0, occluding.size(), !visible);
|
||||
uncoveredSurface.clear();
|
||||
for (BitSet directionalVisibility : visibilityDirections.values()) {
|
||||
directionalVisibility.set(0, directionalVisibility.size(), visible);
|
||||
}
|
||||
}
|
||||
|
||||
public void initialize() {
|
||||
visibility.clear();
|
||||
occluding.clear();
|
||||
uncoveredSurface.clear();
|
||||
for (BitSet directionalVisibility : visibilityDirections.values()) {
|
||||
directionalVisibility.clear();
|
||||
}
|
||||
|
||||
long start = System.currentTimeMillis();
|
||||
region.forEach((x, y, z) -> {
|
||||
IntVector block = new IntVector(x, y, z);
|
||||
if (isOccluding(world.getBlockAt(x, y, z).getType())) {
|
||||
occluding.set(block.toId(region));
|
||||
}
|
||||
});
|
||||
forEachBorder((root, direction) -> uncoverBlocks(new NullList<>(), root, direction));
|
||||
plugin.getLogger().log(Level.INFO, () -> "[HullHider] initialisation finished: " + (System.currentTimeMillis() - start) + " ms, visible blocks: " + visibility.cardinality());
|
||||
}
|
||||
|
||||
public void updateBlockVisibility(Block b, Material changedType) {
|
||||
IntVector root = new IntVector(b.getX(), b.getY(), b.getZ());
|
||||
if (root.notInRegion(region)) return;
|
||||
|
||||
int id = root.toId(region);
|
||||
if (!occluding.get(id) || isOccluding(changedType)) return;
|
||||
|
||||
occluding.clear(id);
|
||||
if (!visibility.get(id)) return;
|
||||
|
||||
List<IntVector> uncovered = new ArrayList<>();
|
||||
for (Map.Entry<IntVector, BitSet> directionalVisibility : visibilityDirections.entrySet()) {
|
||||
if (directionalVisibility.getValue().get(id)) {
|
||||
directionalVisibility.getValue().clear(id);
|
||||
uncoverBlocks(uncovered, root, directionalVisibility.getKey());
|
||||
}
|
||||
}
|
||||
|
||||
uncoveredSurface.addAll(new HashSet<>(uncovered));
|
||||
uncoveredSurface.remove(root);
|
||||
}
|
||||
|
||||
public void sendUncoveredBlocks() {
|
||||
Map<IntVector, List<IntVector>> sectionWise = new HashMap<>();
|
||||
|
||||
for (IntVector uncovered : uncoveredSurface) {
|
||||
sectionWise.computeIfAbsent(new IntVector(uncovered.x >> 4, uncovered.y >> 4, uncovered.z >> 4), section -> new ArrayList<>()).add(uncovered);
|
||||
}
|
||||
uncoveredSurface.clear();
|
||||
|
||||
for (Map.Entry<IntVector, List<IntVector>> entry : sectionWise.entrySet()) {
|
||||
Object result;
|
||||
List<IntVector> changes = entry.getValue();
|
||||
Object[] blockdata = new Object[changes.size()];
|
||||
for (int i = 0; i < blockdata.length; i++) {
|
||||
IntVector change = changes.get(i);
|
||||
blockdata[i] = ((CraftBlockData) world.getBlockData(change.getX(), change.getY(), change.getZ())).getState();
|
||||
}
|
||||
|
||||
if (changes.size() > 1) {
|
||||
IntVector section = changes.get(0);
|
||||
section = new IntVector(section.getX() >> 4, section.getY() >> 4, section.getZ() >> 4);
|
||||
int xOffset = 16 * section.getX();
|
||||
int yOffset = 16 * section.getY();
|
||||
int zOffset = 16 * section.getZ();
|
||||
|
||||
short[] pos = new short[changes.size()];
|
||||
for (int i = 0; i < changes.size(); i++) {
|
||||
IntVector change = changes.get(i);
|
||||
|
||||
pos[i] = (short) (((change.getX() - xOffset) << 8) + ((change.getZ() - zOffset) << 4) + (change.getY() - yOffset));
|
||||
}
|
||||
|
||||
result = new ClientboundSectionBlocksUpdatePacket(SectionPos.of(section.getX(), section.getY(), section.getZ()), new Short2ObjectArrayMap<>(pos, blockdata, blockdata.length));
|
||||
} else {
|
||||
IntVector pos = changes.get(0);
|
||||
result = new ClientboundBlockUpdatePacket(new BlockPos(pos.getX(), pos.getY(), pos.getZ()), (BlockState) blockdata[0]);
|
||||
}
|
||||
Object packet = result;
|
||||
Bukkit.getOnlinePlayers().stream().filter(player -> !isViewer(player)).forEach(player -> TinyProtocol.instance.sendPacket(player, packet));
|
||||
}
|
||||
}
|
||||
|
||||
private void forEachBorder(BiConsumer<IntVector, IntVector> f) {
|
||||
for (int x = region.getMinX(); x < region.getMaxX(); x++) {
|
||||
for (int z = region.getMinZ(); z < region.getMaxZ(); z++) {
|
||||
if (groundVisible) {
|
||||
f.accept(new IntVector(x, region.getMinY(), z), new IntVector(0, 1, 0));
|
||||
}
|
||||
f.accept(new IntVector(x, region.getMaxY() - 1, z), new IntVector(0, -1, 0));
|
||||
}
|
||||
}
|
||||
|
||||
for (int x = region.getMinX(); x < region.getMaxX(); x++) {
|
||||
for (int y = region.getMinY(); y < region.getMaxY(); y++) {
|
||||
f.accept(new IntVector(x, y, region.getMinZ()), new IntVector(0, 0, 1));
|
||||
f.accept(new IntVector(x, y, region.getMaxZ() - 1), new IntVector(0, 0, -1));
|
||||
}
|
||||
}
|
||||
|
||||
for (int z = region.getMinZ(); z < region.getMaxZ(); z++) {
|
||||
for (int y = region.getMinY(); y < region.getMaxY(); y++) {
|
||||
f.accept(new IntVector(region.getMinX(), y, z), new IntVector(1, 0, 0));
|
||||
f.accept(new IntVector(region.getMaxX() - 1, y, z), new IntVector(-1, 0, 0));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void uncoverBlocks(List<IntVector> uncovered, IntVector block, IntVector direction) {
|
||||
uncoverBlocks(
|
||||
uncovered, block, direction, visibilityDirections.get(direction),
|
||||
direction.sqLength() == 1
|
||||
? Arrays.stream(directions)
|
||||
.filter(v -> v.sqLength() >= 2 && v.x * direction.x + v.y * direction.y + v.z * direction.z == 1)
|
||||
.toArray(IntVector[]::new)
|
||||
: NO_BRANCHES
|
||||
);
|
||||
}
|
||||
|
||||
private void uncoverBlocks(List<IntVector> uncovered, IntVector block, IntVector direction, BitSet directionalVisibility, IntVector[] branchDirections) {
|
||||
if (block.notInRegion(region)) return;
|
||||
|
||||
int id = block.toId(region);
|
||||
if (directionalVisibility.get(id)) return;
|
||||
directionalVisibility.set(id);
|
||||
|
||||
if (!visibility.get(id)) {
|
||||
visibility.set(id);
|
||||
uncovered.add(block);
|
||||
}
|
||||
|
||||
if (occluding.get(id)) return;
|
||||
|
||||
uncoverBlocks(uncovered, block.add(direction), direction, directionalVisibility, branchDirections);
|
||||
for (IntVector branchDirection : branchDirections) {
|
||||
uncoverBlocks(uncovered, block.add(branchDirection), branchDirection, visibilityDirections.get(branchDirection), NO_BRANCHES);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Getter
|
||||
@AllArgsConstructor
|
||||
public static class IntVector {
|
||||
private final int x;
|
||||
private final int y;
|
||||
private final int z;
|
||||
|
||||
public IntVector(Location location) {
|
||||
this.x = location.getBlockX();
|
||||
this.y = location.getBlockY();
|
||||
this.z = location.getBlockZ();
|
||||
}
|
||||
|
||||
public boolean notInRegion(HullRegion region) {
|
||||
return !region.contains(x, y, z);
|
||||
}
|
||||
|
||||
public int toId(HullRegion region) {
|
||||
return region.toIndex(x, y, z);
|
||||
}
|
||||
|
||||
public IntVector add(int x, int y, int z) {
|
||||
return new IntVector(this.x + x, this.y + y, this.z + z);
|
||||
}
|
||||
|
||||
public IntVector add(IntVector v) {
|
||||
return add(v.x, v.y, v.z);
|
||||
}
|
||||
|
||||
public int sqLength() {
|
||||
return x * x + y * y + z * z;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return y << 24 ^ x << 12 ^ z;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (o == null || this.getClass() != o.getClass()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
IntVector v = (IntVector) o;
|
||||
return x == v.x && y == v.y && z == v.z;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return x + "," + y + "," + z;
|
||||
}
|
||||
}
|
||||
|
||||
private static class NullList<E> extends AbstractList<E> {
|
||||
@Override
|
||||
public void add(int index, E element) {
|
||||
// Straight to /dev/null!
|
||||
}
|
||||
|
||||
@Override
|
||||
public E get(int index) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int size() {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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