diff --git a/CLAUDE.md b/CLAUDE.md index 4bb7d15..61b5539 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -45,7 +45,7 @@ public class MyTest { private MockedStatic mockedBukkit; @BeforeEach - public void setUp() { + void setUp() { server = MockBukkit.mock(); // always first mockedBukkit = Mockito.mockStatic(Bukkit.class, Mockito.RETURNS_DEEP_STUBS); mockedBukkit.when(Bukkit::getMinecraftVersion).thenReturn("1.21.11"); @@ -54,7 +54,7 @@ public class MyTest { } @AfterEach - public void tearDown() { + void tearDown() { mockedBukkit.closeOnDemand(); MockBukkit.unmock(); } @@ -68,6 +68,12 @@ Key rules: - MockBukkit's JUnit transitive deps are excluded in `pom.xml` to avoid JUnit 6 version conflicts with surefire. - Do not use `new ItemStack(Material.AIR)` in tests — use `null` for empty armor slots; Paper 1.21's ItemStack handles AIR differently. - Do not reference `world.bentobox.bentobox.lists.Flags` static fields in tests — the class static initializer requires full BentoBox initialization. Use the string flag ID instead (e.g., `"ANIMAL_NATURAL_SPAWN"`). +- **No `public` modifier** on test methods (`@Test`, `@BeforeEach`, `@AfterEach`, `@BeforeAll`, `@AfterAll`) — JUnit 5 does not require it and SonarCloud flags it. +- **No `throws Exception`** on `@BeforeEach`/`@AfterEach` unless the method body actually declares a checked exception. +- **`assertDoesNotThrow(() -> ...)`** for tests that verify no exception is thrown — bare method calls with no assertions are flagged by SonarCloud. +- **`assertEquals(expected, actual)`** for numeric equality — `assertTrue(x == y)` is flagged (S5785). +- **`@Disabled` must include a reason string** — e.g. `@Disabled("PotionEffectType cannot be mocked without full server initialisation")`. +- **Do not use `eq()` in `verify()` or `when()` for concrete values** — pass the value directly; `eq()` wrappers on non-matcher arguments are redundant and flagged (S6068). ### Locales @@ -80,6 +86,27 @@ All 24 locale files use **MiniMessage format** (e.g., ``, ``). L - `src/main/resources/locales/` — 24 language translation files (MiniMessage format) - `src/main/resources/blueprints/` — Island templates (overworld, nether, end) +## Code Conventions + +### Java 21 idioms (enforced by SonarCloud) + +- **Pattern-matching instanceof** — use `instanceof Type varName` instead of `instanceof Type` + explicit cast (S6201). +- **`Math.clamp`** — use `Math.clamp(value, min, max)` instead of `Math.max(min, Math.min(max, value))` (S6885). +- **`Map.putIfAbsent`** — replace `if (!map.containsKey(k)) { map.put(k, v); ... }` with `if (map.putIfAbsent(k, v) == null) { ... }` (S3824). +- **`@Deprecated` annotation** — always include `since` and `forRemoval` arguments, e.g. `@Deprecated(since = "1.21", forRemoval = true)` (S6355). If `forRemoval` intent is unclear use `since` only (S1123). +- **Reduce cognitive complexity** by extracting private helper methods rather than nesting conditions. + +### SonarCloud issue lookup + +Query open issues via the public API (no auth needed for public projects): + +```bash +curl -s "https://sonarcloud.io/api/issues/search?componentKeys=BentoBoxWorld_AcidIsland&statuses=OPEN&impactSeverities=MEDIUM&ps=100" \ + | python3 -c "import json,sys; [print(f'{i[\"component\"].split(\":\",1)[-1]}:{i.get(\"line\",\"?\")} [{i[\"rule\"]}] {i[\"message\"]}') for i in json.load(sys.stdin)[\"issues\"]]" +``` + +Change `impactSeverities` to `HIGH`, `MEDIUM`, or `LOW` as needed. + ## CI GitHub Actions workflow on `develop` branch and PRs: builds with Java 21, runs JaCoCo coverage, reports to SonarCloud. diff --git a/README.md b/README.md index d3f3a3d..5d9abc4 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,5 @@ -AcidIsland™ -=========== +AcidIsland +========== [![Build Status](https://ci.codemc.org/buildStatus/icon?job=BentoBoxWorld/AcidIsland)](https://ci.codemc.org/job/BentoBoxWorld/job/AcidIsland/) [![Lines Of Code](https://sonarcloud.io/api/project_badges/measure?project=BentoBoxWorld_AcidIsland&metric=ncloc)](https://sonarcloud.io/component_measures?id=BentoBoxWorld_AcidIsland&metric=ncloc) [![Maintainability Rating](https://sonarcloud.io/api/project_badges/measure?project=BentoBoxWorld_AcidIsland&metric=sqale_rating)](https://sonarcloud.io/component_measures?id=BentoBoxWorld_AcidIslandd&metric=Maintainability) @@ -8,16 +8,15 @@ AcidIsland™ [![Bugs](https://sonarcloud.io/api/project_badges/measure?project=BentoBoxWorld_AcidIsland&metric=bugs)](https://sonarcloud.io/project/issues?id=BentoBoxWorld_AcidIsland&resolved=false&types=BUG) # Introduction -AcidIsland™ add-on for BentoBox, so to run an AcidIsland™ game, you must have BentoBox installed. Docs can be found at [https://docs.bentobox.world](https://docs.bentobox.world). +AcidIsland add-on for BentoBox, so to run an AcidIsland game, you must have BentoBox installed. Docs can be found at [https://docs.bentobox.world](https://docs.bentobox.world). +AcidIsland ## The Story You're on an island, in a sea of acid! If you like Skyblock, try the AcidIsland™ game mode for a new challenge! Instead of falling you must contend with acid water when expanding your island, and players can boat to each other's islands. -acidislandart - ## Download You can download from GitHub, or ready made plugin packs from [https://download.bentobox.world](https://download.bentobox.world). diff --git a/pom.xml b/pom.xml index 1349a94..e5a3f1c 100644 --- a/pom.xml +++ b/pom.xml @@ -56,7 +56,7 @@ 5.11.4 1.21.11-R0.1-SNAPSHOT - 3.12.0-SNAPSHOT + 3.14.0-SNAPSHOT ${build.version}-SNAPSHOT diff --git a/src/main/java/world/bentobox/acidisland/AISettings.java b/src/main/java/world/bentobox/acidisland/AISettings.java index 8ee38e0..463c313 100644 --- a/src/main/java/world/bentobox/acidisland/AISettings.java +++ b/src/main/java/world/bentobox/acidisland/AISettings.java @@ -140,6 +140,29 @@ public class AISettings implements WorldSettings { @ConfigEntry(path = "acid.damage.protection.full-armor") private boolean fullArmorProtection; + /* PURIFIED WATER */ + @ConfigComment("Enable the purified water mechanic. Drinking acid water from a bottle damages") + @ConfigComment("the player; drinking purified water heals them. Purified water can be made by") + @ConfigComment("collecting rain in a cauldron (dripstone fills it as purified; rain/bucket fills") + @ConfigComment("it as acid), smelting a water bottle in a furnace, brewing water bottles with coal,") + @ConfigComment("or smelting a water bucket in a furnace (if bucket-furnace-enabled is true).") + @ConfigEntry(path = "acid.purified-water.enabled", since = "1.21") + private boolean purifiedWaterEnabled = true; + + @ConfigComment("Damage dealt to a player who drinks an acid water bottle (in half-hearts)") + @ConfigEntry(path = "acid.purified-water.drink-damage", since = "1.21") + private double acidDrinkDamage = 4.0; + + @ConfigComment("Health restored to a player who drinks a purified water bottle (in half-hearts)") + @ConfigEntry(path = "acid.purified-water.heal-amount", since = "1.21") + private double purifiedWaterHeal = 4.0; + + @ConfigComment("Allow purifying a water bucket by smelting it in a furnace.") + @ConfigComment("The cook time is 2000 ticks (100 seconds) to simulate the effort of boiling.") + @ConfigComment("Disable if this feels too easy for your server's balance.") + @ConfigEntry(path = "acid.purified-water.bucket-furnace-enabled", since = "1.21") + private boolean purifiedBucketFurnaceEnabled = true; + /* WORLD */ @ConfigComment("Friendly name for this world. Used in admin commands. Must be a single word") @@ -670,6 +693,12 @@ public int getAcidDamageMonster() { public long getAcidDestroyItemTime() { return acidDestroyItemTime; } + /** + * @return damage dealt when drinking an acid water bottle (half-hearts) + */ + public double getAcidDrinkDamage() { + return acidDrinkDamage; + } /** * @return the acidEffects */ @@ -682,6 +711,24 @@ public List getAcidEffects() { public int getAcidRainDamage() { return acidRainDamage; } + /** + * @return health restored when drinking purified water (half-hearts) + */ + public double getPurifiedWaterHeal() { + return purifiedWaterHeal; + } + /** + * @return true if the purified water mechanic is enabled + */ + public boolean isPurifiedWaterEnabled() { + return purifiedWaterEnabled; + } + /** + * @return true if water buckets can be purified by smelting in a furnace + */ + public boolean isPurifiedBucketFurnaceEnabled() { + return purifiedBucketFurnaceEnabled; + } @Override public int getBanLimit() { @@ -742,6 +789,7 @@ public Map getDefaultIslandSettingNames() * @return the defaultIslandProtection * @deprecated since 1.21 */ + @Deprecated(since = "1.21") @Override public Map getDefaultIslandFlags() { return Collections.emptyMap(); @@ -752,6 +800,7 @@ public Map getDefaultIslandFlags() { * @return the defaultIslandSettings * @deprecated since 1.21 */ + @Deprecated(since = "1.21") @Override public Map getDefaultIslandSettings() { return Collections.emptyMap(); @@ -1157,6 +1206,30 @@ public boolean isWaterUnsafe() { public void setAcidDamage(int acidDamage) { this.acidDamage = acidDamage; } + /** + * @param acidDrinkDamage damage dealt when drinking an acid water bottle (half-hearts) + */ + public void setAcidDrinkDamage(double acidDrinkDamage) { + this.acidDrinkDamage = acidDrinkDamage; + } + /** + * @param purifiedWaterEnabled true to enable the purified water mechanic + */ + public void setPurifiedWaterEnabled(boolean purifiedWaterEnabled) { + this.purifiedWaterEnabled = purifiedWaterEnabled; + } + /** + * @param purifiedWaterHeal health restored when drinking purified water (half-hearts) + */ + public void setPurifiedWaterHeal(double purifiedWaterHeal) { + this.purifiedWaterHeal = purifiedWaterHeal; + } + /** + * @param purifiedBucketFurnaceEnabled true to allow furnace-purifying water buckets + */ + public void setPurifiedBucketFurnaceEnabled(boolean purifiedBucketFurnaceEnabled) { + this.purifiedBucketFurnaceEnabled = purifiedBucketFurnaceEnabled; + } /** * @param acidDamageAnimal the acidDamageAnimal to set */ diff --git a/src/main/java/world/bentobox/acidisland/AcidIsland.java b/src/main/java/world/bentobox/acidisland/AcidIsland.java index a9439f7..7902439 100644 --- a/src/main/java/world/bentobox/acidisland/AcidIsland.java +++ b/src/main/java/world/bentobox/acidisland/AcidIsland.java @@ -16,6 +16,7 @@ import world.bentobox.acidisland.commands.IslandAboutCommand; import world.bentobox.acidisland.listeners.AcidEffect; import world.bentobox.acidisland.listeners.LavaCheck; +import world.bentobox.acidisland.listeners.PurifiedWaterListener; import world.bentobox.acidisland.world.AcidBiomeProvider; import world.bentobox.acidisland.world.AcidTask; import world.bentobox.acidisland.world.ChunkGeneratorWorld; @@ -103,6 +104,7 @@ public void onEnable() { // Acid Effects registerListener(new AcidEffect(this)); registerListener(new LavaCheck(this)); + registerListener(new PurifiedWaterListener(this)); // Burn everything acidTask = new AcidTask(this); } @@ -153,34 +155,29 @@ public void createWorlds() { * @return world loaded or generated */ private World getWorld(String worldName2, Environment env, @Nullable ChunkGenerator 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).type(WorldType.NORMAL); + String name = getWorldName(worldName2, env); + WorldCreator wc = WorldCreator.name(name).environment(env).type(WorldType.NORMAL); World w = settings.isUseOwnGenerator() ? wc.createWorld() : wc.generator(chunkGenerator2).createWorld(); - // Set spawn rates if (w != null && getSettings() != null) { - 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()); - } + configureSpawnRates(w); } return w; + } + + private String getWorldName(String base, Environment env) { + if (env.equals(World.Environment.NETHER)) return base + NETHER; + if (env.equals(World.Environment.THE_END)) return base + THE_END; + return base; + } + private void configureSpawnRates(World w) { + AISettings s = getSettings(); + if (s.getSpawnLimitMonsters() > 0) w.setSpawnLimit(SpawnCategory.MONSTER, s.getSpawnLimitMonsters()); + if (s.getSpawnLimitAmbient() > 0) w.setSpawnLimit(SpawnCategory.AMBIENT, s.getSpawnLimitAmbient()); + if (s.getSpawnLimitAnimals() > 0) w.setSpawnLimit(SpawnCategory.ANIMAL, s.getSpawnLimitAnimals()); + if (s.getSpawnLimitWaterAnimals() > 0) w.setSpawnLimit(SpawnCategory.WATER_ANIMAL, s.getSpawnLimitWaterAnimals()); + if (s.getTicksPerAnimalSpawns() > 0) w.setTicksPerSpawns(SpawnCategory.ANIMAL, s.getTicksPerAnimalSpawns()); + if (s.getTicksPerMonsterSpawns() > 0) w.setTicksPerSpawns(SpawnCategory.MONSTER, s.getTicksPerMonsterSpawns()); } @Override diff --git a/src/main/java/world/bentobox/acidisland/events/ItemFillWithAcidEvent.java b/src/main/java/world/bentobox/acidisland/events/ItemFillWithAcidEvent.java index f233b2a..2c8dee2 100644 --- a/src/main/java/world/bentobox/acidisland/events/ItemFillWithAcidEvent.java +++ b/src/main/java/world/bentobox/acidisland/events/ItemFillWithAcidEvent.java @@ -1,6 +1,7 @@ package world.bentobox.acidisland.events; import org.bukkit.entity.Player; +import org.bukkit.event.Cancellable; import org.bukkit.event.HandlerList; import org.bukkit.inventory.ItemStack; @@ -8,16 +9,16 @@ import world.bentobox.bentobox.database.objects.Island; /** - * Fired when an ItemStack (water bottle or bucket) is filled with acid + * Fired when an ItemStack (water bottle or bucket) is filled with acid water. + * Cancel this event to prevent the acid bottle from being given to the player. * @author Poslovitch * @since 1.0 - * @deprecated never used */ -@Deprecated -public class ItemFillWithAcidEvent extends IslandBaseEvent { +public class ItemFillWithAcidEvent extends IslandBaseEvent implements Cancellable { private final Player player; private final ItemStack item; + private boolean cancelled; private static final HandlerList handlers = new HandlerList(); @Override @@ -50,4 +51,14 @@ public Player getPlayer() { public ItemStack getItem() { return item; } + + @Override + public boolean isCancelled() { + return cancelled; + } + + @Override + public void setCancelled(boolean cancel) { + this.cancelled = cancel; + } } diff --git a/src/main/java/world/bentobox/acidisland/events/PlayerDrinkAcidEvent.java b/src/main/java/world/bentobox/acidisland/events/PlayerDrinkAcidEvent.java index 9ab6e4f..86798a4 100644 --- a/src/main/java/world/bentobox/acidisland/events/PlayerDrinkAcidEvent.java +++ b/src/main/java/world/bentobox/acidisland/events/PlayerDrinkAcidEvent.java @@ -1,21 +1,23 @@ package world.bentobox.acidisland.events; import org.bukkit.entity.Player; +import org.bukkit.event.Cancellable; import org.bukkit.event.HandlerList; import world.bentobox.bentobox.api.events.IslandBaseEvent; import world.bentobox.bentobox.database.objects.Island; /** - * Fired when a player drinks acid and... DIES + * Fired when a player drinks acid water from a bottle. + * Cancel this event to prevent the damage from being applied. * @author Poslovitch * @since 1.0 - * @deprecated - never fired */ -@Deprecated -public class PlayerDrinkAcidEvent extends IslandBaseEvent { +public class PlayerDrinkAcidEvent extends IslandBaseEvent implements Cancellable { private final Player player; + private double damage; + private boolean cancelled; private static final HandlerList handlers = new HandlerList(); @Override @@ -27,16 +29,43 @@ public static HandlerList getHandlerList() { return handlers; } - public PlayerDrinkAcidEvent(Island island, Player player) { + public PlayerDrinkAcidEvent(Island island, Player player, double damage) { super(island); this.player = player; + this.damage = damage; } /** - * Gets the player which is getting killed by its stupid thirsty - * @return the killed player + * Gets the player who drank acid water + * @return the player */ public Player getPlayer() { return player; } + + /** + * Gets the damage that will be applied to the player + * @return damage amount + */ + public double getDamage() { + return damage; + } + + /** + * Sets the damage that will be applied to the player + * @param damage new damage amount + */ + public void setDamage(double damage) { + this.damage = damage; + } + + @Override + public boolean isCancelled() { + return cancelled; + } + + @Override + public void setCancelled(boolean cancel) { + this.cancelled = cancel; + } } diff --git a/src/main/java/world/bentobox/acidisland/events/PlayerDrinkPurifiedWaterEvent.java b/src/main/java/world/bentobox/acidisland/events/PlayerDrinkPurifiedWaterEvent.java new file mode 100644 index 0000000..19fea7b --- /dev/null +++ b/src/main/java/world/bentobox/acidisland/events/PlayerDrinkPurifiedWaterEvent.java @@ -0,0 +1,71 @@ +package world.bentobox.acidisland.events; + +import org.bukkit.entity.Player; +import org.bukkit.event.Cancellable; +import org.bukkit.event.HandlerList; + +import world.bentobox.bentobox.api.events.IslandBaseEvent; +import world.bentobox.bentobox.database.objects.Island; + +/** + * Fired when a player drinks purified water from a bottle. + * Cancel this event to prevent the healing from being applied. + * @author tastybento + * @since 1.21 + */ +public class PlayerDrinkPurifiedWaterEvent extends IslandBaseEvent implements Cancellable { + + private final Player player; + private double healAmount; + private boolean cancelled; + private static final HandlerList handlers = new HandlerList(); + + @Override + public HandlerList getHandlers() { + return getHandlerList(); + } + + public static HandlerList getHandlerList() { + return handlers; + } + + public PlayerDrinkPurifiedWaterEvent(Island island, Player player, double healAmount) { + super(island); + this.player = player; + this.healAmount = healAmount; + } + + /** + * Gets the player who drank purified water + * @return the player + */ + public Player getPlayer() { + return player; + } + + /** + * Gets the amount of health that will be restored + * @return heal amount in half-hearts + */ + public double getHealAmount() { + return healAmount; + } + + /** + * Sets the amount of health that will be restored + * @param healAmount new heal amount in half-hearts + */ + public void setHealAmount(double healAmount) { + this.healAmount = healAmount; + } + + @Override + public boolean isCancelled() { + return cancelled; + } + + @Override + public void setCancelled(boolean cancel) { + this.cancelled = cancel; + } +} diff --git a/src/main/java/world/bentobox/acidisland/listeners/AcidEffect.java b/src/main/java/world/bentobox/acidisland/listeners/AcidEffect.java index cf3ea78..5e54d30 100644 --- a/src/main/java/world/bentobox/acidisland/listeners/AcidEffect.java +++ b/src/main/java/world/bentobox/acidisland/listeners/AcidEffect.java @@ -7,6 +7,7 @@ import org.bukkit.Bukkit; import org.bukkit.GameMode; +import org.bukkit.Location; import org.bukkit.Material; import org.bukkit.Sound; import org.bukkit.World.Environment; @@ -108,58 +109,52 @@ public void onSeaBounce(PlayerMoveEvent e) { @EventHandler(priority = EventPriority.LOWEST, ignoreCancelled = true) public void onPlayerMove(PlayerMoveEvent e) { Player player = e.getPlayer(); - // Fast checks - if ((addon.getSettings().getAcidRainDamage() == 0 && addon.getSettings().getAcidDamage() == 0) - || player.isDead() || player.getGameMode().equals(GameMode.CREATIVE) + if (isExemptFromAcid(player)) { + return; + } + handleRainExposure(player); + if (!burningPlayers.containsKey(player) && !isSafeFromAcid(player)) { + startAcidBurn(player); + } + } + + private boolean isExemptFromAcid(Player player) { + return (addon.getSettings().getAcidRainDamage() == 0 && addon.getSettings().getAcidDamage() == 0) + || player.isDead() + || player.getGameMode().equals(GameMode.CREATIVE) || player.getGameMode().equals(GameMode.SPECTATOR) || addon.getPlayers().isInTeleport(player.getUniqueId()) || !Util.sameWorld(addon.getOverWorld(), player.getWorld()) || (!player.isOp() && player.hasPermission("acidisland.mod.noburn")) - || (player.isOp() && !addon.getSettings().isAcidDamageOp())) { + || (player.isOp() && !addon.getSettings().isAcidDamageOp()); + } + + private void handleRainExposure(Player player) { + if (addon.getSettings().getAcidRainDamage() <= 0D || !addon.getOverWorld().hasStorm()) { return; } - // Slow checks - // Check for acid rain - if (addon.getSettings().getAcidRainDamage() > 0D && addon.getOverWorld().hasStorm()) { - if (isSafeFromRain(player)) { - wetPlayers.remove(player); - } else if (!wetPlayers.containsKey(player)) { - // Start hurting them - // Add to the list - wetPlayers.put(player, System.currentTimeMillis() + addon.getSettings().getAcidDamageDelay() * 1000); - // This runnable continuously hurts the player even if - // they are not - // moving but are in acid rain. - - new BukkitRunnable() { - @Override - public void run() { - // Check if it is still raining or player is safe or dead or there is no damage - if (checkForRain(player)) { - this.cancel(); - } - + if (isSafeFromRain(player)) { + wetPlayers.remove(player); + } else if (wetPlayers.putIfAbsent(player, System.currentTimeMillis() + addon.getSettings().getAcidDamageDelay() * 1000L) == null) { + new BukkitRunnable() { + @Override + public void run() { + if (checkForRain(player)) { + this.cancel(); } - }.runTaskTimer(addon.getPlugin(), 0L, 20L); - } - - } - // If they are already burning in acid then return - if (burningPlayers.containsKey(player) || isSafeFromAcid(player)) { - return; + } + }.runTaskTimer(addon.getPlugin(), 0L, 20L); } - // ACID! - // Put the player into the acid list + } + + private void startAcidBurn(Player player) { burningPlayers.put(player, System.currentTimeMillis() + addon.getSettings().getAcidDamageDelay() * 1000); - // This runnable continuously hurts the player even if they are not - // moving but are in acid. new BukkitRunnable() { @Override public void run() { if (continuouslyHurtPlayer(player)) { this.cancel(); } - } }.runTaskTimer(addon.getPlugin(), 0L, 20L); } @@ -180,8 +175,7 @@ protected boolean checkForRain(Player player) { User user = User.getInstance(player); // Get the percentage reduction and ensure the value is between 0 and 100 - double percent = (100 - - Math.max(0, Math.min(100, user.getPermissionValue("acidisland.protection.rain", 0)))) / 100D; + double percent = (100 - Math.clamp(user.getPermissionValue("acidisland.protection.rain", 0), 0, 100)) / 100D; double totalDamage = Math.max(0, addon.getSettings().getAcidRainDamage() - protection) * percent; @@ -198,7 +192,8 @@ protected boolean checkForRain(Player player) { Bukkit.getPluginManager().callEvent(e); if (!e.isCancelled()) { player.damage(event.getRainDamage()); - player.getWorld().playSound(player.getLocation(), Sound.ENTITY_CREEPER_PRIMED, 3F, 3F); + Location rainLoc = player.getLocation(); + if (rainLoc != null) player.getWorld().playSound(rainLoc, Sound.ENTITY_CREEPER_PRIMED, 3F, 3F); } } } @@ -215,8 +210,7 @@ protected boolean continuouslyHurtPlayer(Player player) { User user = User.getInstance(player); // Get the percentage reduction and ensure the value is between 0 and 100 - double percent = (100 - - Math.max(0, Math.min(100, user.getPermissionValue("acidisland.protection.acid", 0)))) / 100D; + double percent = (100 - Math.clamp(user.getPermissionValue("acidisland.protection.acid", 0), 0, 100)) / 100D; double totalDamage = Math.max(0, addon.getSettings().getAcidDamage() - protection) * percent; @@ -232,7 +226,8 @@ protected boolean continuouslyHurtPlayer(Player player) { Bukkit.getPluginManager().callEvent(e); if (!e.isCancelled()) { player.damage(event.getTotalDamage()); - player.getWorld().playSound(player.getLocation(), Sound.ENTITY_CREEPER_PRIMED, 3F, 3F); + Location acidLoc = player.getLocation(); + if (acidLoc != null) player.getWorld().playSound(acidLoc, Sound.ENTITY_CREEPER_PRIMED, 3F, 3F); } } } diff --git a/src/main/java/world/bentobox/acidisland/listeners/PurifiedWaterListener.java b/src/main/java/world/bentobox/acidisland/listeners/PurifiedWaterListener.java new file mode 100644 index 0000000..7785c59 --- /dev/null +++ b/src/main/java/world/bentobox/acidisland/listeners/PurifiedWaterListener.java @@ -0,0 +1,518 @@ +package world.bentobox.acidisland.listeners; + +import java.io.File; +import java.io.IOException; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.Set; + +import net.kyori.adventure.text.Component; + +import org.bukkit.configuration.file.YamlConfiguration; + +import org.bukkit.Bukkit; +import org.bukkit.FluidCollisionMode; +import org.bukkit.Location; +import org.bukkit.Material; +import org.bukkit.World; +import org.bukkit.NamespacedKey; +import org.bukkit.block.Block; +import org.bukkit.block.BlockFace; +import org.bukkit.block.data.Levelled; +import org.bukkit.block.data.type.PointedDripstone; +import org.bukkit.attribute.Attribute; +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.CauldronLevelChangeEvent; +import org.bukkit.event.block.CauldronLevelChangeEvent.ChangeReason; +import org.bukkit.event.inventory.BrewEvent; +import org.bukkit.event.inventory.FurnaceSmeltEvent; +import org.bukkit.event.player.PlayerBucketFillEvent; +import org.bukkit.event.player.PlayerInteractEvent; +import org.bukkit.event.player.PlayerItemConsumeEvent; +import org.bukkit.inventory.BrewerInventory; +import org.bukkit.inventory.FurnaceRecipe; +import org.bukkit.inventory.PlayerInventory; +import org.bukkit.inventory.ItemStack; +import org.bukkit.inventory.RecipeChoice; +import org.bukkit.inventory.meta.ItemMeta; +import org.bukkit.inventory.meta.PotionMeta; +import org.bukkit.persistence.PersistentDataContainer; +import org.bukkit.persistence.PersistentDataType; +import org.bukkit.potion.PotionType; + +import world.bentobox.acidisland.AcidIsland; +import world.bentobox.acidisland.events.ItemFillWithAcidEvent; +import world.bentobox.acidisland.events.PlayerDrinkPurifiedWaterEvent; +import world.bentobox.bentobox.database.objects.Island; +import world.bentobox.bentobox.util.Util; + +/** + * Handles the purified water mechanic. + *

+ * Water in AcidIsland is acid. Glass bottles filled from the ocean deal damage when drunk. + * Purified water heals the player instead. Purified water can be obtained by: + *

    + *
  • Collecting rain in a cauldron filled by dripstone (stalactite)
  • + *
  • Smelting a water bottle in a furnace
  • + *
  • Brewing water bottles with a Coal ingredient in a brewing stand
  • + *
  • Smelting a water bucket in a furnace (if enabled in config)
  • + *
+ * @author tastybento + * @since 1.21 + */ +public class PurifiedWaterListener implements Listener { + + /** PDC key that marks a water item as acid or purified */ + static final NamespacedKey WATER_TYPE_KEY = new NamespacedKey("acidisland", "water_type"); + static final String ACID = "acid"; + static final String PURIFIED = "purified"; + + private final AcidIsland addon; + /** Tracks whether each cauldron location holds purified (true) or acid (false) water */ + private final Map cauldronPurity = new HashMap<>(); + /** Persists cauldron purity across server restarts */ + private final File cauldronDataFile; + private final YamlConfiguration cauldronYaml; + + public PurifiedWaterListener(AcidIsland addon) { + this.addon = addon; + registerFurnaceRecipes(); + File dataFolder = addon.getDataFolder(); + if (dataFolder != null) { + cauldronDataFile = new File(dataFolder, "cauldrons.yml"); + cauldronYaml = YamlConfiguration.loadConfiguration(cauldronDataFile); + loadCauldronData(); + } else { + cauldronDataFile = null; + cauldronYaml = new YamlConfiguration(); + } + } + + private void registerFurnaceRecipes() { + // Plain vanilla water bottle — the only untagged input we accept in the furnace + ItemStack plainWaterBottle = new ItemStack(Material.POTION); + PotionMeta wMeta = (PotionMeta) plainWaterBottle.getItemMeta(); + wMeta.setBasePotionType(PotionType.WATER); + plainWaterBottle.setItemMeta(wMeta); + + NamespacedKey bottleKey = NamespacedKey.fromString("acidisland:purified_water_bottle"); + FurnaceRecipe bottleRecipe = new FurnaceRecipe( + bottleKey, + makePurifiedBottle(), + new RecipeChoice.ExactChoice(plainWaterBottle, makeAcidBottle()), + 0.1f, 200); + Bukkit.addRecipe(bottleRecipe); + + if (addon.getSettings().isPurifiedBucketFurnaceEnabled()) { + NamespacedKey bucketKey = NamespacedKey.fromString("acidisland:purified_water_bucket"); + FurnaceRecipe bucketRecipe = new FurnaceRecipe( + bucketKey, + makePurifiedBucket(), + Material.WATER_BUCKET, + 0.1f, 2000); // 100 seconds — simulates boiling + Bukkit.addRecipe(bucketRecipe); + } + } + + // ----------------------------------------------------------------------- + // Item helpers + // ----------------------------------------------------------------------- + + /** + * @return true if the item is tagged as purified water + */ + static boolean isPurified(ItemStack item) { + if (item == null) return false; + ItemMeta meta = item.getItemMeta(); + if (meta == null) return false; + PersistentDataContainer pdc = meta.getPersistentDataContainer(); + return PURIFIED.equals(pdc.get(WATER_TYPE_KEY, PersistentDataType.STRING)); + } + + /** + * @return true if the item is a water bottle (plain or acid-tagged) that should cause acid damage + */ + static boolean isWaterBottle(ItemStack item) { + if (item == null || item.getType() != Material.POTION) return false; + ItemMeta meta = item.getItemMeta(); + if (!(meta instanceof PotionMeta pm)) return false; + // Check for our acid tag + String tag = pm.getPersistentDataContainer().get(WATER_TYPE_KEY, PersistentDataType.STRING); + if (ACID.equals(tag)) return true; + if (PURIFIED.equals(tag)) return false; + // Plain water bottle (no custom tag): PotionType.WATER or no type with no effects + PotionType base = pm.getBasePotionType(); + return (base == null || base == PotionType.WATER) && pm.getCustomEffects().isEmpty(); + } + + /** + * @return a Potion of Poison with the acid PDC tag and red lore + */ + ItemStack makeAcidBottle() { + ItemStack bottle = new ItemStack(Material.POTION); + PotionMeta meta = (PotionMeta) bottle.getItemMeta(); + meta.setBasePotionType(PotionType.POISON); + meta.lore(List.of(lore("lore-acid"))); + meta.getPersistentDataContainer().set(WATER_TYPE_KEY, PersistentDataType.STRING, ACID); + bottle.setItemMeta(meta); + return bottle; + } + + /** + * @return a Potion of Healing with the purified PDC tag and green lore + */ + ItemStack makePurifiedBottle() { + ItemStack bottle = new ItemStack(Material.POTION); + PotionMeta meta = (PotionMeta) bottle.getItemMeta(); + meta.setBasePotionType(PotionType.WATER); + meta.lore(List.of(lore("lore-purified"))); + meta.getPersistentDataContainer().set(WATER_TYPE_KEY, PersistentDataType.STRING, PURIFIED); + bottle.setItemMeta(meta); + return bottle; + } + + /** + * @return a water bucket marked as acid (red lore, PDC tag) + */ + ItemStack makeAcidBucket() { + ItemStack bucket = new ItemStack(Material.WATER_BUCKET); + ItemMeta meta = bucket.getItemMeta(); + meta.lore(List.of(lore("lore-acid"))); + meta.getPersistentDataContainer().set(WATER_TYPE_KEY, PersistentDataType.STRING, ACID); + bucket.setItemMeta(meta); + return bucket; + } + + /** + * @return a water bucket marked as purified (green lore, PDC tag) + */ + ItemStack makePurifiedBucket() { + ItemStack bucket = new ItemStack(Material.WATER_BUCKET); + ItemMeta meta = bucket.getItemMeta(); + meta.lore(List.of(lore("lore-purified"))); + meta.getPersistentDataContainer().set(WATER_TYPE_KEY, PersistentDataType.STRING, PURIFIED); + bucket.setItemMeta(meta); + return bucket; + } + + /** + * Returns the translated lore Component for the given purified-water locale sub-key. + * Falls back to parsing the key as a MiniMessage string if the locale entry is missing. + */ + private Component lore(String key) { + String raw = addon.getPlugin().getLocalesManager() + .getOrDefault("acidisland.purified-water." + key, "" + key); + return Util.parseMiniMessageOrLegacy(raw); + } + + // ----------------------------------------------------------------------- + // Event handlers + // ----------------------------------------------------------------------- + + /** + * Track whether a cauldron holds acid or purified water based on how it was filled. + *
    + *
  • Rain / water bucket → acid
  • + *
  • Dripstone stalactite → purified
  • + *
  • Cauldron emptied → remove from map
  • + *
+ */ + @EventHandler(ignoreCancelled = true) + public void onCauldronChange(CauldronLevelChangeEvent e) { + if (!addon.getSettings().isPurifiedWaterEnabled()) return; + if (!e.getBlock().getWorld().equals(addon.getOverWorld())) return; + + Location loc = e.getBlock().getLocation(); + ChangeReason reason = e.getReason(); + + if (reason == ChangeReason.NATURAL_FILL) { + // NATURAL_FILL covers both rain and dripstone stalactite drip. + // Distinguish by checking for a downward-pointing dripstone tip above the cauldron. + boolean pure = hasDripstoneStalactiteAbove(e.getBlock()); + cauldronPurity.put(loc, pure); + persistCauldronPurity(loc, pure); + } else if (reason == ChangeReason.BUCKET_EMPTY) { + // Player poured a bucket into the cauldron — check if it was a purified water bucket + boolean pure; + if (e.getEntity() instanceof Player p) { + ItemStack held = p.getInventory().getItemInMainHand(); + pure = isPurified(held); + } else { + pure = false; // acid if no player (dispenser, etc.) + } + cauldronPurity.put(loc, pure); + persistCauldronPurity(loc, pure); + } else if (reason == ChangeReason.BOTTLE_EMPTY) { + // Player emptied a bottle into the cauldron — check if it was purified + if (e.getEntity() instanceof Player p) { + ItemStack held = p.getInventory().getItemInMainHand(); + boolean pure = isPurified(held); + cauldronPurity.put(loc, pure); + persistCauldronPurity(loc, pure); + } + } else if (reason == ChangeReason.UNKNOWN) { + // Unrecognised reason — treat as acid + cauldronPurity.put(loc, false); + persistCauldronPurity(loc, false); + } else if (e.getNewLevel() == 0) { + // Any other reason that empties the cauldron (including BUCKET_FILL taking the last water) + cauldronPurity.remove(loc); + evictCauldronPurity(loc); + } + } + + /** + * Intercept bottle-filling from water blocks and water cauldrons to give + * acid or purified water bottles instead of the vanilla water bottle. + */ + @EventHandler(priority = EventPriority.HIGH, ignoreCancelled = false) + public void onBottleFill(PlayerInteractEvent e) { + if (!addon.getSettings().isPurifiedWaterEnabled()) return; + + Player player = e.getPlayer(); + if (!player.getWorld().equals(addon.getOverWorld())) return; + + ItemStack item = e.getItem(); + if (item == null || item.getType() != Material.GLASS_BOTTLE) return; + + // Paper fires bottle-fill-from-water as RIGHT_CLICK_AIR with getClickedBlock() == null + // because water is a fluid, not a solid block. Fall back to a ray-cast that includes fluids. + Block block = e.getClickedBlock(); + if (block == null) { + block = player.getTargetBlockExact(5, FluidCollisionMode.ALWAYS); + } + if (block == null) return; + + if (block.getType() == Material.WATER) { + // Let vanilla fill the bottle — client prediction stays in sync. + // Snapshot which slots already hold water bottles; on the next tick we find the + // newly-filled vanilla water bottle and swap it for an acid bottle. + PlayerInventory inv = player.getInventory(); + Set preExisting = new HashSet<>(); + for (int i = 0; i < inv.getSize(); i++) { + if (isWaterBottle(inv.getItem(i))) preExisting.add(i); + } + Bukkit.getScheduler().runTask(addon.getPlugin(), () -> { + for (int i = 0; i < inv.getSize(); i++) { + if (preExisting.contains(i) || !isWaterBottle(inv.getItem(i))) continue; + + ItemFillWithAcidEvent fillEvent = new ItemFillWithAcidEvent( + getIsland(player).orElse(null), player, makeAcidBottle()); + Bukkit.getPluginManager().callEvent(fillEvent); + if (!fillEvent.isCancelled()) { + inv.setItem(i, makeAcidBottle()); + } + break; + } + }); + + } else if (block.getType() == Material.WATER_CAULDRON) { + if (e.isCancelled()) return; // respect another plugin's cancellation + // Filling from cauldron + boolean pure = cauldronPurity.getOrDefault(block.getLocation(), false); + ItemStack result = pure ? makePurifiedBottle() : makeAcidBottle(); + + if (!pure) { + ItemFillWithAcidEvent fillEvent = new ItemFillWithAcidEvent( + getIsland(player).orElse(null), player, result); + Bukkit.getPluginManager().callEvent(fillEvent); + if (fillEvent.isCancelled()) return; + } + + e.setCancelled(true); + consumeBottleAndGive(player, result); + decrementCauldron(block); + } + } + + /** + * When a player fills a water bucket in the acid world, tag it as acid water. + */ + @EventHandler(ignoreCancelled = true) + public void onBucketFill(PlayerBucketFillEvent e) { + if (!addon.getSettings().isPurifiedWaterEnabled()) return; + if (!e.getPlayer().getWorld().equals(addon.getOverWorld())) return; + + ItemStack result = e.getItemStack(); + if (result == null || result.getType() != Material.WATER_BUCKET) return; + + Player player = e.getPlayer(); + + // A purified cauldron yields a purified bucket; everything else is acid. + boolean purified = e.getBlock().getType() == Material.WATER_CAULDRON + && cauldronPurity.getOrDefault(e.getBlock().getLocation(), false); + + if (purified) { + e.setItemStack(makePurifiedBucket()); + } else { + ItemFillWithAcidEvent fillEvent = new ItemFillWithAcidEvent( + getIsland(player).orElse(null), player, makeAcidBucket()); + Bukkit.getPluginManager().callEvent(fillEvent); + if (fillEvent.isCancelled()) return; + e.setItemStack(makeAcidBucket()); + } + } + + /** + * When a player drinks a purified water bottle, apply a configurable health boost. + * Vanilla handles returning the glass bottle; we only need to add the heal. + */ + @EventHandler(ignoreCancelled = true) + public void onDrinkPurified(PlayerItemConsumeEvent e) { + if (!addon.getSettings().isPurifiedWaterEnabled()) return; + ItemStack item = e.getItem(); + if (item.getType() != Material.POTION || !isPurified(item)) return; + + Player player = e.getPlayer(); + double healAmount = addon.getSettings().getPurifiedWaterHeal(); + PlayerDrinkPurifiedWaterEvent drinkEvent = new PlayerDrinkPurifiedWaterEvent( + getIsland(player).orElse(null), player, healAmount); + Bukkit.getPluginManager().callEvent(drinkEvent); + if (drinkEvent.isCancelled()) return; + + double maxHealth = player.getAttribute(Attribute.MAX_HEALTH).getValue(); + player.setHealth(Math.min(maxHealth, player.getHealth() + drinkEvent.getHealAmount())); + } + + /** + * When a water bottle or water bucket finishes smelting, mark the output as purified. + * Non-water potions cancel the smelt so other potions are not converted. + */ + @EventHandler(ignoreCancelled = true) + public void onFurnaceSmelt(FurnaceSmeltEvent e) { + if (!addon.getSettings().isPurifiedWaterEnabled()) return; + ItemStack source = e.getSource(); + + if (source.getType() == Material.POTION) { + if (!isWaterBottle(source)) { + // Not a water bottle — cancel so other potions are not consumed + e.setCancelled(true); + return; + } + e.setResult(makePurifiedBottle()); + } else if (source.getType() == Material.WATER_BUCKET + && addon.getSettings().isPurifiedBucketFurnaceEnabled()) { + e.setResult(makePurifiedBucket()); + } + } + + /** + * When coal is used as a brewing ingredient, purify any water bottles in the stand. + */ + @EventHandler(ignoreCancelled = true) + public void onBrew(BrewEvent e) { + if (!addon.getSettings().isPurifiedWaterEnabled()) return; + BrewerInventory inv = e.getContents(); + ItemStack ingredient = inv.getIngredient(); + if (ingredient == null || ingredient.getType() != Material.COAL) return; + + for (int i = 0; i < 3; i++) { + ItemStack slot = inv.getItem(i); + if (isWaterBottle(slot)) { + inv.setItem(i, makePurifiedBottle()); + } + } + } + + // ----------------------------------------------------------------------- + // Private helpers + // ----------------------------------------------------------------------- + + private void consumeBottleAndGive(Player player, ItemStack result) { + ItemStack held = player.getInventory().getItemInMainHand(); + if (held.getAmount() == 1) { + player.getInventory().setItemInMainHand(result); + } else { + held.setAmount(held.getAmount() - 1); + player.getInventory().addItem(result); + } + } + + private void decrementCauldron(Block block) { + if (block.getBlockData() instanceof Levelled levelled) { + int newLevel = levelled.getLevel() - 1; + if (newLevel <= 0) { + block.setType(Material.CAULDRON); + cauldronPurity.remove(block.getLocation()); + } else { + levelled.setLevel(newLevel); + block.setBlockData(levelled); + } + } + } + + private Optional getIsland(Player player) { + return addon.getIslands().getIslandAt(player.getLocation()); + } + + /** + * Returns {@code true} if there is a downward-pointing dripstone stalactite tip + * within 12 blocks directly above the given block. + * Used to distinguish dripstone fills (purified) from rain fills (acid). + */ + private boolean hasDripstoneStalactiteAbove(Block block) { + for (int i = 1; i <= 12; i++) { + Block above = block.getRelative(BlockFace.UP, i); + if (above.getType() == Material.POINTED_DRIPSTONE + && above.getBlockData() instanceof PointedDripstone ds + && ds.getVerticalDirection() == BlockFace.DOWN) { + return true; + } + } + return false; + } + + private void loadCauldronData() { + for (String key : cauldronYaml.getKeys(false)) { + String[] parts = key.split(","); + if (parts.length != 4) continue; + try { + World world = Bukkit.getWorld(parts[0]); + if (world == null) continue; + Location loc = new Location(world, + Integer.parseInt(parts[1]), + Integer.parseInt(parts[2]), + Integer.parseInt(parts[3])); + cauldronPurity.put(loc, cauldronYaml.getBoolean(key)); + } catch (NumberFormatException ignored) { + // Skip malformed entries + } + } + } + + private void persistCauldronPurity(Location loc, boolean purified) { + if (cauldronDataFile == null || loc.getWorld() == null) return; + String key = loc.getWorld().getName() + "," + loc.getBlockX() + "," + loc.getBlockY() + "," + loc.getBlockZ(); + cauldronYaml.set(key, purified); + saveCauldronYaml(); + } + + private void evictCauldronPurity(Location loc) { + if (cauldronDataFile == null || loc.getWorld() == null) return; + String key = loc.getWorld().getName() + "," + loc.getBlockX() + "," + loc.getBlockY() + "," + loc.getBlockZ(); + cauldronYaml.set(key, null); + saveCauldronYaml(); + } + + private void saveCauldronYaml() { + try { + cauldronDataFile.getParentFile().mkdirs(); + cauldronYaml.save(cauldronDataFile); + } catch (IOException ex) { + addon.logError("Could not save cauldron purity data: " + ex.getMessage()); + } + } + + /** + * @return the cauldron purity map (package-private for testing) + */ + Map getCauldronPurity() { + return cauldronPurity; + } +} diff --git a/src/main/java/world/bentobox/acidisland/world/AcidTask.java b/src/main/java/world/bentobox/acidisland/world/AcidTask.java index fd2a416..2617330 100644 --- a/src/main/java/world/bentobox/acidisland/world/AcidTask.java +++ b/src/main/java/world/bentobox/acidisland/world/AcidTask.java @@ -57,44 +57,48 @@ public AcidTask(AcidIsland addon) { void findEntities() { Map burnList = new WeakHashMap<>(); for (Entity e : getEntityStream()) { - if (e instanceof Item || (!IMMUNE.contains(e.getType()) && !(e instanceof WaterMob))) { + if (isAcidSusceptible(e)) { int x = e.getLocation().getBlockX() >> 4; int z = e.getLocation().getBlockZ() >> 4; - if (e.getWorld().isChunkLoaded(x,z)) { - if (e.getLocation().getBlock().getType().equals(Material.WATER)) { - if ((e instanceof Monster || e instanceof MagmaCube) && addon.getSettings().getAcidDamageMonster() > 0D) { - burnList.put(e, (long)addon.getSettings().getAcidDamageMonster()); - - } else if ((e instanceof Animals) && addon.getSettings().getAcidDamageAnimal() > 0D - && (!e.getType().equals(EntityType.CHICKEN) || addon.getSettings().isAcidDamageChickens())) { - burnList.put(e, (long)addon.getSettings().getAcidDamageAnimal()); - } else if (addon.getSettings().getAcidDestroyItemTime() > 0 && e instanceof Item) { - burnList.put(e, System.currentTimeMillis()); - } - } + if (e.getWorld().isChunkLoaded(x, z) + && e.getLocation().getBlock().getType().equals(Material.WATER)) { + addToBurnList(e, burnList); } } } // Remove any entities not on the burn list itemsInWater.keySet().removeIf(i -> !burnList.containsKey(i)); - if (!burnList.isEmpty()) { - Bukkit.getScheduler().runTask(addon.getPlugin(), () -> // Burn everything - burnList.forEach(this::applyDamage)); + Bukkit.getScheduler().runTask(addon.getPlugin(), () -> burnList.forEach(this::applyDamage)); + } + } + + private boolean isAcidSusceptible(Entity e) { + return e instanceof Item || (!IMMUNE.contains(e.getType()) && !(e instanceof WaterMob)); + } + + private void addToBurnList(Entity e, Map burnList) { + if ((e instanceof Monster || e instanceof MagmaCube) && addon.getSettings().getAcidDamageMonster() > 0D) { + burnList.put(e, (long) addon.getSettings().getAcidDamageMonster()); + } else if (e instanceof Animals && addon.getSettings().getAcidDamageAnimal() > 0D + && (!e.getType().equals(EntityType.CHICKEN) || addon.getSettings().isAcidDamageChickens())) { + burnList.put(e, (long) addon.getSettings().getAcidDamageAnimal()); + } else if (addon.getSettings().getAcidDestroyItemTime() > 0 && e instanceof Item) { + burnList.put(e, System.currentTimeMillis()); } } void applyDamage(Entity e, long damage) { - if (e instanceof LivingEntity) { - double actualDamage = Math.max(0, damage - damage * AcidEffect.getDamageReduced((LivingEntity)e)); + if (e instanceof LivingEntity livingEntity) { + double actualDamage = Math.max(0, damage - damage * AcidEffect.getDamageReduced(livingEntity)); EntityDamageByAcidEvent event = new EntityDamageByAcidEvent(e, actualDamage, Acid.WATER); // Fire event Bukkit.getPluginManager().callEvent(event); if (!event.isCancelled()) { - ((LivingEntity)e).damage(actualDamage); + livingEntity.damage(actualDamage); } - } else if (addon.getSettings().getAcidDestroyItemTime() > 0 && e instanceof Item){ + } else if (addon.getSettings().getAcidDestroyItemTime() > 0 && e instanceof Item item) { // Item if (e.getLocation().getBlock().getType().equals(Material.WATER)) { itemsInWater.putIfAbsent(e, damage + addon.getSettings().getAcidDestroyItemTime() * 1000); @@ -103,7 +107,7 @@ void applyDamage(Entity e, long damage) { e.remove(); itemsInWater.remove(e); // Fire event - Bukkit.getPluginManager().callEvent(new ItemDestroyByAcidEvent((Item)e)); + Bukkit.getPluginManager().callEvent(new ItemDestroyByAcidEvent(item)); } } else { itemsInWater.remove(e); diff --git a/src/main/java/world/bentobox/acidisland/world/ChunkGeneratorWorld.java b/src/main/java/world/bentobox/acidisland/world/ChunkGeneratorWorld.java index 748c0c0..3452e73 100644 --- a/src/main/java/world/bentobox/acidisland/world/ChunkGeneratorWorld.java +++ b/src/main/java/world/bentobox/acidisland/world/ChunkGeneratorWorld.java @@ -134,68 +134,70 @@ public List getDefaultPopulators(final World world) { * Nether Section */ private void makeNetherRoof() { - - // Make the roof - common across the world for (int x = 0; x < 16; x++) { for (int z = 0; z < 16; z++) { - // Do the ceiling setBlock(x, -1, z, Material.BEDROCK); - // Next three layers are a mix of bedrock and netherrack - for (int y = 2; y < 5; y++) { - double r = gen.noise(x, - y, z, 0.5, 0.5); - if (r > 0D) { - setBlock(x, - y, z, Material.BEDROCK); - } + makeBedrockLayers(x, z); + makeNetherrackLayers(x, z); + makeGlowstoneLayer(x, z); + } + } + } + + /** Layers y=2..4: solid bedrock where noise > 0. */ + private void makeBedrockLayers(int x, int z) { + for (int y = 2; y < 5; y++) { + if (gen.noise(x, -y, z, 0.5, 0.5) > 0D) { + setBlock(x, -y, z, Material.BEDROCK); + } + } + } + + /** Layers y=5..7: netherrack or air based on noise. */ + private void makeNetherrackLayers(int x, int z) { + for (int y = 5; y < 8; y++) { + Material m = gen.noise(x, -y, z, 0.5, 0.5) > 0D ? Material.NETHERRACK : Material.AIR; + setBlock(x, -y, z, m); + } + } + + /** Layer y=8: glowstone blobs or air based on noise. */ + private void makeGlowstoneLayer(int x, int z) { + if (gen.noise(x, -8, z, rand.nextFloat(), rand.nextFloat()) > 0.5D) { + placeGlowstoneBlob(x, z); + setBlock(x, -8, z, Material.GLOWSTONE); + } else { + setBlock(x, -8, z, Material.AIR); + } + } + + private void placeGlowstoneBlob(int x, int z) { + switch (rand.nextInt(4)) { + case 1 -> { + setBlock(x, -8, z, Material.GLOWSTONE); + if (x < 14 && z < 14) { + setBlock(x + 1, -8, z + 1, Material.GLOWSTONE); + setBlock(x + 2, -8, z + 2, Material.GLOWSTONE); + setBlock(x + 1, -8, z + 2, Material.GLOWSTONE); } - // Next three layers are a mix of netherrack and air - for (int y = 5; y < 8; y++) { - double r = gen.noise(x, - y, z, 0.5, 0.5); - if (r > 0D) { - setBlock(x, -y, z, Material.NETHERRACK); - } else { - setBlock(x, -y, z, Material.AIR); - } + } + case 2 -> { + // Stalactite + for (int i = 0; i < rand.nextInt(10); i++) { + setBlock(x, -8 - i, z, Material.GLOWSTONE); } - // Layer 8 may be glowstone - double r = gen.noise(x, - 8, z, rand.nextFloat(), rand.nextFloat()); - if (r > 0.5D) { - // Have blobs of glowstone - switch (rand.nextInt(4)) { - case 1: - // Single block - setBlock(x, -8, z, Material.GLOWSTONE); - if (x < 14 && z < 14) { - setBlock(x + 1, -8, z + 1, Material.GLOWSTONE); - setBlock(x + 2, -8, z + 2, Material.GLOWSTONE); - setBlock(x + 1, -8, z + 2, Material.GLOWSTONE); - setBlock(x + 1, -8, z + 2, Material.GLOWSTONE); - } - break; - case 2: - // Stalatite - for (int i = 0; i < rand.nextInt(10); i++) { - setBlock(x, - 8 - i, z, Material.GLOWSTONE); - } - break; - case 3: - setBlock(x, -8, z, Material.GLOWSTONE); - if (x > 3 && z > 3) { - for (int xx = 0; xx < 3; xx++) { - for (int zz = 0; zz < 3; zz++) { - setBlock(x - xx, - 8 - rand.nextInt(2), z - xx, Material.GLOWSTONE); - } - } + } + case 3 -> { + setBlock(x, -8, z, Material.GLOWSTONE); + if (x > 3 && z > 3) { + for (int xx = 0; xx < 3; xx++) { + for (int zz = 0; zz < 3; zz++) { + setBlock(x - xx, -8 - rand.nextInt(2), z - zz, Material.GLOWSTONE); } - break; - default: - setBlock(x, -8, z, Material.GLOWSTONE); } - setBlock(x, -8, z, Material.GLOWSTONE); - } else { - setBlock(x, -8, z, Material.AIR); } } - + default -> setBlock(x, -8, z, Material.GLOWSTONE); } } diff --git a/src/main/resources/config.yml b/src/main/resources/config.yml index 984485e..cbc72a5 100644 --- a/src/main/resources/config.yml +++ b/src/main/resources/config.yml @@ -72,6 +72,20 @@ acid: helmet: false # If player wears any set of full armor, they will not suffer from acid damage full-armor: false + # Purified water mechanic. Enable to allow players to collect and purify water. + # Acid water damages the player when drunk; purified water heals them. + # Added since 1.21. + purified-water: + # Enable or disable the entire purified water mechanic. + enabled: true + # Damage (in half-hearts) dealt to a player who drinks an acid water bottle. + drink-damage: 4.0 + # Health (in half-hearts) restored to a player who drinks a purified water bottle. + heal-amount: 4.0 + # Allow purifying a water bucket by smelting it in a furnace. + # Cook time is 2000 ticks (100 seconds) to balance the effort. + # Disable if this feels too easy for your server's balance. + bucket-furnace-enabled: true world: # Friendly name for this world. Used in admin commands. Must be a single word # /!\ BentoBox currently does not support changing this value mid-game. If you do need to change it, do a full reset of your databases and worlds. diff --git a/src/main/resources/locales/en-US.yml b/src/main/resources/locales/en-US.yml index 3c40c39..5009f15 100755 --- a/src/main/resources/locales/en-US.yml +++ b/src/main/resources/locales/en-US.yml @@ -9,4 +9,6 @@ acidisland: line1: "[name]" line2: "Water is acid!" line3: "Be careful! <3" - \ No newline at end of file + purified-water: + lore-acid: "Acid Water" + lore-purified: "Purified Water" diff --git a/src/test/java/world/bentobox/acidisland/AISettingsTest.java b/src/test/java/world/bentobox/acidisland/AISettingsTest.java index 81d11ba..5985e7c 100644 --- a/src/test/java/world/bentobox/acidisland/AISettingsTest.java +++ b/src/test/java/world/bentobox/acidisland/AISettingsTest.java @@ -33,420 +33,420 @@ public class AISettingsTest { private AISettings s; @BeforeEach - public void setUp() throws Exception { + void setUp() { MockBukkit.mock(); s = new AISettings(); } @AfterEach - public void tearDown() { + void tearDown() { MockBukkit.unmock(); } @Test - public void testGetAcidDamage() { + void testGetAcidDamage() { assertEquals(10, s.getAcidDamage()); } @Test - public void testGetAcidDamageAnimal() { + void testGetAcidDamageAnimal() { assertEquals(1, s.getAcidDamageAnimal()); } @Test - public void testGetAcidDamageDelay() { + void testGetAcidDamageDelay() { assertEquals(2, s.getAcidDamageDelay()); } @Test - public void testGetAcidDamageMonster() { + void testGetAcidDamageMonster() { assertEquals(5, s.getAcidDamageMonster()); } @Test - public void testGetAcidDestroyItemTime() { + void testGetAcidDestroyItemTime() { assertEquals(0, s.getAcidDestroyItemTime()); } @Test - public void testGetAcidEffects() { + void testGetAcidEffects() { assertTrue(s.getAcidEffects().isEmpty()); } @Test - public void testGetAcidRainDamage() { + void testGetAcidRainDamage() { assertEquals(1, s.getAcidRainDamage()); } @Test - public void testGetBanLimit() { + void testGetBanLimit() { assertEquals(-1, s.getBanLimit()); } @Test - public void testGetCustomRanks() { + void testGetCustomRanks() { assertTrue(s.getCustomRanks().isEmpty()); } @Test - public void testGetDeathsMax() { + void testGetDeathsMax() { assertEquals(10, s.getDeathsMax()); } @Test - public void testGetDefaultBiome() { + void testGetDefaultBiome() { assertEquals(Biome.WARM_OCEAN, s.getDefaultBiome()); } @Test - public void testGetDefaultGameMode() { + void testGetDefaultGameMode() { assertEquals(GameMode.SURVIVAL, s.getDefaultGameMode()); } @Test - public void testGetDifficulty() { + void testGetDifficulty() { assertEquals(Difficulty.NORMAL, s.getDifficulty()); } @Test - public void testGetEndSeaHeight() { + void testGetEndSeaHeight() { assertEquals(54, s.getEndSeaHeight()); } @Test - public void testGetFriendlyName() { + void testGetFriendlyName() { assertEquals("AcidIsland", s.getFriendlyName()); } @Test - public void testGetGeoLimitSettings() { + void testGetGeoLimitSettings() { assertTrue(s.getGeoLimitSettings().isEmpty()); } @Test - public void testGetIslandDistance() { + void testGetIslandDistance() { assertEquals(64, s.getIslandDistance()); } @Test - public void testGetIslandHeight() { + void testGetIslandHeight() { assertEquals(50, s.getIslandHeight()); } @Test - public void testGetIslandProtectionRange() { + void testGetIslandProtectionRange() { assertEquals(50, s.getIslandProtectionRange()); } @Test - public void testGetIslandStartX() { + void testGetIslandStartX() { assertEquals(0, s.getIslandStartX()); } @Test - public void testGetIslandStartZ() { + void testGetIslandStartZ() { assertEquals(0, s.getIslandStartZ()); } @Test - public void testGetIslandXOffset() { + void testGetIslandXOffset() { assertEquals(0, s.getIslandXOffset()); } @Test - public void testGetIslandZOffset() { + void testGetIslandZOffset() { assertEquals(0, s.getIslandZOffset()); } @Test - public void testGetIvSettings() { + void testGetIvSettings() { assertTrue(s.getIvSettings().isEmpty()); } @Test - public void testGetMaxHomes() { + void testGetMaxHomes() { assertEquals(5, s.getMaxHomes()); } @Test - public void testGetMaxIslands() { + void testGetMaxIslands() { assertEquals(-1, s.getMaxIslands()); } @Test - public void testGetMaxTeamSize() { + void testGetMaxTeamSize() { assertEquals(4, s.getMaxTeamSize()); } @Test - public void testGetNetherSeaHeight() { + void testGetNetherSeaHeight() { assertEquals(54, s.getNetherSeaHeight()); } @Test - public void testGetNetherSpawnRadius() { + void testGetNetherSpawnRadius() { assertEquals(32, s.getNetherSpawnRadius()); } @Test - public void testGetPermissionPrefix() { + void testGetPermissionPrefix() { assertEquals("acidisland", s.getPermissionPrefix()); } @Test - public void testGetRemoveMobsWhitelist() { + void testGetRemoveMobsWhitelist() { assertTrue(s.getRemoveMobsWhitelist().isEmpty()); } @Test - public void testGetResetEpoch() { + void testGetResetEpoch() { assertEquals(0, s.getResetEpoch()); } @Test - public void testGetResetLimit() { + void testGetResetLimit() { assertEquals(-1, s.getResetLimit()); } @Test - public void testGetSeaHeight() { + void testGetSeaHeight() { assertEquals(54, s.getSeaHeight()); } @Test - public void testGetHiddenFlags() { + void testGetHiddenFlags() { assertTrue(s.getHiddenFlags().isEmpty()); } @Test - public void testGetVisitorBannedCommands() { + void testGetVisitorBannedCommands() { assertTrue(s.getVisitorBannedCommands().isEmpty()); } @Test - public void testGetFallingBannedCommands() { + void testGetFallingBannedCommands() { assertTrue(s.getFallingBannedCommands().isEmpty()); } @Test - public void testGetWorldFlags() { + void testGetWorldFlags() { assertTrue(s.getWorldFlags().isEmpty()); } @Test - public void testGetWorldName() { + void testGetWorldName() { assertEquals("acidisland_world", s.getWorldName()); } @Test - public void testIsAcidDamageChickens() { + void testIsAcidDamageChickens() { assertFalse(s.isAcidDamageChickens()); } @Test - public void testIsAcidDamageOp() { + void testIsAcidDamageOp() { assertFalse(s.isAcidDamageOp()); } @Test - public void testIsAllowSetHomeInNether() { + void testIsAllowSetHomeInNether() { assertTrue(s.isAllowSetHomeInNether()); } @Test - public void testIsAllowSetHomeInTheEnd() { + void testIsAllowSetHomeInTheEnd() { assertTrue(s.isAllowSetHomeInTheEnd()); } @Test - public void testIsDeathsCounted() { + void testIsDeathsCounted() { assertTrue(s.isDeathsCounted()); } @Test - public void testIsDragonSpawn() { + void testIsDragonSpawn() { assertFalse(s.isDragonSpawn()); } @Test - public void testIsEndGenerate() { + void testIsEndGenerate() { assertTrue(s.isEndGenerate()); } @Test - public void testIsEndIslands() { + void testIsEndIslands() { assertTrue(s.isEndIslands()); } @Test - public void testIsFullArmorProtection() { + void testIsFullArmorProtection() { assertFalse(s.isFullArmorProtection()); } @Test - public void testIsHelmetProtection() { + void testIsHelmetProtection() { assertFalse(s.isHelmetProtection()); } @Test - public void testIsKickedKeepInventory() { + void testIsKickedKeepInventory() { assertFalse(s.isKickedKeepInventory()); } @Test - public void testIsCreateIslandOnFirstLoginEnabled() { + void testIsCreateIslandOnFirstLoginEnabled() { assertFalse(s.isCreateIslandOnFirstLoginEnabled()); } @Test - public void testGetCreateIslandOnFirstLoginDelay() { + void testGetCreateIslandOnFirstLoginDelay() { assertEquals(5, s.getCreateIslandOnFirstLoginDelay()); } @Test - public void testIsCreateIslandOnFirstLoginAbortOnLogout() { + void testIsCreateIslandOnFirstLoginAbortOnLogout() { assertTrue(s.isCreateIslandOnFirstLoginAbortOnLogout()); } @Test - public void testIsLeaversLoseReset() { + void testIsLeaversLoseReset() { assertFalse(s.isLeaversLoseReset()); } @Test - public void testIsNetherGenerate() { + void testIsNetherGenerate() { assertTrue(s.isNetherGenerate()); } @Test - public void testIsNetherIslands() { + void testIsNetherIslands() { assertTrue(s.isNetherIslands()); } @Test - public void testIsNetherRoof() { + void testIsNetherRoof() { assertTrue(s.isNetherRoof()); } @Test - public void testIsOnJoinResetEnderChest() { + void testIsOnJoinResetEnderChest() { assertFalse(s.isOnJoinResetEnderChest()); } @Test - public void testIsOnJoinResetInventory() { + void testIsOnJoinResetInventory() { assertFalse(s.isOnJoinResetInventory()); } @Test - public void testIsOnJoinResetMoney() { + void testIsOnJoinResetMoney() { assertFalse(s.isOnJoinResetMoney()); } @Test - public void testIsOnLeaveResetEnderChest() { + void testIsOnLeaveResetEnderChest() { assertFalse(s.isOnLeaveResetEnderChest()); } @Test - public void testIsOnLeaveResetInventory() { + void testIsOnLeaveResetInventory() { assertFalse(s.isOnLeaveResetInventory()); } @Test - public void testIsOnLeaveResetMoney() { + void testIsOnLeaveResetMoney() { assertFalse(s.isOnLeaveResetMoney()); } @Test - public void testIsRequireConfirmationToSetHomeInNether() { + void testIsRequireConfirmationToSetHomeInNether() { assertTrue(s.isRequireConfirmationToSetHomeInNether()); } @Test - public void testIsRequireConfirmationToSetHomeInTheEnd() { + void testIsRequireConfirmationToSetHomeInTheEnd() { assertTrue(s.isRequireConfirmationToSetHomeInTheEnd()); } @Test - public void testIsTeamJoinDeathReset() { + void testIsTeamJoinDeathReset() { assertTrue(s.isTeamJoinDeathReset()); } @Test - public void testIsUseOwnGenerator() { + void testIsUseOwnGenerator() { assertFalse(s.isUseOwnGenerator()); } @Test - public void testIsWaterUnsafe() { + void testIsWaterUnsafe() { assertTrue(s.isWaterUnsafe()); } @Test - public void testSetAcidDamage() { + void testSetAcidDamage() { s.setAcidDamage(99); assertEquals(99, s.getAcidDamage()); } @Test - public void testSetAcidDamageAnimal() { + void testSetAcidDamageAnimal() { s.setAcidDamageAnimal(99); assertEquals(99, s.getAcidDamageAnimal()); } @Test - public void testSetAcidDamageChickens() { + void testSetAcidDamageChickens() { s.setAcidDamageChickens(true); assertTrue(s.isAcidDamageChickens()); } @Test - public void testSetAcidDamageDelay() { + void testSetAcidDamageDelay() { s.setAcidDamageDelay(99); assertEquals(99, s.getAcidDamageDelay()); } @Test - public void testSetAcidDamageMonster() { + void testSetAcidDamageMonster() { s.setAcidDamageMonster(99); assertEquals(99, s.getAcidDamageMonster()); } @Test - public void testSetAcidDamageOp() { + void testSetAcidDamageOp() { s.setAcidDamageOp(true); assertTrue(s.isAcidDamageOp()); } @Test - public void testSetAcidDestroyItemTime() { + void testSetAcidDestroyItemTime() { s.setAcidDestroyItemTime(99); assertEquals(99, s.getAcidDestroyItemTime()); } @Test - @Disabled - public void testSetAcidEffects() { + @Disabled("PotionEffectType cannot be mocked without full server initialisation") + void testSetAcidEffects() { List list = Collections.singletonList(PotionEffectType.ABSORPTION); s.setAcidEffects(list); assertEquals(list, s.getAcidEffects()); } @Test - public void testSetAcidRainDamage() { + void testSetAcidRainDamage() { s.setAcidRainDamage(99); assertEquals(99, s.getAcidRainDamage()); } @Test - public void testSetAdminCommand() { + void testSetAdminCommand() { s.setAdminCommand("admin"); assertEquals("admin", s.getAdminCommandAliases()); } @Test - public void testSetAllowSetHomeInNether() { + void testSetAllowSetHomeInNether() { s.setAllowSetHomeInNether(false); assertFalse(s.isAllowSetHomeInNether()); s.setAllowSetHomeInNether(true); @@ -454,7 +454,7 @@ public void testSetAllowSetHomeInNether() { } @Test - public void testSetAllowSetHomeInTheEnd() { + void testSetAllowSetHomeInTheEnd() { s.setAllowSetHomeInTheEnd(false); assertFalse(s.isAllowSetHomeInTheEnd()); s.setAllowSetHomeInTheEnd(true); @@ -462,19 +462,19 @@ public void testSetAllowSetHomeInTheEnd() { } @Test - public void testSetBanLimit() { + void testSetBanLimit() { s.setBanLimit(99); assertEquals(99, s.getBanLimit()); } @Test - public void testSetCustomRanks() { + void testSetCustomRanks() { s.setCustomRanks(Collections.singletonMap("string", 10)); assertEquals(10, (int) s.getCustomRanks().get("string")); } @Test - public void testSetDeathsCounted() { + void testSetDeathsCounted() { s.setDeathsCounted(false); assertFalse(s.isDeathsCounted()); s.setDeathsCounted(true); @@ -482,43 +482,43 @@ public void testSetDeathsCounted() { } @Test - public void testSetDeathsMax() { + void testSetDeathsMax() { s.setDeathsMax(99); assertEquals(99, s.getDeathsMax()); } @Test - public void testSetDefaultBiome() { + void testSetDefaultBiome() { s.setDefaultBiome(Biome.BADLANDS); assertEquals(Biome.BADLANDS, s.getDefaultBiome()); } @Test - public void testSetDefaultGameMode() { + void testSetDefaultGameMode() { s.setDefaultGameMode(GameMode.SPECTATOR); assertEquals(GameMode.SPECTATOR, s.getDefaultGameMode()); } @Test - public void testSetDefaultIslandFlags() { + void testSetDefaultIslandFlags() { s.setDefaultIslandFlagNames(Collections.singletonMap("ANIMAL_NATURAL_SPAWN", 10)); assertEquals(10, (int) s.getDefaultIslandFlagNames().get("ANIMAL_NATURAL_SPAWN")); } @Test - public void testSetDefaultIslandSettings() { + void testSetDefaultIslandSettings() { s.setDefaultIslandSettingNames(Collections.singletonMap("ANIMAL_NATURAL_SPAWN", 10)); assertEquals(10, (int) s.getDefaultIslandSettingNames().get("ANIMAL_NATURAL_SPAWN")); } @Test - public void testSetDifficulty() { + void testSetDifficulty() { s.setDifficulty(Difficulty.PEACEFUL); assertEquals(Difficulty.PEACEFUL, s.getDifficulty()); } @Test - public void testSetDragonSpawn() { + void testSetDragonSpawn() { s.setDragonSpawn(false); assertFalse(s.isDragonSpawn()); s.setDragonSpawn(true); @@ -526,7 +526,7 @@ public void testSetDragonSpawn() { } @Test - public void testSetEndGenerate() { + void testSetEndGenerate() { s.setEndGenerate(false); assertFalse(s.isEndGenerate()); s.setEndGenerate(true); @@ -534,7 +534,7 @@ public void testSetEndGenerate() { } @Test - public void testSetEndIslands() { + void testSetEndIslands() { s.setEndIslands(false); assertFalse(s.isEndIslands()); s.setEndIslands(true); @@ -542,13 +542,13 @@ public void testSetEndIslands() { } @Test - public void testSetFriendlyName() { + void testSetFriendlyName() { s.setFriendlyName("hshshs"); assertEquals("hshshs", s.getFriendlyName()); } @Test - public void testSetFullArmorProtection() { + void testSetFullArmorProtection() { s.setFullArmorProtection(false); assertFalse(s.isFullArmorProtection()); s.setFullArmorProtection(true); @@ -556,13 +556,13 @@ public void testSetFullArmorProtection() { } @Test - public void testSetGeoLimitSettings() { + void testSetGeoLimitSettings() { s.setGeoLimitSettings(Collections.singletonList("ghghhg")); assertEquals("ghghhg", s.getGeoLimitSettings().get(0)); } @Test - public void testSetHelmetProtection() { + void testSetHelmetProtection() { s.setHelmetProtection(false); assertFalse(s.isHelmetProtection()); s.setHelmetProtection(true); @@ -570,61 +570,61 @@ public void testSetHelmetProtection() { } @Test - public void testSetIslandCommand() { + void testSetIslandCommand() { s.setIslandCommand("command"); assertEquals("command", s.getPlayerCommandAliases()); } @Test - public void testSetIslandDistance() { + void testSetIslandDistance() { s.setIslandDistance(99); assertEquals(99, s.getIslandDistance()); } @Test - public void testSetIslandHeight() { + void testSetIslandHeight() { s.setIslandHeight(99); assertEquals(99, s.getIslandHeight()); } @Test - public void testSetIslandProtectionRange() { + void testSetIslandProtectionRange() { s.setIslandProtectionRange(99); assertEquals(99, s.getIslandProtectionRange()); } @Test - public void testSetIslandStartX() { + void testSetIslandStartX() { s.setIslandStartX(99); assertEquals(99, s.getIslandStartX()); } @Test - public void testSetIslandStartZ() { + void testSetIslandStartZ() { s.setIslandStartZ(99); assertEquals(99, s.getIslandStartZ()); } @Test - public void testSetIslandXOffset() { + void testSetIslandXOffset() { s.setIslandXOffset(99); assertEquals(99, s.getIslandXOffset()); } @Test - public void testSetIslandZOffset() { + void testSetIslandZOffset() { s.setIslandZOffset(99); assertEquals(99, s.getIslandZOffset()); } @Test - public void testSetIvSettings() { + void testSetIvSettings() { s.setIvSettings(Collections.singletonList("ffff")); assertEquals("ffff", s.getIvSettings().get(0)); } @Test - public void testSetKickedKeepInventory() { + void testSetKickedKeepInventory() { s.setKickedKeepInventory(false); assertFalse(s.isKickedKeepInventory()); s.setKickedKeepInventory(true); @@ -632,7 +632,7 @@ public void testSetKickedKeepInventory() { } @Test - public void testSetLeaversLoseReset() { + void testSetLeaversLoseReset() { s.setLeaversLoseReset(false); assertFalse(s.isLeaversLoseReset()); s.setLeaversLoseReset(true); @@ -640,25 +640,25 @@ public void testSetLeaversLoseReset() { } @Test - public void testSetMaxHomes() { + void testSetMaxHomes() { s.setMaxHomes(99); assertEquals(99, s.getMaxHomes()); } @Test - public void testSetMaxIslands() { + void testSetMaxIslands() { s.setMaxIslands(99); assertEquals(99, s.getMaxIslands()); } @Test - public void testSetMaxTeamSize() { + void testSetMaxTeamSize() { s.setMaxTeamSize(99); assertEquals(99, s.getMaxTeamSize()); } @Test - public void testSetNetherGenerate() { + void testSetNetherGenerate() { s.setNetherGenerate(false); assertFalse(s.isNetherGenerate()); s.setNetherGenerate(true); @@ -666,7 +666,7 @@ public void testSetNetherGenerate() { } @Test - public void testSetNetherIslands() { + void testSetNetherIslands() { s.setNetherIslands(false); assertFalse(s.isNetherIslands()); s.setNetherIslands(true); @@ -674,7 +674,7 @@ public void testSetNetherIslands() { } @Test - public void testSetNetherRoof() { + void testSetNetherRoof() { s.setNetherRoof(false); assertFalse(s.isNetherRoof()); s.setNetherRoof(true); @@ -682,13 +682,13 @@ public void testSetNetherRoof() { } @Test - public void testSetNetherSpawnRadius() { + void testSetNetherSpawnRadius() { s.setNetherSpawnRadius(99); assertEquals(99, s.getNetherSpawnRadius()); } @Test - public void testSetOnJoinResetEnderChest() { + void testSetOnJoinResetEnderChest() { s.setOnJoinResetEnderChest(false); assertFalse(s.isOnJoinResetEnderChest()); s.setOnJoinResetEnderChest(true); @@ -696,7 +696,7 @@ public void testSetOnJoinResetEnderChest() { } @Test - public void testSetOnJoinResetInventory() { + void testSetOnJoinResetInventory() { s.setOnJoinResetInventory(false); assertFalse(s.isOnJoinResetInventory()); s.setOnJoinResetInventory(true); @@ -704,7 +704,7 @@ public void testSetOnJoinResetInventory() { } @Test - public void testSetOnJoinResetMoney() { + void testSetOnJoinResetMoney() { s.setOnJoinResetMoney(false); assertFalse(s.isOnJoinResetMoney()); s.setOnJoinResetMoney(true); @@ -712,7 +712,7 @@ public void testSetOnJoinResetMoney() { } @Test - public void testSetOnLeaveResetEnderChest() { + void testSetOnLeaveResetEnderChest() { s.setOnLeaveResetEnderChest(false); assertFalse(s.isOnLeaveResetEnderChest()); s.setOnLeaveResetEnderChest(true); @@ -720,7 +720,7 @@ public void testSetOnLeaveResetEnderChest() { } @Test - public void testSetOnLeaveResetInventory() { + void testSetOnLeaveResetInventory() { s.setOnLeaveResetInventory(false); assertFalse(s.isOnLeaveResetInventory()); s.setOnLeaveResetInventory(true); @@ -728,7 +728,7 @@ public void testSetOnLeaveResetInventory() { } @Test - public void testSetOnLeaveResetMoney() { + void testSetOnLeaveResetMoney() { s.setOnLeaveResetMoney(false); assertFalse(s.isOnLeaveResetMoney()); s.setOnLeaveResetMoney(true); @@ -736,13 +736,13 @@ public void testSetOnLeaveResetMoney() { } @Test - public void testSetRemoveMobsWhitelist() { + void testSetRemoveMobsWhitelist() { s.setRemoveMobsWhitelist(Collections.singleton(EntityType.GHAST)); assertTrue(s.getRemoveMobsWhitelist().contains(EntityType.GHAST)); } @Test - public void testSetRequireConfirmationToSetHomeInNether() { + void testSetRequireConfirmationToSetHomeInNether() { s.setRequireConfirmationToSetHomeInNether(false); assertFalse(s.isRequireConfirmationToSetHomeInNether()); s.setRequireConfirmationToSetHomeInNether(true); @@ -750,7 +750,7 @@ public void testSetRequireConfirmationToSetHomeInNether() { } @Test - public void testSetRequireConfirmationToSetHomeInTheEnd() { + void testSetRequireConfirmationToSetHomeInTheEnd() { s.setRequireConfirmationToSetHomeInTheEnd(false); assertFalse(s.isRequireConfirmationToSetHomeInTheEnd()); s.setRequireConfirmationToSetHomeInTheEnd(true); @@ -758,37 +758,37 @@ public void testSetRequireConfirmationToSetHomeInTheEnd() { } @Test - public void testSetResetEpoch() { + void testSetResetEpoch() { s.setResetEpoch(3456L); assertEquals(3456L, s.getResetEpoch()); } @Test - public void testSetResetLimit() { + void testSetResetLimit() { s.setResetLimit(99); assertEquals(99, s.getResetLimit()); } @Test - public void testSetSeaHeight() { + void testSetSeaHeight() { s.setSeaHeight(99); assertEquals(99, s.getSeaHeight()); } @Test - public void testSetNetherSeaHeight() { + void testSetNetherSeaHeight() { s.setNetherSeaHeight(99); assertEquals(99, s.getNetherSeaHeight()); } @Test - public void testSetEndSeaHeight() { + void testSetEndSeaHeight() { s.setEndSeaHeight(99); assertEquals(99, s.getEndSeaHeight()); } @Test - public void testSetTeamJoinDeathReset() { + void testSetTeamJoinDeathReset() { s.setTeamJoinDeathReset(false); assertFalse(s.isTeamJoinDeathReset()); s.setTeamJoinDeathReset(true); @@ -796,7 +796,7 @@ public void testSetTeamJoinDeathReset() { } @Test - public void testSetUseOwnGenerator() { + void testSetUseOwnGenerator() { s.setUseOwnGenerator(false); assertFalse(s.isUseOwnGenerator()); s.setUseOwnGenerator(true); @@ -804,42 +804,42 @@ public void testSetUseOwnGenerator() { } @Test - public void testSetHiddenFlags() { + void testSetHiddenFlags() { s.setHiddenFlags(Collections.singletonList("flag")); assertEquals("flag", s.getHiddenFlags().get(0)); } @Test - public void testSetVisitorBannedCommands() { + void testSetVisitorBannedCommands() { s.setVisitorBannedCommands(Collections.singletonList("flag")); assertEquals("flag", s.getVisitorBannedCommands().get(0)); } @Test - public void testSetFallingBannedCommands() { + void testSetFallingBannedCommands() { s.setFallingBannedCommands(Collections.singletonList("flag")); assertEquals("flag", s.getFallingBannedCommands().get(0)); } @Test - public void testSetWorldFlags() { + void testSetWorldFlags() { s.setWorldFlags(Collections.singletonMap("flag", true)); assertTrue(s.getWorldFlags().get("flag")); } @Test - public void testSetWorldName() { + void testSetWorldName() { s.setWorldName("ugga"); assertEquals("ugga", s.getWorldName()); } @Test - public void testIsAcidDamageSnow() { + void testIsAcidDamageSnow() { assertFalse(s.isAcidDamageSnow()); } @Test - public void testSetAcidDamageSnow() { + void testSetAcidDamageSnow() { s.setAcidDamageSnow(false); assertFalse(s.isAcidDamageSnow()); s.setAcidDamageSnow(true); @@ -847,12 +847,12 @@ public void testSetAcidDamageSnow() { } @Test - public void testIsDeathsResetOnNewIsland() { + void testIsDeathsResetOnNewIsland() { assertTrue(s.isDeathsResetOnNewIsland()); } @Test - public void testSetDeathsResetOnNewIsland() { + void testSetDeathsResetOnNewIsland() { s.setDeathsResetOnNewIsland(false); assertFalse(s.isDeathsResetOnNewIsland()); s.setDeathsResetOnNewIsland(true); @@ -860,34 +860,34 @@ public void testSetDeathsResetOnNewIsland() { } @Test - public void testGetOnJoinCommands() { + void testGetOnJoinCommands() { assertTrue(s.getOnJoinCommands().isEmpty()); } @Test - public void testSetOnJoinCommands() { + void testSetOnJoinCommands() { s.setOnJoinCommands(Collections.singletonList("command")); assertEquals("command", s.getOnJoinCommands().get(0)); } @Test - public void testGetOnLeaveCommands() { + void testGetOnLeaveCommands() { assertTrue(s.getOnLeaveCommands().isEmpty()); } @Test - public void testSetOnLeaveCommands() { + void testSetOnLeaveCommands() { s.setOnLeaveCommands(Collections.singletonList("command")); assertEquals("command", s.getOnLeaveCommands().get(0)); } @Test - public void testIsOnJoinResetHealth() { + void testIsOnJoinResetHealth() { assertTrue(s.isOnJoinResetHealth()); } @Test - public void testSetOnJoinResetHealth() { + void testSetOnJoinResetHealth() { s.setOnJoinResetHealth(false); assertFalse(s.isOnJoinResetHealth()); s.setOnJoinResetHealth(true); @@ -895,12 +895,12 @@ public void testSetOnJoinResetHealth() { } @Test - public void testIsOnJoinResetHunger() { + void testIsOnJoinResetHunger() { assertTrue(s.isOnJoinResetHunger()); } @Test - public void testSetOnJoinResetHunger() { + void testSetOnJoinResetHunger() { s.setOnJoinResetHunger(false); assertFalse(s.isOnJoinResetHunger()); s.setOnJoinResetHunger(true); @@ -908,12 +908,12 @@ public void testSetOnJoinResetHunger() { } @Test - public void testIsOnJoinResetXP() { + void testIsOnJoinResetXP() { assertFalse(s.isOnJoinResetXP()); } @Test - public void testSetOnJoinResetXP() { + void testSetOnJoinResetXP() { s.setOnJoinResetXP(false); assertFalse(s.isOnJoinResetXP()); s.setOnJoinResetXP(true); @@ -921,12 +921,12 @@ public void testSetOnJoinResetXP() { } @Test - public void testIsOnLeaveResetHealth() { + void testIsOnLeaveResetHealth() { assertFalse(s.isOnLeaveResetHealth()); } @Test - public void testSetOnLeaveResetHealth() { + void testSetOnLeaveResetHealth() { s.setOnLeaveResetHealth(false); assertFalse(s.isOnLeaveResetHealth()); s.setOnLeaveResetHealth(true); @@ -934,12 +934,12 @@ public void testSetOnLeaveResetHealth() { } @Test - public void testIsOnLeaveResetHunger() { + void testIsOnLeaveResetHunger() { assertFalse(s.isOnLeaveResetHunger()); } @Test - public void testSetOnLeaveResetHunger() { + void testSetOnLeaveResetHunger() { s.setOnLeaveResetHunger(false); assertFalse(s.isOnLeaveResetHunger()); s.setOnLeaveResetHunger(true); @@ -947,12 +947,12 @@ public void testSetOnLeaveResetHunger() { } @Test - public void testIsOnLeaveResetXP() { + void testIsOnLeaveResetXP() { assertFalse(s.isOnLeaveResetXP()); } @Test - public void testSetOnLeaveResetXP() { + void testSetOnLeaveResetXP() { s.setOnLeaveResetXP(false); assertFalse(s.isOnLeaveResetXP()); s.setOnLeaveResetXP(true); @@ -960,7 +960,7 @@ public void testSetOnLeaveResetXP() { } @Test - public void testSetCreateIslandOnFirstLoginEnabled() { + void testSetCreateIslandOnFirstLoginEnabled() { s.setCreateIslandOnFirstLoginEnabled(false); assertFalse(s.isCreateIslandOnFirstLoginEnabled()); s.setCreateIslandOnFirstLoginEnabled(true); @@ -968,13 +968,13 @@ public void testSetCreateIslandOnFirstLoginEnabled() { } @Test - public void testSetCreateIslandOnFirstLoginDelay() { + void testSetCreateIslandOnFirstLoginDelay() { s.setCreateIslandOnFirstLoginDelay(40); assertEquals(40, s.getCreateIslandOnFirstLoginDelay()); } @Test - public void testSetCreateIslandOnFirstLoginAbortOnLogout() { + void testSetCreateIslandOnFirstLoginAbortOnLogout() { s.setCreateIslandOnFirstLoginAbortOnLogout(false); assertFalse(s.isCreateIslandOnFirstLoginAbortOnLogout()); s.setCreateIslandOnFirstLoginAbortOnLogout(true); @@ -982,12 +982,12 @@ public void testSetCreateIslandOnFirstLoginAbortOnLogout() { } @Test - public void testIsPasteMissingIslands() { + void testIsPasteMissingIslands() { assertFalse(s.isPasteMissingIslands()); } @Test - public void testSetPasteMissingIslands() { + void testSetPasteMissingIslands() { s.setPasteMissingIslands(false); assertFalse(s.isPasteMissingIslands()); s.setPasteMissingIslands(true); @@ -995,189 +995,189 @@ public void testSetPasteMissingIslands() { } @Test - public void testGetAcidRainEffects() { + void testGetAcidRainEffects() { assertTrue(s.getAcidRainEffects().isEmpty()); } @Test @Disabled("Bukkit made this so we can't test") - public void testSetAcidRainEffects() { + void testSetAcidRainEffects() { s.setAcidRainEffects(Collections.singletonList(PotionEffectType.BAD_OMEN)); assertEquals(PotionEffectType.BAD_OMEN, s.getAcidRainEffects().get(0)); } @Test - public void testGetRainEffectDuation() { + void testGetRainEffectDuation() { assertEquals(10, s.getRainEffectDuation()); } @Test - public void testSetRainEffectDuation() { + void testSetRainEffectDuation() { s.setRainEffectDuation(99); assertEquals(99, s.getRainEffectDuation()); } @Test - public void testGetAcidEffectDuation() { + void testGetAcidEffectDuation() { assertEquals(30, s.getAcidEffectDuation()); } @Test - public void testSetAcidEffectDuation() { + void testSetAcidEffectDuation() { s.setAcidEffectDuation(99); assertEquals(99, s.getAcidEffectDuation()); } @Test - public void testGetSpawnLimitMonsters() { + void testGetSpawnLimitMonsters() { assertEquals(-1, s.getSpawnLimitMonsters()); } @Test - public void testSetSpawnLimitMonsters() { + void testSetSpawnLimitMonsters() { s.setSpawnLimitMonsters(99); assertEquals(99, s.getSpawnLimitMonsters()); } @Test - public void testGetSpawnLimitAnimals() { + void testGetSpawnLimitAnimals() { assertEquals(-1, s.getSpawnLimitAnimals()); } @Test - public void testSetSpawnLimitAnimals() { + void testSetSpawnLimitAnimals() { s.setSpawnLimitAnimals(99); assertEquals(99, s.getSpawnLimitAnimals()); } @Test - public void testGetSpawnLimitWaterAnimals() { + void testGetSpawnLimitWaterAnimals() { assertEquals(-1, s.getSpawnLimitWaterAnimals()); } @Test - public void testSetSpawnLimitWaterAnimals() { + void testSetSpawnLimitWaterAnimals() { s.setSpawnLimitWaterAnimals(99); assertEquals(99, s.getSpawnLimitWaterAnimals()); } @Test - public void testGetSpawnLimitAmbient() { + void testGetSpawnLimitAmbient() { assertEquals(-1, s.getSpawnLimitAmbient()); } @Test - public void testSetSpawnLimitAmbient() { + void testSetSpawnLimitAmbient() { s.setSpawnLimitAmbient(99); assertEquals(99, s.getSpawnLimitAmbient()); } @Test - public void testGetTicksPerAnimalSpawns() { + void testGetTicksPerAnimalSpawns() { assertEquals(-1, s.getTicksPerAnimalSpawns()); } @Test - public void testSetTicksPerAnimalSpawns() { + void testSetTicksPerAnimalSpawns() { s.setTicksPerAnimalSpawns(99); assertEquals(99, s.getTicksPerAnimalSpawns()); } @Test - public void testGetTicksPerMonsterSpawns() { + void testGetTicksPerMonsterSpawns() { assertEquals(-1, s.getTicksPerMonsterSpawns()); } @Test - public void testSetTicksPerMonsterSpawns() { + void testSetTicksPerMonsterSpawns() { s.setTicksPerMonsterSpawns(99); assertEquals(99, s.getTicksPerMonsterSpawns()); } @Test - public void testGetMaxCoopSize() { + void testGetMaxCoopSize() { assertEquals(4, s.getMaxCoopSize()); } @Test - public void testSetMaxCoopSize() { + void testSetMaxCoopSize() { s.setMaxCoopSize(99); assertEquals(99, s.getMaxCoopSize()); } @Test - public void testGetMaxTrustSize() { + void testGetMaxTrustSize() { assertEquals(4, s.getMaxTrustSize()); } @Test - public void testSetMaxTrustSize() { + void testSetMaxTrustSize() { s.setMaxTrustSize(99); assertEquals(99, s.getMaxTrustSize()); } @Test - public void testGetPlayerCommandAliases() { + void testGetPlayerCommandAliases() { assertEquals("ai", s.getPlayerCommandAliases()); } @Test - public void testSetPlayerCommandAliases() { + void testSetPlayerCommandAliases() { s.setPlayerCommandAliases("adm"); assertEquals("adm", s.getPlayerCommandAliases()); } @Test - public void testGetAdminCommandAliases() { + void testGetAdminCommandAliases() { assertEquals("acid", s.getAdminCommandAliases()); } @Test - public void testSetAdminCommandAliases() { + void testSetAdminCommandAliases() { s.setAdminCommandAliases("adm"); assertEquals("adm", s.getAdminCommandAliases()); } @Test - public void testGetDefaultNewPlayerAction() { + void testGetDefaultNewPlayerAction() { assertEquals("create", s.getDefaultNewPlayerAction()); } @Test - public void testSetDefaultNewPlayerAction() { + void testSetDefaultNewPlayerAction() { s.setDefaultNewPlayerAction("cr"); assertEquals("cr", s.getDefaultNewPlayerAction()); } @Test - public void testGetDefaultPlayerAction() { + void testGetDefaultPlayerAction() { assertEquals("go", s.getDefaultPlayerAction()); } @Test - public void testSetDefaultPlayerAction() { + void testSetDefaultPlayerAction() { s.setDefaultPlayerAction("go2"); assertEquals("go2", s.getDefaultPlayerAction()); } @Test - public void testGetDefaultNetherBiome() { + void testGetDefaultNetherBiome() { assertEquals(Biome.NETHER_WASTES, s.getDefaultNetherBiome()); } @Test - public void testSetDefaultNetherBiome() { + void testSetDefaultNetherBiome() { s.setDefaultNetherBiome(Biome.END_BARRENS); assertEquals(Biome.END_BARRENS, s.getDefaultNetherBiome()); } @Test - public void testGetDefaultEndBiome() { + void testGetDefaultEndBiome() { assertEquals(Biome.THE_END, s.getDefaultEndBiome()); } @Test - public void testSetDefaultEndBiome() { + void testSetDefaultEndBiome() { s.setDefaultEndBiome(Biome.END_BARRENS); assertEquals(Biome.END_BARRENS, s.getDefaultEndBiome()); } diff --git a/src/test/java/world/bentobox/acidisland/AcidIslandTest.java b/src/test/java/world/bentobox/acidisland/AcidIslandTest.java index ce003b8..7d58d9a 100644 --- a/src/test/java/world/bentobox/acidisland/AcidIslandTest.java +++ b/src/test/java/world/bentobox/acidisland/AcidIslandTest.java @@ -1,5 +1,6 @@ package world.bentobox.acidisland; +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertNotNull; @@ -61,6 +62,7 @@ import world.bentobox.bentobox.managers.FlagsManager; import world.bentobox.bentobox.managers.IslandWorldManager; import world.bentobox.bentobox.managers.IslandsManager; +import world.bentobox.bentobox.managers.LocalesManager; import world.bentobox.bentobox.managers.RanksManager; /** @@ -90,6 +92,8 @@ public class AcidIslandTest { private Settings settings; @Mock private RanksManager rm; + @Mock + private LocalesManager localesManager; private static AbstractDatabaseHandler h; private static MockedStatic mockedDbSetup; @@ -99,7 +103,7 @@ public class AcidIslandTest { @SuppressWarnings("unchecked") @BeforeAll - public static void beforeAll() throws Exception { + static void beforeAll() throws Exception { // This has to be done beforeAll otherwise the tests will interfere with each other h = mock(AbstractDatabaseHandler.class); // Database @@ -111,12 +115,12 @@ public static void beforeAll() throws Exception { } @AfterAll - public static void afterAll() { + static void afterAll() { mockedDbSetup.close(); } @AfterEach - public void tearDown() throws IOException { + void tearDown() throws IOException { User.clearUsers(); if (mockedBukkit != null) { mockedBukkit.closeOnDemand(); @@ -136,7 +140,7 @@ private void deleteAll(File file) throws IOException { } @BeforeEach - public void setUp() throws Exception { + void setUp() throws Exception { // Set up plugin via reflection Field instanceField = BentoBox.class.getDeclaredField("instance"); instanceField.setAccessible(true); @@ -215,13 +219,16 @@ public void setUp() throws Exception { // Settings when(plugin.getSettings()).thenReturn(settings); + // Locales + when(plugin.getLocalesManager()).thenReturn(localesManager); + when(localesManager.getOrDefault(any(), any())).thenAnswer(inv -> inv.getArgument(1)); } /** * Test method for {@link world.bentobox.acidisland.AcidIsland#onLoad()}. */ @Test - public void testOnLoad() { + void testOnLoad() { addon.onLoad(); // Check that config.yml file has been saved File check = new File("addons/AcidIsland", "config.yml"); @@ -232,7 +239,7 @@ public void testOnLoad() { * Test method for {@link world.bentobox.acidisland.AcidIsland#onEnable()}. */ @Test - public void testOnEnable() { + void testOnEnable() { testOnLoad(); addon.onEnable(); assertTrue(addon.getPlayerCommand().isPresent()); @@ -243,7 +250,7 @@ public void testOnEnable() { * Test method for {@link world.bentobox.acidisland.AcidIsland#onReload()}. */ @Test - public void testOnReload() { + void testOnReload() { addon.onReload(); // Check that config.yml file has been saved File check = new File("addons/AcidIsland", "config.yml"); @@ -254,7 +261,7 @@ public void testOnReload() { * Test method for {@link world.bentobox.acidisland.AcidIsland#createWorlds()}. */ @Test - public void testCreateWorlds() { + void testCreateWorlds() { addon.onLoad(); addon.createWorlds(); Mockito.verify(plugin).log("[AcidIsland] Creating AcidIsland..."); @@ -266,7 +273,7 @@ public void testCreateWorlds() { * Test method for {@link world.bentobox.acidisland.AcidIsland#getSettings()}. */ @Test - public void testGetSettings() { + void testGetSettings() { addon.onLoad(); assertNotNull(addon.getSettings()); } @@ -275,7 +282,7 @@ public void testGetSettings() { * Test method for {@link world.bentobox.acidisland.AcidIsland#getWorldSettings()}. */ @Test - public void testGetWorldSettings() { + void testGetWorldSettings() { addon.onLoad(); assertEquals(addon.getSettings(), addon.getWorldSettings()); } @@ -284,7 +291,7 @@ public void testGetWorldSettings() { * Test method for {@link world.bentobox.acidisland.AcidIsland#getDefaultWorldGenerator(java.lang.String, java.lang.String)}. */ @Test - public void testGetDefaultWorldGeneratorStringString() { + void testGetDefaultWorldGeneratorStringString() { assertNull(addon.getDefaultWorldGenerator("", "")); addon.onLoad(); addon.createWorlds(); @@ -296,7 +303,7 @@ public void testGetDefaultWorldGeneratorStringString() { * Test method for {@link world.bentobox.acidisland.AcidIsland#allLoaded()}. */ @Test - public void testAllLoaded() { + void testAllLoaded() { addon.allLoaded(); } @@ -304,35 +311,32 @@ public void testAllLoaded() { * Test method for {@link world.bentobox.acidisland.AcidIsland#saveWorldSettings()}. */ @Test - public void testSaveWorldSettings() { - addon.saveWorldSettings(); + void testSaveWorldSettings() { + assertDoesNotThrow(() -> addon.saveWorldSettings()); } /** * Test onDisable cancels acidTask. */ @Test - public void testOnDisable() { + void testOnDisable() { testOnEnable(); - addon.onDisable(); - // Should not throw - acidTask.cancelTasks() is called + assertDoesNotThrow(() -> addon.onDisable()); } /** * Test onDisable when acidTask is null (no prior onEnable). */ @Test - public void testOnDisableNoTask() { - // No onEnable called, acidTask is null - addon.onDisable(); - // Should not throw + void testOnDisableNoTask() { + assertDoesNotThrow(() -> addon.onDisable()); } /** * Test onEnable does nothing when settings is null. */ @Test - public void testOnEnableNullSettings() { + void testOnEnableNullSettings() { // Don't call onLoad, so settings is null addon.onEnable(); // Should return early without registering listeners diff --git a/src/test/java/world/bentobox/acidisland/commands/IslandAboutCommandTest.java b/src/test/java/world/bentobox/acidisland/commands/IslandAboutCommandTest.java index 402a621..e75aee6 100644 --- a/src/test/java/world/bentobox/acidisland/commands/IslandAboutCommandTest.java +++ b/src/test/java/world/bentobox/acidisland/commands/IslandAboutCommandTest.java @@ -48,7 +48,7 @@ public class IslandAboutCommandTest { private IslandAboutCommand command; @BeforeEach - public void setUp() { + void setUp() { server = MockBukkit.mock(); mockedBukkit = Mockito.mockStatic(Bukkit.class, Mockito.RETURNS_DEEP_STUBS); mockedBukkit.when(Bukkit::getMinecraftVersion).thenReturn("1.21.11"); @@ -71,14 +71,14 @@ public void setUp() { } @AfterEach - public void tearDown() { + void tearDown() { mockedBukkit.closeOnDemand(); Mockito.framework().clearInlineMocks(); MockBukkit.unmock(); } @Test - public void testExecute() { + void testExecute() { boolean result = command.execute(user, "about", Collections.emptyList()); assertTrue(result); verify(user, atLeastOnce()).sendRawMessage(anyString()); diff --git a/src/test/java/world/bentobox/acidisland/events/AcidEventTest.java b/src/test/java/world/bentobox/acidisland/events/AcidEventTest.java index d41978f..f7b1daa 100644 --- a/src/test/java/world/bentobox/acidisland/events/AcidEventTest.java +++ b/src/test/java/world/bentobox/acidisland/events/AcidEventTest.java @@ -25,62 +25,62 @@ public class AcidEventTest { private AcidEvent e; @BeforeEach - public void setUp() { + void setUp() { List effects = List.of(); e = new AcidEvent(player, 10, 5, effects); } @Test - public void testAcidEvent() { + void testAcidEvent() { assertNotNull(e); } @Test - public void testGetPlayer() { + void testGetPlayer() { assertEquals(player, e.getPlayer()); } @Test - public void testSetPlayer() { + void testSetPlayer() { Player player2 = mock(Player.class); e.setPlayer(player2); assertEquals(player2, e.getPlayer()); } @Test - public void testGetTotalDamage() { + void testGetTotalDamage() { assertEquals(10D, e.getTotalDamage(), 0D); } @Test - public void testGetProtection() { + void testGetProtection() { assertEquals(5D, e.getProtection(), 0D); } @Test - public void testSetTotalDamage() { + void testSetTotalDamage() { e.setTotalDamage(50); assertEquals(50D, e.getTotalDamage(), 0D); } @Test - public void testGetPotionEffects() { + void testGetPotionEffects() { assertEquals(0, e.getPotionEffects().toArray().length); } @Test - public void testSetPotionEffects() { + void testSetPotionEffects() { e.setPotionEffects(new ArrayList<>()); assertTrue(e.getPotionEffects().isEmpty()); } @Test - public void testIsCancelled() { + void testIsCancelled() { assertFalse(e.isCancelled()); } @Test - public void testSetCancelled() { + void testSetCancelled() { e.setCancelled(true); assertTrue(e.isCancelled()); e.setCancelled(false); diff --git a/src/test/java/world/bentobox/acidisland/events/AcidRainEventTest.java b/src/test/java/world/bentobox/acidisland/events/AcidRainEventTest.java index 0aafc02..1559a71 100644 --- a/src/test/java/world/bentobox/acidisland/events/AcidRainEventTest.java +++ b/src/test/java/world/bentobox/acidisland/events/AcidRainEventTest.java @@ -25,62 +25,62 @@ public class AcidRainEventTest { private AcidRainEvent e; @BeforeEach - public void setUp() { + void setUp() { List effects = List.of(); e = new AcidRainEvent(player, 10, 5, effects); } @Test - public void testAcidEvent() { + void testAcidEvent() { assertNotNull(e); } @Test - public void testGetPlayer() { + void testGetPlayer() { assertEquals(player, e.getPlayer()); } @Test - public void testSetPlayer() { + void testSetPlayer() { Player player2 = mock(Player.class); e.setPlayer(player2); assertEquals(player2, e.getPlayer()); } @Test - public void testGetTotalDamage() { - assertTrue(e.getRainDamage() == 10D); + void testGetTotalDamage() { + assertEquals(10D, e.getRainDamage()); } @Test - public void testGetProtection() { - assertTrue(e.getProtection() == 5D); + void testGetProtection() { + assertEquals(5D, e.getProtection()); } @Test - public void testSetTotalDamage() { + void testSetTotalDamage() { e.setRainDamage(50); - assertTrue(e.getRainDamage() == 50D); + assertEquals(50D, e.getRainDamage()); } @Test - public void testGetPotionEffects() { + void testGetPotionEffects() { assertEquals(0, e.getPotionEffects().toArray().length); } @Test - public void testSetPotionEffects() { + void testSetPotionEffects() { e.setPotionEffects(new ArrayList<>()); assertTrue(e.getPotionEffects().isEmpty()); } @Test - public void testIsCancelled() { + void testIsCancelled() { assertFalse(e.isCancelled()); } @Test - public void testSetCancelled() { + void testSetCancelled() { e.setCancelled(true); assertTrue(e.isCancelled()); e.setCancelled(false); diff --git a/src/test/java/world/bentobox/acidisland/events/EntityDamageByAcidEventTest.java b/src/test/java/world/bentobox/acidisland/events/EntityDamageByAcidEventTest.java index eaa5db9..7f655b4 100644 --- a/src/test/java/world/bentobox/acidisland/events/EntityDamageByAcidEventTest.java +++ b/src/test/java/world/bentobox/acidisland/events/EntityDamageByAcidEventTest.java @@ -23,51 +23,51 @@ public class EntityDamageByAcidEventTest { private EntityDamageByAcidEvent rainEvent; @BeforeEach - public void setUp() { + void setUp() { waterEvent = new EntityDamageByAcidEvent(entity, 10.0, Acid.WATER); rainEvent = new EntityDamageByAcidEvent(entity, 5.0, Acid.RAIN); } @Test - public void testConstructor() { + void testConstructor() { assertNotNull(waterEvent); assertNotNull(rainEvent); } @Test - public void testGetEntity() { + void testGetEntity() { assertEquals(entity, waterEvent.getEntity()); } @Test - public void testGetDamage() { + void testGetDamage() { assertEquals(10.0, waterEvent.getDamage(), 0D); assertEquals(5.0, rainEvent.getDamage(), 0D); } @Test - public void testSetDamage() { + void testSetDamage() { waterEvent.setDamage(25.0); assertEquals(25.0, waterEvent.getDamage(), 0D); } @Test - public void testGetCauseWater() { + void testGetCauseWater() { assertEquals(Acid.WATER, waterEvent.getCause()); } @Test - public void testGetCauseRain() { + void testGetCauseRain() { assertEquals(Acid.RAIN, rainEvent.getCause()); } @Test - public void testIsCancelled() { + void testIsCancelled() { assertFalse(waterEvent.isCancelled()); } @Test - public void testSetCancelled() { + void testSetCancelled() { waterEvent.setCancelled(true); assertTrue(waterEvent.isCancelled()); waterEvent.setCancelled(false); @@ -75,12 +75,12 @@ public void testSetCancelled() { } @Test - public void testGetHandlers() { + void testGetHandlers() { assertNotNull(waterEvent.getHandlers()); } @Test - public void testGetHandlerList() { + void testGetHandlerList() { assertNotNull(EntityDamageByAcidEvent.getHandlerList()); } } diff --git a/src/test/java/world/bentobox/acidisland/events/ItemDestroyByAcidEventTest.java b/src/test/java/world/bentobox/acidisland/events/ItemDestroyByAcidEventTest.java index c128d30..bea4f09 100644 --- a/src/test/java/world/bentobox/acidisland/events/ItemDestroyByAcidEventTest.java +++ b/src/test/java/world/bentobox/acidisland/events/ItemDestroyByAcidEventTest.java @@ -18,27 +18,27 @@ public class ItemDestroyByAcidEventTest { private ItemDestroyByAcidEvent event; @BeforeEach - public void setUp() { + void setUp() { event = new ItemDestroyByAcidEvent(item); } @Test - public void testConstructor() { + void testConstructor() { assertNotNull(event); } @Test - public void testGetItem() { + void testGetItem() { assertEquals(item, event.getItem()); } @Test - public void testGetHandlers() { + void testGetHandlers() { assertNotNull(event.getHandlers()); } @Test - public void testGetHandlerList() { + void testGetHandlerList() { assertNotNull(ItemDestroyByAcidEvent.getHandlerList()); } } diff --git a/src/test/java/world/bentobox/acidisland/events/PlayerDrinkPurifiedWaterEventTest.java b/src/test/java/world/bentobox/acidisland/events/PlayerDrinkPurifiedWaterEventTest.java new file mode 100644 index 0000000..7f1bae0 --- /dev/null +++ b/src/test/java/world/bentobox/acidisland/events/PlayerDrinkPurifiedWaterEventTest.java @@ -0,0 +1,81 @@ +package world.bentobox.acidisland.events; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.Mockito.mock; + +import org.bukkit.entity.Player; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.junit.jupiter.MockitoExtension; + +import world.bentobox.bentobox.database.objects.Island; + +/** + * @author tastybento + */ +@ExtendWith(MockitoExtension.class) +class PlayerDrinkPurifiedWaterEventTest { + + @Test + void testGetPlayer() { + Player player = mock(Player.class); + PlayerDrinkPurifiedWaterEvent event = new PlayerDrinkPurifiedWaterEvent(null, player, 4.0); + assertEquals(player, event.getPlayer()); + } + + @Test + void testGetHealAmount() { + Player player = mock(Player.class); + PlayerDrinkPurifiedWaterEvent event = new PlayerDrinkPurifiedWaterEvent(null, player, 4.0); + assertEquals(4.0, event.getHealAmount()); + } + + @Test + void testSetHealAmount() { + Player player = mock(Player.class); + PlayerDrinkPurifiedWaterEvent event = new PlayerDrinkPurifiedWaterEvent(null, player, 4.0); + event.setHealAmount(8.0); + assertEquals(8.0, event.getHealAmount()); + } + + @Test + void testNotCancelledByDefault() { + Player player = mock(Player.class); + PlayerDrinkPurifiedWaterEvent event = new PlayerDrinkPurifiedWaterEvent(null, player, 4.0); + assertFalse(event.isCancelled()); + } + + @Test + void testCancelEvent() { + Player player = mock(Player.class); + PlayerDrinkPurifiedWaterEvent event = new PlayerDrinkPurifiedWaterEvent(null, player, 4.0); + event.setCancelled(true); + assertTrue(event.isCancelled()); + } + + @Test + void testUncancelEvent() { + Player player = mock(Player.class); + PlayerDrinkPurifiedWaterEvent event = new PlayerDrinkPurifiedWaterEvent(null, player, 4.0); + event.setCancelled(true); + event.setCancelled(false); + assertFalse(event.isCancelled()); + } + + @Test + void testWithIsland() { + Player player = mock(Player.class); + Island island = mock(Island.class); + PlayerDrinkPurifiedWaterEvent event = new PlayerDrinkPurifiedWaterEvent(island, player, 4.0); + assertEquals(player, event.getPlayer()); + assertEquals(4.0, event.getHealAmount()); + assertFalse(event.isCancelled()); + } + + @Test + void testHandlerList() { + assertFalse(PlayerDrinkPurifiedWaterEvent.getHandlerList() == null); + } +} diff --git a/src/test/java/world/bentobox/acidisland/listeners/AcidEffectTest.java b/src/test/java/world/bentobox/acidisland/listeners/AcidEffectTest.java index 4b42705..6d8a91f 100644 --- a/src/test/java/world/bentobox/acidisland/listeners/AcidEffectTest.java +++ b/src/test/java/world/bentobox/acidisland/listeners/AcidEffectTest.java @@ -5,7 +5,6 @@ import static org.junit.jupiter.api.Assertions.assertTrue; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyInt; -import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; import static org.mockito.Mockito.times; @@ -25,7 +24,6 @@ import org.bukkit.World.Environment; import org.bukkit.attribute.Attribute; import org.bukkit.attribute.AttributeInstance; -import org.bukkit.attribute.AttributeModifier; import org.bukkit.block.Block; import org.bukkit.entity.Boat; import org.bukkit.entity.Entity; @@ -128,7 +126,7 @@ public class AcidEffectTest { private MockedStatic mockedUtil; @BeforeEach - public void setUp() { + void setUp() { mockServer = MockBukkit.mock(); mockedBukkit = Mockito.mockStatic(Bukkit.class, Mockito.RETURNS_DEEP_STUBS); mockedBukkit.when(Bukkit::getMinecraftVersion).thenReturn("1.21.11"); @@ -139,7 +137,7 @@ public void setUp() { // Essentials mockedBukkit.when(Bukkit::getPluginManager).thenReturn(pim); - when(pim.getPlugin(eq("Essentials"))).thenReturn(essentials); + when(pim.getPlugin("Essentials")).thenReturn(essentials); when(essentials.getUser(any(Player.class))).thenReturn(essentialsUser); // Player @@ -203,7 +201,7 @@ public void setUp() { } @AfterEach - public void tearDown() { + void tearDown() { mockedUtil.closeOnDemand(); mockedBukkit.closeOnDemand(); MockBukkit.unmock(); @@ -213,7 +211,7 @@ public void tearDown() { * Test method for {@link world.bentobox.acidisland.listeners.AcidEffect#onPlayerDeath(org.bukkit.event.entity.PlayerDeathEvent)}. */ @Test - public void testOnPlayerDeath() { + void testOnPlayerDeath() { PlayerDeathEvent e = mock(PlayerDeathEvent.class); ae.onPlayerDeath(e); verify(e, times(2)).getEntity(); @@ -223,19 +221,19 @@ public void testOnPlayerDeath() { * Test method for {@link world.bentobox.acidisland.listeners.AcidEffect#onSeaBounce(org.bukkit.event.player.PlayerMoveEvent)}. */ @Test - public void testOnSeaBounce() { + void testOnSeaBounce() { PlayerMoveEvent e = new PlayerMoveEvent(player, from, to); ae.onSeaBounce(e); ArgumentCaptor argument = ArgumentCaptor.forClass(Vector.class); verify(player).setVelocity(argument.capture()); - assertTrue(argument.getValue().getBlockY() == 1D); + assertEquals(1D, argument.getValue().getBlockY()); } /** * Test method for {@link world.bentobox.acidisland.listeners.AcidEffect#onSeaBounce(org.bukkit.event.player.PlayerMoveEvent)}. */ @Test - public void testOnSeaBounceCreative() { + void testOnSeaBounceCreative() { when(player.getGameMode()).thenReturn(GameMode.CREATIVE); PlayerMoveEvent e = new PlayerMoveEvent(player, from, to); ae.onSeaBounce(e); @@ -246,7 +244,7 @@ public void testOnSeaBounceCreative() { * Test method for {@link world.bentobox.acidisland.listeners.AcidEffect#onSeaBounce(org.bukkit.event.player.PlayerMoveEvent)}. */ @Test - public void testOnSeaBounceSpectator() { + void testOnSeaBounceSpectator() { when(player.getGameMode()).thenReturn(GameMode.SPECTATOR); PlayerMoveEvent e = new PlayerMoveEvent(player, from, to); ae.onSeaBounce(e); @@ -257,7 +255,7 @@ public void testOnSeaBounceSpectator() { * Test method for {@link world.bentobox.acidisland.listeners.AcidEffect#onSeaBounce(org.bukkit.event.player.PlayerMoveEvent)}. */ @Test - public void testOnSeaBounceWrongWorld() { + void testOnSeaBounceWrongWorld() { when(addon.getOverWorld()).thenReturn(mock(World.class)); PlayerMoveEvent e = new PlayerMoveEvent(player, from, to); ae.onSeaBounce(e); @@ -268,7 +266,7 @@ public void testOnSeaBounceWrongWorld() { * Test method for {@link world.bentobox.acidisland.listeners.AcidEffect#onSeaBounce(org.bukkit.event.player.PlayerMoveEvent)}. */ @Test - public void testOnSeaBounceNotAtBottom() { + void testOnSeaBounceNotAtBottom() { when(location.getBlockY()).thenReturn(10); PlayerMoveEvent e = new PlayerMoveEvent(player, from, to); ae.onSeaBounce(e); @@ -279,7 +277,7 @@ public void testOnSeaBounceNotAtBottom() { * Test method for {@link world.bentobox.acidisland.listeners.AcidEffect#onPlayerMove(org.bukkit.event.player.PlayerMoveEvent)}. */ @Test - public void testOnPlayerMoveAcidAndRainDamage() { + void testOnPlayerMoveAcidAndRainDamage() { PlayerMoveEvent e = new PlayerMoveEvent(player, from, to); ae.onPlayerMove(e); verify(settings, times(2)).getAcidDamageDelay(); @@ -289,7 +287,7 @@ public void testOnPlayerMoveAcidAndRainDamage() { * Test method for {@link world.bentobox.acidisland.listeners.AcidEffect#onPlayerMove(org.bukkit.event.player.PlayerMoveEvent)}. */ @Test - public void testOnPlayerMoveVisitorNoAcidAndRainDamage() { + void testOnPlayerMoveVisitorNoAcidAndRainDamage() { when(im.userIsOnIsland(any(), any())).thenReturn(false); PlayerMoveEvent e = new PlayerMoveEvent(player, from, to); ae.onPlayerMove(e); @@ -300,7 +298,7 @@ public void testOnPlayerMoveVisitorNoAcidAndRainDamage() { * Test method for {@link world.bentobox.acidisland.listeners.AcidEffect#onPlayerMove(org.bukkit.event.player.PlayerMoveEvent)}. */ @Test - public void testOnPlayerMoveVisitorAcidAndRainDamage() { + void testOnPlayerMoveVisitorAcidAndRainDamage() { // No protection against CUSTOM damage when(iwm.getIvSettings(any())).thenReturn(Collections.emptyList()); PlayerMoveEvent e = new PlayerMoveEvent(player, from, to); @@ -312,7 +310,7 @@ public void testOnPlayerMoveVisitorAcidAndRainDamage() { * Test method for {@link world.bentobox.acidisland.listeners.AcidEffect#onPlayerMove(org.bukkit.event.player.PlayerMoveEvent)}. */ @Test - public void testOnPlayerMoveGodModeNoAcidAndRainDamage() { + void testOnPlayerMoveGodModeNoAcidAndRainDamage() { when(essentialsUser.isGodModeEnabled()).thenReturn(true); PlayerMoveEvent e = new PlayerMoveEvent(player, from, to); ae.onPlayerMove(e); @@ -323,7 +321,7 @@ public void testOnPlayerMoveGodModeNoAcidAndRainDamage() { * Test method for {@link world.bentobox.acidisland.listeners.AcidEffect#onPlayerMove(org.bukkit.event.player.PlayerMoveEvent)}. */ @Test - public void testOnPlayerMoveDryNoDamage() { + void testOnPlayerMoveDryNoDamage() { when(block.getHumidity()).thenReturn(0D); PlayerMoveEvent e = new PlayerMoveEvent(player, from, to); ae.onPlayerMove(e); @@ -334,7 +332,7 @@ public void testOnPlayerMoveDryNoDamage() { * Test method for {@link world.bentobox.acidisland.listeners.AcidEffect#onPlayerMove(org.bukkit.event.player.PlayerMoveEvent)}. */ @Test - public void testOnPlayerMoveSnowDamage() { + void testOnPlayerMoveSnowDamage() { when(block.getTemperature()).thenReturn(0D); // Cold when(settings.isAcidDamageSnow()).thenReturn(true); PlayerMoveEvent e = new PlayerMoveEvent(player, from, to); @@ -346,7 +344,7 @@ public void testOnPlayerMoveSnowDamage() { * Test method for {@link world.bentobox.acidisland.listeners.AcidEffect#onPlayerMove(org.bukkit.event.player.PlayerMoveEvent)}. */ @Test - public void testOnPlayerMoveNoSnowDamage() { + void testOnPlayerMoveNoSnowDamage() { when(block.getTemperature()).thenReturn(0D); // Cold when(settings.isAcidDamageSnow()).thenReturn(false); PlayerMoveEvent e = new PlayerMoveEvent(player, from, to); @@ -358,7 +356,7 @@ public void testOnPlayerMoveNoSnowDamage() { * Test method for {@link world.bentobox.acidisland.listeners.AcidEffect#onPlayerMove(org.bukkit.event.player.PlayerMoveEvent)}. */ @Test - public void testOnPlayerMoveHelmetProtection() { + void testOnPlayerMoveHelmetProtection() { when(inv.getHelmet()).thenReturn(new ItemStack(Material.DIAMOND_HELMET)); when(player.getInventory()).thenReturn(inv); when(settings.isHelmetProtection()).thenReturn(true); @@ -371,7 +369,7 @@ public void testOnPlayerMoveHelmetProtection() { * Test method for {@link world.bentobox.acidisland.listeners.AcidEffect#onPlayerMove(org.bukkit.event.player.PlayerMoveEvent)}. */ @Test - public void testOnPlayerMoveNoHelmetProtection() { + void testOnPlayerMoveNoHelmetProtection() { when(inv.getHelmet()).thenReturn(new ItemStack(Material.CARVED_PUMPKIN)); when(player.getInventory()).thenReturn(inv); when(settings.isHelmetProtection()).thenReturn(true); @@ -384,7 +382,7 @@ public void testOnPlayerMoveNoHelmetProtection() { * Test method for {@link world.bentobox.acidisland.listeners.AcidEffect#onPlayerMove(org.bukkit.event.player.PlayerMoveEvent)}. */ @Test - public void testOnPlayerMoveUnderObject() { + void testOnPlayerMoveUnderObject() { when(world.getBlockAt(anyInt(), anyInt(),anyInt())).thenReturn(solidBlock); PlayerMoveEvent e = new PlayerMoveEvent(player, from, to); ae.onPlayerMove(e); @@ -395,7 +393,7 @@ public void testOnPlayerMoveUnderObject() { * Test method for {@link world.bentobox.acidisland.listeners.AcidEffect#onPlayerMove(org.bukkit.event.player.PlayerMoveEvent)}. */ @Test - public void testOnPlayerMoveAcidRainWrongWorld() { + void testOnPlayerMoveAcidRainWrongWorld() { World nether = mock(World.class); when(nether.getName()).thenReturn("world_nether"); when(nether.getEnvironment()).thenReturn(Environment.NETHER); @@ -411,7 +409,7 @@ public void testOnPlayerMoveAcidRainWrongWorld() { * Test method for {@link world.bentobox.acidisland.listeners.AcidEffect#onPlayerMove(org.bukkit.event.player.PlayerMoveEvent)}. */ @Test - public void testOnPlayerMoveAcidRainWrongWorldEnd() { + void testOnPlayerMoveAcidRainWrongWorldEnd() { World end = mock(World.class); when(end.getName()).thenReturn("world_end"); when(end.getEnvironment()).thenReturn(Environment.THE_END); @@ -427,7 +425,7 @@ public void testOnPlayerMoveAcidRainWrongWorldEnd() { * Test method for {@link world.bentobox.acidisland.listeners.AcidEffect#onPlayerMove(org.bukkit.event.player.PlayerMoveEvent)}. */ @Test - public void testOnPlayerMoveNoAcidRain() { + void testOnPlayerMoveNoAcidRain() { when(settings.getAcidRainDamage()).thenReturn(0); PlayerMoveEvent e = new PlayerMoveEvent(player, from, to); ae.onPlayerMove(e); @@ -438,7 +436,7 @@ public void testOnPlayerMoveNoAcidRain() { * Test method for {@link world.bentobox.acidisland.listeners.AcidEffect#onPlayerMove(org.bukkit.event.player.PlayerMoveEvent)}. */ @Test - public void testOnPlayerMoveNoAcidDamage() { + void testOnPlayerMoveNoAcidDamage() { when(settings.getAcidRainDamage()).thenReturn(0); when(settings.getAcidDamage()).thenReturn(0); PlayerMoveEvent e = new PlayerMoveEvent(player, from, to); @@ -450,7 +448,7 @@ public void testOnPlayerMoveNoAcidDamage() { * Test method for {@link world.bentobox.acidisland.listeners.AcidEffect#onPlayerMove(org.bukkit.event.player.PlayerMoveEvent)}. */ @Test - public void testOnPlayerMoveBubbleColumn() { + void testOnPlayerMoveBubbleColumn() { when(settings.getAcidRainDamage()).thenReturn(0); when(block.getType()).thenReturn(Material.BUBBLE_COLUMN); PlayerMoveEvent e = new PlayerMoveEvent(player, from, to); @@ -462,7 +460,7 @@ public void testOnPlayerMoveBubbleColumn() { * Test method for {@link world.bentobox.acidisland.listeners.AcidEffect#onPlayerMove(org.bukkit.event.player.PlayerMoveEvent)}. */ @Test - public void testOnPlayerMoveSnow() { + void testOnPlayerMoveSnow() { when(settings.getAcidRainDamage()).thenReturn(0); when(block.getType()).thenReturn(Material.SNOW); when(settings.isAcidDamageSnow()).thenReturn(true); @@ -475,7 +473,7 @@ public void testOnPlayerMoveSnow() { * Test method for {@link world.bentobox.acidisland.listeners.AcidEffect#onPlayerMove(org.bukkit.event.player.PlayerMoveEvent)}. */ @Test - public void testOnPlayerMoveNoSnowAcidDamage() { + void testOnPlayerMoveNoSnowAcidDamage() { when(settings.getAcidRainDamage()).thenReturn(0); when(block.getType()).thenReturn(Material.SNOW); when(settings.isAcidDamageSnow()).thenReturn(false); @@ -488,7 +486,7 @@ public void testOnPlayerMoveNoSnowAcidDamage() { * Test method for {@link world.bentobox.acidisland.listeners.AcidEffect#onPlayerMove(org.bukkit.event.player.PlayerMoveEvent)}. */ @Test - public void testOnPlayerMoveInBoat() { + void testOnPlayerMoveInBoat() { when(settings.getAcidRainDamage()).thenReturn(0); Entity boat = mock(Boat.class); when(boat.getType()).thenReturn(EntityType.ACACIA_BOAT); @@ -502,7 +500,7 @@ public void testOnPlayerMoveInBoat() { * Test method for {@link world.bentobox.acidisland.listeners.AcidEffect#onPlayerMove(org.bukkit.event.player.PlayerMoveEvent)}. */ @Test - public void testOnPlayerMoveNotInBoat() { + void testOnPlayerMoveNotInBoat() { when(settings.getAcidRainDamage()).thenReturn(0); Entity horse = mock(Horse.class); when(horse.getType()).thenReturn(EntityType.HORSE); @@ -516,7 +514,7 @@ public void testOnPlayerMoveNotInBoat() { * Test method for {@link world.bentobox.acidisland.listeners.AcidEffect#onPlayerMove(org.bukkit.event.player.PlayerMoveEvent)}. */ @Test - public void testOnPlayerMoveFullArmor() { + void testOnPlayerMoveFullArmor() { when(settings.getAcidRainDamage()).thenReturn(0); when(settings.isFullArmorProtection()).thenReturn(true); PlayerMoveEvent e = new PlayerMoveEvent(player, from, to); @@ -528,7 +526,7 @@ public void testOnPlayerMoveFullArmor() { * Test method for {@link world.bentobox.acidisland.listeners.AcidEffect#onPlayerMove(org.bukkit.event.player.PlayerMoveEvent)}. */ @Test - public void testOnPlayerMoveNotFullArmor() { + void testOnPlayerMoveNotFullArmor() { when(settings.getAcidRainDamage()).thenReturn(0); when(settings.isFullArmorProtection()).thenReturn(true); ItemStack[] partial = { new ItemStack(Material.CHAINMAIL_HELMET), null }; @@ -543,7 +541,7 @@ public void testOnPlayerMoveNotFullArmor() { */ @Test @Disabled("Cannot be tested because of the PotionEffectType issue") - public void testOnPlayerMoveActivePotions() { + void testOnPlayerMoveActivePotions() { Collection potions = new ArrayList<>(); potions.add(new PotionEffect(PotionEffectType.WATER_BREATHING, 0, 0, false, false, false)); when(player.getActivePotionEffects()).thenReturn(potions); @@ -557,7 +555,7 @@ public void testOnPlayerMoveActivePotions() { */ @Test @Disabled("Cannot be tested because of the PotionEffectType issue") - public void testOnPlayerMoveActivePotionsConduit() { + void testOnPlayerMoveActivePotionsConduit() { Collection potions = new ArrayList<>(); potions.add(new PotionEffect(PotionEffectType.CONDUIT_POWER, 0, 0, false, false, false)); when(player.getActivePotionEffects()).thenReturn(potions); @@ -571,7 +569,7 @@ public void testOnPlayerMoveActivePotionsConduit() { */ @Test @Disabled("Cannot be tested because of the PotionEffectType issue") - public void testOnPlayerMoveActivePotionsBadOmen() { + void testOnPlayerMoveActivePotionsBadOmen() { Collection potions = new ArrayList<>(); potions.add(new PotionEffect(PotionEffectType.BAD_OMEN, 0, 0, false, false, false)); when(player.getActivePotionEffects()).thenReturn(potions); @@ -584,10 +582,10 @@ public void testOnPlayerMoveActivePotionsBadOmen() { * Test method for {@link world.bentobox.acidisland.listeners.AcidEffect#getDamageReduced(org.bukkit.entity.LivingEntity)}. */ @Test - public void testGetDamageReducedFullDiamond() { + void testGetDamageReducedFullDiamond() { AttributeInstance value = mock(AttributeInstance.class); when(value.getValue()).thenReturn(20.0); - when(player.getAttribute(eq(Attribute.ARMOR))).thenReturn(value); + when(player.getAttribute(Attribute.ARMOR)).thenReturn(value); EntityEquipment equip = mock(EntityEquipment.class); when(equip.getBoots()).thenReturn(new ItemStack(Material.DIAMOND_BOOTS)); when(equip.getHelmet()).thenReturn(new ItemStack(Material.DIAMOND_HELMET)); @@ -595,14 +593,14 @@ public void testGetDamageReducedFullDiamond() { when(equip.getChestplate()).thenReturn(new ItemStack(Material.DIAMOND_CHESTPLATE)); when(player.getEquipment()).thenReturn(equip); double a = AcidEffect.getDamageReduced(player); - assertTrue(a == 0.8); + assertEquals(0.8, a); } /** * Test method for {@link world.bentobox.acidisland.listeners.AcidEffect#checkForRain(Player)}. */ @Test - public void testCheckForRain() { + void testCheckForRain() { when(world.hasStorm()).thenReturn(true); when(player.isDead()).thenReturn(false); when(settings.getAcidRainDamage()).thenReturn(10); @@ -634,11 +632,11 @@ public void testCheckForRain() { * Test method for {@link world.bentobox.acidisland.listeners.AcidEffect#checkForRain(Player)}. */ @Test - public void testCheckForRainWetPlayer() { + void testCheckForRainWetPlayer() { AttributeInstance value = mock(AttributeInstance.class); when(value.getValue()).thenReturn(20D); // Diamond armor - when(player.getAttribute(eq(Attribute.ARMOR))).thenReturn(value); + when(player.getAttribute(Attribute.ARMOR)).thenReturn(value); EntityEquipment equip = mock(EntityEquipment.class); when(equip.getBoots()).thenReturn(new ItemStack(Material.DIAMOND_BOOTS)); when(equip.getHelmet()).thenReturn(new ItemStack(Material.DIAMOND_HELMET)); @@ -661,7 +659,7 @@ public void testCheckForRainWetPlayer() { * Test method for {@link world.bentobox.acidisland.listeners.AcidEffect#isSafeFromAcid(Player)}. */ @Test - public void testIsSafeFromAcid() { + void testIsSafeFromAcid() { assertFalse(ae.isSafeFromAcid(player)); } @@ -669,7 +667,7 @@ public void testIsSafeFromAcid() { * Test method for {@link world.bentobox.acidisland.listeners.AcidEffect#isSafeFromAcid(Player)}. */ @Test - public void testIsSafeFromAcidEssentialGodMode() { + void testIsSafeFromAcidEssentialGodMode() { when(essentialsUser.isGodModeEnabled()).thenReturn(true); assertTrue(ae.isSafeFromAcid(player)); } @@ -678,7 +676,7 @@ public void testIsSafeFromAcidEssentialGodMode() { * Test method for {@link world.bentobox.acidisland.listeners.AcidEffect#isSafeFromAcid(Player)}. */ @Test - public void testIsSafeFromAcidBoat() { + void testIsSafeFromAcidBoat() { when(player.isInsideVehicle()).thenReturn(true); Entity boat = mock(Entity.class); when(boat.getType()).thenReturn(EntityType.ACACIA_BOAT); @@ -690,7 +688,7 @@ public void testIsSafeFromAcidBoat() { * Test method for {@link world.bentobox.acidisland.listeners.AcidEffect#isSafeFromAcid(Player)}. */ @Test - public void testIsSafeFromAcidChestBoat() { + void testIsSafeFromAcidChestBoat() { when(player.isInsideVehicle()).thenReturn(true); Entity boat = mock(Entity.class); when(boat.getType()).thenReturn(EntityType.ACACIA_CHEST_BOAT); @@ -702,7 +700,7 @@ public void testIsSafeFromAcidChestBoat() { * Test method for {@link world.bentobox.acidisland.listeners.AcidEffect#isSafeFromAcid(Player)}. */ @Test - public void testIsSafeFromAcidFullArmor() { + void testIsSafeFromAcidFullArmor() { when(settings.isFullArmorProtection()).thenReturn(true); ItemStack[] armor = { new ItemStack(Material.CHAINMAIL_CHESTPLATE), new ItemStack(Material.CHAINMAIL_HELMET) }; when(inv.getArmorContents()).thenReturn(armor); @@ -716,7 +714,7 @@ public void testIsSafeFromAcidFullArmor() { * Test that dead player is ignored by onPlayerMove. */ @Test - public void testOnPlayerMoveDeadPlayer() { + void testOnPlayerMoveDeadPlayer() { when(player.isDead()).thenReturn(true); PlayerMoveEvent e = new PlayerMoveEvent(player, from, to); ae.onPlayerMove(e); @@ -727,7 +725,7 @@ public void testOnPlayerMoveDeadPlayer() { * Test that player in teleport is ignored by onPlayerMove. */ @Test - public void testOnPlayerMoveInTeleport() { + void testOnPlayerMoveInTeleport() { when(pm.isInTeleport(any())).thenReturn(true); PlayerMoveEvent e = new PlayerMoveEvent(player, from, to); ae.onPlayerMove(e); @@ -738,7 +736,7 @@ public void testOnPlayerMoveInTeleport() { * Test that player with noburn permission is ignored. */ @Test - public void testOnPlayerMoveNoburnPermission() { + void testOnPlayerMoveNoburnPermission() { when(player.isOp()).thenReturn(false); when(player.hasPermission("acidisland.mod.noburn")).thenReturn(true); PlayerMoveEvent e = new PlayerMoveEvent(player, from, to); @@ -750,7 +748,7 @@ public void testOnPlayerMoveNoburnPermission() { * Test that OP player with acidDamageOp=false is ignored. */ @Test - public void testOnPlayerMoveOpNoDamage() { + void testOnPlayerMoveOpNoDamage() { when(player.isOp()).thenReturn(true); when(settings.isAcidDamageOp()).thenReturn(false); PlayerMoveEvent e = new PlayerMoveEvent(player, from, to); @@ -762,7 +760,7 @@ public void testOnPlayerMoveOpNoDamage() { * Test isSafeFromAcid when player is not in water (standing on air). */ @Test - public void testIsSafeFromAcidNotInWater() { + void testIsSafeFromAcidNotInWater() { when(block.getType()).thenReturn(Material.AIR); when(block.getRelative(any())).thenReturn(airBlock); assertTrue(ae.isSafeFromAcid(player)); @@ -772,7 +770,7 @@ public void testIsSafeFromAcidNotInWater() { * Test isSafeFromAcid when player is in creative mode. */ @Test - public void testIsSafeFromAcidCreative() { + void testIsSafeFromAcidCreative() { when(player.getGameMode()).thenReturn(GameMode.CREATIVE); assertTrue(ae.isSafeFromAcid(player)); } @@ -781,7 +779,7 @@ public void testIsSafeFromAcidCreative() { * Test isSafeFromAcid for visitors with CUSTOM protection and not on island. */ @Test - public void testIsSafeFromAcidVisitorProtection() { + void testIsSafeFromAcidVisitorProtection() { when(im.userIsOnIsland(any(), any())).thenReturn(false); assertTrue(ae.isSafeFromAcid(player)); } @@ -790,7 +788,7 @@ public void testIsSafeFromAcidVisitorProtection() { * Test checkForRain returns true (safe) when no storm. */ @Test - public void testCheckForRainNoStorm() { + void testCheckForRainNoStorm() { when(world.hasStorm()).thenReturn(false); assertTrue(ae.checkForRain(player)); } @@ -799,7 +797,7 @@ public void testCheckForRainNoStorm() { * Test checkForRain returns true (safe) when player is dead. */ @Test - public void testCheckForRainDeadPlayer() { + void testCheckForRainDeadPlayer() { when(player.isDead()).thenReturn(true); assertTrue(ae.checkForRain(player)); } @@ -808,7 +806,7 @@ public void testCheckForRainDeadPlayer() { * Test checkForRain returns true (safe) when rain damage is zero. */ @Test - public void testCheckForRainZeroDamage() { + void testCheckForRainZeroDamage() { when(settings.getAcidRainDamage()).thenReturn(0); assertTrue(ae.checkForRain(player)); } @@ -817,24 +815,24 @@ public void testCheckForRainZeroDamage() { * Test getDamageReduced returns 0 when no armor. */ @Test - public void testGetDamageReducedNoArmor() { + void testGetDamageReducedNoArmor() { AttributeInstance value = mock(AttributeInstance.class); when(value.getValue()).thenReturn(0D); - when(player.getAttribute(eq(Attribute.ARMOR))).thenReturn(value); + when(player.getAttribute(Attribute.ARMOR)).thenReturn(value); EntityEquipment equip = mock(EntityEquipment.class); when(player.getEquipment()).thenReturn(equip); double a = AcidEffect.getDamageReduced(player); - assertTrue(a == 0D); + assertEquals(0D, a); } /** * Test getDamageReduced with partial armor (some null slots). */ @Test - public void testGetDamageReducedPartialArmor() { + void testGetDamageReducedPartialArmor() { AttributeInstance value = mock(AttributeInstance.class); when(value.getValue()).thenReturn(8D); // partial armor - when(player.getAttribute(eq(Attribute.ARMOR))).thenReturn(value); + when(player.getAttribute(Attribute.ARMOR)).thenReturn(value); EntityEquipment equip = mock(EntityEquipment.class); when(equip.getHelmet()).thenReturn(new ItemStack(Material.IRON_HELMET)); // boots, chest, pants are null diff --git a/src/test/java/world/bentobox/acidisland/listeners/LavaCheckTest.java b/src/test/java/world/bentobox/acidisland/listeners/LavaCheckTest.java index 1d1dfdc..2a3349a 100644 --- a/src/test/java/world/bentobox/acidisland/listeners/LavaCheckTest.java +++ b/src/test/java/world/bentobox/acidisland/listeners/LavaCheckTest.java @@ -2,7 +2,6 @@ import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyFloat; -import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @@ -95,7 +94,7 @@ public class LavaCheckTest { private MockedStatic mockedUtil; @BeforeEach - public void setUp() { + void setUp() { server = MockBukkit.mock(); mockedBukkit = Mockito.mockStatic(Bukkit.class, Mockito.RETURNS_DEEP_STUBS); mockedBukkit.when(Bukkit::getMinecraftVersion).thenReturn("1.21.11"); @@ -118,7 +117,7 @@ public void setUp() { } @AfterEach - public void tearDown() { + void tearDown() { mockedUtil.closeOnDemand(); mockedBukkit.closeOnDemand(); Mockito.framework().clearInlineMocks(); @@ -129,7 +128,7 @@ public void tearDown() { * Test method for {@link world.bentobox.acidisland.listeners.LavaCheck#onCleanstoneGen(org.bukkit.event.block.BlockFromToEvent)}. */ @Test - public void testOnCleanstoneGen() { + void testOnCleanstoneGen() { ArgumentCaptor argument = ArgumentCaptor.forClass(Runnable.class); BlockFromToEvent e = new BlockFromToEvent(airBlock, block); @@ -140,15 +139,15 @@ public void testOnCleanstoneGen() { when(block.getType()).thenReturn(Material.STONE); // Run runnable argument.getValue().run(); - verify(block).setType(eq(Material.WATER)); - verify(world).playSound(eq(location), eq(Sound.ENTITY_CREEPER_PRIMED), eq(1F), eq(2F)); + verify(block).setType(Material.WATER); + verify(world).playSound(location, Sound.ENTITY_CREEPER_PRIMED, 1F, 2F); } /** * Test method for {@link world.bentobox.acidisland.listeners.LavaCheck#onCleanstoneGen(org.bukkit.event.block.BlockFromToEvent)}. */ @Test - public void testOnCleanstoneGenNoStone() { + void testOnCleanstoneGenNoStone() { ArgumentCaptor argument = ArgumentCaptor.forClass(Runnable.class); BlockFromToEvent e = new BlockFromToEvent(airBlock, block); @@ -167,7 +166,7 @@ public void testOnCleanstoneGenNoStone() { * Test method for {@link world.bentobox.acidisland.listeners.LavaCheck#onCleanstoneGen(org.bukkit.event.block.BlockFromToEvent)}. */ @Test - public void testOnCleanstoneGenWrongWorld() { + void testOnCleanstoneGenWrongWorld() { when(block.getWorld()).thenReturn(Mockito.mock(World.class)); when(airBlock.getWorld()).thenReturn(Mockito.mock(World.class)); BlockFromToEvent e = new BlockFromToEvent(airBlock, block); @@ -181,7 +180,7 @@ public void testOnCleanstoneGenWrongWorld() { * Test method for {@link world.bentobox.acidisland.listeners.LavaCheck#onCleanstoneGen(org.bukkit.event.block.BlockFromToEvent)}. */ @Test - public void testOnCleanstoneGenNotWater() { + void testOnCleanstoneGenNotWater() { when(block.getType()).thenReturn(Material.LAVA); BlockFromToEvent e = new BlockFromToEvent(airBlock, block); @@ -194,7 +193,7 @@ public void testOnCleanstoneGenNotWater() { * Test method for {@link world.bentobox.acidisland.listeners.LavaCheck#onCleanstoneGen(org.bukkit.event.block.BlockFromToEvent)}. */ @Test - public void testOnCleanstoneGenNoAcid() { + void testOnCleanstoneGenNoAcid() { // No acid damage settings.setAcidDamage(0); diff --git a/src/test/java/world/bentobox/acidisland/listeners/PurifiedWaterListenerTest.java b/src/test/java/world/bentobox/acidisland/listeners/PurifiedWaterListenerTest.java new file mode 100644 index 0000000..80ecf8f --- /dev/null +++ b/src/test/java/world/bentobox/acidisland/listeners/PurifiedWaterListenerTest.java @@ -0,0 +1,983 @@ +package world.bentobox.acidisland.listeners; + +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import java.util.Optional; + +import org.bukkit.Bukkit; +import org.bukkit.Location; +import org.bukkit.Material; +import org.bukkit.World; +import org.bukkit.block.Block; +import org.bukkit.block.BlockFace; +import org.bukkit.block.data.BlockData; +import org.bukkit.block.data.Levelled; +import org.bukkit.block.data.type.PointedDripstone; +import org.bukkit.entity.Player; +import org.bukkit.event.block.CauldronLevelChangeEvent; +import org.bukkit.event.player.PlayerBucketFillEvent; +import org.bukkit.event.block.CauldronLevelChangeEvent.ChangeReason; +import org.bukkit.event.inventory.BrewEvent; +import org.bukkit.event.inventory.FurnaceSmeltEvent; +import org.bukkit.attribute.Attribute; +import org.bukkit.attribute.AttributeInstance; +import org.bukkit.event.player.PlayerInteractEvent; +import org.bukkit.event.player.PlayerItemConsumeEvent; +import org.bukkit.inventory.BrewerInventory; +import org.bukkit.inventory.ItemStack; +import org.bukkit.inventory.PlayerInventory; +import org.bukkit.inventory.meta.ItemMeta; +import org.bukkit.inventory.meta.PotionMeta; +import org.bukkit.persistence.PersistentDataContainer; +import org.bukkit.persistence.PersistentDataType; +import org.bukkit.plugin.PluginManager; +import org.bukkit.potion.PotionType; +import org.bukkit.scheduler.BukkitScheduler; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockbukkit.mockbukkit.MockBukkit; +import org.mockbukkit.mockbukkit.ServerMock; +import org.mockito.ArgumentCaptor; +import org.mockito.Mock; +import org.mockito.MockedStatic; +import org.mockito.Mockito; +import org.mockito.junit.jupiter.MockitoExtension; +import org.mockito.junit.jupiter.MockitoSettings; +import org.mockito.quality.Strictness; + +import world.bentobox.acidisland.AISettings; +import world.bentobox.acidisland.AcidIsland; +import world.bentobox.acidisland.events.ItemFillWithAcidEvent; +import world.bentobox.acidisland.events.PlayerDrinkPurifiedWaterEvent; +import world.bentobox.bentobox.BentoBox; +import world.bentobox.bentobox.database.objects.Island; +import world.bentobox.bentobox.managers.IslandsManager; +import world.bentobox.bentobox.managers.LocalesManager; + +/** + * @author tastybento + */ +@ExtendWith(MockitoExtension.class) +@MockitoSettings(strictness = Strictness.LENIENT) +class PurifiedWaterListenerTest { + + @Mock + private AcidIsland addon; + @Mock + private AISettings settings; + @Mock + private Player player; + @Mock + private World world; + @Mock + private Location location; + @Mock + private Block block; + @Mock + private PlayerInventory inventory; + @Mock + private PluginManager pluginManager; + @Mock + private BukkitScheduler scheduler; + @Mock + private IslandsManager islandsManager; + @Mock + private BentoBox plugin; + @Mock + private LocalesManager localesManager; + + private ServerMock server; + private MockedStatic mockedBukkit; + + // Class under test + private PurifiedWaterListener listener; + + @BeforeEach + void setUp() { + server = MockBukkit.mock(); + mockedBukkit = Mockito.mockStatic(Bukkit.class, Mockito.RETURNS_DEEP_STUBS); + mockedBukkit.when(Bukkit::getMinecraftVersion).thenReturn("1.21.11"); + mockedBukkit.when(Bukkit::getServer).thenReturn(server); + mockedBukkit.when(Bukkit::getPluginManager).thenReturn(pluginManager); + mockedBukkit.when(Bukkit::getScheduler).thenReturn(scheduler); + + when(addon.getSettings()).thenReturn(settings); + when(addon.getOverWorld()).thenReturn(world); + when(addon.getPlugin()).thenReturn(plugin); + when(addon.getIslands()).thenReturn(islandsManager); + when(plugin.getLocalesManager()).thenReturn(localesManager); + when(localesManager.getOrDefault(any(), any())).thenAnswer(inv -> inv.getArgument(1)); + + when(settings.isPurifiedWaterEnabled()).thenReturn(true); + when(settings.isPurifiedBucketFurnaceEnabled()).thenReturn(true); + when(settings.getAcidDrinkDamage()).thenReturn(4.0); + when(settings.getPurifiedWaterHeal()).thenReturn(4.0); + + when(player.getWorld()).thenReturn(world); + when(player.getLocation()).thenReturn(location); + when(player.getInventory()).thenReturn(inventory); + + when(block.getWorld()).thenReturn(world); + when(block.getLocation()).thenReturn(location); + // Stub blocks above cauldron as AIR by default (no dripstone = acid from rain) + Block airBlock = mock(Block.class); + when(airBlock.getType()).thenReturn(Material.AIR); + when(block.getRelative(any(BlockFace.class), anyInt())).thenReturn(airBlock); + + when(islandsManager.getIslandAt(any(Location.class))).thenReturn(Optional.empty()); + + // Inventory defaults: no items, standard 41-slot size + when(inventory.getSize()).thenReturn(41); + when(inventory.getItem(anyInt())).thenReturn(null); + + listener = new PurifiedWaterListener(addon); + } + + @AfterEach + void tearDown() { + mockedBukkit.closeOnDemand(); + MockBukkit.unmock(); + } + + // ----------------------------------------------------------------------- + // Static helper: isPurified + // ----------------------------------------------------------------------- + + @Test + void testIsPurifiedWithNullItem() { + assertFalse(PurifiedWaterListener.isPurified(null)); + } + + @SuppressWarnings("unchecked") + @Test + void testIsPurifiedWithPurifiedTag() { + ItemStack item = mock(ItemStack.class); + ItemMeta meta = mock(ItemMeta.class); + PersistentDataContainer pdc = mock(PersistentDataContainer.class); + when(item.getItemMeta()).thenReturn(meta); + when(meta.getPersistentDataContainer()).thenReturn(pdc); + when(pdc.get(any(), any())).thenReturn(PurifiedWaterListener.PURIFIED); + assertTrue(PurifiedWaterListener.isPurified(item)); + } + + @SuppressWarnings("unchecked") + @Test + void testIsPurifiedWithAcidTag() { + ItemStack item = mock(ItemStack.class); + ItemMeta meta = mock(ItemMeta.class); + PersistentDataContainer pdc = mock(PersistentDataContainer.class); + when(item.getItemMeta()).thenReturn(meta); + when(meta.getPersistentDataContainer()).thenReturn(pdc); + when(pdc.get(any(), any())).thenReturn(PurifiedWaterListener.ACID); + assertFalse(PurifiedWaterListener.isPurified(item)); + } + + // ----------------------------------------------------------------------- + // Static helper: isWaterBottle + // ----------------------------------------------------------------------- + + @Test + void testIsWaterBottleWithNullItem() { + assertFalse(PurifiedWaterListener.isWaterBottle(null)); + } + + @Test + void testIsWaterBottleWithNonPotion() { + ItemStack item = mock(ItemStack.class); + when(item.getType()).thenReturn(Material.GLASS_BOTTLE); + assertFalse(PurifiedWaterListener.isWaterBottle(item)); + } + + @SuppressWarnings("unchecked") + @Test + void testIsWaterBottleWithAcidTag() { + ItemStack item = mock(ItemStack.class); + when(item.getType()).thenReturn(Material.POTION); + PotionMeta meta = mock(PotionMeta.class); + PersistentDataContainer pdc = mock(PersistentDataContainer.class); + when(item.getItemMeta()).thenReturn(meta); + when(meta.getPersistentDataContainer()).thenReturn(pdc); + when(pdc.get(any(), any())).thenReturn(PurifiedWaterListener.ACID); + assertTrue(PurifiedWaterListener.isWaterBottle(item)); + } + + @SuppressWarnings("unchecked") + @Test + void testIsWaterBottleWithPurifiedTag() { + ItemStack item = mock(ItemStack.class); + when(item.getType()).thenReturn(Material.POTION); + PotionMeta meta = mock(PotionMeta.class); + PersistentDataContainer pdc = mock(PersistentDataContainer.class); + when(item.getItemMeta()).thenReturn(meta); + when(meta.getPersistentDataContainer()).thenReturn(pdc); + when(pdc.get(any(), any())).thenReturn(PurifiedWaterListener.PURIFIED); + assertFalse(PurifiedWaterListener.isWaterBottle(item)); + } + + @SuppressWarnings("unchecked") + @Test + void testIsWaterBottlePlainWaterPotion() { + ItemStack item = mock(ItemStack.class); + when(item.getType()).thenReturn(Material.POTION); + PotionMeta meta = mock(PotionMeta.class); + PersistentDataContainer pdc = mock(PersistentDataContainer.class); + when(item.getItemMeta()).thenReturn(meta); + when(meta.getPersistentDataContainer()).thenReturn(pdc); + when(pdc.get(any(), any())).thenReturn(null); // no custom tag + when(meta.getBasePotionType()).thenReturn(PotionType.WATER); + when(meta.getCustomEffects()).thenReturn(java.util.List.of()); + assertTrue(PurifiedWaterListener.isWaterBottle(item)); + } + + // ----------------------------------------------------------------------- + // CauldronLevelChangeEvent + // ----------------------------------------------------------------------- + + @Test + void testCauldronNaturalFillMarkedAcid() { + CauldronLevelChangeEvent e = mock(CauldronLevelChangeEvent.class); + when(e.getBlock()).thenReturn(block); + when(e.getReason()).thenReturn(ChangeReason.NATURAL_FILL); + when(e.getNewLevel()).thenReturn(1); + + listener.onCauldronChange(e); + + assertFalse(listener.getCauldronPurity().getOrDefault(location, true)); + } + + @Test + void testCauldronBucketFillDoesNotMarkPurity() { + // BUCKET_FILL = player took water from cauldron into bucket (cauldron being emptied, not filled) + // It doesn't change the purity map entry (handled by level-0 removal if fully emptied) + listener.getCauldronPurity().put(location, true); // pre-existing purified + CauldronLevelChangeEvent e = mock(CauldronLevelChangeEvent.class); + when(e.getBlock()).thenReturn(block); + when(e.getReason()).thenReturn(ChangeReason.BUCKET_FILL); + when(e.getNewLevel()).thenReturn(2); // still has some water + + listener.onCauldronChange(e); + + // purity should remain unchanged (still purified) + assertTrue(listener.getCauldronPurity().getOrDefault(location, false)); + } + + @SuppressWarnings("unchecked") + @Test + void testCauldronBucketEmptyWithPurifiedBucketMarkedPurified() { + // BUCKET_EMPTY = player poured a purified water bucket into the cauldron + ItemStack purifiedBucket = mock(ItemStack.class); + ItemMeta bucketMeta = mock(ItemMeta.class); + PersistentDataContainer pdc = mock(PersistentDataContainer.class); + when(purifiedBucket.getItemMeta()).thenReturn(bucketMeta); + when(bucketMeta.getPersistentDataContainer()).thenReturn(pdc); + when(pdc.get(any(), any())).thenReturn(PurifiedWaterListener.PURIFIED); + when(inventory.getItemInMainHand()).thenReturn(purifiedBucket); + + CauldronLevelChangeEvent e = mock(CauldronLevelChangeEvent.class); + when(e.getBlock()).thenReturn(block); + when(e.getReason()).thenReturn(ChangeReason.BUCKET_EMPTY); + when(e.getNewLevel()).thenReturn(3); + when(e.getEntity()).thenReturn(player); + + listener.onCauldronChange(e); + + assertTrue(listener.getCauldronPurity().getOrDefault(location, false)); + } + + @Test + void testCauldronBucketEmptyWithNormalBucketMarkedAcid() { + // BUCKET_EMPTY = player poured a plain water bucket into the cauldron + ItemStack waterBucket = mock(ItemStack.class); + when(waterBucket.getItemMeta()).thenReturn(null); + when(inventory.getItemInMainHand()).thenReturn(waterBucket); + + CauldronLevelChangeEvent e = mock(CauldronLevelChangeEvent.class); + when(e.getBlock()).thenReturn(block); + when(e.getReason()).thenReturn(ChangeReason.BUCKET_EMPTY); + when(e.getNewLevel()).thenReturn(3); + when(e.getEntity()).thenReturn(player); + + listener.onCauldronChange(e); + + assertFalse(listener.getCauldronPurity().getOrDefault(location, true)); + } + + @Test + void testCauldronUnknownMarkedAcid() { + // UNKNOWN reason → acid (no special meaning for dripstone in this API) + CauldronLevelChangeEvent e = mock(CauldronLevelChangeEvent.class); + when(e.getBlock()).thenReturn(block); + when(e.getReason()).thenReturn(ChangeReason.UNKNOWN); + when(e.getNewLevel()).thenReturn(1); + + listener.onCauldronChange(e); + + assertFalse(listener.getCauldronPurity().getOrDefault(location, true)); + } + + @Test + void testCauldronDripstoneNaturalFillMarkedPurified() { + // NATURAL_FILL with a dripstone stalactite tip directly above → purified + Block dripstoneTip = mock(Block.class); + when(dripstoneTip.getType()).thenReturn(Material.POINTED_DRIPSTONE); + PointedDripstone ds = mock(PointedDripstone.class); + when(dripstoneTip.getBlockData()).thenReturn(ds); + when(ds.getVerticalDirection()).thenReturn(BlockFace.DOWN); + when(block.getRelative(BlockFace.UP, 1)).thenReturn(dripstoneTip); + + CauldronLevelChangeEvent e = mock(CauldronLevelChangeEvent.class); + when(e.getBlock()).thenReturn(block); + when(e.getReason()).thenReturn(ChangeReason.NATURAL_FILL); + when(e.getNewLevel()).thenReturn(1); + + listener.onCauldronChange(e); + + assertTrue(listener.getCauldronPurity().getOrDefault(location, false)); + } + + @Test + void testCauldronEmptiedRemovesFromMap() { + // Put something in the map first + listener.getCauldronPurity().put(location, false); + + CauldronLevelChangeEvent e = mock(CauldronLevelChangeEvent.class); + when(e.getBlock()).thenReturn(block); + when(e.getReason()).thenReturn(ChangeReason.EVAPORATE); + when(e.getNewLevel()).thenReturn(0); + + listener.onCauldronChange(e); + + assertFalse(listener.getCauldronPurity().containsKey(location)); + } + + @Test + void testCauldronChangeIgnoredWhenDisabled() { + when(settings.isPurifiedWaterEnabled()).thenReturn(false); + + CauldronLevelChangeEvent e = mock(CauldronLevelChangeEvent.class); + when(e.getBlock()).thenReturn(block); + when(e.getReason()).thenReturn(ChangeReason.NATURAL_FILL); + when(e.getNewLevel()).thenReturn(1); + + listener.onCauldronChange(e); + + assertFalse(listener.getCauldronPurity().containsKey(location)); + } + + @Test + void testCauldronChangeIgnoredInOtherWorld() { + World otherWorld = mock(World.class); + Block otherBlock = mock(Block.class); + when(otherBlock.getWorld()).thenReturn(otherWorld); + when(otherBlock.getLocation()).thenReturn(location); + + CauldronLevelChangeEvent e = mock(CauldronLevelChangeEvent.class); + when(e.getBlock()).thenReturn(otherBlock); + when(e.getReason()).thenReturn(ChangeReason.NATURAL_FILL); + when(e.getNewLevel()).thenReturn(1); + + listener.onCauldronChange(e); + + assertFalse(listener.getCauldronPurity().containsKey(location)); + } + + // ----------------------------------------------------------------------- + // PlayerInteractEvent — bottle fill from water block + // ----------------------------------------------------------------------- + + @SuppressWarnings("unchecked") + @Test + void testBottleFillFromWaterFiresItemFillWithAcidEvent() { + ItemStack glassBottle = mock(ItemStack.class); + when(glassBottle.getType()).thenReturn(Material.GLASS_BOTTLE); + when(inventory.getItemInMainHand()).thenReturn(glassBottle); + + when(block.getType()).thenReturn(Material.WATER); + + PlayerInteractEvent e = mock(PlayerInteractEvent.class); + when(e.getClickedBlock()).thenReturn(block); + when(e.getPlayer()).thenReturn(player); + when(e.getItem()).thenReturn(glassBottle); + + listener.onBottleFill(e); + + // Post-hoc: event is NOT cancelled — vanilla fill is allowed to run + verify(e, never()).setCancelled(true); + + // Capture the scheduled task + ArgumentCaptor runnableCaptor = ArgumentCaptor.forClass(Runnable.class); + verify(scheduler).runTask(any(), runnableCaptor.capture()); + + // Simulate vanilla placing a plain water bottle in slot 0 + ItemStack vanillaWaterBottle = plainWaterBottleMock(); + when(inventory.getItem(0)).thenReturn(vanillaWaterBottle); + + runnableCaptor.getValue().run(); + + // Acid event was fired and slot 0 was replaced with the acid bottle + ArgumentCaptor captor = ArgumentCaptor.forClass(ItemFillWithAcidEvent.class); + verify(pluginManager).callEvent(captor.capture()); + assertFalse(captor.getValue().isCancelled()); + verify(inventory).setItem(anyInt(), any(ItemStack.class)); + } + + @Test + void testBottleFillIgnoredWhenFeatureDisabled() { + when(settings.isPurifiedWaterEnabled()).thenReturn(false); + + PlayerInteractEvent e = mock(PlayerInteractEvent.class); + when(e.getClickedBlock()).thenReturn(block); + when(e.getPlayer()).thenReturn(player); + + listener.onBottleFill(e); + + verify(e, never()).setCancelled(true); + } + + @Test + void testBottleFillIgnoredInOtherWorld() { + World otherWorld = mock(World.class); + when(player.getWorld()).thenReturn(otherWorld); + + ItemStack glassBottle = mock(ItemStack.class); + when(glassBottle.getType()).thenReturn(Material.GLASS_BOTTLE); + + PlayerInteractEvent e = mock(PlayerInteractEvent.class); + when(e.getClickedBlock()).thenReturn(block); + when(e.getPlayer()).thenReturn(player); + when(e.getItem()).thenReturn(glassBottle); + + listener.onBottleFill(e); + + verify(e, never()).setCancelled(true); + } + + @Test + void testBottleFillWithNonBottleIgnored() { + ItemStack sword = mock(ItemStack.class); + when(sword.getType()).thenReturn(Material.IRON_SWORD); + + PlayerInteractEvent e = mock(PlayerInteractEvent.class); + when(e.getClickedBlock()).thenReturn(block); + when(e.getPlayer()).thenReturn(player); + when(e.getItem()).thenReturn(sword); + + listener.onBottleFill(e); + + verify(e, never()).setCancelled(true); + } + + @Test + void testBottleFillFromAcidCauldronGivesAcidBottle() { + // Cauldron marked as acid + listener.getCauldronPurity().put(location, false); + + ItemStack glassBottle = mock(ItemStack.class); + when(glassBottle.getType()).thenReturn(Material.GLASS_BOTTLE); + when(glassBottle.getAmount()).thenReturn(1); + when(inventory.getItemInMainHand()).thenReturn(glassBottle); + + when(block.getType()).thenReturn(Material.WATER_CAULDRON); + Levelled levelled = mock(Levelled.class); + when(levelled.getLevel()).thenReturn(2); + when(block.getBlockData()).thenReturn(levelled); + + PlayerInteractEvent e = mock(PlayerInteractEvent.class); + when(e.getClickedBlock()).thenReturn(block); + when(e.getPlayer()).thenReturn(player); + when(e.getItem()).thenReturn(glassBottle); + + ArgumentCaptor captor = ArgumentCaptor.forClass(ItemFillWithAcidEvent.class); + + listener.onBottleFill(e); + + verify(pluginManager).callEvent(captor.capture()); + assertFalse(captor.getValue().isCancelled()); + verify(e).setCancelled(true); + } + + @Test + void testBottleFillFromPurifiedCauldronNoAcidEvent() { + // Cauldron marked as purified + listener.getCauldronPurity().put(location, true); + + ItemStack glassBottle = mock(ItemStack.class); + when(glassBottle.getType()).thenReturn(Material.GLASS_BOTTLE); + when(glassBottle.getAmount()).thenReturn(1); + when(inventory.getItemInMainHand()).thenReturn(glassBottle); + + when(block.getType()).thenReturn(Material.WATER_CAULDRON); + Levelled levelled = mock(Levelled.class); + when(levelled.getLevel()).thenReturn(2); + when(block.getBlockData()).thenReturn(levelled); + + PlayerInteractEvent e = mock(PlayerInteractEvent.class); + when(e.getClickedBlock()).thenReturn(block); + when(e.getPlayer()).thenReturn(player); + when(e.getItem()).thenReturn(glassBottle); + + listener.onBottleFill(e); + + // No ItemFillWithAcidEvent for purified; event is still cancelled to swap item + verify(pluginManager, never()).callEvent(any(ItemFillWithAcidEvent.class)); + verify(e).setCancelled(true); + } + + @Test + void testBottleFillCancelledWhenItemFillEventCancelled() { + ItemStack glassBottle = mock(ItemStack.class); + when(glassBottle.getType()).thenReturn(Material.GLASS_BOTTLE); + when(block.getType()).thenReturn(Material.WATER); + + PlayerInteractEvent e = mock(PlayerInteractEvent.class); + when(e.getClickedBlock()).thenReturn(block); + when(e.getPlayer()).thenReturn(player); + when(e.getItem()).thenReturn(glassBottle); + + // Cancel the ItemFillWithAcidEvent + Mockito.doAnswer(inv -> { + ItemFillWithAcidEvent event = inv.getArgument(0); + event.setCancelled(true); + return null; + }).when(pluginManager).callEvent(any(ItemFillWithAcidEvent.class)); + + listener.onBottleFill(e); + + // Capture and run the scheduled task + ArgumentCaptor runnableCaptor = ArgumentCaptor.forClass(Runnable.class); + verify(scheduler).runTask(any(), runnableCaptor.capture()); + + // Simulate vanilla giving a plain water bottle in slot 0 + ItemStack vanillaWaterBottle = plainWaterBottleMock(); + when(inventory.getItem(0)).thenReturn(vanillaWaterBottle); + + runnableCaptor.getValue().run(); + + // Fill event was cancelled — player should not receive an acid bottle + verify(inventory, never()).setItem(anyInt(), any(ItemStack.class)); + } + + // ----------------------------------------------------------------------- + // PlayerBucketFillEvent + // ----------------------------------------------------------------------- + + @Test + void testBucketFillFromOceanGivesAcidBucket() { + ItemStack waterBucket = mock(ItemStack.class); + when(waterBucket.getType()).thenReturn(Material.WATER_BUCKET); + when(block.getType()).thenReturn(Material.WATER); + + PlayerBucketFillEvent e = mock(PlayerBucketFillEvent.class); + when(e.getPlayer()).thenReturn(player); + when(e.getItemStack()).thenReturn(waterBucket); + when(e.getBlock()).thenReturn(block); + + listener.onBucketFill(e); + + verify(pluginManager).callEvent(any(ItemFillWithAcidEvent.class)); + verify(e).setItemStack(any(ItemStack.class)); + } + + @Test + void testBucketFillFromPurifiedCauldronGivesPurifiedBucket() { + listener.getCauldronPurity().put(location, true); + + ItemStack waterBucket = mock(ItemStack.class); + when(waterBucket.getType()).thenReturn(Material.WATER_BUCKET); + when(block.getType()).thenReturn(Material.WATER_CAULDRON); + + PlayerBucketFillEvent e = mock(PlayerBucketFillEvent.class); + when(e.getPlayer()).thenReturn(player); + when(e.getItemStack()).thenReturn(waterBucket); + when(e.getBlock()).thenReturn(block); + + listener.onBucketFill(e); + + verify(pluginManager, never()).callEvent(any(ItemFillWithAcidEvent.class)); + verify(e).setItemStack(any(ItemStack.class)); + } + + @Test + void testBucketFillFromAcidCauldronGivesAcidBucket() { + listener.getCauldronPurity().put(location, false); + + ItemStack waterBucket = mock(ItemStack.class); + when(waterBucket.getType()).thenReturn(Material.WATER_BUCKET); + when(block.getType()).thenReturn(Material.WATER_CAULDRON); + + PlayerBucketFillEvent e = mock(PlayerBucketFillEvent.class); + when(e.getPlayer()).thenReturn(player); + when(e.getItemStack()).thenReturn(waterBucket); + when(e.getBlock()).thenReturn(block); + + listener.onBucketFill(e); + + verify(pluginManager).callEvent(any(ItemFillWithAcidEvent.class)); + verify(e).setItemStack(any(ItemStack.class)); + } + + @Test + void testBucketFillIgnoredWhenFeatureDisabled() { + when(settings.isPurifiedWaterEnabled()).thenReturn(false); + + PlayerBucketFillEvent e = mock(PlayerBucketFillEvent.class); + when(e.getPlayer()).thenReturn(player); + + listener.onBucketFill(e); + + verify(e, never()).setItemStack(any()); + } + + @Test + void testBucketFillIgnoredInOtherWorld() { + World otherWorld = mock(World.class); + when(player.getWorld()).thenReturn(otherWorld); + + PlayerBucketFillEvent e = mock(PlayerBucketFillEvent.class); + when(e.getPlayer()).thenReturn(player); + + listener.onBucketFill(e); + + verify(e, never()).setItemStack(any()); + } + + @Test + void testBucketFillIgnoredForNonWaterBucket() { + ItemStack lavaBucket = mock(ItemStack.class); + when(lavaBucket.getType()).thenReturn(Material.LAVA_BUCKET); + + PlayerBucketFillEvent e = mock(PlayerBucketFillEvent.class); + when(e.getPlayer()).thenReturn(player); + when(e.getItemStack()).thenReturn(lavaBucket); + + listener.onBucketFill(e); + + verify(e, never()).setItemStack(any()); + } + + @Test + void testBucketFillCancelledWhenItemFillEventCancelled() { + ItemStack waterBucket = mock(ItemStack.class); + when(waterBucket.getType()).thenReturn(Material.WATER_BUCKET); + when(waterBucket.getItemMeta()).thenReturn(null); + + Mockito.doAnswer(inv -> { + ItemFillWithAcidEvent event = inv.getArgument(0); + event.setCancelled(true); + return null; + }).when(pluginManager).callEvent(any(ItemFillWithAcidEvent.class)); + + when(block.getType()).thenReturn(Material.WATER); + + PlayerBucketFillEvent e = mock(PlayerBucketFillEvent.class); + when(e.getPlayer()).thenReturn(player); + when(e.getItemStack()).thenReturn(waterBucket); + when(e.getBlock()).thenReturn(block); + + listener.onBucketFill(e); + + verify(e, never()).setItemStack(any()); + } + + // ----------------------------------------------------------------------- + // PlayerItemConsumeEvent — drinking purified water + // ----------------------------------------------------------------------- + + @SuppressWarnings("unchecked") + @Test + void testDrinkPurifiedBottleHealsPlayer() { + ItemStack purifiedBottle = mock(ItemStack.class); + when(purifiedBottle.getType()).thenReturn(Material.POTION); + ItemMeta meta = mock(ItemMeta.class); + PersistentDataContainer pdc = mock(PersistentDataContainer.class); + when(purifiedBottle.getItemMeta()).thenReturn(meta); + when(meta.getPersistentDataContainer()).thenReturn(pdc); + when(pdc.get(any(), any())).thenReturn(PurifiedWaterListener.PURIFIED); + + AttributeInstance maxHealthAttr = mock(AttributeInstance.class); + when(maxHealthAttr.getValue()).thenReturn(20.0); + when(player.getAttribute(Attribute.MAX_HEALTH)).thenReturn(maxHealthAttr); + when(player.getHealth()).thenReturn(10.0); + when(settings.getPurifiedWaterHeal()).thenReturn(4.0); + + PlayerItemConsumeEvent e = mock(PlayerItemConsumeEvent.class); + when(e.getItem()).thenReturn(purifiedBottle); + when(e.getPlayer()).thenReturn(player); + + listener.onDrinkPurified(e); + + verify(pluginManager).callEvent(any(PlayerDrinkPurifiedWaterEvent.class)); + verify(player).setHealth(14.0); // 10 + 4 + } + + @SuppressWarnings("unchecked") + @Test + void testDrinkPurifiedBottleCapsAtMaxHealth() { + ItemStack purifiedBottle = mock(ItemStack.class); + when(purifiedBottle.getType()).thenReturn(Material.POTION); + ItemMeta meta = mock(ItemMeta.class); + PersistentDataContainer pdc = mock(PersistentDataContainer.class); + when(purifiedBottle.getItemMeta()).thenReturn(meta); + when(meta.getPersistentDataContainer()).thenReturn(pdc); + when(pdc.get(any(), any())).thenReturn(PurifiedWaterListener.PURIFIED); + + AttributeInstance maxHealthAttr = mock(AttributeInstance.class); + when(maxHealthAttr.getValue()).thenReturn(20.0); + when(player.getAttribute(Attribute.MAX_HEALTH)).thenReturn(maxHealthAttr); + when(player.getHealth()).thenReturn(18.0); + when(settings.getPurifiedWaterHeal()).thenReturn(4.0); + + PlayerItemConsumeEvent e = mock(PlayerItemConsumeEvent.class); + when(e.getItem()).thenReturn(purifiedBottle); + when(e.getPlayer()).thenReturn(player); + + listener.onDrinkPurified(e); + + verify(player).setHealth(20.0); // capped at max + } + + @SuppressWarnings("unchecked") + @Test + void testDrinkPurifiedEventCancellable() { + ItemStack purifiedBottle = mock(ItemStack.class); + when(purifiedBottle.getType()).thenReturn(Material.POTION); + ItemMeta meta = mock(ItemMeta.class); + PersistentDataContainer pdc = mock(PersistentDataContainer.class); + when(purifiedBottle.getItemMeta()).thenReturn(meta); + when(meta.getPersistentDataContainer()).thenReturn(pdc); + when(pdc.get(any(), any())).thenReturn(PurifiedWaterListener.PURIFIED); + + Mockito.doAnswer(inv -> { + PlayerDrinkPurifiedWaterEvent event = inv.getArgument(0); + event.setCancelled(true); + return null; + }).when(pluginManager).callEvent(any(PlayerDrinkPurifiedWaterEvent.class)); + + PlayerItemConsumeEvent e = mock(PlayerItemConsumeEvent.class); + when(e.getItem()).thenReturn(purifiedBottle); + when(e.getPlayer()).thenReturn(player); + + listener.onDrinkPurified(e); + + verify(player, never()).setHealth(any(Double.class)); + } + + @Test + void testDrinkNonPurifiedBottleIgnored() { + ItemStack acidBottle = acidBottleMock(); + + PlayerItemConsumeEvent e = mock(PlayerItemConsumeEvent.class); + when(e.getItem()).thenReturn(acidBottle); + when(e.getPlayer()).thenReturn(player); + + listener.onDrinkPurified(e); + + verify(pluginManager, never()).callEvent(any(PlayerDrinkPurifiedWaterEvent.class)); + verify(player, never()).setHealth(any(Double.class)); + } + + @Test + void testDrinkPurifiedIgnoredWhenFeatureDisabled() { + when(settings.isPurifiedWaterEnabled()).thenReturn(false); + + PlayerItemConsumeEvent e = mock(PlayerItemConsumeEvent.class); + when(e.getPlayer()).thenReturn(player); + when(e.getItem()).thenReturn(mock(ItemStack.class)); + + assertDoesNotThrow(() -> listener.onDrinkPurified(e)); + verify(player, never()).setHealth(any(Double.class)); + } + + // ----------------------------------------------------------------------- + // FurnaceSmeltEvent + // ----------------------------------------------------------------------- + + @Test + void testFurnaceSmeltWaterBottleResultIsPurified() { + ItemStack source = acidBottleMock(); + ItemStack result = mock(ItemStack.class); + + FurnaceSmeltEvent e = mock(FurnaceSmeltEvent.class); + when(e.getSource()).thenReturn(source); + when(e.getResult()).thenReturn(result); + + assertDoesNotThrow(() -> listener.onFurnaceSmelt(e)); + verify(e).setResult(any(ItemStack.class)); + } + + @Test + void testFurnaceSmeltNonWaterPotionCancelled() { + ItemStack source = mock(ItemStack.class); + when(source.getType()).thenReturn(Material.POTION); + PotionMeta meta = mock(PotionMeta.class); + PersistentDataContainer pdc = mock(PersistentDataContainer.class); + when(source.getItemMeta()).thenReturn(meta); + when(meta.getPersistentDataContainer()).thenReturn(pdc); + when(pdc.get(PurifiedWaterListener.WATER_TYPE_KEY, PersistentDataType.STRING)).thenReturn(null); + when(meta.getBasePotionType()).thenReturn(PotionType.STRENGTH); // Not water + when(meta.getCustomEffects()).thenReturn(java.util.List.of()); + + FurnaceSmeltEvent e = mock(FurnaceSmeltEvent.class); + when(e.getSource()).thenReturn(source); + + listener.onFurnaceSmelt(e); + + verify(e).setCancelled(true); + } + + @Test + void testFurnaceSmeltWaterBucketResultIsPurified() { + ItemStack source = mock(ItemStack.class); + when(source.getType()).thenReturn(Material.WATER_BUCKET); + + FurnaceSmeltEvent e = mock(FurnaceSmeltEvent.class); + when(e.getSource()).thenReturn(source); + + assertDoesNotThrow(() -> listener.onFurnaceSmelt(e)); + verify(e).setResult(any(ItemStack.class)); + } + + @Test + void testFurnaceSmeltWaterBucketDisabledNoChange() { + when(settings.isPurifiedBucketFurnaceEnabled()).thenReturn(false); + + ItemStack source = mock(ItemStack.class); + when(source.getType()).thenReturn(Material.WATER_BUCKET); + + FurnaceSmeltEvent e = mock(FurnaceSmeltEvent.class); + when(e.getSource()).thenReturn(source); + + listener.onFurnaceSmelt(e); + + verify(e, never()).setResult(any()); + } + + @Test + void testFurnaceSmeltFeatureDisabled() { + when(settings.isPurifiedWaterEnabled()).thenReturn(false); + + ItemStack source = acidBottleMock(); + FurnaceSmeltEvent e = mock(FurnaceSmeltEvent.class); + when(e.getSource()).thenReturn(source); + + listener.onFurnaceSmelt(e); + + verify(e, never()).setResult(any()); + } + + // ----------------------------------------------------------------------- + // BrewEvent + // ----------------------------------------------------------------------- + + @Test + void testBrewWithCoalPurifiesWaterBottles() { + ItemStack coal = mock(ItemStack.class); + when(coal.getType()).thenReturn(Material.COAL); + + ItemStack waterBottle = acidBottleMock(); + + BrewerInventory inv = mock(BrewerInventory.class); + when(inv.getIngredient()).thenReturn(coal); + when(inv.getItem(anyInt())).thenReturn(waterBottle); + + BrewEvent e = mock(BrewEvent.class); + when(e.getContents()).thenReturn(inv); + + listener.onBrew(e); + + // Each of the 3 slots should have been updated + verify(inv, Mockito.times(3)).setItem(anyInt(), any(ItemStack.class)); + } + + @Test + void testBrewWithNonCoalNoChange() { + ItemStack blazePowder = mock(ItemStack.class); + when(blazePowder.getType()).thenReturn(Material.BLAZE_POWDER); + + BrewerInventory inv = mock(BrewerInventory.class); + when(inv.getIngredient()).thenReturn(blazePowder); + + BrewEvent e = mock(BrewEvent.class); + when(e.getContents()).thenReturn(inv); + + listener.onBrew(e); + + verify(inv, never()).setItem(anyInt(), any(ItemStack.class)); + } + + @Test + void testBrewFeatureDisabled() { + when(settings.isPurifiedWaterEnabled()).thenReturn(false); + + BrewEvent e = mock(BrewEvent.class); + + listener.onBrew(e); + + verify(e, never()).getContents(); + } + + @Test + void testBrewWithNullIngredient() { + BrewerInventory inv = mock(BrewerInventory.class); + when(inv.getIngredient()).thenReturn(null); + + BrewEvent e = mock(BrewEvent.class); + when(e.getContents()).thenReturn(inv); + + assertDoesNotThrow(() -> listener.onBrew(e)); + verify(inv, never()).setItem(anyInt(), any(ItemStack.class)); + } + + // ----------------------------------------------------------------------- + // Cauldron purity map accessor + // ----------------------------------------------------------------------- + + @Test + void testGetCauldronPurityMapNotNull() { + assertNotNull(listener.getCauldronPurity()); + } + + // ----------------------------------------------------------------------- + // Private helpers to build mocked items + // ----------------------------------------------------------------------- + + @SuppressWarnings("unchecked") + private ItemStack plainWaterBottleMock() { + ItemStack item = mock(ItemStack.class); + when(item.getType()).thenReturn(Material.POTION); + PotionMeta meta = mock(PotionMeta.class); + PersistentDataContainer pdc = mock(PersistentDataContainer.class); + when(item.getItemMeta()).thenReturn(meta); + when(meta.getPersistentDataContainer()).thenReturn(pdc); + when(pdc.get(any(), any())).thenReturn(null); // no custom tag + when(meta.getBasePotionType()).thenReturn(PotionType.WATER); + when(meta.getCustomEffects()).thenReturn(java.util.List.of()); + return item; + } + + @SuppressWarnings("unchecked") + private ItemStack acidBottleMock() { + ItemStack item = mock(ItemStack.class); + when(item.getType()).thenReturn(Material.POTION); + PotionMeta meta = mock(PotionMeta.class); + PersistentDataContainer pdc = mock(PersistentDataContainer.class); + when(item.getItemMeta()).thenReturn(meta); + when(meta.getPersistentDataContainer()).thenReturn(pdc); + // Use any() to avoid generic type erasure issues with PersistentDataType + when(pdc.get(any(), any())).thenReturn(PurifiedWaterListener.ACID); + return item; + } + +} diff --git a/src/test/java/world/bentobox/acidisland/world/AcidBiomeProviderTest.java b/src/test/java/world/bentobox/acidisland/world/AcidBiomeProviderTest.java index a0f736c..aa0a892 100644 --- a/src/test/java/world/bentobox/acidisland/world/AcidBiomeProviderTest.java +++ b/src/test/java/world/bentobox/acidisland/world/AcidBiomeProviderTest.java @@ -29,7 +29,7 @@ public class AcidBiomeProviderTest { private AcidBiomeProvider provider; @BeforeEach - public void setUp() { + void setUp() { MockBukkit.mock(); settings = new AISettings(); when(addon.getSettings()).thenReturn(settings); @@ -37,37 +37,37 @@ public void setUp() { } @AfterEach - public void tearDown() { + void tearDown() { MockBukkit.unmock(); } @Test - public void testGetBiomeNormal() { + void testGetBiomeNormal() { when(worldInfo.getEnvironment()).thenReturn(Environment.NORMAL); assertEquals(Biome.WARM_OCEAN, provider.getBiome(worldInfo, 0, 0, 0)); } @Test - public void testGetBiomeNether() { + void testGetBiomeNether() { when(worldInfo.getEnvironment()).thenReturn(Environment.NETHER); assertEquals(Biome.NETHER_WASTES, provider.getBiome(worldInfo, 0, 0, 0)); } @Test - public void testGetBiomeEnd() { + void testGetBiomeEnd() { when(worldInfo.getEnvironment()).thenReturn(Environment.THE_END); assertEquals(Biome.THE_END, provider.getBiome(worldInfo, 0, 0, 0)); } @Test - public void testGetBiomeCustom() { + void testGetBiomeCustom() { settings.setDefaultBiome(Biome.DEEP_OCEAN); when(worldInfo.getEnvironment()).thenReturn(Environment.NORMAL); assertEquals(Biome.DEEP_OCEAN, provider.getBiome(worldInfo, 0, 0, 0)); } @Test - public void testGetBiomeCustomNether() { + void testGetBiomeCustomNether() { settings.setDefaultNetherBiome(Biome.SOUL_SAND_VALLEY); when(worldInfo.getEnvironment()).thenReturn(Environment.NETHER); assertEquals(Biome.SOUL_SAND_VALLEY, provider.getBiome(worldInfo, 0, 0, 0)); diff --git a/src/test/java/world/bentobox/acidisland/world/AcidTaskTest.java b/src/test/java/world/bentobox/acidisland/world/AcidTaskTest.java index d4add35..3a88618 100644 --- a/src/test/java/world/bentobox/acidisland/world/AcidTaskTest.java +++ b/src/test/java/world/bentobox/acidisland/world/AcidTaskTest.java @@ -88,7 +88,7 @@ public class AcidTaskTest { private MockedStatic mockedBukkit; @BeforeEach - public void setUp() { + void setUp() { server = MockBukkit.mock(); mockedBukkit = Mockito.mockStatic(Bukkit.class, Mockito.RETURNS_DEEP_STUBS); mockedBukkit.when(Bukkit::getMinecraftVersion).thenReturn("1.21.11"); @@ -148,7 +148,7 @@ public void setUp() { } @AfterEach - public void tearDown() { + void tearDown() { mockedBukkit.closeOnDemand(); MockBukkit.unmock(); } @@ -157,7 +157,7 @@ public void tearDown() { * Test method for {@link world.bentobox.acidisland.world.AcidTask#AcidTask(world.bentobox.acidisland.AcidIsland)}. */ @Test - public void testAcidTask() { + void testAcidTask() { verify(scheduler).runTaskTimer(eq(null), any(Runnable.class), eq(0L), eq(20L)); } @@ -165,7 +165,7 @@ public void testAcidTask() { * Test method for {@link world.bentobox.acidisland.world.AcidTask#findEntities()}. */ @Test - public void testFindEntities() { + void testFindEntities() { at.findEntities(); verify(scheduler).runTask(eq(null), any(Runnable.class)); @@ -175,7 +175,7 @@ public void testFindEntities() { * Test method for {@link world.bentobox.acidisland.world.AcidTask#applyDamage(org.bukkit.entity.Entity, long)}. */ @Test - public void testApplyDamageRemoveItems() { + void testApplyDamageRemoveItems() { Item e = mock(Item.class); when(e.getLocation()).thenReturn(l); when(e.getWorld()).thenReturn(world); @@ -195,7 +195,7 @@ public void testApplyDamageRemoveItems() { * Test method for {@link world.bentobox.acidisland.world.AcidTask#applyDamage(org.bukkit.entity.Entity, long)}. */ @Test - public void testApplyDamageNoItemDamage() { + void testApplyDamageNoItemDamage() { settings.setAcidDestroyItemTime(0L); Item e = mock(Item.class); at.applyDamage(e, 0); @@ -208,13 +208,13 @@ public void testApplyDamageNoItemDamage() { * Test method for {@link world.bentobox.acidisland.world.AcidTask#applyDamage(org.bukkit.entity.Entity, long)}. */ @Test - public void testApplyDamageKeepItems() { + void testApplyDamageKeepItems() { Item e = mock(Item.class); - Location l = mock(Location.class); + Location loc = mock(Location.class); Block block = mock(Block.class); when(block.getType()).thenReturn(Material.AIR); - when(l.getBlock()).thenReturn(block); - when(e.getLocation()).thenReturn(l); + when(loc.getBlock()).thenReturn(block); + when(e.getLocation()).thenReturn(loc); when(e.getWorld()).thenReturn(world); // Put the item in the water @@ -231,7 +231,7 @@ public void testApplyDamageKeepItems() { * Test method for {@link world.bentobox.acidisland.world.AcidTask#getEntityStream()}. */ @Test - public void testGetEntityStream() { + void testGetEntityStream() { List es = at.getEntityStream(); assertEquals(12, es.size()); } @@ -240,7 +240,7 @@ public void testGetEntityStream() { * Test method for {@link world.bentobox.acidisland.world.AcidTask#cancelTasks()}. */ @Test - public void testCancelTasks() { + void testCancelTasks() { at.cancelTasks(); verify(task).cancel(); } @@ -251,7 +251,7 @@ public void testCancelTasks() { * Test getEntityStream with nether and end disabled. */ @Test - public void testGetEntityStreamOverworldOnly() { + void testGetEntityStreamOverworldOnly() { settings.setNetherGenerate(false); settings.setEndGenerate(false); // Recreate to use updated settings @@ -265,7 +265,7 @@ public void testGetEntityStreamOverworldOnly() { * Test getEntityStream with nether enabled but netherIslands disabled. */ @Test - public void testGetEntityStreamNetherNoIslands() { + void testGetEntityStreamNetherNoIslands() { settings.setNetherIslands(false); settings.setEndGenerate(false); at = new AcidTask(addon); @@ -278,7 +278,7 @@ public void testGetEntityStreamNetherNoIslands() { * Test that applyDamage on LivingEntity with cancelled event does no damage. */ @Test - public void testApplyDamageLivingEntityCancelled() { + void testApplyDamageLivingEntityCancelled() { Skeleton s = mock(Skeleton.class); when(s.getLocation()).thenReturn(l); when(s.getWorld()).thenReturn(world); @@ -307,7 +307,7 @@ public void testApplyDamageLivingEntityCancelled() { * Test that applyDamage removes item from tracking when not in water. */ @Test - public void testApplyDamageItemNotInWaterAnymore() { + void testApplyDamageItemNotInWaterAnymore() { Item e = mock(Item.class); Location loc = mock(Location.class); Block b = mock(Block.class); diff --git a/src/test/java/world/bentobox/acidisland/world/ChunkGeneratorWorldTest.java b/src/test/java/world/bentobox/acidisland/world/ChunkGeneratorWorldTest.java index cfaa0d6..03451cc 100644 --- a/src/test/java/world/bentobox/acidisland/world/ChunkGeneratorWorldTest.java +++ b/src/test/java/world/bentobox/acidisland/world/ChunkGeneratorWorldTest.java @@ -11,7 +11,6 @@ import org.junit.jupiter.api.extension.ExtendWith; import org.mockbukkit.mockbukkit.MockBukkit; import org.mockito.Mock; -import org.mockito.Mockito; import org.mockito.junit.jupiter.MockitoExtension; import world.bentobox.acidisland.AISettings; @@ -33,7 +32,7 @@ public class ChunkGeneratorWorldTest { private AISettings settings; @BeforeEach - public void setUp() { + void setUp() { MockBukkit.mock(); // Settings settings = new AISettings(); @@ -41,7 +40,7 @@ public void setUp() { } @AfterEach - public void tearDown() { + void tearDown() { MockBukkit.unmock(); } @@ -49,7 +48,7 @@ public void tearDown() { * Test method for {@link world.bentobox.acidisland.world.ChunkGeneratorWorld#canSpawn(org.bukkit.World, int, int)}. */ @Test - public void testCanSpawnWorldIntInt() { + void testCanSpawnWorldIntInt() { cg = new ChunkGeneratorWorld(addon); assertTrue(cg.canSpawn(mock(World.class), 0, 1)); } @@ -58,7 +57,7 @@ public void testCanSpawnWorldIntInt() { * Test method for {@link world.bentobox.acidisland.world.ChunkGeneratorWorld#getDefaultPopulators(org.bukkit.World)}. */ @Test - public void testGetDefaultPopulatorsWorld() { + void testGetDefaultPopulatorsWorld() { cg = new ChunkGeneratorWorld(addon); assertTrue(cg.getDefaultPopulators(mock(World.class)).isEmpty()); }