Skip to content

Commit 8bbb523

Browse files
tastybentoclaude
andcommitted
Refine purified water mechanics
- Purified bottles revert to PotionType.WATER so they work as brewing stand base ingredients; add PlayerItemConsumeEvent handler to apply configurable health boost (getPurifiedWaterHeal()) on drink - onBucketFill now checks cauldron purity: purified cauldron yields a purified bucket, acid cauldron/ocean yields an acid bucket - Add acid bucket lore (makeAcidBucket) for ocean bucket fills - 7 new tests covering drink-heal, max-health cap, cancellable event, and cauldron-purity bucket routing Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent f6edbcd commit 8bbb523

2 files changed

Lines changed: 195 additions & 8 deletions

File tree

src/main/java/world/bentobox/acidisland/listeners/PurifiedWaterListener.java

Lines changed: 38 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
import org.bukkit.block.BlockFace;
2424
import org.bukkit.block.data.Levelled;
2525
import org.bukkit.block.data.type.PointedDripstone;
26+
import org.bukkit.attribute.Attribute;
2627
import org.bukkit.entity.Player;
2728
import org.bukkit.event.EventHandler;
2829
import org.bukkit.event.EventPriority;
@@ -33,6 +34,7 @@
3334
import org.bukkit.event.inventory.FurnaceSmeltEvent;
3435
import org.bukkit.event.player.PlayerBucketFillEvent;
3536
import org.bukkit.event.player.PlayerInteractEvent;
37+
import org.bukkit.event.player.PlayerItemConsumeEvent;
3638
import org.bukkit.inventory.BrewerInventory;
3739
import org.bukkit.inventory.FurnaceRecipe;
3840
import org.bukkit.inventory.PlayerInventory;
@@ -46,6 +48,7 @@
4648

4749
import world.bentobox.acidisland.AcidIsland;
4850
import world.bentobox.acidisland.events.ItemFillWithAcidEvent;
51+
import world.bentobox.acidisland.events.PlayerDrinkPurifiedWaterEvent;
4952
import world.bentobox.bentobox.database.objects.Island;
5053
import world.bentobox.bentobox.util.Util;
5154

@@ -167,7 +170,7 @@ ItemStack makeAcidBottle() {
167170
ItemStack makePurifiedBottle() {
168171
ItemStack bottle = new ItemStack(Material.POTION);
169172
PotionMeta meta = (PotionMeta) bottle.getItemMeta();
170-
meta.setBasePotionType(PotionType.HEALING);
173+
meta.setBasePotionType(PotionType.WATER);
171174
meta.lore(List.of(lore("lore-purified")));
172175
meta.getPersistentDataContainer().set(WATER_TYPE_KEY, PersistentDataType.STRING, PURIFIED);
173176
bottle.setItemMeta(meta);
@@ -340,12 +343,41 @@ public void onBucketFill(PlayerBucketFillEvent e) {
340343
if (result == null || result.getType() != Material.WATER_BUCKET) return;
341344

342345
Player player = e.getPlayer();
343-
ItemFillWithAcidEvent fillEvent = new ItemFillWithAcidEvent(
344-
getIsland(player).orElse(null), player, makeAcidBucket());
345-
Bukkit.getPluginManager().callEvent(fillEvent);
346-
if (fillEvent.isCancelled()) return;
347346

348-
e.setItemStack(makeAcidBucket());
347+
// A purified cauldron yields a purified bucket; everything else is acid.
348+
boolean purified = e.getBlock().getType() == Material.WATER_CAULDRON
349+
&& cauldronPurity.getOrDefault(e.getBlock().getLocation(), false);
350+
351+
if (purified) {
352+
e.setItemStack(makePurifiedBucket());
353+
} else {
354+
ItemFillWithAcidEvent fillEvent = new ItemFillWithAcidEvent(
355+
getIsland(player).orElse(null), player, makeAcidBucket());
356+
Bukkit.getPluginManager().callEvent(fillEvent);
357+
if (fillEvent.isCancelled()) return;
358+
e.setItemStack(makeAcidBucket());
359+
}
360+
}
361+
362+
/**
363+
* When a player drinks a purified water bottle, apply a configurable health boost.
364+
* Vanilla handles returning the glass bottle; we only need to add the heal.
365+
*/
366+
@EventHandler(ignoreCancelled = true)
367+
public void onDrinkPurified(PlayerItemConsumeEvent e) {
368+
if (!addon.getSettings().isPurifiedWaterEnabled()) return;
369+
ItemStack item = e.getItem();
370+
if (item.getType() != Material.POTION || !isPurified(item)) return;
371+
372+
Player player = e.getPlayer();
373+
double healAmount = addon.getSettings().getPurifiedWaterHeal();
374+
PlayerDrinkPurifiedWaterEvent drinkEvent = new PlayerDrinkPurifiedWaterEvent(
375+
getIsland(player).orElse(null), player, healAmount);
376+
Bukkit.getPluginManager().callEvent(drinkEvent);
377+
if (drinkEvent.isCancelled()) return;
378+
379+
double maxHealth = player.getAttribute(Attribute.MAX_HEALTH).getValue();
380+
player.setHealth(Math.min(maxHealth, player.getHealth() + drinkEvent.getHealAmount()));
349381
}
350382

351383
/**

src/test/java/world/bentobox/acidisland/listeners/PurifiedWaterListenerTest.java

Lines changed: 157 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,10 @@
2929
import org.bukkit.event.block.CauldronLevelChangeEvent.ChangeReason;
3030
import org.bukkit.event.inventory.BrewEvent;
3131
import org.bukkit.event.inventory.FurnaceSmeltEvent;
32+
import org.bukkit.attribute.Attribute;
33+
import org.bukkit.attribute.AttributeInstance;
3234
import org.bukkit.event.player.PlayerInteractEvent;
35+
import org.bukkit.event.player.PlayerItemConsumeEvent;
3336
import org.bukkit.inventory.BrewerInventory;
3437
import org.bukkit.inventory.ItemStack;
3538
import org.bukkit.inventory.PlayerInventory;
@@ -57,6 +60,7 @@
5760
import world.bentobox.acidisland.AISettings;
5861
import world.bentobox.acidisland.AcidIsland;
5962
import world.bentobox.acidisland.events.ItemFillWithAcidEvent;
63+
import world.bentobox.acidisland.events.PlayerDrinkPurifiedWaterEvent;
6064
import world.bentobox.bentobox.BentoBox;
6165
import world.bentobox.bentobox.database.objects.Island;
6266
import world.bentobox.bentobox.managers.IslandsManager;
@@ -570,14 +574,53 @@ void testBottleFillCancelledWhenItemFillEventCancelled() {
570574
// -----------------------------------------------------------------------
571575

572576
@Test
573-
void testBucketFillInAcidWorldGivesAcidBucket() {
577+
void testBucketFillFromOceanGivesAcidBucket() {
574578
ItemStack waterBucket = mock(ItemStack.class);
575579
when(waterBucket.getType()).thenReturn(Material.WATER_BUCKET);
576-
when(waterBucket.getItemMeta()).thenReturn(null); // plain, untagged
580+
when(block.getType()).thenReturn(Material.WATER);
577581

578582
PlayerBucketFillEvent e = mock(PlayerBucketFillEvent.class);
579583
when(e.getPlayer()).thenReturn(player);
580584
when(e.getItemStack()).thenReturn(waterBucket);
585+
when(e.getBlock()).thenReturn(block);
586+
587+
listener.onBucketFill(e);
588+
589+
verify(pluginManager).callEvent(any(ItemFillWithAcidEvent.class));
590+
verify(e).setItemStack(any(ItemStack.class));
591+
}
592+
593+
@Test
594+
void testBucketFillFromPurifiedCauldronGivesPurifiedBucket() {
595+
listener.getCauldronPurity().put(location, true);
596+
597+
ItemStack waterBucket = mock(ItemStack.class);
598+
when(waterBucket.getType()).thenReturn(Material.WATER_BUCKET);
599+
when(block.getType()).thenReturn(Material.WATER_CAULDRON);
600+
601+
PlayerBucketFillEvent e = mock(PlayerBucketFillEvent.class);
602+
when(e.getPlayer()).thenReturn(player);
603+
when(e.getItemStack()).thenReturn(waterBucket);
604+
when(e.getBlock()).thenReturn(block);
605+
606+
listener.onBucketFill(e);
607+
608+
verify(pluginManager, never()).callEvent(any(ItemFillWithAcidEvent.class));
609+
verify(e).setItemStack(any(ItemStack.class));
610+
}
611+
612+
@Test
613+
void testBucketFillFromAcidCauldronGivesAcidBucket() {
614+
listener.getCauldronPurity().put(location, false);
615+
616+
ItemStack waterBucket = mock(ItemStack.class);
617+
when(waterBucket.getType()).thenReturn(Material.WATER_BUCKET);
618+
when(block.getType()).thenReturn(Material.WATER_CAULDRON);
619+
620+
PlayerBucketFillEvent e = mock(PlayerBucketFillEvent.class);
621+
when(e.getPlayer()).thenReturn(player);
622+
when(e.getItemStack()).thenReturn(waterBucket);
623+
when(e.getBlock()).thenReturn(block);
581624

582625
listener.onBucketFill(e);
583626

@@ -636,15 +679,127 @@ void testBucketFillCancelledWhenItemFillEventCancelled() {
636679
return null;
637680
}).when(pluginManager).callEvent(any(ItemFillWithAcidEvent.class));
638681

682+
when(block.getType()).thenReturn(Material.WATER);
683+
639684
PlayerBucketFillEvent e = mock(PlayerBucketFillEvent.class);
640685
when(e.getPlayer()).thenReturn(player);
641686
when(e.getItemStack()).thenReturn(waterBucket);
687+
when(e.getBlock()).thenReturn(block);
642688

643689
listener.onBucketFill(e);
644690

645691
verify(e, never()).setItemStack(any());
646692
}
647693

694+
// -----------------------------------------------------------------------
695+
// PlayerItemConsumeEvent — drinking purified water
696+
// -----------------------------------------------------------------------
697+
698+
@SuppressWarnings("unchecked")
699+
@Test
700+
void testDrinkPurifiedBottleHealsPlayer() {
701+
ItemStack purifiedBottle = mock(ItemStack.class);
702+
when(purifiedBottle.getType()).thenReturn(Material.POTION);
703+
ItemMeta meta = mock(ItemMeta.class);
704+
PersistentDataContainer pdc = mock(PersistentDataContainer.class);
705+
when(purifiedBottle.getItemMeta()).thenReturn(meta);
706+
when(meta.getPersistentDataContainer()).thenReturn(pdc);
707+
when(pdc.get(any(), any())).thenReturn(PurifiedWaterListener.PURIFIED);
708+
709+
AttributeInstance maxHealthAttr = mock(AttributeInstance.class);
710+
when(maxHealthAttr.getValue()).thenReturn(20.0);
711+
when(player.getAttribute(Attribute.MAX_HEALTH)).thenReturn(maxHealthAttr);
712+
when(player.getHealth()).thenReturn(10.0);
713+
when(settings.getPurifiedWaterHeal()).thenReturn(4.0);
714+
715+
PlayerItemConsumeEvent e = mock(PlayerItemConsumeEvent.class);
716+
when(e.getItem()).thenReturn(purifiedBottle);
717+
when(e.getPlayer()).thenReturn(player);
718+
719+
listener.onDrinkPurified(e);
720+
721+
verify(pluginManager).callEvent(any(PlayerDrinkPurifiedWaterEvent.class));
722+
verify(player).setHealth(14.0); // 10 + 4
723+
}
724+
725+
@SuppressWarnings("unchecked")
726+
@Test
727+
void testDrinkPurifiedBottleCapsAtMaxHealth() {
728+
ItemStack purifiedBottle = mock(ItemStack.class);
729+
when(purifiedBottle.getType()).thenReturn(Material.POTION);
730+
ItemMeta meta = mock(ItemMeta.class);
731+
PersistentDataContainer pdc = mock(PersistentDataContainer.class);
732+
when(purifiedBottle.getItemMeta()).thenReturn(meta);
733+
when(meta.getPersistentDataContainer()).thenReturn(pdc);
734+
when(pdc.get(any(), any())).thenReturn(PurifiedWaterListener.PURIFIED);
735+
736+
AttributeInstance maxHealthAttr = mock(AttributeInstance.class);
737+
when(maxHealthAttr.getValue()).thenReturn(20.0);
738+
when(player.getAttribute(Attribute.MAX_HEALTH)).thenReturn(maxHealthAttr);
739+
when(player.getHealth()).thenReturn(18.0);
740+
when(settings.getPurifiedWaterHeal()).thenReturn(4.0);
741+
742+
PlayerItemConsumeEvent e = mock(PlayerItemConsumeEvent.class);
743+
when(e.getItem()).thenReturn(purifiedBottle);
744+
when(e.getPlayer()).thenReturn(player);
745+
746+
listener.onDrinkPurified(e);
747+
748+
verify(player).setHealth(20.0); // capped at max
749+
}
750+
751+
@SuppressWarnings("unchecked")
752+
@Test
753+
void testDrinkPurifiedEventCancellable() {
754+
ItemStack purifiedBottle = mock(ItemStack.class);
755+
when(purifiedBottle.getType()).thenReturn(Material.POTION);
756+
ItemMeta meta = mock(ItemMeta.class);
757+
PersistentDataContainer pdc = mock(PersistentDataContainer.class);
758+
when(purifiedBottle.getItemMeta()).thenReturn(meta);
759+
when(meta.getPersistentDataContainer()).thenReturn(pdc);
760+
when(pdc.get(any(), any())).thenReturn(PurifiedWaterListener.PURIFIED);
761+
762+
Mockito.doAnswer(inv -> {
763+
PlayerDrinkPurifiedWaterEvent event = inv.getArgument(0);
764+
event.setCancelled(true);
765+
return null;
766+
}).when(pluginManager).callEvent(any(PlayerDrinkPurifiedWaterEvent.class));
767+
768+
PlayerItemConsumeEvent e = mock(PlayerItemConsumeEvent.class);
769+
when(e.getItem()).thenReturn(purifiedBottle);
770+
when(e.getPlayer()).thenReturn(player);
771+
772+
listener.onDrinkPurified(e);
773+
774+
verify(player, never()).setHealth(any(Double.class));
775+
}
776+
777+
@Test
778+
void testDrinkNonPurifiedBottleIgnored() {
779+
ItemStack acidBottle = acidBottleMock();
780+
781+
PlayerItemConsumeEvent e = mock(PlayerItemConsumeEvent.class);
782+
when(e.getItem()).thenReturn(acidBottle);
783+
when(e.getPlayer()).thenReturn(player);
784+
785+
listener.onDrinkPurified(e);
786+
787+
verify(pluginManager, never()).callEvent(any(PlayerDrinkPurifiedWaterEvent.class));
788+
verify(player, never()).setHealth(any(Double.class));
789+
}
790+
791+
@Test
792+
void testDrinkPurifiedIgnoredWhenFeatureDisabled() {
793+
when(settings.isPurifiedWaterEnabled()).thenReturn(false);
794+
795+
PlayerItemConsumeEvent e = mock(PlayerItemConsumeEvent.class);
796+
when(e.getPlayer()).thenReturn(player);
797+
when(e.getItem()).thenReturn(mock(ItemStack.class));
798+
799+
assertDoesNotThrow(() -> listener.onDrinkPurified(e));
800+
verify(player, never()).setHealth(any(Double.class));
801+
}
802+
648803
// -----------------------------------------------------------------------
649804
// FurnaceSmeltEvent
650805
// -----------------------------------------------------------------------

0 commit comments

Comments
 (0)