Skip to content

Commit aaac8d8

Browse files
authored
Merge pull request #49 from BentoBoxWorld/copilot/fix-inventory-clear-non-bentobox-world
Intercept BentoBox player reset events to protect non-BentoBox world inventories
2 parents ded7313 + b6ae2ed commit aaac8d8

5 files changed

Lines changed: 575 additions & 12 deletions

File tree

pom.xml

Lines changed: 4 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@
5858
<byte-buddy.version>1.17.5</byte-buddy.version>
5959
<!-- More visible way how to change dependency versions -->
6060
<paper.version>1.21.11-R0.1-SNAPSHOT</paper.version>
61-
<bentobox.version>2.7.1-SNAPSHOT</bentobox.version>
61+
<bentobox.version>3.17.0-SNAPSHOT</bentobox.version>
6262
<!-- Revision variable removes warning about dynamic version -->
6363
<revision>${build.version}-SNAPSHOT</revision>
6464
<!-- Do not change unless you want different name for local builds. -->
@@ -114,14 +114,6 @@
114114
</profiles>
115115

116116
<repositories>
117-
<!-- jitpack first so MockBukkit snapshots resolve without hitting other repos -->
118-
<repository>
119-
<id>jitpack.io</id>
120-
<url>https://jitpack.io</url>
121-
<snapshots>
122-
<enabled>true</enabled>
123-
</snapshots>
124-
</repository>
125117
<repository>
126118
<id>spigot-repo</id>
127119
<url>https://hub.spigotmc.org/nexus/content/repositories/snapshots</url>
@@ -146,9 +138,9 @@
146138
</dependency>
147139
<!-- MockBukkit -->
148140
<dependency>
149-
<groupId>com.github.MockBukkit</groupId>
150-
<artifactId>MockBukkit</artifactId>
151-
<version>v1.21-SNAPSHOT</version>
141+
<groupId>org.mockbukkit.mockbukkit</groupId>
142+
<artifactId>mockbukkit-v1.21</artifactId>
143+
<version>4.110.0</version>
152144
<scope>test</scope>
153145
<exclusions>
154146
<exclusion>

src/main/java/com/wasteofplastic/invswitcher/Store.java

Lines changed: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -676,4 +676,134 @@ private static void setTotalExperience(final Player player, final int exp)
676676
public void saveOnShutdown() {
677677
Bukkit.getOnlinePlayers().forEach(p -> this.storeAndSave(p, p.getWorld(), true));
678678
}
679+
680+
/**
681+
* Compute the storage key for a player and island event world, without using
682+
* the player's current location. Used when the player is not in the target world.
683+
* @param player - player
684+
* @param world - the BentoBox event world
685+
* @param island - the island involved in the event (may be null)
686+
* @return storage key for this world/island combination
687+
*/
688+
String getStorageKeyForEvent(Player player, World world, Island island) {
689+
String overworldName = getOverworldName(world);
690+
if (!addon.getSettings().isIslandsActive()) {
691+
return overworldName;
692+
}
693+
World overworld = Util.getWorld(world);
694+
if (overworld == null) {
695+
return overworldName;
696+
}
697+
int count = addon.getIslands().getNumberOfConcurrentIslands(player.getUniqueId(), overworld);
698+
if (count <= 1) {
699+
return overworldName;
700+
}
701+
// Only use island-specific key if the player owns the island
702+
if (island != null && island.getOwner() != null && island.getOwner().equals(player.getUniqueId())) {
703+
return overworldName + "/" + island.getUniqueId();
704+
}
705+
return overworldName;
706+
}
707+
708+
/**
709+
* Clears the stored inventory for a BentoBox world when the player is not currently in
710+
* that world. Called when BentoBox fires a {@code PlayerResetInventoryEvent} while the
711+
* player is in a non-BentoBox world so the player's current inventory is not affected.
712+
* @param player - online player
713+
* @param world - the BentoBox world whose stored inventory should be cleared
714+
* @param island - the island involved in the reset (may be null)
715+
*/
716+
public void clearStoredInventoryForWorld(Player player, World world, Island island) {
717+
InventoryStorage store = getInv(player);
718+
String key = getStorageKeyForEvent(player, world, island);
719+
String worldKey = getOverworldName(world);
720+
Settings settings = addon.getSettings();
721+
if (settings.isInventory()) {
722+
String k = settings.isIslandsInventory() ? key : worldKey;
723+
store.setInventory(k, Collections.emptyList());
724+
}
725+
database.saveObjectAsync(store);
726+
}
727+
728+
/**
729+
* Clears the stored ender chest for a BentoBox world when the player is not currently in
730+
* that world. Called when BentoBox fires a {@code PlayerResetEnderChestEvent} while the
731+
* player is in a non-BentoBox world.
732+
* @param player - online player
733+
* @param world - the BentoBox world whose stored ender chest should be cleared
734+
* @param island - the island involved in the reset (may be null)
735+
*/
736+
public void clearStoredEnderChestForWorld(Player player, World world, Island island) {
737+
InventoryStorage store = getInv(player);
738+
String key = getStorageKeyForEvent(player, world, island);
739+
String worldKey = getOverworldName(world);
740+
Settings settings = addon.getSettings();
741+
if (settings.isEnderChest()) {
742+
String k = settings.isIslandsEnderChest() ? key : worldKey;
743+
store.setEnderChest(k, Collections.emptyList());
744+
}
745+
database.saveObjectAsync(store);
746+
}
747+
748+
/**
749+
* Zeroes the stored experience for a BentoBox world when the player is not currently in
750+
* that world. Called when BentoBox fires a {@code PlayerResetExpEvent} while the
751+
* player is in a non-BentoBox world.
752+
* @param player - online player
753+
* @param world - the BentoBox world whose stored experience should be zeroed
754+
* @param island - the island involved in the reset (may be null)
755+
*/
756+
public void clearStoredExpForWorld(Player player, World world, Island island) {
757+
InventoryStorage store = getInv(player);
758+
String key = getStorageKeyForEvent(player, world, island);
759+
String worldKey = getOverworldName(world);
760+
Settings settings = addon.getSettings();
761+
if (settings.isExperience()) {
762+
String k = settings.isIslandsExperience() ? key : worldKey;
763+
store.setExp(k, 0);
764+
}
765+
database.saveObjectAsync(store);
766+
}
767+
768+
/**
769+
* Removes the stored health for a BentoBox world when the player is not currently in
770+
* that world. Called when BentoBox fires a {@code PlayerResetHealthEvent} while the
771+
* player is in a non-BentoBox world. Removing the entry means the player will receive
772+
* maximum health the next time they enter the world.
773+
* @param player - online player
774+
* @param world - the BentoBox world whose stored health should be removed
775+
* @param island - the island involved in the reset (may be null)
776+
*/
777+
public void clearStoredHealthForWorld(Player player, World world, Island island) {
778+
InventoryStorage store = getInv(player);
779+
String key = getStorageKeyForEvent(player, world, island);
780+
String worldKey = getOverworldName(world);
781+
Settings settings = addon.getSettings();
782+
if (settings.isHealth()) {
783+
String k = settings.isIslandsHealth() ? key : worldKey;
784+
store.getHealth().remove(k);
785+
}
786+
database.saveObjectAsync(store);
787+
}
788+
789+
/**
790+
* Resets the stored food level to full (20) for a BentoBox world when the player is not
791+
* currently in that world. Called when BentoBox fires a {@code PlayerResetHungerEvent}
792+
* while the player is in a non-BentoBox world.
793+
* @param player - online player
794+
* @param world - the BentoBox world whose stored food level should be reset
795+
* @param island - the island involved in the reset (may be null)
796+
*/
797+
public void clearStoredFoodForWorld(Player player, World world, Island island) {
798+
InventoryStorage store = getInv(player);
799+
String key = getStorageKeyForEvent(player, world, island);
800+
String worldKey = getOverworldName(world);
801+
Settings settings = addon.getSettings();
802+
if (settings.isFood()) {
803+
String k = settings.isIslandsFood() ? key : worldKey;
804+
store.setFood(k, 20);
805+
}
806+
database.saveObjectAsync(store);
807+
}
808+
679809
}

src/main/java/com/wasteofplastic/invswitcher/listeners/PlayerListener.java

Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,12 @@
1717
import com.wasteofplastic.invswitcher.InvSwitcher;
1818

1919
import world.bentobox.bentobox.api.events.island.IslandEnterEvent;
20+
import world.bentobox.bentobox.api.events.player.PlayerBaseEvent;
21+
import world.bentobox.bentobox.api.events.player.PlayerResetEnderChestEvent;
22+
import world.bentobox.bentobox.api.events.player.PlayerResetExpEvent;
23+
import world.bentobox.bentobox.api.events.player.PlayerResetHealthEvent;
24+
import world.bentobox.bentobox.api.events.player.PlayerResetHungerEvent;
25+
import world.bentobox.bentobox.api.events.player.PlayerResetInventoryEvent;
2026
import world.bentobox.bentobox.database.objects.Island;
2127
import world.bentobox.bentobox.util.Util;
2228

@@ -183,5 +189,100 @@ public void onPlayerQuit(final PlayerQuitEvent event) {
183189
addon.getStore().removeFromCache(event.getPlayer());
184190
}
185191

192+
/**
193+
* Intercepts BentoBox's inventory reset when the player is not in the BentoBox world.
194+
* Cancels the direct clear and instead wipes the stored inventory data for that world.
195+
* @param event - event
196+
*/
197+
@EventHandler(priority = EventPriority.HIGH, ignoreCancelled = true)
198+
public void onPlayerResetInventory(PlayerResetInventoryEvent event) {
199+
if (!shouldInterceptPlayerReset(event)) return;
200+
event.setCancelled(true);
201+
Player player = Bukkit.getPlayer(event.getPlayerUUID());
202+
if (player != null) {
203+
addon.getStore().clearStoredInventoryForWorld(player, event.getWorld(), event.getIsland());
204+
}
205+
}
206+
207+
/**
208+
* Intercepts BentoBox's ender chest reset when the player is not in the BentoBox world.
209+
* Cancels the direct clear and instead wipes the stored ender chest data for that world.
210+
* @param event - event
211+
*/
212+
@EventHandler(priority = EventPriority.HIGH, ignoreCancelled = true)
213+
public void onPlayerResetEnderChest(PlayerResetEnderChestEvent event) {
214+
if (!shouldInterceptPlayerReset(event)) return;
215+
event.setCancelled(true);
216+
Player player = Bukkit.getPlayer(event.getPlayerUUID());
217+
if (player != null) {
218+
addon.getStore().clearStoredEnderChestForWorld(player, event.getWorld(), event.getIsland());
219+
}
220+
}
221+
222+
/**
223+
* Intercepts BentoBox's experience reset when the player is not in the BentoBox world.
224+
* Cancels the direct clear and instead zeroes the stored experience for that world.
225+
* @param event - event
226+
*/
227+
@EventHandler(priority = EventPriority.HIGH, ignoreCancelled = true)
228+
public void onPlayerResetExp(PlayerResetExpEvent event) {
229+
if (!shouldInterceptPlayerReset(event)) return;
230+
event.setCancelled(true);
231+
Player player = Bukkit.getPlayer(event.getPlayerUUID());
232+
if (player != null) {
233+
addon.getStore().clearStoredExpForWorld(player, event.getWorld(), event.getIsland());
234+
}
235+
}
236+
237+
/**
238+
* Intercepts BentoBox's health reset when the player is not in the BentoBox world.
239+
* Cancels the direct reset and instead removes the stored health for that world
240+
* (so the player receives max health when they next enter the world).
241+
* @param event - event
242+
*/
243+
@EventHandler(priority = EventPriority.HIGH, ignoreCancelled = true)
244+
public void onPlayerResetHealth(PlayerResetHealthEvent event) {
245+
if (!shouldInterceptPlayerReset(event)) return;
246+
event.setCancelled(true);
247+
Player player = Bukkit.getPlayer(event.getPlayerUUID());
248+
if (player != null) {
249+
addon.getStore().clearStoredHealthForWorld(player, event.getWorld(), event.getIsland());
250+
}
251+
}
252+
253+
/**
254+
* Intercepts BentoBox's hunger reset when the player is not in the BentoBox world.
255+
* Cancels the direct reset and instead sets stored food to full (20) for that world.
256+
* @param event - event
257+
*/
258+
@EventHandler(priority = EventPriority.HIGH, ignoreCancelled = true)
259+
public void onPlayerResetHunger(PlayerResetHungerEvent event) {
260+
if (!shouldInterceptPlayerReset(event)) return;
261+
event.setCancelled(true);
262+
Player player = Bukkit.getPlayer(event.getPlayerUUID());
263+
if (player != null) {
264+
addon.getStore().clearStoredFoodForWorld(player, event.getWorld(), event.getIsland());
265+
}
266+
}
267+
268+
/**
269+
* Determines whether InvSwitcher should intercept a BentoBox player reset event.
270+
* Returns true if the event's world is managed by InvSwitcher, the player is online,
271+
* and the player is currently in a different world (not the event world).
272+
* @param event - the reset event
273+
* @return true if InvSwitcher should cancel the event and handle it itself
274+
*/
275+
private boolean shouldInterceptPlayerReset(PlayerBaseEvent event) {
276+
World eventWorld = event.getWorld();
277+
if (!addon.getWorlds().contains(eventWorld)) {
278+
return false;
279+
}
280+
Player player = Bukkit.getPlayer(event.getPlayerUUID());
281+
if (player == null) {
282+
return false;
283+
}
284+
// Only intercept if the player is not currently in the event world
285+
return !Util.sameWorld(player.getWorld(), eventWorld);
286+
}
186287

187288
}

0 commit comments

Comments
 (0)