diff --git a/src/main/java/world/bentobox/aoneblock/AOneBlock.java b/src/main/java/world/bentobox/aoneblock/AOneBlock.java index fab493b..44acf90 100644 --- a/src/main/java/world/bentobox/aoneblock/AOneBlock.java +++ b/src/main/java/world/bentobox/aoneblock/AOneBlock.java @@ -46,20 +46,20 @@ */ public class AOneBlock extends GameModeAddon { - private static final String NETHER = "_nether"; - private static final String THE_END = "_the_end"; - private boolean hasItemsAdder = false; - - // Settings - private Settings settings; - private ChunkGeneratorWorld chunkGenerator; - private final Config configObject = new Config<>(this, Settings.class); - private BlockListener blockListener; - private OneBlocksManager oneBlockManager; + private static final String NETHER = "_nether"; + private static final String THE_END = "_the_end"; + private boolean hasItemsAdder = false; + + // Settings + private Settings settings; + private ChunkGeneratorWorld chunkGenerator; + private final Config configObject = new Config<>(this, Settings.class); + private BlockListener blockListener; + private OneBlocksManager oneBlockManager; private AOneBlockPlaceholders phManager; - private HoloListener holoListener; - - // Flag + private HoloListener holoListener; + + // Flag public final Flag START_SAFETY = new Flag.Builder("START_SAFETY", Material.BAMBOO_BLOCK) .mode(Mode.BASIC) .type(Type.WORLD_SETTING) @@ -70,263 +70,267 @@ public class AOneBlock extends GameModeAddon { public final Flag ONEBLOCK_BOSSBAR = new Flag.Builder("ONEBLOCK_BOSSBAR", Material.DRAGON_HEAD).mode(Mode.BASIC) .type(Type.SETTING).listener(bossBar).defaultSetting(true).build(); - @Override - public void onLoad() { - // Check if ItemsAdder exists, if yes register listener - if (Bukkit.getPluginManager().getPlugin("ItemsAdder") != null) { - registerListener(new ItemsAdderListener(this)); - OneBlockCustomBlockCreator.register(ItemsAdderCustomBlock::fromId); - OneBlockCustomBlockCreator.register("itemsadder", ItemsAdderCustomBlock::fromMap); - hasItemsAdder = true; - } - // Save the default config from config.yml - saveDefaultConfig(); - // Load settings from config.yml. This will check if there are any issues with - // it too. - if (loadSettings()) { - // Chunk generator - chunkGenerator = settings.isUseOwnGenerator() ? null : new ChunkGeneratorWorld(this); - // Register commands - playerCommand = new PlayerCommand(this); - adminCommand = new AdminCommand(this); + @Override + public void onLoad() { + // Check if ItemsAdder exists, if yes register listener + if (Bukkit.getPluginManager().getPlugin("ItemsAdder") != null) { + registerListener(new ItemsAdderListener(this)); + OneBlockCustomBlockCreator.register(ItemsAdderCustomBlock::fromId); + OneBlockCustomBlockCreator.register("itemsadder", ItemsAdderCustomBlock::fromMap); + hasItemsAdder = true; + } + // Save the default config from config.yml + saveDefaultConfig(); + // Load settings from config.yml. This will check if there are any issues with + // it too. + if (loadSettings()) { + // Chunk generator + chunkGenerator = settings.isUseOwnGenerator() ? null : new ChunkGeneratorWorld(this); + // Register commands + playerCommand = new PlayerCommand(this); + adminCommand = new AdminCommand(this); // Register flag with BentoBox // Register protection flag with BentoBox getPlugin().getFlagsManager().registerFlag(this, START_SAFETY); // Bossbar getPlugin().getFlagsManager().registerFlag(this, this.ONEBLOCK_BOSSBAR); - } - } + } + } private boolean loadSettings() { - // Load settings again to get worlds - settings = configObject.loadConfigObject(); - if (settings == null) { - // Disable - logError("AOneBlock settings could not load! Addon disabled."); - setState(State.DISABLED); - return false; - } else { - // Save the settings - configObject.saveConfigObject(settings); - } - return true; - } - - @Override - public void onEnable() { - oneBlockManager = new OneBlocksManager(this); - if (loadData()) { - // Failed to load - don't register anything - return; - } - blockListener = new BlockListener(this); - registerListener(blockListener); - registerListener(new NoBlockHandler(this)); - registerListener(new BlockProtect(this)); - registerListener(new JoinLeaveListener(this)); - registerListener(new InfoListener(this)); + // Load settings again to get worlds + settings = configObject.loadConfigObject(); + if (settings == null) { + // Disable + logError("AOneBlock settings could not load! Addon disabled."); + setState(State.DISABLED); + return false; + } else { + // Save the settings + configObject.saveConfigObject(settings); + } + return true; + } + + @Override + public void onEnable() { + oneBlockManager = new OneBlocksManager(this); + if (loadData()) { + // Failed to load - don't register anything + return; + } + blockListener = new BlockListener(this); + registerListener(blockListener); + registerListener(new NoBlockHandler(this)); + registerListener(new BlockProtect(this)); + registerListener(new JoinLeaveListener(this)); + registerListener(new InfoListener(this)); registerListener(bossBar); - // Register placeholders + // Register placeholders phManager = new AOneBlockPlaceholders(this, getPlugin().getPlaceholdersManager()); - // Register request handlers - registerRequestHandler(new IslandStatsHandler(this)); - registerRequestHandler(new LocationStatsHandler(this)); - - // Register Holograms - holoListener = new HoloListener(this); - registerListener(holoListener); - } - - // Load phase data - public boolean loadData() { - try { - oneBlockManager.loadPhases(); - } catch (IOException e) { - // Disable - logError("AOneBlock settings could not load (oneblock.yml error)! Addon disabled."); - logError(e.getMessage()); - setState(State.DISABLED); - return true; - } - return false; - } - - @Override - public void onDisable() { - // save cache - blockListener.saveCache(); - - // Clear holograms - holoListener.clear(); - } - - @Override - public void onReload() { - // save cache - blockListener.saveCache(); - if (loadSettings()) { - log("Reloaded AOneBlock settings"); - loadData(); - } - } - - /** - * @return the settings - */ - public Settings getSettings() { - return settings; - } - - @Override - public void createWorlds() { - String worldName = settings.getWorldName().toLowerCase(); - if (getServer().getWorld(worldName) == null) { - log("Creating AOneBlock world ..."); - } - - // Create the world if it does not exist - islandWorld = getWorld(worldName, World.Environment.NORMAL, chunkGenerator); - // Make the nether if it does not exist - if (settings.isNetherGenerate()) { - if (getServer().getWorld(worldName + NETHER) == null) { - log("Creating AOneBlock's Nether..."); - } - netherWorld = settings.isNetherIslands() ? getWorld(worldName, World.Environment.NETHER, chunkGenerator) - : getWorld(worldName, World.Environment.NETHER, null); - } - // Make the end if it does not exist - if (settings.isEndGenerate()) { - if (getServer().getWorld(worldName + THE_END) == null) { - log("Creating AOneBlock's End World..."); - } - endWorld = settings.isEndIslands() ? getWorld(worldName, World.Environment.THE_END, chunkGenerator) - : getWorld(worldName, World.Environment.THE_END, null); - } - } - - /** - * Gets a world or generates a new world if it does not exist - * - * @param worldName2 - the overworld name - * @param env - the environment - * @param chunkGenerator2 - the chunk generator. If null then the - * generator will not be specified - * @return world loaded or generated - */ - private World getWorld(String worldName2, Environment env, ChunkGeneratorWorld chunkGenerator2) { - // Set world name - worldName2 = env.equals(World.Environment.NETHER) ? worldName2 + NETHER : worldName2; - worldName2 = env.equals(World.Environment.THE_END) ? worldName2 + THE_END : worldName2; + // Register request handlers + registerRequestHandler(new IslandStatsHandler(this)); + registerRequestHandler(new LocationStatsHandler(this)); + + // Register Holograms + holoListener = new HoloListener(this); + registerListener(holoListener); + } + + // Load phase data + public boolean loadData() { + try { + oneBlockManager.loadPhases(); + } catch (IOException e) { + // Disable + logError("AOneBlock settings could not load (oneblock.yml error)! Addon disabled."); + logError(e.getMessage()); + setState(State.DISABLED); + return true; + } + return false; + } + + @Override + public void onDisable() { + // save cache + if (blockListener != null) { + blockListener.saveCache(); + } + + // Clear holograms + if (holoListener != null) { + holoListener.onDisable(); + } + } + + @Override + public void onReload() { + // save cache + blockListener.saveCache(); + if (loadSettings()) { + log("Reloaded AOneBlock settings"); + loadData(); + } + } + + /** + * @return the settings + */ + public Settings getSettings() { + return settings; + } + + @Override + public void createWorlds() { + String worldName = settings.getWorldName().toLowerCase(); + if (getServer().getWorld(worldName) == null) { + log("Creating AOneBlock world ..."); + } + + // Create the world if it does not exist + islandWorld = getWorld(worldName, World.Environment.NORMAL, chunkGenerator); + // Make the nether if it does not exist + if (settings.isNetherGenerate()) { + if (getServer().getWorld(worldName + NETHER) == null) { + log("Creating AOneBlock's Nether..."); + } + netherWorld = settings.isNetherIslands() ? getWorld(worldName, World.Environment.NETHER, chunkGenerator) + : getWorld(worldName, World.Environment.NETHER, null); + } + // Make the end if it does not exist + if (settings.isEndGenerate()) { + if (getServer().getWorld(worldName + THE_END) == null) { + log("Creating AOneBlock's End World..."); + } + endWorld = settings.isEndIslands() ? getWorld(worldName, World.Environment.THE_END, chunkGenerator) + : getWorld(worldName, World.Environment.THE_END, null); + } + } + + /** + * Gets a world or generates a new world if it does not exist + * + * @param worldName2 - the overworld name + * @param env - the environment + * @param chunkGenerator2 - the chunk generator. If null then the + * generator will not be specified + * @return world loaded or generated + */ + private World getWorld(String worldName2, Environment env, ChunkGeneratorWorld chunkGenerator2) { + // Set world name + worldName2 = env.equals(World.Environment.NETHER) ? worldName2 + NETHER : worldName2; + worldName2 = env.equals(World.Environment.THE_END) ? worldName2 + THE_END : worldName2; WorldCreator wc = WorldCreator.name(worldName2).environment(env); - World w = settings.isUseOwnGenerator() ? wc.createWorld() : wc.generator(chunkGenerator2).createWorld(); - // Set spawn rates - if (w != null) { - setSpawnRates(w); - } - return w; - - } - - private void setSpawnRates(World w) { - if (getSettings().getSpawnLimitMonsters() > 0) { - w.setSpawnLimit(SpawnCategory.MONSTER, getSettings().getSpawnLimitMonsters()); - } - if (getSettings().getSpawnLimitAmbient() > 0) { - w.setSpawnLimit(SpawnCategory.AMBIENT, getSettings().getSpawnLimitAmbient()); - } - if (getSettings().getSpawnLimitAnimals() > 0) { - w.setSpawnLimit(SpawnCategory.ANIMAL, getSettings().getSpawnLimitAnimals()); - } - if (getSettings().getSpawnLimitWaterAnimals() > 0) { - w.setSpawnLimit(SpawnCategory.WATER_ANIMAL, getSettings().getSpawnLimitWaterAnimals()); - } - if (getSettings().getTicksPerAnimalSpawns() > 0) { - w.setTicksPerSpawns(SpawnCategory.ANIMAL, getSettings().getTicksPerAnimalSpawns()); - } - if (getSettings().getTicksPerMonsterSpawns() > 0) { - w.setTicksPerSpawns(SpawnCategory.MONSTER, getSettings().getTicksPerMonsterSpawns()); - } - - } - - @Override - public WorldSettings getWorldSettings() { - return getSettings(); - } - - @Override - public @Nullable ChunkGenerator getDefaultWorldGenerator(String worldName, String id) { - return chunkGenerator; - } - - @Override - public void saveWorldSettings() { - if (settings != null) { - configObject.saveConfigObject(settings); - } - } - - @Override - public void saveDefaultConfig() { - super.saveDefaultConfig(); - // Save default phases panel - this.saveResource("panels/phases_panel.yml", false); - } - - /* - * (non-Javadoc) - * - * @see world.bentobox.bentobox.api.addons.Addon#allLoaded() - */ - @Override - public void allLoaded() { - // save settings. This will occur after all addons have loaded - this.saveWorldSettings(); - } - - /** - * @param i - island - * @return one block island data - */ - @NonNull - public OneBlockIslands getOneBlocksIsland(@NonNull Island i) { - return blockListener.getIsland(Objects.requireNonNull(i)); - } - - public OneBlocksManager getOneBlockManager() { - return oneBlockManager; - } - - /** - * @return the blockListener - */ - public BlockListener getBlockListener() { - return blockListener; - } - - /** - * Get the placeholder manager - * - * @return the phManager - */ + World w = settings.isUseOwnGenerator() ? wc.createWorld() : wc.generator(chunkGenerator2).createWorld(); + // Set spawn rates + if (w != null) { + setSpawnRates(w); + } + return w; + + } + + private void setSpawnRates(World w) { + if (getSettings().getSpawnLimitMonsters() > 0) { + w.setSpawnLimit(SpawnCategory.MONSTER, getSettings().getSpawnLimitMonsters()); + } + if (getSettings().getSpawnLimitAmbient() > 0) { + w.setSpawnLimit(SpawnCategory.AMBIENT, getSettings().getSpawnLimitAmbient()); + } + if (getSettings().getSpawnLimitAnimals() > 0) { + w.setSpawnLimit(SpawnCategory.ANIMAL, getSettings().getSpawnLimitAnimals()); + } + if (getSettings().getSpawnLimitWaterAnimals() > 0) { + w.setSpawnLimit(SpawnCategory.WATER_ANIMAL, getSettings().getSpawnLimitWaterAnimals()); + } + if (getSettings().getTicksPerAnimalSpawns() > 0) { + w.setTicksPerSpawns(SpawnCategory.ANIMAL, getSettings().getTicksPerAnimalSpawns()); + } + if (getSettings().getTicksPerMonsterSpawns() > 0) { + w.setTicksPerSpawns(SpawnCategory.MONSTER, getSettings().getTicksPerMonsterSpawns()); + } + + } + + @Override + public WorldSettings getWorldSettings() { + return getSettings(); + } + + @Override + public @Nullable ChunkGenerator getDefaultWorldGenerator(String worldName, String id) { + return chunkGenerator; + } + + @Override + public void saveWorldSettings() { + if (settings != null) { + configObject.saveConfigObject(settings); + } + } + + @Override + public void saveDefaultConfig() { + super.saveDefaultConfig(); + // Save default phases panel + this.saveResource("panels/phases_panel.yml", false); + } + + /* + * (non-Javadoc) + * + * @see world.bentobox.bentobox.api.addons.Addon#allLoaded() + */ + @Override + public void allLoaded() { + // save settings. This will occur after all addons have loaded + this.saveWorldSettings(); + } + + /** + * @param i - island + * @return one block island data + */ + @NonNull + public OneBlockIslands getOneBlocksIsland(@NonNull Island i) { + return blockListener.getIsland(Objects.requireNonNull(i)); + } + + public OneBlocksManager getOneBlockManager() { + return oneBlockManager; + } + + /** + * @return the blockListener + */ + public BlockListener getBlockListener() { + return blockListener; + } + + /** + * Get the placeholder manager + * + * @return the phManager + */ public AOneBlockPlaceholders getPlaceholdersManager() { - return phManager; - } - - /** - * @return the holoListener - */ - public HoloListener getHoloListener() { - return holoListener; - } - - /** - * @return true if ItemsAdder is on the server - */ - public boolean hasItemsAdder() { - return hasItemsAdder; - } + return phManager; + } + + /** + * @return the holoListener + */ + public HoloListener getHoloListener() { + return holoListener; + } + + /** + * @return true if ItemsAdder is on the server + */ + public boolean hasItemsAdder() { + return hasItemsAdder; + } /** * Set the addon's world. Used only for testing. diff --git a/src/main/java/world/bentobox/aoneblock/listeners/HoloListener.java b/src/main/java/world/bentobox/aoneblock/listeners/HoloListener.java index 34b0560..9a0cc5f 100644 --- a/src/main/java/world/bentobox/aoneblock/listeners/HoloListener.java +++ b/src/main/java/world/bentobox/aoneblock/listeners/HoloListener.java @@ -1,13 +1,10 @@ package world.bentobox.aoneblock.listeners; -import java.util.IdentityHashMap; -import java.util.Map; -import java.util.Optional; -import java.util.UUID; +import java.util.HashSet; +import java.util.Set; import org.bukkit.Bukkit; import org.bukkit.Location; -import org.bukkit.World; import org.bukkit.entity.Display.Billboard; import org.bukkit.entity.Entity; import org.bukkit.entity.EntityType; @@ -28,154 +25,170 @@ /** * Handles Holographic elements - * - * @author tastybento, HSGamer */ public class HoloListener implements Listener { private final AOneBlock addon; - private final Map cachedHolograms; + private final Set activeHolograms; + private static final Vector DEFAULT_OFFSET = new Vector(0.5, 1.1, 0.5); /** * @param addon - OneBlock */ public HoloListener(@NonNull AOneBlock addon) { this.addon = addon; - this.cachedHolograms = new IdentityHashMap<>(); + this.activeHolograms = new HashSet<>(); } + /** + * Handles the event when an island is deleted. + * Removes the associated hologram. + * + * @param e the IslandDeleteEvent + */ @EventHandler(priority = EventPriority.NORMAL, ignoreCancelled = true) public void onDeletedIsland(IslandDeleteEvent e) { - deleteHologram(e.getIsland()); - } - - private Optional getHologram(Island island) { - return Optional.ofNullable(cachedHolograms.get(island)).filter(TextDisplay::isValid); + removeHologramAt(e.getIsland()); } - private TextDisplay createHologram(Island island) { - Location pos = island.getCenter().clone().add(parseVector(addon.getSettings().getOffset())); - World world = pos.getWorld(); - assert world != null; - - TextDisplay newDisplay = world.spawn(pos, TextDisplay.class); - newDisplay.setAlignment(TextDisplay.TextAlignment.CENTER); - newDisplay.setBillboard(Billboard.CENTER); - - cachedHolograms.put(island, newDisplay); - - return newDisplay; + /** + * Clears all cached holograms and removes their entities. + * Called when disabling + */ + public void onDisable() { + activeHolograms.forEach(this::cleanupHologram); + activeHolograms.clear(); } - private static Vector parseVector(String str) { - if (str == null) { - return new Vector(0.5, 1.1, 0.5); - } - String[] parts = str.split(","); - if (parts.length != 3) { - return new Vector(0.5, 1.1, 0.5); + /** + * Sets up the hologram for a specific island. + * If it's a new island, sets the starting hologram line. + * + * @param island the island to set up + * @param is the OneBlockIslands data object + * @param newIsland whether this is a new island + */ + protected void setUp(@NonNull Island island, @NonNull OneBlockIslands is, boolean newIsland) { + if (!addon.getSettings().isUseHolograms() || island.getOwner() == null) { + return; } - try { - double x = Double.parseDouble(parts[0].trim()); - double y = Double.parseDouble(parts[1].trim()); - double z = Double.parseDouble(parts[2].trim()); - return new Vector(x, y, z); - } catch (NumberFormatException e) { - return new Vector(0.5, 1.1, 0.5); + if (newIsland) { + String startingText = User.getInstance(island.getOwner()) + .getTranslation("aoneblock.island.starting-hologram"); + is.setHologram(startingText == null ? "" : startingText); } + updateHologram(island, is.getHologram()); } - private void clearIfInitialized(TextDisplay hologram) { - if (hologram.isValid()) { - hologram.remove(); - } + /** + * Processes the phase change for an island and updates its hologram. + * + * @param i the island + * @param is the OneBlockIslands data object + * @param phase the current OneBlockPhase + */ + protected void process(@NonNull Island i, @NonNull OneBlockIslands is, @NonNull OneBlockPhase phase) { + String holoText = phase.getHologramLine(is.getBlockNumber()); + is.setHologram(holoText == null ? "" : Util.translateColorCodes(holoText)); + updateHologram(i, is.getHologram()); } - private void updateLines(Island island, OneBlockIslands oneBlockIsland) { - // Ignore if holograms are disabled - if (!addon.getSettings().isUseHolograms()) { - return; - } - - Optional optionalHologram = getHologram(island); - String holoLine = oneBlockIsland.getHologram(); - - // Clear hologram if empty - if (holoLine.isBlank() && optionalHologram.isPresent()) { - clearIfInitialized(optionalHologram.get()); + /** + * Updates the hologram lines for the given island. + * Handles creation, updating, and scheduled deletion of holograms. + * + * @param island the island to update + * @param text the text to display + */ + private void updateHologram(Island island, String text) { + if (!addon.getSettings().isUseHolograms() || text.isBlank()) { return; } + removeHologramAt(island); + Location pos = getHologramLocation(island); + createHologram(pos, text); - // Get or create hologram if needed - TextDisplay hologram = optionalHologram.orElseGet(() -> createHologram(island)); - - // Convert and set lines to hologram - hologram.setText(holoLine); - - // Set up auto delete + // Set up auto delete if duration is set if (addon.getSettings().getHologramDuration() > 0) { - Bukkit.getScheduler().runTaskLater(addon.getPlugin(), () -> clearIfInitialized(hologram), addon.getSettings().getHologramDuration() * 20L); + Bukkit.getScheduler().runTaskLater(addon.getPlugin(), + () -> removeHologramAt(island), + addon.getSettings().getHologramDuration() * 20L); } } /** - * Setup holograms on startup + * Gets the location for the hologram based on the island's center and the configured offset. + * + * @param island the island + * @return the location for the hologram */ - public void setUp() { - addon.getIslands().getIslands().stream() - .filter(i -> addon.inWorld(i.getWorld())) - .forEach(island -> setUp(island, addon.getOneBlocksIsland(island), false)); + private Location getHologramLocation(Island island) { + Vector offset = parseVector(addon.getSettings().getOffset()); + return island.getCenter().clone().add(offset); } - public void clear() { - cachedHolograms.values().forEach(this::clearIfInitialized); - cachedHolograms.clear(); + /** + * Creates a new hologram (TextDisplay) at the given location. + * Caches the hologram for future reference. + * + * @param pos the location to create the hologram at + * @param text the text to display + */ + private void createHologram(Location pos, String text) { + TextDisplay display = pos.getWorld().spawn(pos, TextDisplay.class); + display.setAlignment(TextDisplay.TextAlignment.CENTER); + display.setBillboard(Billboard.CENTER); + display.setPersistent(true); + display.setText(text); + activeHolograms.add(pos); } /** - * Delete hologram + * Deletes the hologram for the given island and removes any residual holograms nearby. * - * @param island island + * @param island the island whose hologram should be deleted */ - private void deleteHologram(@NonNull Island island) { - TextDisplay hologram = cachedHolograms.remove(island); - if (hologram != null) { - clearIfInitialized(hologram); + private void removeHologramAt(@NonNull Island island) { + if (island.getWorld() == null) { + return; } - // Clear any residual ones that are not cached for some reason - clearTextDisplayNearBlock(island); + cleanupHologram(getHologramLocation(island)); } - private void clearTextDisplayNearBlock(Island island) { - World world = island.getWorld(); - if (world == null) - return; - Location pos = island.getCenter().clone().add(parseVector(addon.getSettings().getOffset())); - // Search for entities in a small radius (e.g., 1 block around) - for (Entity entity : world.getNearbyEntities(pos, 1, 1, 1)) { - if (entity.getType() == EntityType.TEXT_DISPLAY) { - ((TextDisplay) entity).remove(); - } - } + /** + * Removes any holograms at this location + * @param pos location + */ + private void cleanupHologram(Location pos) { + activeHolograms.remove(pos); + // Chunks have to be loaded for the entity to exist to be deleted + Util.getChunkAtAsync(pos).thenRun(() -> + pos.getWorld().getNearbyEntities(pos, 1, 1, 1).stream() + .filter(e -> e.getType() == EntityType.TEXT_DISPLAY) + .forEach(Entity::remove)); } - protected void setUp(@NonNull Island island, @NonNull OneBlockIslands is, boolean newIsland) { - UUID ownerUUID = island.getOwner(); - if (ownerUUID == null) { - return; + /** + * Parses a string in the format "x,y,z" into a Vector. + * If parsing fails, returns a default vector. + * + * @param str the string to parse + * @return the parsed Vector + */ + private static Vector parseVector(String str) { + if (str == null) { + return DEFAULT_OFFSET; } - - User owner = User.getInstance(ownerUUID); - if (newIsland) { - String holoLine = owner.getTranslation("aoneblock.island.starting-hologram"); - is.setHologram(holoLine == null ? "" : holoLine); + try { + String[] parts = str.split(","); + return parts.length == 3 + ? new Vector( + Double.parseDouble(parts[0].trim()), + Double.parseDouble(parts[1].trim()), + Double.parseDouble(parts[2].trim())) + : DEFAULT_OFFSET; + } catch (NumberFormatException e) { + return DEFAULT_OFFSET; } - updateLines(island, is); - } - - protected void process(@NonNull Island i, @NonNull OneBlockIslands is, @NonNull OneBlockPhase phase) { - String holoLine = phase.getHologramLine(is.getBlockNumber()); - is.setHologram(holoLine == null ? "" : Util.translateColorCodes(holoLine)); - updateLines(i, is); } } diff --git a/src/test/java/world/bentobox/aoneblock/listeners/HoloListenerTest.java b/src/test/java/world/bentobox/aoneblock/listeners/HoloListenerTest.java new file mode 100644 index 0000000..5b4f40a --- /dev/null +++ b/src/test/java/world/bentobox/aoneblock/listeners/HoloListenerTest.java @@ -0,0 +1,241 @@ +package world.bentobox.aoneblock.listeners; + +import static org.junit.Assert.assertNotNull; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyDouble; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.anyLong; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.ArgumentMatchers.isNull; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import java.util.Collection; +import java.util.List; +import java.util.UUID; +import java.util.concurrent.CompletableFuture; + +import org.bukkit.Bukkit; +import org.bukkit.Chunk; +import org.bukkit.Location; +import org.bukkit.World; +import org.bukkit.entity.Entity; +import org.bukkit.entity.EntityType; +import org.bukkit.entity.Player; +import org.bukkit.entity.TextDisplay; +import org.bukkit.scheduler.BukkitScheduler; +import org.bukkit.util.Vector; +import org.eclipse.jdt.annotation.NonNull; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.Mockito; +import org.powermock.api.mockito.PowerMockito; +import org.powermock.core.classloader.annotations.PrepareForTest; +import org.powermock.modules.junit4.PowerMockRunner; +import org.powermock.reflect.Whitebox; + +import world.bentobox.aoneblock.AOneBlock; +import world.bentobox.aoneblock.Settings; +import world.bentobox.aoneblock.dataobjects.OneBlockIslands; +import world.bentobox.aoneblock.oneblocks.OneBlockPhase; +import world.bentobox.bentobox.BentoBox; +import world.bentobox.bentobox.api.events.island.IslandDeleteEvent; +import world.bentobox.bentobox.api.user.User; +import world.bentobox.bentobox.database.objects.Island; +import world.bentobox.bentobox.managers.LocalesManager; +import world.bentobox.bentobox.managers.PlaceholdersManager; +import world.bentobox.bentobox.util.Util; + +/** + * @author tastybento + * + */ +@RunWith(PowerMockRunner.class) +@PrepareForTest({ Bukkit.class, BentoBox.class, User.class, Util.class }) +public class HoloListenerTest { + @Mock + private BentoBox plugin; + @Mock + AOneBlock addon; + private HoloListener hl; + @Mock + private Island island; + private UUID uuid = UUID.randomUUID(); + @Mock + private Location location; + @Mock + private World world; + @Mock + private TextDisplay hologram; + private Settings settings; + @Mock + private @NonNull OneBlockIslands is; + @Mock + private Player player; + @Mock + private LocalesManager lm; + @Mock + private PlaceholdersManager pm; + @Mock + private BukkitScheduler scheduler; + @Mock + private @NonNull OneBlockPhase phase; + + + /** + * @throws java.lang.Exception + */ + @Before + public void setUp() throws Exception { + // Set up plugin + Whitebox.setInternalState(BentoBox.class, "instance", plugin); + // Settings + settings = new Settings(); + when(addon.getSettings()).thenReturn(settings); + + // Island + when(island.getWorld()).thenReturn(world); + when(island.getCenter()).thenReturn(location); + when(island.getOwner()).thenReturn(uuid); + + // World + when(world.spawn(any(Location.class), eq(TextDisplay.class))).thenReturn(hologram); + + // OneBlock Island + when(is.getHologram()).thenReturn("Hologram"); + // Location + when(location.getWorld()).thenReturn(world); + when(location.clone()).thenReturn(location); + when(location.add(any(Vector.class))).thenReturn(location); + // Chunks + PowerMockito.mockStatic(Util.class, Mockito.RETURNS_MOCKS); + CompletableFuture future = CompletableFuture.completedFuture(null); + when(Util.getChunkAtAsync(location)).thenReturn(future); // Load the chunk immediately + + Player nonHolo = mock(Player.class); + + Collection entities = List.of(nonHolo , hologram); + // Get entities + when(hologram.getType()).thenReturn(EntityType.TEXT_DISPLAY); + when(world.getNearbyEntities(any(Location.class), anyDouble(), anyDouble(), anyDouble())).thenReturn(entities); + + // Player and translations + when(player.getUniqueId()).thenReturn(uuid); + User.getInstance(player); + User.setPlugin(plugin); + + when(plugin.getLocalesManager()).thenReturn(lm); + when(plugin.getPlaceholdersManager()).thenReturn(pm); + + // Bukkit + PowerMockito.mockStatic(Bukkit.class, Mockito.RETURNS_MOCKS); + when(Bukkit.getScheduler()).thenReturn(scheduler); + + // DUT + hl = new HoloListener(addon); + } + + @After + public void tearDown() { + User.clearUsers(); + } + + /** + * Test method for {@link world.bentobox.aoneblock.listeners.HoloListener#HoloListener(world.bentobox.aoneblock.AOneBlock)}. + */ + @Test + public void testHoloListener() { + assertNotNull(hl); + } + + /** + * Test method for {@link world.bentobox.aoneblock.listeners.HoloListener#onDeletedIsland(world.bentobox.bentobox.api.events.island.IslandDeleteEvent)}. + */ + @Test + public void testOnDeletedIsland() { + IslandDeleteEvent event = new IslandDeleteEvent(island, uuid, false, location); + this.hl.onDeletedIsland(event); + verify(hologram).remove(); + } + + /** + * Test method for {@link world.bentobox.aoneblock.listeners.HoloListener#onDisable()}. + */ + @Test + public void testOnDisable() { + hl.setUp(island, is, true); + hl.onDisable(); + verify(hologram, times(2)).remove(); + } + + + /** + * Test method for {@link world.bentobox.aoneblock.listeners.HoloListener#setUp(world.bentobox.bentobox.database.objects.Island, world.bentobox.aoneblock.dataobjects.OneBlockIslands, boolean)}. + */ + @Test + public void testSetUpNoHolograms() { + settings.setUseHolograms(false); + hl.setUp(island, is, true); + verify(scheduler, never()).runTaskLater(isNull(), any(Runnable.class), anyLong()); + } + + /** + * Test method for {@link world.bentobox.aoneblock.listeners.HoloListener#setUp(world.bentobox.bentobox.database.objects.Island, world.bentobox.aoneblock.dataobjects.OneBlockIslands, boolean)}. + */ + @Test + public void testSetUpNoScheduling() { + settings.setHologramDuration(0); + hl.setUp(island, is, true); + verify(scheduler, never()).runTaskLater(isNull(), any(Runnable.class), anyLong()); + } + + /** + * Test method for {@link world.bentobox.aoneblock.listeners.HoloListener#setUp(world.bentobox.bentobox.database.objects.Island, world.bentobox.aoneblock.dataobjects.OneBlockIslands, boolean)}. + */ + @Test + public void testSetUpNewIsland() { + hl.setUp(island, is, true); + verify(is).setHologram(""); + verify(scheduler).runTaskLater(isNull(), any(Runnable.class), anyLong()); + } + + /** + * Test method for {@link world.bentobox.aoneblock.listeners.HoloListener#setUp(world.bentobox.bentobox.database.objects.Island, world.bentobox.aoneblock.dataobjects.OneBlockIslands, boolean)}. + */ + @Test + public void testSetUpNotNewIsland() { + hl.setUp(island, is, false); + verify(is, never()).setHologram(anyString()); + verify(scheduler).runTaskLater(isNull(), any(Runnable.class), anyLong()); + } + + + /** + * Test method for {@link world.bentobox.aoneblock.listeners.HoloListener#process(world.bentobox.bentobox.database.objects.Island, world.bentobox.aoneblock.dataobjects.OneBlockIslands, world.bentobox.aoneblock.oneblocks.OneBlockPhase)}. + */ + @Test + public void testProcessNull() { + when(phase.getHologramLine(anyInt())).thenReturn(null); + when(is.getHologram()).thenReturn(""); // Return blank + hl.process(island, is, phase); + verify(scheduler, never()).runTaskLater(isNull(), any(Runnable.class), anyLong()); + } + + /** + * Test method for {@link world.bentobox.aoneblock.listeners.HoloListener#process(world.bentobox.bentobox.database.objects.Island, world.bentobox.aoneblock.dataobjects.OneBlockIslands, world.bentobox.aoneblock.oneblocks.OneBlockPhase)}. + */ + @Test + public void testProcess() { + when(phase.getHologramLine(anyInt())).thenReturn("my Holo"); + hl.process(island, is, phase); + verify(scheduler).runTaskLater(isNull(), any(Runnable.class), anyLong()); + } + +}