+ * The drops list must not be touched here: taking the items out of the event and dropping
+ * them by hand breaks every plugin that stores death drops (death chests, graves, keep
+ * inventory). Instead this runs at MONITOR, after those plugins have decided what happens
+ * to the items, and only notes the death spot. Whatever the server then actually drops is
+ * picked up in {@link #onDeathDropSpawn(ItemSpawnEvent)} and tracked from there.
*
* @param event event
*/
- @EventHandler(priority = EventPriority.NORMAL, ignoreCancelled = true)
+ @EventHandler(priority = EventPriority.MONITOR)
public void onPlayerDeath(PlayerDeathEvent event) {
- if (addon.getSettings().isBounceBack()
- && addon.inGameWorld(event.getPlayer().getWorld())
- && isOn(event.getPlayer())) {
- Location loc = event.getPlayer().getLocation();
- addon.getIslands().getIslandAt(Objects.requireNonNull(loc)).ifPresent(is -> {
- event.getDrops().forEach(item -> trackItem(event.getPlayer().getWorld().dropItemNaturally(loc, item), is));
- event.getDrops().clear(); // We handled them
- });
+ if (!addon.getSettings().isBounceBack()
+ || event.getDrops().isEmpty()
+ || !addon.inGameWorld(event.getPlayer().getWorld())
+ || !isOn(event.getPlayer())) {
+ return;
+ }
+ Location loc = event.getPlayer().getLocation();
+ addon.getIslands().getIslandAt(Objects.requireNonNull(loc)).ifPresent(is -> {
+ DeathDrops deathDrops = new DeathDrops(loc, is);
+ pendingDeathDrops.add(deathDrops);
+ // The server spawns the drops in this tick, straight after the event returns
+ Bukkit.getScheduler().runTask(addon.getPlugin(), () -> pendingDeathDrops.remove(deathDrops));
+ });
+ }
+
+ /**
+ * Tracks death drops as the server spawns them, so they cannot leave the island's
+ * protection zone. Items spawning this tick near a recorded death spot are the drops the
+ * server kept after every plugin had its say on the death event.
+ *
+ * @param event event
+ */
+ @EventHandler(priority = EventPriority.NORMAL, ignoreCancelled = true)
+ public void onDeathDropSpawn(ItemSpawnEvent event) {
+ if (pendingDeathDrops.isEmpty()) {
+ return;
}
+ Location loc = event.getLocation();
+ pendingDeathDrops.stream()
+ .filter(dd -> Objects.equals(loc.getWorld(), dd.location().getWorld()))
+ .filter(dd -> loc.distanceSquared(dd.location()) <= DEATH_DROP_RADIUS_SQUARED)
+ .findFirst()
+ .ifPresent(dd -> trackItem(event.getEntity(), dd.island()));
}
/**
diff --git a/src/main/resources/addon.yml b/src/main/resources/addon.yml
index aca8f56..a0d2570 100644
--- a/src/main/resources/addon.yml
+++ b/src/main/resources/addon.yml
@@ -15,6 +15,18 @@ permissions:
'[gamemode].border.type':
description: Player can use border type setting command
default: true
- '[gamemode].bordertype':
+ '[gamemode].border.bordertype':
description: Player can use bordertype command to change the border type
- default: false
\ No newline at end of file
+ default: false
+ '[gamemode].border.color':
+ description: Player can use border color setting command
+ default: true
+ '[gamemode].border.color.red':
+ description: Player can set their border color to red
+ default: op
+ '[gamemode].border.color.green':
+ description: Player can set their border color to green
+ default: op
+ '[gamemode].border.color.blue':
+ description: Player can set their border color to blue
+ default: op
\ No newline at end of file
diff --git a/src/test/java/world/bentobox/border/listeners/PlayerListenerTest.java b/src/test/java/world/bentobox/border/listeners/PlayerListenerTest.java
index ed3d562..5ee7391 100644
--- a/src/test/java/world/bentobox/border/listeners/PlayerListenerTest.java
+++ b/src/test/java/world/bentobox/border/listeners/PlayerListenerTest.java
@@ -1,5 +1,6 @@
package world.bentobox.border.listeners;
+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;
@@ -11,6 +12,7 @@
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
+import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import java.util.UUID;
@@ -19,8 +21,12 @@
import org.bukkit.Bukkit;
import org.bukkit.GameMode;
import org.bukkit.Location;
+import org.bukkit.entity.Item;
import org.bukkit.entity.Player;
import org.bukkit.entity.Vehicle;
+import org.bukkit.event.entity.ItemSpawnEvent;
+import org.bukkit.event.entity.PlayerDeathEvent;
+import org.bukkit.inventory.ItemStack;
import org.bukkit.event.player.PlayerJoinEvent;
import org.bukkit.event.player.PlayerMoveEvent;
import org.bukkit.event.player.PlayerQuitEvent;
@@ -74,6 +80,16 @@ class PlayerListenerTest extends CommonTestSetup {
private Vehicle vehicle;
@Mock
private GameModeAddon gma;
+ @Mock
+ private PlayerDeathEvent deathEvent;
+ @Mock
+ private ItemSpawnEvent itemSpawnEvent;
+ @Mock
+ private Item item;
+ @Mock
+ private ItemStack itemStack;
+ @Mock
+ private Location farAway;
private MockedStatic