From 14717bd4c7248284032749a2bf81c62511bf1013 Mon Sep 17 00:00:00 2001 From: ryanhamshire Date: Sat, 16 May 2015 13:00:55 -0700 Subject: [PATCH] Added /ignore and /separate. Also companion commands like /unseparate, /unignore, and /ignorelist. --- plugin.yml | 24 +++ .../GriefPrevention/DataStore.java | 63 ++++++- .../GriefPrevention/DatabaseDataStore.java | 2 +- .../GriefPrevention/FlatFileDataStore.java | 7 +- .../GriefPrevention/GriefPrevention.java | 170 +++++++++++++++++- .../GriefPrevention/IgnoreLoaderThread.java | 85 +++++++++ .../GriefPrevention/Messages.java | 2 +- .../GriefPrevention/PlayerData.java | 8 + .../GriefPrevention/PlayerEventHandler.java | 102 ++++++++--- 9 files changed, 430 insertions(+), 33 deletions(-) create mode 100644 src/me/ryanhamshire/GriefPrevention/IgnoreLoaderThread.java diff --git a/plugin.yml b/plugin.yml index 1cbe935..77bb333 100644 --- a/plugin.yml +++ b/plugin.yml @@ -152,6 +152,26 @@ commands: description: Allows an administrator to get technical information about blocks in the world and items in hand. usage: /GPBlockInfo permission: griefprevention.gpblockinfo + ignoreplayer: + description: Ignores another player's chat messages. + usage: /IgnorePlayer + aliases: [ignore] + unignoreplayer: + description: Unignores another player's chat messages. + usage: /UnIgnorePlayer + aliases: [unignore] + ignoredplayerlist: + description: Lists the players you're ignoring in chat. + usage: /IgnoredPlayerList + aliases: [ignores, ignored, ignorelist, ignoredlist, listignores, listignored, ignoring] + separate: + description: Forces two players to ignore each other in chat. + usage: /Separate + permission: griefprevention.separate + unseparate: + description: Reverses /separate. + usage: /UnSeparate + permission: griefprevention.separate permissions: griefprevention.createclaims: description: Grants permission to create claims. @@ -176,6 +196,7 @@ permissions: griefprevention.transferclaim: true griefprevention.claimslistother: true griefprevention.siegeimmune: true + griefprevention.separate: true griefprevention.siegeimmune: description: Makes a player immune to /Siege. default: op @@ -239,3 +260,6 @@ permissions: griefprevention.overrideclaimcountlimit: description: Allows players to create more claims than the limit specified by the config. default: op + griefprevention.separate: + description: Grants access to /Separate and /UnSeparate. + default: op diff --git a/src/me/ryanhamshire/GriefPrevention/DataStore.java b/src/me/ryanhamshire/GriefPrevention/DataStore.java index dcd186e..a5dba89 100644 --- a/src/me/ryanhamshire/GriefPrevention/DataStore.java +++ b/src/me/ryanhamshire/GriefPrevention/DataStore.java @@ -19,6 +19,9 @@ package me.ryanhamshire.GriefPrevention; import java.io.*; +import java.text.DateFormat; +import java.text.ParseException; +import java.text.SimpleDateFormat; import java.util.*; import java.util.concurrent.ConcurrentHashMap; import java.util.regex.Pattern; @@ -31,6 +34,8 @@ import org.bukkit.configuration.file.YamlConfiguration; import org.bukkit.entity.Player; import org.bukkit.inventory.ItemStack; +import com.google.common.io.Files; + //singleton class which manages all GriefPrevention data (except for config options) public abstract class DataStore { @@ -55,7 +60,8 @@ public abstract class DataStore //path information, for where stuff stored on disk is well... stored protected final static String dataLayerFolderPath = "plugins" + File.separator + "GriefPreventionData"; - final static String configFilePath = dataLayerFolderPath + File.separator + "config.yml"; + final static String playerDataFolderPath = dataLayerFolderPath + File.separator + "PlayerData"; + final static String configFilePath = dataLayerFolderPath + File.separator + "config.yml"; final static String messagesFilePath = dataLayerFolderPath + File.separator + "messages.yml"; final static String softMuteFilePath = dataLayerFolderPath + File.separator + "softMute.txt"; @@ -104,6 +110,13 @@ public abstract class DataStore { GriefPrevention.AddLogEntry(this.claims.size() + " total claims loaded."); + //ensure data folders exist + File playerDataFolder = new File(playerDataFolderPath); + if(!playerDataFolder.exists()) + { + playerDataFolder.mkdirs(); + } + //load up all the messages from messages.yml this.loadMessages(); GriefPrevention.AddLogEntry("Customizable messages loaded."); @@ -756,7 +769,47 @@ public abstract class DataStore new SavePlayerDataThread(playerID, playerData).start(); } - public abstract void asyncSavePlayerData(UUID playerID, PlayerData playerData); + public void asyncSavePlayerData(UUID playerID, PlayerData playerData) + { + //save everything except the ignore list + this.overrideSavePlayerData(playerID, playerData); + + //save the ignore list + if(playerData.ignoreListChanged) + { + StringBuilder fileContent = new StringBuilder(); + try + { + for(UUID uuidKey : playerData.ignoredPlayers.keySet()) + { + Boolean value = playerData.ignoredPlayers.get(uuidKey); + if(value == null) continue; + + //admin-enforced ignores begin with an asterisk + if(value) + { + fileContent.append("*"); + } + + fileContent.append(uuidKey); + fileContent.append("\n"); + } + + //write data to file + File playerDataFile = new File(playerDataFolderPath + File.separator + playerID + ".ignore"); + Files.write(fileContent.toString().trim().getBytes("UTF-8"), playerDataFile); + } + + //if any problem, log it + catch(Exception e) + { + GriefPrevention.AddLogEntry("GriefPrevention: Unexpected exception saving data for player \"" + playerID.toString() + "\": " + e.getMessage()); + e.printStackTrace(); + } + } + } + + abstract void overrideSavePlayerData(UUID playerID, PlayerData playerData); //extends a claim to a new depth //respects the max depth config variable @@ -1275,6 +1328,12 @@ public abstract class DataStore this.addDefault(defaults, Messages.NoChatUntilMove, "Sorry, but you have to move a little more before you can chat. We get lots of spam bots here. :)", null); this.addDefault(defaults, Messages.SiegeImmune, "That player is immune to /siege.", null); this.addDefault(defaults, Messages.SetClaimBlocksSuccess, "Updated accrued claim blocks.", null); + this.addDefault(defaults, Messages.IgnoreConfirmation, "You're now ignoring chat messages from that player.", null); + this.addDefault(defaults, Messages.UnIgnoreConfirmation, "You're no longer ignoring chat messages from that player.", null); + this.addDefault(defaults, Messages.NotIgnoringPlayer, "You're not ignoring that player.", null); + this.addDefault(defaults, Messages.SeparateConfirmation, "Those players will now ignore each other in chat.", null); + this.addDefault(defaults, Messages.UnSeparateConfirmation, "Those players will no longer ignore each other in chat.", null); + this.addDefault(defaults, Messages.NotIgnoringAnyone, "You're not ignoring anyone.", null); //load the config file FileConfiguration config = YamlConfiguration.loadConfiguration(new File(messagesFilePath)); diff --git a/src/me/ryanhamshire/GriefPrevention/DatabaseDataStore.java b/src/me/ryanhamshire/GriefPrevention/DatabaseDataStore.java index 9b0a14d..b41c549 100644 --- a/src/me/ryanhamshire/GriefPrevention/DatabaseDataStore.java +++ b/src/me/ryanhamshire/GriefPrevention/DatabaseDataStore.java @@ -524,7 +524,7 @@ public class DatabaseDataStore extends DataStore //saves changes to player data. MUST be called after you're done making changes, otherwise a reload will lose them @Override - public void asyncSavePlayerData(UUID playerID, PlayerData playerData) + public void overrideSavePlayerData(UUID playerID, PlayerData playerData) { //never save data for the "administrative" account. an empty string for player name indicates administrative account if(playerID == null) return; diff --git a/src/me/ryanhamshire/GriefPrevention/FlatFileDataStore.java b/src/me/ryanhamshire/GriefPrevention/FlatFileDataStore.java index 6271ed2..62f58a9 100644 --- a/src/me/ryanhamshire/GriefPrevention/FlatFileDataStore.java +++ b/src/me/ryanhamshire/GriefPrevention/FlatFileDataStore.java @@ -32,17 +32,15 @@ import com.google.common.io.Files; //manages data stored in the file system public class FlatFileDataStore extends DataStore { - private final static String playerDataFolderPath = dataLayerFolderPath + File.separator + "PlayerData"; private final static String claimDataFolderPath = dataLayerFolderPath + File.separator + "ClaimData"; private final static String nextClaimIdFilePath = claimDataFolderPath + File.separator + "_nextClaimID"; private final static String schemaVersionFilePath = dataLayerFolderPath + File.separator + "_schemaVersion"; static boolean hasData() { - File playerDataFolder = new File(playerDataFolderPath); File claimsDataFolder = new File(claimDataFolderPath); - return playerDataFolder.exists() || claimsDataFolder.exists(); + return claimsDataFolder.exists(); } //initialization! @@ -554,7 +552,7 @@ public class FlatFileDataStore extends DataStore //saves changes to player data. MUST be called after you're done making changes, otherwise a reload will lose them @Override - public void asyncSavePlayerData(UUID playerID, PlayerData playerData) + public void overrideSavePlayerData(UUID playerID, PlayerData playerData) { //never save data for the "administrative" account. null for claim owner ID indicates administrative account if(playerID == null) return; @@ -596,6 +594,7 @@ public class FlatFileDataStore extends DataStore catch(Exception e) { GriefPrevention.AddLogEntry("GriefPrevention: Unexpected exception saving data for player \"" + playerID.toString() + "\": " + e.getMessage()); + e.printStackTrace(); } } diff --git a/src/me/ryanhamshire/GriefPrevention/GriefPrevention.java b/src/me/ryanhamshire/GriefPrevention/GriefPrevention.java index d929aaa..987b832 100644 --- a/src/me/ryanhamshire/GriefPrevention/GriefPrevention.java +++ b/src/me/ryanhamshire/GriefPrevention/GriefPrevention.java @@ -24,6 +24,7 @@ import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; import java.util.List; +import java.util.Map.Entry; import java.util.UUID; import java.util.Vector; import java.util.concurrent.ConcurrentHashMap; @@ -281,7 +282,7 @@ public class GriefPrevention extends JavaPlugin String dataMode = (this.dataStore instanceof FlatFileDataStore)?"(File Mode)":"(Database Mode)"; AddLogEntry("Finished loading data " + dataMode + "."); - //unless claim block accrual is disabled, start the recurring per 5 minute event to give claim blocks to online players + //unless claim block accrual is disabled, start the recurring per 10 minute event to give claim blocks to online players //20L ~ 1 second if(this.config_claims_blocksAccruedPerHour > 0) { @@ -354,6 +355,13 @@ public class GriefPrevention extends JavaPlugin namesThread.setPriority(Thread.MIN_PRIORITY); namesThread.start(); + //load ignore lists for any already-online players + Collection players = (Collection)GriefPrevention.instance.getServer().getOnlinePlayers(); + for(Player player : players) + { + new IgnoreLoaderThread(player.getUniqueId(), this.dataStore.getPlayerData(player.getUniqueId()).ignoredPlayers).start(); + } + AddLogEntry("Boot finished."); } @@ -2049,9 +2057,169 @@ public class GriefPrevention extends JavaPlugin return true; } + //ignoreplayer + else if(cmd.getName().equalsIgnoreCase("ignoreplayer") && player != null) + { + //requires target player name + if(args.length < 1) return false; + + //validate target player + OfflinePlayer targetPlayer = this.resolvePlayerByName(args[0]); + if(targetPlayer == null) + { + GriefPrevention.sendMessage(player, TextMode.Err, Messages.PlayerNotFound2); + return true; + } + + this.setIgnoreStatus(player, targetPlayer, IgnoreMode.StandardIgnore); + + GriefPrevention.sendMessage(player, TextMode.Success, Messages.IgnoreConfirmation); + + return true; + } + + //unignoreplayer + else if(cmd.getName().equalsIgnoreCase("unignoreplayer") && player != null) + { + //requires target player name + if(args.length < 1) return false; + + //validate target player + OfflinePlayer targetPlayer = this.resolvePlayerByName(args[0]); + if(targetPlayer == null) + { + GriefPrevention.sendMessage(player, TextMode.Err, Messages.PlayerNotFound2); + return true; + } + + PlayerData playerData = this.dataStore.getPlayerData(player.getUniqueId()); + Boolean ignoreStatus = playerData.ignoredPlayers.get(targetPlayer.getUniqueId()); + if(ignoreStatus == null || ignoreStatus == true) + { + GriefPrevention.sendMessage(player, TextMode.Err, Messages.NotIgnoringPlayer); + return true; + } + + this.setIgnoreStatus(player, targetPlayer, IgnoreMode.None); + + GriefPrevention.sendMessage(player, TextMode.Success, Messages.UnIgnoreConfirmation); + + return true; + } + + //ignoredplayerlist + else if(cmd.getName().equalsIgnoreCase("ignoredplayerlist") && player != null) + { + PlayerData playerData = this.dataStore.getPlayerData(player.getUniqueId()); + StringBuilder builder = new StringBuilder(); + for(Entry entry : playerData.ignoredPlayers.entrySet()) + { + if(entry.getValue() != null) + { + //if not an admin ignore, add it to the list + if(!entry.getValue()) + { + builder.append(GriefPrevention.lookupPlayerName(entry.getKey())); + builder.append(" "); + } + } + } + + String list = builder.toString().trim(); + if(list.isEmpty()) + { + GriefPrevention.sendMessage(player, TextMode.Info, Messages.NotIgnoringAnyone); + } + else + { + GriefPrevention.sendMessage(player, TextMode.Info, list); + } + + return true; + } + + //separateplayers + else if(cmd.getName().equalsIgnoreCase("separate") && player != null) + { + //requires two player names + if(args.length < 2) return false; + + //validate target players + OfflinePlayer targetPlayer = this.resolvePlayerByName(args[0]); + if(targetPlayer == null) + { + GriefPrevention.sendMessage(player, TextMode.Err, Messages.PlayerNotFound2); + return true; + } + + OfflinePlayer targetPlayer2 = this.resolvePlayerByName(args[1]); + if(targetPlayer2 == null) + { + GriefPrevention.sendMessage(player, TextMode.Err, Messages.PlayerNotFound2); + return true; + } + + this.setIgnoreStatus(targetPlayer, targetPlayer2, IgnoreMode.AdminIgnore); + + GriefPrevention.sendMessage(player, TextMode.Success, Messages.SeparateConfirmation); + + return true; + } + + //unseparateplayers + else if(cmd.getName().equalsIgnoreCase("unseparate") && player != null) + { + //requires two player names + if(args.length < 2) return false; + + //validate target players + OfflinePlayer targetPlayer = this.resolvePlayerByName(args[0]); + if(targetPlayer == null) + { + GriefPrevention.sendMessage(player, TextMode.Err, Messages.PlayerNotFound2); + return true; + } + + OfflinePlayer targetPlayer2 = this.resolvePlayerByName(args[1]); + if(targetPlayer2 == null) + { + GriefPrevention.sendMessage(player, TextMode.Err, Messages.PlayerNotFound2); + return true; + } + + this.setIgnoreStatus(targetPlayer, targetPlayer2, IgnoreMode.None); + this.setIgnoreStatus(targetPlayer2, targetPlayer, IgnoreMode.None); + + GriefPrevention.sendMessage(player, TextMode.Success, Messages.UnSeparateConfirmation); + + return true; + } + return false; } + void setIgnoreStatus(OfflinePlayer ignorer, OfflinePlayer ignoree, IgnoreMode mode) + { + PlayerData playerData = this.dataStore.getPlayerData(ignorer.getUniqueId()); + if(mode == IgnoreMode.None) + { + playerData.ignoredPlayers.remove(ignoree.getUniqueId()); + } + else + { + playerData.ignoredPlayers.put(ignoree.getUniqueId(), mode == IgnoreMode.StandardIgnore ? false : true); + } + + playerData.ignoreListChanged = true; + if(!ignorer.isOnline()) + { + this.dataStore.savePlayerData(ignorer.getUniqueId(), playerData); + this.dataStore.clearCachedPlayerData(ignorer.getUniqueId()); + } + } + + enum IgnoreMode {None, StandardIgnore, AdminIgnore} + private String trustEntryToPlayerName(String entry) { if(entry.startsWith("[") || entry.equals("public")) diff --git a/src/me/ryanhamshire/GriefPrevention/IgnoreLoaderThread.java b/src/me/ryanhamshire/GriefPrevention/IgnoreLoaderThread.java new file mode 100644 index 0000000..8b939ca --- /dev/null +++ b/src/me/ryanhamshire/GriefPrevention/IgnoreLoaderThread.java @@ -0,0 +1,85 @@ +package me.ryanhamshire.GriefPrevention; + +import java.io.File; +import java.nio.charset.Charset; +import java.util.List; +import java.util.UUID; +import java.util.concurrent.ConcurrentHashMap; + +import com.google.common.io.Files; + +//loads ignore data from file into a hash map +class IgnoreLoaderThread extends Thread +{ + private UUID playerToLoad; + private ConcurrentHashMap destinationMap; + + IgnoreLoaderThread(UUID playerToLoad, ConcurrentHashMap destinationMap) + { + this.playerToLoad = playerToLoad; + this.destinationMap = destinationMap; + this.setPriority(MIN_PRIORITY); + } + + @Override + public void run() + { + File ignoreFile = new File(DataStore.playerDataFolderPath + File.separator + this.playerToLoad + ".ignore"); + + //if the file doesn't exist, there's nothing to do here + if(!ignoreFile.exists()) return; + + boolean needRetry = false; + int retriesRemaining = 5; + Exception latestException = null; + do + { + try + { + needRetry = false; + + //read the file content and immediately close it + List lines = Files.readLines(ignoreFile, Charset.forName("UTF-8")); + + //each line is one ignore. asterisks indicate administrative ignores + for(String line : lines) + { + boolean adminIgnore = false; + if(line.startsWith("*")) + { + adminIgnore = true; + line = line.substring(1); + } + try + { + UUID ignoredUUID = UUID.fromString(line); + this.destinationMap.put(ignoredUUID, adminIgnore); + } + catch(IllegalArgumentException e){} //if a bad UUID, ignore the line + } + } + + //if there's any problem with the file's content, retry up to 5 times with 5 milliseconds between + catch(Exception e) + { + latestException = e; + needRetry = true; + retriesRemaining--; + } + + try + { + if(needRetry) Thread.sleep(5); + } + catch(InterruptedException exception) {} + + }while(needRetry && retriesRemaining >= 0); + + //if last attempt failed, log information about the problem + if(needRetry) + { + GriefPrevention.AddLogEntry("Retry attempts exhausted. Unable to load ignore data for player \"" + playerToLoad.toString() + "\": " + latestException.toString()); + latestException.printStackTrace(); + } + } +} \ No newline at end of file diff --git a/src/me/ryanhamshire/GriefPrevention/Messages.java b/src/me/ryanhamshire/GriefPrevention/Messages.java index d817046..25a421b 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, SubdivisionVideo2, 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, CreativeBasicsVideo2, SurvivalBasicsVideo2, 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, AdvertiseACandACB, AdvertiseAdminClaims, AdvertiseACB, NotYourPet, PetGiveawayConfirmation, PetTransferCancellation, ReadyToTransferPet, AvoidGriefClaimLand, BecomeMayor, ClaimCreationFailedOverClaimCountLimit, CreateClaimFailOverlapRegion, ResizeFailOverlapRegion, NoBuildPortalPermission, ShowNearbyClaims, NoChatUntilMove, SiegeImmune, SetClaimBlocksSuccess + 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, SubdivisionVideo2, 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, CreativeBasicsVideo2, SurvivalBasicsVideo2, 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, AdvertiseACandACB, AdvertiseAdminClaims, AdvertiseACB, NotYourPet, PetGiveawayConfirmation, PetTransferCancellation, ReadyToTransferPet, AvoidGriefClaimLand, BecomeMayor, ClaimCreationFailedOverClaimCountLimit, CreateClaimFailOverlapRegion, ResizeFailOverlapRegion, NoBuildPortalPermission, ShowNearbyClaims, NoChatUntilMove, SiegeImmune, SetClaimBlocksSuccess, IgnoreConfirmation, NotIgnoringPlayer, UnIgnoreConfirmation, SeparateConfirmation, UnSeparateConfirmation, NotIgnoringAnyone } diff --git a/src/me/ryanhamshire/GriefPrevention/PlayerData.java b/src/me/ryanhamshire/GriefPrevention/PlayerData.java index e067df3..79701b7 100644 --- a/src/me/ryanhamshire/GriefPrevention/PlayerData.java +++ b/src/me/ryanhamshire/GriefPrevention/PlayerData.java @@ -19,9 +19,12 @@ package me.ryanhamshire.GriefPrevention; import java.net.InetAddress; import java.util.Calendar; +import java.util.Collections; import java.util.Date; +import java.util.Set; import java.util.UUID; import java.util.Vector; +import java.util.concurrent.ConcurrentHashMap; import me.ryanhamshire.GriefPrevention.Claim; import me.ryanhamshire.GriefPrevention.GriefPrevention; @@ -136,6 +139,11 @@ public class PlayerData //this is an anti-bot strategy. Location noChatLocation = null; + //ignore list + //true means invisible (admin-forced ignore), false means player-created ignore + ConcurrentHashMap ignoredPlayers = new ConcurrentHashMap(); + boolean ignoreListChanged = false; + //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 2fc0cb4..0386667 100644 --- a/src/me/ryanhamshire/GriefPrevention/PlayerEventHandler.java +++ b/src/me/ryanhamshire/GriefPrevention/PlayerEventHandler.java @@ -30,6 +30,7 @@ import java.util.UUID; import java.util.concurrent.ConcurrentHashMap; import java.util.regex.Pattern; + import org.bukkit.Achievement; import org.bukkit.ChatColor; import org.bukkit.Chunk; @@ -134,10 +135,32 @@ class PlayerEventHandler implements Listener GriefPrevention.AddLogEntry(notificationMessage, CustomLogEntryTypes.Debug, true); } - //unfiltered messages go to the abridged chat logs + //remaining messages else { + //enter in abridged chat logs this.makeSocialLogEntry(player.getName(), message); + + //based on ignore lists, remove some of the audience + Set recipientsToRemove = new HashSet(); + PlayerData playerData = this.dataStore.getPlayerData(player.getUniqueId()); + for(Player recipient : recipients) + { + if(playerData.ignoredPlayers.containsKey(recipient.getUniqueId())) + { + recipientsToRemove.add(recipient); + } + else + { + PlayerData targetPlayerData = this.dataStore.getPlayerData(recipient.getUniqueId()); + if(targetPlayerData.ignoredPlayers.containsKey(player.getUniqueId())) + { + recipientsToRemove.add(recipient); + } + } + } + + recipients.removeAll(recipientsToRemove); } } @@ -419,32 +442,61 @@ class PlayerEventHandler implements Listener { String [] args = event.getMessage().split(" "); - //if eavesdrop enabled, eavesdrop String command = args[0].toLowerCase(); - if(GriefPrevention.instance.config_whisperNotifications && GriefPrevention.instance.config_eavesdrop_whisperCommands.contains(command) && !event.getPlayer().hasPermission("griefprevention.eavesdrop") && args.length > 1) - { - StringBuilder logMessageBuilder = new StringBuilder(); - logMessageBuilder.append("[[").append(event.getPlayer().getName()).append("]] "); - - for(int i = 1; i < args.length; i++) - { - logMessageBuilder.append(args[i]).append(" "); - } - - String logMessage = logMessageBuilder.toString(); - - Collection players = (Collection)GriefPrevention.instance.getServer().getOnlinePlayers(); - for(Player player : players) - { - if(player.hasPermission("griefprevention.eavesdrop") && !player.getName().equalsIgnoreCase(args[1])) - { - player.sendMessage(ChatColor.GRAY + logMessage); - } - } + + Player player = event.getPlayer(); + PlayerData playerData = null; + + //if a whisper + if(GriefPrevention.instance.config_eavesdrop_whisperCommands.contains(command) && args.length > 1) + { + //if eavesdrop enabled, eavesdrop + if(GriefPrevention.instance.config_whisperNotifications && !event.getPlayer().hasPermission("griefprevention.eavesdrop")) + { + StringBuilder logMessageBuilder = new StringBuilder(); + logMessageBuilder.append("[[").append(event.getPlayer().getName()).append("]] "); + + for(int i = 1; i < args.length; i++) + { + logMessageBuilder.append(args[i]).append(" "); + } + + String logMessage = logMessageBuilder.toString(); + + Collection players = (Collection)GriefPrevention.instance.getServer().getOnlinePlayers(); + for(Player onlinePlayer : players) + { + if(onlinePlayer.hasPermission("griefprevention.eavesdrop") && !onlinePlayer.getName().equalsIgnoreCase(args[1])) + { + onlinePlayer.sendMessage(ChatColor.GRAY + logMessage); + } + } + } + + //determine target player + Player targetPlayer = GriefPrevention.instance.getServer().getPlayer(args[1]); + if(targetPlayer != null && targetPlayer.isOnline()) + { + //if either is ignoring the other, cancel this command + playerData = this.dataStore.getPlayerData(player.getUniqueId()); + if(playerData.ignoredPlayers.containsKey(targetPlayer.getUniqueId())) + { + event.setCancelled(true); + return; + } + + PlayerData targetPlayerData = this.dataStore.getPlayerData(targetPlayer.getUniqueId()); + if(targetPlayerData.ignoredPlayers.containsKey(player.getUniqueId())) + { + event.setCancelled(true); + return; + } + } } //if in pvp, block any pvp-banned slash commands - PlayerData playerData = this.dataStore.getPlayerData(event.getPlayer().getUniqueId()); + if(playerData == null) playerData = this.dataStore.getPlayerData(event.getPlayer().getUniqueId()); + if((playerData.inPvpCombat() || playerData.siegeData != null) && GriefPrevention.instance.config_pvp_blockedCommands.contains(command)) { event.setCancelled(true); @@ -497,7 +549,6 @@ class PlayerEventHandler implements Listener if(isMonitoredCommand) { - Player player = event.getPlayer(); Claim claim = this.dataStore.getClaimAt(player.getLocation(), false, playerData.lastClaim); if(claim != null) { @@ -713,6 +764,9 @@ class PlayerEventHandler implements Listener } } } + + //create a thread to load ignore information + new IgnoreLoaderThread(playerID, playerData.ignoredPlayers).start(); } //when a player spawns, conditionally apply temporary pvp protection