diff --git a/plugin.yml b/plugin.yml index 496e085..5b64f76 100644 --- a/plugin.yml +++ b/plugin.yml @@ -119,10 +119,9 @@ commands: description: Converts an administrative claim to a private claim. usage: /TransferClaim permission: griefprevention.adjustclaimblocks - deathblow: - description: Kills a player, optionally giving his inventory to another player. - usage: /DeathBlow [recipientPlayer] - permission: griefprevention.deathblow + unlockdrops: + description: Allows other players to pick up the items you dropped when you died. + usage: /UnlockDrops claimslist: description: Lists information about a player's claim blocks and claims. usage: /ClaimsList or /ClaimsList diff --git a/src/me/ryanhamshire/GriefPrevention/DataStore.java b/src/me/ryanhamshire/GriefPrevention/DataStore.java index 9571561..8b07646 100644 --- a/src/me/ryanhamshire/GriefPrevention/DataStore.java +++ b/src/me/ryanhamshire/GriefPrevention/DataStore.java @@ -1166,6 +1166,9 @@ public abstract class DataStore this.addDefault(defaults, Messages.NoPistonsOutsideClaims, "Warning: Pistons won't move blocks outside land claims.", null); this.addDefault(defaults, Messages.SoftMuted, "Soft-muted {0}.", "0: The changed player's name."); this.addDefault(defaults, Messages.UnSoftMuted, "Un-soft-muted {0}.", "0: The changed player's name."); + this.addDefault(defaults, Messages.DropUnlockAdvertisement, "Other players can't pick up your dropped items unless you /UnlockDrops first.", null); + this.addDefault(defaults, Messages.PickupBlockedExplanation, "You can't pick this up unless {0} uses /UnlockDrops.", "0: The item stack's owner."); + this.addDefault(defaults, Messages.DropUnlockConfirmation, "Unlocked your drops. Other players may now pick them up (until you die again).", null); //load the config file FileConfiguration config = YamlConfiguration.loadConfiguration(new File(messagesFilePath)); diff --git a/src/me/ryanhamshire/GriefPrevention/EntityEventHandler.java b/src/me/ryanhamshire/GriefPrevention/EntityEventHandler.java index 2efde08..6055412 100644 --- a/src/me/ryanhamshire/GriefPrevention/EntityEventHandler.java +++ b/src/me/ryanhamshire/GriefPrevention/EntityEventHandler.java @@ -23,6 +23,7 @@ import java.util.List; import org.bukkit.Location; import org.bukkit.Material; +import org.bukkit.World; import org.bukkit.World.Environment; import org.bukkit.block.Block; import org.bukkit.block.Hopper; @@ -62,6 +63,7 @@ import org.bukkit.event.inventory.InventoryMoveItemEvent; import org.bukkit.event.vehicle.VehicleDamageEvent; import org.bukkit.inventory.Inventory; import org.bukkit.inventory.InventoryHolder; +import org.bukkit.inventory.ItemStack; //handles events related to entities class EntityEventHandler implements Listener @@ -217,7 +219,32 @@ class EntityEventHandler implements Listener { LivingEntity entity = event.getEntity(); - //don't track in worlds where claims are not enabled + //FEATURE: lock dropped items to player who dropped them + + if(entity instanceof Player) + { + Player player = (Player)entity; + World world = entity.getWorld(); + + //decide whether or not to apply this feature to this situation (depends on the world wher it happens) + boolean isPvPWorld = GriefPrevention.instance.config_pvp_enabledWorlds.contains(world); + if((isPvPWorld && GriefPrevention.instance.config_lockDeathDropsInPvpWorlds) || + (!isPvPWorld && GriefPrevention.instance.config_lockDeathDropsInNonPvpWorlds)) + { + //mark the dropped stacks with player's UUID + List drops = event.getDrops(); + for(ItemStack stack : drops) + { + GriefPrevention.instance.itemStackOwnerMap.put(stack, player.getUniqueId()); + } + + //allow the player to receive a message about how to unlock any drops + PlayerData playerData = this.dataStore.getPlayerData(player.getUniqueId()); + playerData.receivedDropUnlockAdvertisement = false; + } + } + + //don't do the rest in worlds where claims are not enabled if(!GriefPrevention.instance.claimsEnabledForWorld(entity.getWorld())) return; //special rule for creative worlds: killed entities don't drop items or experience orbs diff --git a/src/me/ryanhamshire/GriefPrevention/GriefPrevention.java b/src/me/ryanhamshire/GriefPrevention/GriefPrevention.java index 699aac5..4ff05b1 100644 --- a/src/me/ryanhamshire/GriefPrevention/GriefPrevention.java +++ b/src/me/ryanhamshire/GriefPrevention/GriefPrevention.java @@ -64,6 +64,9 @@ public class GriefPrevention extends JavaPlugin //this handles data storage, like player and region data public DataStore dataStore; + //this remembers which item stacks dropped in the world belong to which players + ConcurrentHashMap itemStackOwnerMap = new ConcurrentHashMap(); + //configuration variables, loaded/saved from a config.yml //claim mode for each world @@ -121,6 +124,9 @@ public class GriefPrevention extends JavaPlugin public boolean config_pvp_noCombatInAdminLandClaims; //whether players may fight in admin-owned land claims public boolean config_pvp_noCombatInAdminSubdivisions; //whether players may fight in subdivisions of admin-owned land claims + public boolean config_lockDeathDropsInPvpWorlds; //whether players' dropped on death items are protected in pvp worlds + public boolean config_lockDeathDropsInNonPvpWorlds; //whether players' dropped on death items are protected in non-pvp worlds + public double config_economy_claimBlocksPurchaseCost; //cost to purchase a claim block. set to zero to disable purchase. public double config_economy_claimBlocksSellValue; //return on a sold claim block. set to zero to disable sale. @@ -527,6 +533,9 @@ public class GriefPrevention extends JavaPlugin this.config_economy_claimBlocksPurchaseCost = config.getDouble("GriefPrevention.Economy.ClaimBlocksPurchaseCost", 0); this.config_economy_claimBlocksSellValue = config.getDouble("GriefPrevention.Economy.ClaimBlocksSellValue", 0); + this.config_lockDeathDropsInPvpWorlds = config.getBoolean("GriefPrevention.ProtectItemsDroppedOnDeath.PvPWorlds", false); + this.config_lockDeathDropsInNonPvpWorlds = config.getBoolean("GriefPrevention.ProtectItemsDroppedOnDeath.NonPvPWorlds", true); + this.config_blockSurfaceCreeperExplosions = config.getBoolean("GriefPrevention.BlockSurfaceCreeperExplosions", true); this.config_blockSurfaceOtherExplosions = config.getBoolean("GriefPrevention.BlockSurfaceOtherExplosions", true); this.config_blockSkyTrees = config.getBoolean("GriefPrevention.LimitSkyTrees", true); @@ -737,6 +746,9 @@ public class GriefPrevention extends JavaPlugin outConfig.set("GriefPrevention.Economy.ClaimBlocksPurchaseCost", this.config_economy_claimBlocksPurchaseCost); outConfig.set("GriefPrevention.Economy.ClaimBlocksSellValue", this.config_economy_claimBlocksSellValue); + outConfig.set("GriefPrevention.ProtectItemsDroppedOnDeath.PvPWorlds", this.config_lockDeathDropsInPvpWorlds); + outConfig.set("GriefPrevention.ProtectItemsDroppedOnDeath.NonPvPWorlds", this.config_lockDeathDropsInNonPvpWorlds); + outConfig.set("GriefPrevention.BlockSurfaceCreeperExplosions", this.config_blockSurfaceCreeperExplosions); outConfig.set("GriefPrevention.BlockSurfaceOtherExplosions", this.config_blockSurfaceOtherExplosions); outConfig.set("GriefPrevention.LimitSkyTrees", this.config_blockSkyTrees); @@ -1622,62 +1634,12 @@ public class GriefPrevention extends JavaPlugin return true; } - //deathblow [recipientPlayer] - else if(cmd.getName().equalsIgnoreCase("deathblow")) + //unlockItems + else if(cmd.getName().equalsIgnoreCase("unlockdrops") && player != null) { - //requires at least one parameter, the target player's name - if(args.length < 1) return false; - - //try to find that player - Player targetPlayer = this.getServer().getPlayer(args[0]); - if(targetPlayer == null) - { - GriefPrevention.sendMessage(player, TextMode.Err, Messages.PlayerNotFound2); - return true; - } - - //try to find the recipient player, if specified - Player recipientPlayer = null; - if(args.length > 1) - { - recipientPlayer = this.getServer().getPlayer(args[1]); - if(recipientPlayer == null) - { - GriefPrevention.sendMessage(player, TextMode.Err, Messages.PlayerNotFound2); - return true; - } - } - - //if giving inventory to another player, teleport the target player to that receiving player - if(recipientPlayer != null) - { - targetPlayer.teleport(recipientPlayer); - } - - //otherwise, plan to "pop" the player in place - else - { - //if in a normal world, shoot him up to the sky first, so his items will fall on the surface. - if(targetPlayer.getWorld().getEnvironment() == Environment.NORMAL) - { - Location location = targetPlayer.getLocation(); - location.setY(location.getWorld().getMaxHeight()); - targetPlayer.teleport(location); - } - } - - //kill target player - targetPlayer.setHealth(0); - - //log entry - if(player != null) - { - GriefPrevention.AddLogEntry(player.getName() + " used /DeathBlow to kill " + targetPlayer.getName() + "."); - } - else - { - GriefPrevention.AddLogEntry("Killed " + targetPlayer.getName() + "."); - } + PlayerData playerData = this.dataStore.getPlayerData(player.getUniqueId()); + playerData.dropsAreUnlocked = true; + GriefPrevention.sendMessage(player, TextMode.Success, Messages.DropUnlockConfirmation); return true; } @@ -2207,6 +2169,7 @@ public class GriefPrevention extends JavaPlugin //helper method to resolve a player by name ConcurrentHashMap playerNameToIDMap = new ConcurrentHashMap(); + private OfflinePlayer resolvePlayerByName(String name) { //try online players first diff --git a/src/me/ryanhamshire/GriefPrevention/Messages.java b/src/me/ryanhamshire/GriefPrevention/Messages.java index a94c0a4..5aca5f0 100644 --- a/src/me/ryanhamshire/GriefPrevention/Messages.java +++ b/src/me/ryanhamshire/GriefPrevention/Messages.java @@ -20,5 +20,5 @@ package me.ryanhamshire.GriefPrevention; public enum Messages { - RespectingClaims, IgnoringClaims, SuccessfulAbandon, RestoreNatureActivate, RestoreNatureAggressiveActivate, FillModeActive, TransferClaimPermission, TransferClaimMissing, TransferClaimAdminOnly, PlayerNotFound2, TransferTopLevel, TransferSuccess, TrustListNoClaim, ClearPermsOwnerOnly, UntrustIndividualAllClaims, UntrustEveryoneAllClaims, NoPermissionTrust, ClearPermissionsOneClaim, UntrustIndividualSingleClaim, OnlySellBlocks, BlockPurchaseCost, ClaimBlockLimit, InsufficientFunds, PurchaseConfirmation, OnlyPurchaseBlocks, BlockSaleValue, NotEnoughBlocksForSale, BlockSaleConfirmation, AdminClaimsMode, BasicClaimsMode, SubdivisionMode, SubdivisionVideo, DeleteClaimMissing, DeletionSubdivisionWarning, DeleteSuccess, CantDeleteAdminClaim, DeleteAllSuccess, NoDeletePermission, AllAdminDeleted, AdjustBlocksSuccess, NotTrappedHere, RescuePending, NonSiegeWorld, AlreadySieging, NotSiegableThere, SiegeTooFarAway, NoSiegeDefenseless, AlreadyUnderSiegePlayer, AlreadyUnderSiegeArea, NoSiegeAdminClaim, SiegeOnCooldown, SiegeAlert, SiegeConfirmed, AbandonClaimMissing, NotYourClaim, DeleteTopLevelClaim, AbandonSuccess, CantGrantThatPermission, GrantPermissionNoClaim, GrantPermissionConfirmation, ManageUniversalPermissionsInstruction, ManageOneClaimPermissionsInstruction, CollectivePublic, BuildPermission, ContainersPermission, AccessPermission, PermissionsPermission, LocationCurrentClaim, LocationAllClaims, PvPImmunityStart, SiegeNoDrop, DonateItemsInstruction, ChestFull, DonationSuccess, PlayerTooCloseForFire, TooDeepToClaim, ChestClaimConfirmation, AutomaticClaimNotification, UnprotectedChestWarning, ThatPlayerPvPImmune, CantFightWhileImmune, NoDamageClaimedEntity, ShovelBasicClaimMode, RemainingBlocks, CreativeBasicsVideo, SurvivalBasicsVideo, TrappedChatKeyword, TrappedInstructions, PvPNoDrop, SiegeNoTeleport, BesiegedNoTeleport, SiegeNoContainers, PvPNoContainers, PvPImmunityEnd, NoBedPermission, NoWildernessBuckets, NoLavaNearOtherPlayer, TooFarAway, BlockNotClaimed, BlockClaimed, SiegeNoShovel, RestoreNaturePlayerInChunk, NoCreateClaimPermission, ResizeClaimTooSmall, ResizeNeedMoreBlocks, NoCreativeUnClaim, ClaimResizeSuccess, ResizeFailOverlap, ResizeStart, ResizeFailOverlapSubdivision, SubdivisionStart, CreateSubdivisionOverlap, SubdivisionSuccess, CreateClaimFailOverlap, CreateClaimFailOverlapOtherPlayer, ClaimsDisabledWorld, ClaimStart, NewClaimTooSmall, CreateClaimInsufficientBlocks, AbandonClaimAdvertisement, CreateClaimFailOverlapShort, CreateClaimSuccess, SiegeWinDoorsOpen, RescueAbortedMoved, SiegeDoorsLockedEjection, NoModifyDuringSiege, OnlyOwnersModifyClaims, NoBuildUnderSiege, NoBuildPvP, NoBuildPermission, NonSiegeMaterial, NoOwnerBuildUnderSiege, NoAccessPermission, NoContainersSiege, NoContainersPermission, OwnerNameForAdminClaims, ClaimTooSmallForEntities, TooManyEntitiesInClaim, YouHaveNoClaims, ConfirmFluidRemoval, AutoBanNotify, AdjustGroupBlocksSuccess, InvalidPermissionID, UntrustOwnerOnly, HowToClaimRegex, NoBuildOutsideClaims, PlayerOfflineTime, BuildingOutsideClaims, TrappedWontWorkHere, CommandBannedInPvP, UnclaimCleanupWarning, BuySellNotConfigured, NoTeleportPvPCombat, NoTNTDamageAboveSeaLevel, NoTNTDamageClaims, IgnoreClaimsAdvertisement, NoPermissionForCommand, ClaimsListNoPermission, ExplosivesDisabled, ExplosivesEnabled, ClaimExplosivesAdvertisement, PlayerInPvPSafeZone, NoPistonsOutsideClaims, SoftMuted, UnSoftMuted + RespectingClaims, IgnoringClaims, SuccessfulAbandon, RestoreNatureActivate, RestoreNatureAggressiveActivate, FillModeActive, TransferClaimPermission, TransferClaimMissing, TransferClaimAdminOnly, PlayerNotFound2, TransferTopLevel, TransferSuccess, TrustListNoClaim, ClearPermsOwnerOnly, UntrustIndividualAllClaims, UntrustEveryoneAllClaims, NoPermissionTrust, ClearPermissionsOneClaim, UntrustIndividualSingleClaim, OnlySellBlocks, BlockPurchaseCost, ClaimBlockLimit, InsufficientFunds, PurchaseConfirmation, OnlyPurchaseBlocks, BlockSaleValue, NotEnoughBlocksForSale, BlockSaleConfirmation, AdminClaimsMode, BasicClaimsMode, SubdivisionMode, SubdivisionVideo, DeleteClaimMissing, DeletionSubdivisionWarning, DeleteSuccess, CantDeleteAdminClaim, DeleteAllSuccess, NoDeletePermission, AllAdminDeleted, AdjustBlocksSuccess, NotTrappedHere, RescuePending, NonSiegeWorld, AlreadySieging, NotSiegableThere, SiegeTooFarAway, NoSiegeDefenseless, AlreadyUnderSiegePlayer, AlreadyUnderSiegeArea, NoSiegeAdminClaim, SiegeOnCooldown, SiegeAlert, SiegeConfirmed, AbandonClaimMissing, NotYourClaim, DeleteTopLevelClaim, AbandonSuccess, CantGrantThatPermission, GrantPermissionNoClaim, GrantPermissionConfirmation, ManageUniversalPermissionsInstruction, ManageOneClaimPermissionsInstruction, CollectivePublic, BuildPermission, ContainersPermission, AccessPermission, PermissionsPermission, LocationCurrentClaim, LocationAllClaims, PvPImmunityStart, SiegeNoDrop, DonateItemsInstruction, ChestFull, DonationSuccess, PlayerTooCloseForFire, TooDeepToClaim, ChestClaimConfirmation, AutomaticClaimNotification, UnprotectedChestWarning, ThatPlayerPvPImmune, CantFightWhileImmune, NoDamageClaimedEntity, ShovelBasicClaimMode, RemainingBlocks, CreativeBasicsVideo, SurvivalBasicsVideo, TrappedChatKeyword, TrappedInstructions, PvPNoDrop, SiegeNoTeleport, BesiegedNoTeleport, SiegeNoContainers, PvPNoContainers, PvPImmunityEnd, NoBedPermission, NoWildernessBuckets, NoLavaNearOtherPlayer, TooFarAway, BlockNotClaimed, BlockClaimed, SiegeNoShovel, RestoreNaturePlayerInChunk, NoCreateClaimPermission, ResizeClaimTooSmall, ResizeNeedMoreBlocks, NoCreativeUnClaim, ClaimResizeSuccess, ResizeFailOverlap, ResizeStart, ResizeFailOverlapSubdivision, SubdivisionStart, CreateSubdivisionOverlap, SubdivisionSuccess, CreateClaimFailOverlap, CreateClaimFailOverlapOtherPlayer, ClaimsDisabledWorld, ClaimStart, NewClaimTooSmall, CreateClaimInsufficientBlocks, AbandonClaimAdvertisement, CreateClaimFailOverlapShort, CreateClaimSuccess, SiegeWinDoorsOpen, RescueAbortedMoved, SiegeDoorsLockedEjection, NoModifyDuringSiege, OnlyOwnersModifyClaims, NoBuildUnderSiege, NoBuildPvP, NoBuildPermission, NonSiegeMaterial, NoOwnerBuildUnderSiege, NoAccessPermission, NoContainersSiege, NoContainersPermission, OwnerNameForAdminClaims, ClaimTooSmallForEntities, TooManyEntitiesInClaim, YouHaveNoClaims, ConfirmFluidRemoval, AutoBanNotify, AdjustGroupBlocksSuccess, InvalidPermissionID, UntrustOwnerOnly, HowToClaimRegex, NoBuildOutsideClaims, PlayerOfflineTime, BuildingOutsideClaims, TrappedWontWorkHere, CommandBannedInPvP, UnclaimCleanupWarning, BuySellNotConfigured, NoTeleportPvPCombat, NoTNTDamageAboveSeaLevel, NoTNTDamageClaims, IgnoreClaimsAdvertisement, NoPermissionForCommand, ClaimsListNoPermission, ExplosivesDisabled, ExplosivesEnabled, ClaimExplosivesAdvertisement, PlayerInPvPSafeZone, NoPistonsOutsideClaims, SoftMuted, UnSoftMuted, DropUnlockAdvertisement, PickupBlockedExplanation, DropUnlockConfirmation } diff --git a/src/me/ryanhamshire/GriefPrevention/PlayerData.java b/src/me/ryanhamshire/GriefPrevention/PlayerData.java index 69b9fd6..102a645 100644 --- a/src/me/ryanhamshire/GriefPrevention/PlayerData.java +++ b/src/me/ryanhamshire/GriefPrevention/PlayerData.java @@ -108,6 +108,15 @@ public class PlayerData public boolean warnedAboutMajorDeletion = false; public InetAddress ipAddress; + + //whether or not this player has received a message about unlocking death drops since his last death + boolean receivedDropUnlockAdvertisement = false; + + //whether or not this player's dropped items (on death) are unlocked for other players to pick up + boolean dropsAreUnlocked = false; + + //message to send to player after he respawns + String messageOnRespawn = null; //whether or not this player is "in" pvp combat public boolean inPvpCombat() diff --git a/src/me/ryanhamshire/GriefPrevention/PlayerEventHandler.java b/src/me/ryanhamshire/GriefPrevention/PlayerEventHandler.java index 1ccf8d7..e84663d 100644 --- a/src/me/ryanhamshire/GriefPrevention/PlayerEventHandler.java +++ b/src/me/ryanhamshire/GriefPrevention/PlayerEventHandler.java @@ -594,9 +594,17 @@ class PlayerEventHandler implements Listener @EventHandler(ignoreCancelled = true) void onPlayerRespawn (PlayerRespawnEvent event) { - PlayerData playerData = GriefPrevention.instance.dataStore.getPlayerData(event.getPlayer().getUniqueId()); + Player player = event.getPlayer(); + PlayerData playerData = GriefPrevention.instance.dataStore.getPlayerData(player.getUniqueId()); playerData.lastSpawn = Calendar.getInstance().getTimeInMillis(); - GriefPrevention.instance.checkPvpProtectionNeeded(event.getPlayer()); + GriefPrevention.instance.checkPvpProtectionNeeded(player); + + //also send him any messaged from grief prevention he would have received while dead + if(playerData.messageOnRespawn != null) + { + GriefPrevention.sendMessage(player, ChatColor.RESET /*color is alrady embedded in message in this case*/, playerData.messageOnRespawn, 40L); + playerData.messageOnRespawn = null; + } } //when a player dies... @@ -612,6 +620,10 @@ class PlayerEventHandler implements Listener } playerData.lastDeathTimeStamp = now; + + //these are related to locking dropped items on death to prevent theft + playerData.dropsAreUnlocked = false; + playerData.receivedDropUnlockAdvertisement = false; } //when a player gets kicked... @@ -906,7 +918,44 @@ class PlayerEventHandler implements Listener { Player player = event.getPlayer(); - if(!event.getPlayer().getWorld().getPVP()) return; + //FEATURE: lock dropped items to player who dropped them + + //who owns this stack? + ItemStack stack = event.getItem().getItemStack(); + UUID ownerID = GriefPrevention.instance.itemStackOwnerMap.get(stack); + if(ownerID != null) + { + //has that player unlocked his drops? + OfflinePlayer owner = GriefPrevention.instance.getServer().getOfflinePlayer(ownerID); + String ownerName = GriefPrevention.lookupPlayerName(ownerID); + if(owner.isOnline() && !player.equals(owner)) + { + PlayerData playerData = this.dataStore.getPlayerData(ownerID); + + //if locked, don't allow pickup + if(!playerData.dropsAreUnlocked) + { + event.setCancelled(true); + + //if hasn't been instructed how to unlock, send explanatory messages + if(!playerData.receivedDropUnlockAdvertisement) + { + GriefPrevention.sendMessage(owner.getPlayer(), TextMode.Instr, Messages.DropUnlockAdvertisement); + GriefPrevention.sendMessage(player, TextMode.Err, Messages.PickupBlockedExplanation, ownerName); + playerData.receivedDropUnlockAdvertisement = true; + } + } + } + + //if allowed to pick up, remove from ownership map + if(!event.isCancelled()) + { + GriefPrevention.instance.itemStackOwnerMap.remove(stack); + } + } + + //the rest of this code is specific to pvp worlds + if(!GriefPrevention.instance.config_pvp_enabledWorlds.contains(player.getWorld())) return; //if we're preventing spawn camping and the player was previously empty handed... if(GriefPrevention.instance.config_pvp_protectFreshSpawns && (player.getItemInHand().getType() == Material.AIR)) diff --git a/src/me/ryanhamshire/GriefPrevention/SendPlayerMessageTask.java b/src/me/ryanhamshire/GriefPrevention/SendPlayerMessageTask.java index ce1c509..feb970b 100644 --- a/src/me/ryanhamshire/GriefPrevention/SendPlayerMessageTask.java +++ b/src/me/ryanhamshire/GriefPrevention/SendPlayerMessageTask.java @@ -39,6 +39,17 @@ class SendPlayerMessageTask implements Runnable @Override public void run() { - GriefPrevention.sendMessage(this.player, this.color, this.message); + //if the player is dead, save it for after his respawn + if(this.player.isDead()) + { + PlayerData playerData = GriefPrevention.instance.dataStore.getPlayerData(this.player.getUniqueId()); + playerData.messageOnRespawn = this.color + this.message; + } + + //otherwise send it immediately + else + { + GriefPrevention.sendMessage(this.player, this.color, this.message); + } } }