Implement name-tags for chests

This commit is contained in:
Gabriel Tofvesson 2021-06-27 17:49:01 +02:00
parent 6ac5c1de0e
commit 67b2488976
7 changed files with 975 additions and 68 deletions

View File

@ -1,9 +0,0 @@
<component name="libraryTable">
<library name="SpigotWizCompat">
<CLASSES>
<root url="jar://$PROJECT_DIR$/lib/SpigotWizCompat.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES />
</library>
</component>

View File

@ -2,7 +2,9 @@ package dev.w1zzrd.invtweaks;
import dev.w1zzrd.invtweaks.command.*;
import dev.w1zzrd.invtweaks.enchantment.CapitatorEnchantment;
import dev.w1zzrd.invtweaks.feature.NamedChestManager;
import dev.w1zzrd.invtweaks.listener.*;
import dev.w1zzrd.invtweaks.serialization.ChestNameConfig;
import dev.w1zzrd.invtweaks.serialization.MagnetConfig;
import dev.w1zzrd.invtweaks.serialization.MagnetData;
import dev.w1zzrd.invtweaks.serialization.SearchConfig;
@ -41,6 +43,7 @@ public final class InvTweaksPlugin extends JavaPlugin {
private NamedChestCommand namedChestCommandExecutor;
private CapitatorCommand capitatorCommand;
private PersistentData data;
private NamedChestManager chestManager;
private EnchantmentRegistryEntry<CapitatorEnchantment> capitatorEnchantment = null;
@Override
@ -118,6 +121,8 @@ public final class InvTweaksPlugin extends JavaPlugin {
pluginManager.registerEvents(new MagnetismListener(magnetCommandExecutor), this);
pluginManager.registerEvents(new TabCompletionListener(), this);
pluginManager.registerEvents(new TreeCapitatorListener(activateCapitator ? capitatorEnchantment.getEnchantment() : null), this);
pluginManager.registerEvents(new PlayerMoveRenderListener(chestManager), this);
pluginManager.registerEvents(new ChestBreakListener(chestManager), this);
}
/**
@ -137,7 +142,7 @@ public final class InvTweaksPlugin extends JavaPlugin {
sortCommandExecutor = new SortCommandExecutor();
magnetCommandExecutor = new MagnetCommandExecutor(this, "magnet", getPersistentData());
searchCommandExecutor = new SearchCommandExecutor(this, "search");
namedChestCommandExecutor = new NamedChestCommand(this);
namedChestCommandExecutor = new NamedChestCommand(chestManager);
if (activateCapitator)
capitatorCommand = new CapitatorCommand(capitatorEnchantment.getEnchantment());
@ -174,6 +179,10 @@ public final class InvTweaksPlugin extends JavaPlugin {
ConfigurationSerialization.registerClass(MagnetConfig.class);
ConfigurationSerialization.registerClass(MagnetData.class);
ConfigurationSerialization.registerClass(SearchConfig.class);
ConfigurationSerialization.registerClass(ChestNameConfig.class);
ConfigurationSerialization.registerClass(ChestNameConfig.ChestNameWorldEntry.class);
ConfigurationSerialization.registerClass(ChestNameConfig.ChestNameWorldEntry.ChestNameChunkEntry.class);
ConfigurationSerialization.registerClass(ChestNameConfig.ChestNameWorldEntry.ChestNameChunkEntry.ChestNameConfigEntry.class);
}
/**
@ -182,6 +191,10 @@ public final class InvTweaksPlugin extends JavaPlugin {
* @see #registerSerializers()
*/
private void unregisterSerializers() {
ConfigurationSerialization.registerClass(ChestNameConfig.ChestNameWorldEntry.ChestNameChunkEntry.ChestNameConfigEntry.class);
ConfigurationSerialization.registerClass(ChestNameConfig.ChestNameWorldEntry.ChestNameChunkEntry.class);
ConfigurationSerialization.registerClass(ChestNameConfig.ChestNameWorldEntry.class);
ConfigurationSerialization.registerClass(ChestNameConfig.class);
ConfigurationSerialization.unregisterClass(MagnetConfig.class);
ConfigurationSerialization.unregisterClass(MagnetData.class);
ConfigurationSerialization.unregisterClass(SearchConfig.class);
@ -199,12 +212,16 @@ public final class InvTweaksPlugin extends JavaPlugin {
// Implicit load
data = new PersistentData(PERSISTENT_DATA_NAME, this);
chestManager = new NamedChestManager(data);
}
/**
* De-activate and finalize persistent data storage sources and handlers
*/
private void disablePersistentData() {
chestManager = null;
data.saveData();
data = null;

View File

@ -1,29 +1,23 @@
package dev.w1zzrd.invtweaks.command;
import org.bukkit.Bukkit;
import org.bukkit.Location;
import dev.w1zzrd.invtweaks.feature.NamedChestManager;
import org.bukkit.Material;
import org.bukkit.block.Block;
import org.bukkit.block.Chest;
import org.bukkit.block.DoubleChest;
import org.bukkit.command.Command;
import org.bukkit.command.CommandExecutor;
import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player;
import org.bukkit.inventory.InventoryHolder;
import org.bukkit.plugin.Plugin;
import java.util.Objects;
import static dev.w1zzrd.invtweaks.listener.PlayerMoveRenderListener.RENDER_RADIUS;
import static dev.w1zzrd.spigot.wizcompat.command.CommandUtils.assertTrue;
import static dev.w1zzrd.spigot.wizcompat.command.CommandUtils.*;
import static dev.w1zzrd.spigot.wizcompat.packet.EntityCreator.*;
public final class NamedChestCommand implements CommandExecutor {
public class NamedChestCommand implements CommandExecutor {
private final NamedChestManager manager;
private final Plugin plugin;
public NamedChestCommand(final Plugin plugin) {
this.plugin = plugin;
public NamedChestCommand(final NamedChestManager manager) {
this.manager = manager;
}
@Override
@ -31,10 +25,7 @@ public class NamedChestCommand implements CommandExecutor {
if (assertTrue(sender instanceof Player && ((Player) sender).isOnline(), "Command can only be run by a player!", sender))
return true;
if (assertTrue(args.length != 0, "Expected a name for the chest", sender))
return true;
if (assertTrue(args.length == 1, "Too many arguments for command", sender))
if (assertTrue(args.length <= 1, "Too many arguments for command", sender))
return true;
final Player player = (Player) sender;
@ -44,43 +35,18 @@ public class NamedChestCommand implements CommandExecutor {
if (assertTrue(block != null && (block.getType() == Material.CHEST || block.getType() == Material.TRAPPED_CHEST), "You must be targeting a chest", sender))
return true;
final Location loc = getCenterChestLocation(block);
final Chest chest = (Chest) block.getState();
final Object entity = createFakeSlime(player);
setSlimeSize(entity, 1);
if (args.length == 0) {
manager.removeTag(chest);
} else {
if (manager.hasNamedChest(chest))
manager.removeTag(chest);
setEntityCollision(entity, false);
setEntityCustomName(entity, args[0]);
setEntityInvulnerable(entity, true);
setEntityLocation(entity, loc.getX(), loc.getY(), loc.getZ(), 0f, 0f);
setEntityCustomNameVisible(entity, true);
sendEntitySpawnPacket(player, entity);
sendEntityMetadataPacket(player, entity);
final int entityID = getEntityID(entity);
Bukkit.getScheduler().runTaskLater(plugin, () -> {
sendEntityDespawnPacket(player, entityID);
}, 60);
manager.addTag(chest, args[0]);
}
manager.renderTags(chest.getChunk(), RENDER_RADIUS);
return true;
}
private static Location getCenterChestLocation(final Block chestBlock) {
final InventoryHolder holder = Objects.requireNonNull(((Chest) chestBlock.getState()).getBlockInventory().getHolder()).getInventory().getHolder();
if (holder instanceof final DoubleChest dChest) {
final Location left = getBlockCenter(Objects.requireNonNull((Chest)dChest.getLeftSide()).getBlock());
final Location right = getBlockCenter(Objects.requireNonNull((Chest)dChest.getRightSide()).getBlock());
return new Location(left.getWorld(), (left.getX() + right.getX()) / 2.0, left.getY() + 0.2, (left.getZ() + right.getZ()) / 2.0);
} else {
return getBlockCenter(chestBlock).add(0.0, 0.2, 0.0);
}
}
private static Location getBlockCenter(final Block block) {
return block.getLocation().add(0.5, 0, 0.5);
}
}

View File

@ -0,0 +1,469 @@
package dev.w1zzrd.invtweaks.feature;
import dev.w1zzrd.invtweaks.serialization.ChestNameConfig;
import dev.w1zzrd.spigot.wizcompat.serialization.PersistentData;
import org.bukkit.Bukkit;
import org.bukkit.Chunk;
import org.bukkit.Location;
import org.bukkit.World;
import org.bukkit.block.Block;
import org.bukkit.block.Chest;
import org.bukkit.entity.Player;
import java.util.*;
import java.util.stream.Stream;
import static dev.w1zzrd.spigot.wizcompat.block.Chests.*;
import static dev.w1zzrd.spigot.wizcompat.packet.EntityCreator.*;
public final class NamedChestManager {
private static final String PATH_NAMED_CHESTS = "namedChests";
private final RenderRegistry renders = new RenderRegistry();
private final ChestNameConfig config;
public NamedChestManager(final PersistentData data) {
config = data.loadData(PATH_NAMED_CHESTS, ChestNameConfig::new);
}
public boolean hasNamedChest(final World world, final Location loc) {
return getChestNameAt(world, loc) != null;
}
public boolean hasNamedChest(final Location loc) {
return hasNamedChest(Objects.requireNonNull(loc.getWorld()), loc);
}
public boolean hasNamedChest(final Chest chest) {
final Block left = getLeftChest(chest);
return hasNamedChest(left.getWorld(), left.getLocation());
}
private String getChestName(final Block block) {
return getChestNameAt(block.getWorld(), getLeftChest((Chest)block.getState()).getLocation());
}
private void setChestName(final Block block, final String name) {
addChestName(block.getWorld(), block.getLocation(), name);
}
private void addChestName(final World world, final Location location, final String name) {
config.getEntry(world.getUID()).add(location, name);
}
public String getChestNameAt(final World world, final Location location) {
return config.getEntry(world.getUID()).getName(location);
}
public String getChestNameAt(final Location location) {
return getChestNameAt(Objects.requireNonNull(location.getWorld()), location);
}
public void untrackPlayer(final Player player) {
renders.removeRender(player.getUniqueId());
}
public void renderTags(final Chunk chunk, final int chunkRadius) {
chunk.getWorld()
.getPlayers()
.stream()
.filter(it -> {
final Chunk playerChunk = it.getLocation().getChunk();
return Math.abs(playerChunk.getX() - chunk.getX()) <= chunkRadius && Math.abs(playerChunk.getZ() - chunk.getZ()) <= chunkRadius;
})
.forEach(player -> renderTags(player, chunkRadius));
final ChestNameConfig.ChestNameWorldEntry.ChestNameChunkEntry chestChunk = config.getChunkEntry(
chunk.getWorld().getUID(),
chunk.getX(),
chunk.getZ()
);
if (chestChunk != null)
chestChunk.setDirty(false);
}
public void renderTags(final Player target, final int chunkRadius) {
final UUID worldID = target.getWorld().getUID();
renders.updateRenders(
target,
chunkRadius,
addedChunk -> {
final ChestNameConfig.ChestNameWorldEntry.ChestNameChunkEntry chunk = config.getChunkEntry(worldID, addedChunk.getRender().x(), addedChunk.getRender().z());
if (chunk == null)
return;
final int baseX = chunk.getX() << 4;
final int baseZ = chunk.getZ() << 4;
chunk.streamEntries().forEach(entry -> {
final Object entity = entry.getEntity(() -> {
final Location loc = getCenterChestLocation(target.getWorld().getBlockAt(baseX + entry.getChunkX(), entry.getY(), baseZ + entry.getChunkZ()));
final Object newEntity = createFakeSlime(target);
setEntityCollision(newEntity, false);
setEntityInvulnerable(newEntity, true);
setEntityLocation(newEntity, loc.getX(), loc.getY(), loc.getZ(), 0f, 0f);
setEntityCustomName(newEntity, entry.getName());
setEntityCustomNameVisible(newEntity, true);
return newEntity;
});
sendEntitySpawnPacket(target, entity);
sendEntityMetadataPacket(target, entity);
});
},
removedChunk -> {
final ChestNameConfig.ChestNameWorldEntry.ChestNameChunkEntry chunk = config.getChunkEntry(worldID, removedChunk.getRender().x(), removedChunk.getRender().z());
if (chunk == null)
return;
chunk.streamEntries().forEach(entry -> {
final Object entity = entry.getEntity(() -> null);
if (entity != null)
sendEntityDespawnPacket(target, getEntityID(entity));
});
});
}
public void removeTag(final World world, final Location location) {
final ChestNameConfig.ChestNameWorldEntry.ChestNameChunkEntry.ChestNameConfigEntry entry = config.getEntryAt(world.getUID(), location);
if (entry != null) {
final ChunkRenderEntry chunk = renders.getChunk(location.getChunk().getX(), location.getChunk().getZ(), false);
if (chunk != null) {
final ChestNameConfig.ChestNameWorldEntry.ChestNameChunkEntry chestChunk = config.getChunkEntry(world.getUID(), chunk.getRender().x(), chunk.getRender().z());
if (chestChunk != null)
chestChunk.removeEntry(entry);
chunk.streamRenders().forEach(it -> {
final Player player = Bukkit.getPlayer(it.getRender());
if (player == null)
renders.removeRender(it.getRender());
else
sendEntityDespawnPacket(player, getEntityID(entry.getEntity(() -> null)));
});
if (chestChunk != null) {
chestChunk.setDirty(false);
// Make sure we don't leave blank data in persistent data file
final ChestNameConfig.ChestNameWorldEntry worldEntry = config.getEntry(world.getUID());
worldEntry.deleteEmptyChunk(chestChunk);
config.deleteEmptyWorld(worldEntry);
}
}
}
}
public void removeTag(final Location location) {
removeTag(Objects.requireNonNull(location.getWorld()), location);
}
public void addTag(final World world, final Location location, final String name) {
config.getEntry(world.getUID(), true).add(location, name);
}
public void addTag(final Chest chest, final String name) {
final Block left = getLeftChest(chest);
addTag(left.getWorld(), left.getLocation(), name);
}
public void removeTag(final Chest chest) {
final Block left = getLeftChest(chest);
removeTag(left.getWorld(), left.getLocation());
}
private static Location getCenterChestLocation(final Block chestBlock) {
if (isDoubleChest(chestBlock)) {
final Location left = getBlockCenter(getLeftChest((Chest) chestBlock.getState()));
final Location right = getBlockCenter(getRightChest((Chest) chestBlock.getState()));
return new Location(left.getWorld(), (left.getX() + right.getX()) / 2.0, left.getY() + 0.2, (left.getZ() + right.getZ()) / 2.0);
} else {
return getBlockCenter(chestBlock).add(0.0, 0.2, 0.0);
}
}
private static Location getBlockCenter(final Block block) {
return block.getLocation().add(0.5, 0, 0.5);
}
private final class RenderRegistry {
private final List<PlayerRenderEntry> playerRegistry = new ArrayList<>();
private final List<ChunkRenderEntry> chunkRegistry = new ArrayList<>();
public void addRender(final Player target, final int chunkX, final int chunkZ) {
final PlayerRenderEntry player = getPlayer(target.getUniqueId(), true);
final ChunkRenderEntry chunk = getChunk(chunkX, chunkZ, true);
assert player != null;
player.addRender(chunk);
assert chunk != null;
chunk.addRender(player);
}
public void removeRender(final Player target) {
removeRender(target.getUniqueId());
}
void removeRender(final UUID offlinePlayer) {
final PlayerRenderEntry player = getPlayer(offlinePlayer, false);
if (player != null) {
player.streamRenders().forEach(this::doRemoveChunk);
doRemovePlayer(player);
}
}
public void removeRender(final int chunkX, final int chunkZ) {
final ChunkRenderEntry chunk = getChunk(chunkX, chunkZ, false);
if (chunk != null) {
chunk.streamRenders().forEach(this::doRemovePlayer);
doRemoveChunk(chunk);
}
}
private void doRemoveChunk(final ChunkRenderEntry chunk) {
final int index = Collections.binarySearch(chunkRegistry, chunk);
if (index >= 0)
chunkRegistry.remove(index);
}
private void doRemovePlayer(final PlayerRenderEntry player) {
final int index = Collections.binarySearch(playerRegistry, player);
if (index >= 0)
playerRegistry.remove(index);
}
public void updateRenders(final Player target, final int chunkRadius, final ChunkEntryChangeHandler onAdd, final ChunkEntryChangeHandler onRemove) {
final PlayerRenderEntry player = getPlayer(target.getUniqueId(), true);
final UUID worldID = target.getWorld().getUID();
final int chunkX = target.getLocation().getBlockX() >> 4;
final int chunkZ = target.getLocation().getBlockZ() >> 4;
final int xMax = chunkX + chunkRadius;
final int xMin = chunkX - chunkRadius;
final int zMax = chunkZ + chunkRadius;
final int zMin = chunkZ - chunkRadius;
assert player != null;
final List<ChunkRenderEntry> toRemove = new ArrayList<>();
player.streamRenders()
.filter(chunk -> {
final ChestNameConfig.ChestNameWorldEntry.ChestNameChunkEntry chestChunk = config.getChunkEntry(worldID, chunk.getRender().x(), chunk.getRender().z());
return chunk.getRender().x() < xMax ||
chunk.getRender().x() < xMin ||
chunk.getRender().z() > zMax ||
chunk.getRender().z() < zMin ||
(chestChunk != null && chestChunk.isDirty());
}
)
.forEach(chunk -> {
toRemove.add(chunk);
onRemove.onChange(chunk);
});
toRemove.forEach(player::removeRender);
for (int x = xMin; x <= xMax; ++x)
for (int z = zMin; z <= zMax; ++z) {
final ChunkRenderEntry chunk = getChunk(x, z, true);
assert chunk != null;
if (player.addRender(chunk))
onAdd.onChange(chunk);
chunk.addRender(player);
}
}
public Stream<PlayerRenderEntry> streamPlayers() {
return playerRegistry.stream();
}
public Stream<ChestNameConfig.ChestNameWorldEntry.ChestNameChunkEntry> streamEntries(final Player target) {
final PlayerRenderEntry player = getPlayer(target.getUniqueId(), false);
final UUID worldID = target.getWorld().getUID();
if (player == null)
return null;
else
return player.streamRenders()
.map(chunkEntry -> config.getChunkEntry(worldID, chunkEntry.getRender().x(), chunkEntry.getRender().z()));
}
private ChunkRenderEntry getChunk(final int chunkX, final int chunkZ, final boolean addIfMissing) {
final ChunkRenderEntry find = new ChunkRenderEntry(chunkX, chunkZ);
final int index = Collections.binarySearch(chunkRegistry, find);
if (index >= 0)
return chunkRegistry.get(index);
else if (addIfMissing) {
chunkRegistry.add(-(index + 1), find);
return find;
}
return null;
}
private PlayerRenderEntry getPlayer(final UUID player, final boolean addIfMissing) {
final PlayerRenderEntry find = new PlayerRenderEntry(player);
final int index = Collections.binarySearch(playerRegistry, find);
if (index >= 0)
return playerRegistry.get(index);
else if (addIfMissing) {
playerRegistry.add(-(index + 1), find);
return find;
}
return null;
}
}
private interface RenderEntry<T extends Comparable<T>, R> extends Comparable<RenderEntry<T, R>> {
T getRender();
boolean addRender(final R r);
boolean removeRender(final R r);
boolean containsRender(final R r);
Stream<R> streamRenders();
@Override
default int compareTo(final RenderEntry<T, R> o) {
return getRender().compareTo(o.getRender());
}
}
private static final class PlayerRenderEntry implements RenderEntry<UUID, ChunkRenderEntry> {
private final UUID player;
private final List<ChunkRenderEntry> chunks = new ArrayList<>();
public PlayerRenderEntry(final UUID player) {
this.player = player;
}
@Override
public UUID getRender() {
return player;
}
@Override
public boolean addRender(final ChunkRenderEntry chunk) {
final int index = Collections.binarySearch(chunks, chunk);
if (index < 0) {
chunks.add(-(index + 1), chunk);
return true;
}
return false;
}
@Override
public boolean removeRender(final ChunkRenderEntry chunk) {
final int index = Collections.binarySearch(chunks, chunk);
if (index >= 0) {
chunks.remove(index);
return true;
}
return false;
}
@Override
public boolean containsRender(final ChunkRenderEntry chunkRenderEntry) {
return Collections.binarySearch(chunks, chunkRenderEntry) >= 0;
}
@Override
public Stream<ChunkRenderEntry> streamRenders() {
return chunks.stream();
}
}
private static final class ChunkRenderEntry implements RenderEntry<ChunkRenderEntry.ChunkCoordinate, PlayerRenderEntry> {
private final ChunkCoordinate coords;
private final List<PlayerRenderEntry> players = new ArrayList<>();
public ChunkRenderEntry(final int chunkX, final int chunkZ) {
coords = new ChunkCoordinate(chunkX, chunkZ);
}
@Override
public ChunkCoordinate getRender() {
return coords;
}
@Override
public boolean addRender(final PlayerRenderEntry player) {
final int index = Collections.binarySearch(players, player);
if (index < 0) {
players.add(-(index + 1), player);
return true;
}
return false;
}
@Override
public boolean removeRender(final PlayerRenderEntry player) {
final int index = Collections.binarySearch(players, player);
if (index >= 0) {
players.remove(index);
return true;
}
return false;
}
@Override
public boolean containsRender(final PlayerRenderEntry chunkRenderEntry) {
return Collections.binarySearch(players, chunkRenderEntry) >= 0;
}
@Override
public Stream<PlayerRenderEntry> streamRenders() {
return players.stream();
}
public record ChunkCoordinate(int x, int z) implements Comparable<ChunkCoordinate> {
@Override
public int compareTo(final ChunkCoordinate o) {
final int compX = Integer.compare(x, o.x);
if (compX == 0)
return Integer.compare(z, o.z);
return compX;
}
}
}
private interface ChunkEntryChangeHandler {
void onChange(final ChunkRenderEntry chunk);
}
}

View File

@ -0,0 +1,43 @@
package dev.w1zzrd.invtweaks.listener;
import dev.w1zzrd.invtweaks.feature.NamedChestManager;
import org.bukkit.Material;
import org.bukkit.block.Block;
import org.bukkit.block.Chest;
import org.bukkit.event.EventHandler;
import org.bukkit.event.Listener;
import org.bukkit.event.block.BlockBreakEvent;
import org.bukkit.event.block.BlockEvent;
import org.bukkit.event.block.BlockPlaceEvent;
import static dev.w1zzrd.invtweaks.listener.PlayerMoveRenderListener.RENDER_RADIUS;
import static dev.w1zzrd.spigot.wizcompat.block.Chests.getLeftChest;
public final class ChestBreakListener implements Listener {
private final NamedChestManager manager;
public ChestBreakListener(final NamedChestManager manager) {
this.manager = manager;
}
@EventHandler
public void onChestBreak(final BlockBreakEvent event) {
if (!event.isCancelled())
processBlockEvent(event);
}
@EventHandler
public void onChestPlace(final BlockPlaceEvent event) {
if (!event.isCancelled())
processBlockEvent(event);
}
private void processBlockEvent(final BlockEvent event) {
if (event.getBlock().getType() == Material.CHEST || event.getBlock().getType() == Material.TRAPPED_CHEST) {
final Chest chest = (Chest) event.getBlock().getState();
manager.removeTag(chest);
manager.renderTags(chest.getChunk(), RENDER_RADIUS);
}
}
}

View File

@ -0,0 +1,73 @@
package dev.w1zzrd.invtweaks.listener;
import dev.w1zzrd.invtweaks.feature.NamedChestManager;
import org.bukkit.Chunk;
import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
import org.bukkit.event.Listener;
import org.bukkit.event.player.PlayerJoinEvent;
import org.bukkit.event.player.PlayerMoveEvent;
import org.bukkit.event.player.PlayerQuitEvent;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.UUID;
public final class PlayerMoveRenderListener implements Listener {
public static final int RENDER_RADIUS = 3;
private final List<Chunk> trackers = new ArrayList<>();
private final List<UUID> tracked = new ArrayList<>();
private final NamedChestManager manager;
public PlayerMoveRenderListener(final NamedChestManager manager) {
this.manager = manager;
}
@EventHandler
public void onPlayerMove(final PlayerMoveEvent event) {
final int index = Collections.binarySearch(tracked, event.getPlayer().getUniqueId());
final Player who = event.getPlayer();
final Chunk chunk = who.getLocation().getChunk();
if (index < 0) {
final int actualIndex = -(index + 1);
trackers.add(actualIndex, chunk);
tracked.add(actualIndex, who.getUniqueId());
triggerRender(who);
}
else if (!trackers.get(index).equals(event.getPlayer().getLocation().getChunk())) {
trackers.set(index, chunk);
triggerRender(event.getPlayer());
}
}
@EventHandler
public void onPlayerJoin(final PlayerJoinEvent event) {
final int index = Collections.binarySearch(tracked, event.getPlayer().getUniqueId());
// Should always be true
if (index < 0) {
trackers.add(-(index + 1), event.getPlayer().getLocation().getChunk());
tracked.add(-(index + 1), event.getPlayer().getUniqueId());
triggerRender(event.getPlayer());
}
}
@EventHandler
public void onPlayerLeave(final PlayerQuitEvent event) {
final int index = Collections.binarySearch(tracked, event.getPlayer().getUniqueId());
// Should always be true
if (index >= 0) {
trackers.remove(index);
tracked.remove(index);
manager.untrackPlayer(event.getPlayer());
}
}
private void triggerRender(final Player player) {
manager.renderTags(player, RENDER_RADIUS);
}
}

View File

@ -1,19 +1,367 @@
package dev.w1zzrd.invtweaks.serialization;
import dev.w1zzrd.spigot.wizcompat.serialization.SimpleReflectiveConfigItem;
import org.bukkit.Location;
import java.util.Map;
import java.util.*;
import java.util.stream.Stream;
public class ChestNameConfig extends SimpleReflectiveConfigItem {
private Map<String, String> locs;
private List<ChestNameWorldEntry> worldEntries;
/**
* Required constructor for deserializing data
*
* @param mappings Data to deserialize
*/
public ChestNameConfig(Map<String, Object> mappings) {
super(mappings);
}
public ChestNameConfig() {
this(Collections.emptyMap());
worldEntries = new ArrayList<>();
}
public ChestNameWorldEntry getEntry(final UUID worldID, final boolean addIfMissing) {
final int index = indexOf(worldID);
if (index >= 0)
return worldEntries.get(index);
else if (addIfMissing) {
final ChestNameWorldEntry entry = new ChestNameWorldEntry(worldID);
worldEntries.add(-(index + 1), entry);
return entry;
}
return null;
}
public ChestNameWorldEntry getEntry(final UUID worldID) {
return getEntry(worldID, true);
}
public ChestNameWorldEntry.ChestNameChunkEntry getChunkEntry(final UUID worldID, final int chunkX, final int chunkZ) {
final int index = indexOf(worldID);
if (index >= 0)
return worldEntries.get(index).getChunk(chunkX, chunkZ);
return null;
}
public ChestNameWorldEntry.ChestNameChunkEntry.ChestNameConfigEntry getEntryAt(final UUID worldID, final Location location) {
final ChestNameWorldEntry.ChestNameChunkEntry chunk = getChunkEntry(worldID, location.getBlockX() >> 4, location.getBlockZ() >> 4);
if (chunk != null) {
return chunk.getEntry(location.getBlockX(), location.getBlockY(), location.getBlockZ(), false);
}
return null;
}
public boolean contains(final UUID worldID) {
return getEntry(worldID) != null;
}
public void add(final UUID worldID) {
getEntry(worldID);
}
public void remove(final UUID worldID) {
final int index = indexOf(worldID);
if (index >= 0)
worldEntries.remove(index);
}
private int indexOf(final UUID worldID) {
return Collections.binarySearch(worldEntries, new ChestNameWorldEntry(worldID));
}
public void deleteEmptyWorld(final ChestNameWorldEntry world) {
if (!world.hasEntries()) {
final int index = Collections.binarySearch(worldEntries, world);
if (index >= 0)
worldEntries.remove(index);
}
}
public static final class ChestNameWorldEntry extends SimpleReflectiveConfigItem implements Comparable<ChestNameWorldEntry> {
private String worldIDStr;
private final transient UUID worldID;
private List<ChestNameChunkEntry> chunks;
/**
* Required constructor for deserializing data
*
* @param mappings Data to deserialize
*/
public ChestNameWorldEntry(Map<String, Object> mappings) {
super(mappings);
worldID = UUID.fromString(worldIDStr);
}
ChestNameWorldEntry(final UUID worldID) {
super(Collections.emptyMap());
this.worldID = worldID;
worldIDStr = worldID.toString();
chunks = new ArrayList<>();
}
public UUID getWorldID() {
return worldID;
}
@Override
public int compareTo(final ChestNameWorldEntry o) {
return getWorldID().compareTo(o.getWorldID());
}
public String getName(final Location location) {
final ChestNameChunkEntry chunk = getChunk(location, false);
if (chunk == null)
return null;
else
return chunk.getName(location);
}
public boolean contains(final Location location) {
return getName(location) != null;
}
public boolean hasEntries() {
return chunks.size() > 0;
}
public void add(final Location location, final String name) {
Objects.requireNonNull(getChunk(location, true)).add(location, name);
}
public void remove(final Location location) {
final int index = indexOf(location);
if (index >= 0)
chunks.remove(index);
}
private int indexOf(final Location location) {
return Collections.binarySearch(chunks, new ChestNameChunkEntry(location));
}
private int indexOf(final int chunkX, final int chunkZ) {
return Collections.binarySearch(chunks, new ChestNameChunkEntry(chunkX, chunkZ));
}
private ChestNameChunkEntry getChunk(final Location location, final boolean addIfMissing) {
final int index = indexOf(location);
if (index >= 0)
return chunks.get(index);
else if (addIfMissing) {
final ChestNameChunkEntry entry = new ChestNameChunkEntry(location);
chunks.add(-(index + 1), entry);
return entry;
}
return null;
}
private ChestNameChunkEntry getChunk(final int chunkX, final int chunkZ) {
final int index = indexOf(chunkX, chunkZ);
if (index >= 0)
return chunks.get(index);
else
return null;
}
private void clearChunk(final Location location) {
final int index = indexOf(location);
if (index >= 0)
chunks.remove(index);
}
public void deleteEmptyChunk(final ChestNameChunkEntry chunk) {
if (!chunk.hasEntries()) {
final int index = Collections.binarySearch(chunks, chunk);
if (index >= 0)
chunks.remove(index);
}
}
public static final class ChestNameChunkEntry extends SimpleReflectiveConfigItem implements Comparable<ChestNameChunkEntry> {
private int x, z;
private List<ChestNameConfigEntry> entries;
private transient boolean dirty = false;
public ChestNameChunkEntry(Map<String, Object> mappings) {
super(mappings);
}
ChestNameChunkEntry(final int x, final int z) {
super(Collections.emptyMap());
this.x = x;
this.z = z;
entries = new ArrayList<>();
}
ChestNameChunkEntry(final Location location) {
// Convert world coordinates to chunk coordinates
this(location.getBlockX() >> 4, location.getBlockZ() >> 4);
}
public int getX() {
return x;
}
public int getZ() {
return z;
}
public boolean isDirty() {
return dirty;
}
public void setDirty(final boolean dirty) {
this.dirty = dirty;
}
public boolean hasEntries() {
return entries.size() > 0;
}
public void add(final int x, final int y, final int z, final String name) {
final ChestNameConfigEntry check = getEntry(x, y, z, true);
assert check != null;
check.setName(name);
setDirty(true);
}
public void add(final Location location, final String name) {
add(location.getBlockX(), location.getBlockY(), location.getBlockZ(), name);
}
public String getName(final Location location) {
final ChestNameConfigEntry entry = getEntry(location.getBlockX(), location.getBlockY(), location.getBlockZ(), false);
if (entry == null)
return null;
else
return entry.getName();
}
private ChestNameConfigEntry getEntry(final int x, final int y, final int z, final boolean createIfMissing) {
final ChestNameConfigEntry find = new ChestNameConfigEntry(x, y, z);
final int index = indexOf(find);
if (index >= 0)
return entries.get(index);
else if (createIfMissing) {
entries.add(-(index + 1), find);
return find;
}
return null;
}
public void removeEntry(final ChestNameConfigEntry entry) {
final int index = indexOf(entry);
if (index >= 0) {
entries.remove(index);
setDirty(true);
}
}
public Stream<ChestNameConfigEntry> streamEntries() {
return entries.stream();
}
private int indexOf(final ChestNameConfigEntry find) {
return Collections.binarySearch(entries, find);
}
@Override
public int compareTo(final ChestNameChunkEntry o) {
final int compX = Integer.compare(x, o.x);
if (compX == 0)
return Integer.compare(z, o.z);
return compX;
}
public static final class ChestNameConfigEntry extends SimpleReflectiveConfigItem implements Comparable<ChestNameConfigEntry> {
private transient Object entity;
private transient int locInt;
private String loc;
private String name;
public ChestNameConfigEntry(Map<String, Object> mappings) {
super(mappings);
locInt = Integer.parseInt(loc, 16);
}
ChestNameConfigEntry(final int x, final int y, final int z, final String name) {
super(Collections.emptyMap());
locInt = ((y & 0xFFF) << 8) | ((x & 0xF) << 4) | (z & 0xF);
loc = Integer.toString(locInt, 16);
this.name = name;
}
ChestNameConfigEntry(final int x, final int y, final int z) {
this(x, y, z, null);
}
ChestNameConfigEntry(final Location location, final String name) {
this(location.getBlockX() & 0xF, location.getBlockY(), location.getBlockZ() & 0xF, name);
}
ChestNameConfigEntry(final Location location) {
this(location, null);
}
public Object getEntity(final EntityCreator creator) {
if (entity == null)
entity = creator.createFakeEntity();
return entity;
}
public int getChunkX() {
return (locInt >>> 4) & 0xF;
}
public int getChunkZ() {
return locInt & 0xF;
}
public int getY() {
return (locInt >>> 8) & 0xFFF;
}
public String getName() {
return name;
}
void setName(final String name) {
this.name = name;
}
@Override
public int compareTo(final ChestNameConfigEntry o) {
return Integer.compare(locInt, o.locInt);
}
public interface EntityCreator {
Object createFakeEntity();
}
}
}
}
}