From 9c1094b95b3f9a56919b6f03526aa4ff0de2e5f1 Mon Sep 17 00:00:00 2001 From: ryanhamshire Date: Sat, 1 Nov 2014 19:18:27 -0700 Subject: [PATCH] Added /SoftMute --- plugin.yml | 10 +- .../GriefPrevention/DataStore.java | 127 +++++++++++++++++- .../GriefPrevention/GriefPrevention.java | 26 ++++ .../GriefPrevention/Messages.java | 2 +- .../GriefPrevention/PlayerEventHandler.java | 34 ++++- 5 files changed, 189 insertions(+), 10 deletions(-) diff --git a/plugin.yml b/plugin.yml index 620e4bb..43c5362 100644 --- a/plugin.yml +++ b/plugin.yml @@ -129,6 +129,10 @@ commands: claimexplosions: description: Toggles whether explosives may be used in a specific land claim. usage: /ClaimExplosions + softmute: + description: Toggles whether a player's messages will only reach other soft-muted players. + usage: /SoftMute + permission: griefprevention.softmute permissions: griefprevention.createclaims: description: Grants permission to create claims. @@ -146,6 +150,7 @@ permissions: griefprevention.lava: true griefprevention.eavesdrop: true griefprevention.deathblow: true + griefprevention.softmute: true griefprevention.restorenature: description: Grants permission to use /RestoreNature. default: op @@ -168,7 +173,7 @@ permissions: description: Grants permission to place lava near the surface and outside of claims. default: op griefprevention.eavesdrop: - description: Allows a player to see whispered chat messages (/tell). + description: Allows a player to see whispered chat messages (/tell) and softmuted messages. default: op griefprevention.restorenatureaggressive: description: Grants access to /RestoreNatureAggressive and /RestoreNatureFill. @@ -176,6 +181,9 @@ permissions: griefprevention.deathblow: description: Grants access to /DeathBlow. default: op + griefprevention.softmute: + description: Grants access to /SoftMute. + default: op griefprevention.claims: description: Grants access to claim-related slash commands. default: true diff --git a/src/me/ryanhamshire/GriefPrevention/DataStore.java b/src/me/ryanhamshire/GriefPrevention/DataStore.java index 32e5333..cadb14f 100644 --- a/src/me/ryanhamshire/GriefPrevention/DataStore.java +++ b/src/me/ryanhamshire/GriefPrevention/DataStore.java @@ -55,6 +55,7 @@ public abstract class DataStore protected final static String dataLayerFolderPath = "plugins" + File.separator + "GriefPreventionData"; 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"; //the latest version of the data schema implemented here protected static final int latestSchemaVersion = 1; @@ -71,6 +72,9 @@ public abstract class DataStore static final String CREATIVE_VIDEO_URL = "http://bit.ly/mcgpcrea"; static final String SUBDIVISION_VIDEO_URL = "http://bit.ly/mcgpsub"; + //list of UUIDs which are soft-muted + ConcurrentHashMap softMuteMap = new ConcurrentHashMap(); + protected int getSchemaVersion() { if(this.currentSchemaVersion >= 0) @@ -118,11 +122,124 @@ public abstract class DataStore GriefPrevention.AddLogEntry("Update finished."); } - //make a note of the data store schema version + //load list of soft mutes + this.loadSoftMutes(); + + //make a note of the data store schema version this.setSchemaVersion(latestSchemaVersion); } - //removes cached player data from memory + private void loadSoftMutes() + { + File softMuteFile = new File(softMuteFilePath); + if(softMuteFile.exists()) + { + BufferedReader inStream = null; + try + { + //open the file + inStream = new BufferedReader(new FileReader(softMuteFile.getAbsolutePath())); + + //while there are lines left + String nextID = inStream.readLine(); + while(nextID != null) + { + //parse line into a UUID + UUID playerID; + try + { + playerID = UUID.fromString(nextID); + } + catch(Exception e) + { + playerID = null; + GriefPrevention.AddLogEntry("Failed to parse soft mute entry as a UUID: " + nextID); + } + + //push it into the map + if(playerID != null) + { + this.softMuteMap.put(playerID, true); + } + + //move to the next + nextID = inStream.readLine(); + } + } + catch(Exception e) + { + GriefPrevention.AddLogEntry("Failed to read from the soft mute data file: " + e.toString()); + e.printStackTrace(); + } + + try + { + if(inStream != null) inStream.close(); + } + catch(IOException exception) {} + } + } + + //updates soft mute map and data file + boolean toggleSoftMute(UUID playerID) + { + boolean newValue = !this.isSoftMuted(playerID); + + this.softMuteMap.put(playerID, newValue); + this.saveSoftMutes(); + + return newValue; + } + + boolean isSoftMuted(UUID playerID) + { + Boolean mapEntry = this.softMuteMap.get(playerID); + if(mapEntry == null || mapEntry == Boolean.FALSE) + { + return false; + } + + return true; + } + + private void saveSoftMutes() + { + BufferedWriter outStream = null; + + try + { + //open the file and write the new value + File softMuteFile = new File(softMuteFilePath); + softMuteFile.createNewFile(); + outStream = new BufferedWriter(new FileWriter(softMuteFile)); + + for(Map.Entry entry : softMuteMap.entrySet()) + { + if(entry.getValue().booleanValue()) + { + outStream.write(entry.getKey().toString()); + outStream.newLine(); + } + } + + } + + //if any problem, log it + catch(Exception e) + { + GriefPrevention.AddLogEntry("Unexpected exception saving soft mute data: " + e.getMessage()); + e.printStackTrace(); + } + + //close the file + try + { + if(outStream != null) outStream.close(); + } + catch(IOException exception) {} + } + + //removes cached player data from memory synchronized void clearCachedPlayerData(UUID playerID) { this.playerNameToPlayerDataMap.remove(playerID); @@ -557,13 +674,15 @@ public abstract class DataStore //saves changes to player data to secondary storage. MUST be called after you're done making changes, otherwise a reload will lose them public void savePlayerDataSync(UUID playerID, PlayerData playerData) { + //ensure player data is already read from file before trying to save + playerData.getAccruedClaimBlocks(); + playerData.getClaims(); this.asyncSavePlayerData(playerID, playerData); } //saves changes to player data to secondary storage. MUST be called after you're done making changes, otherwise a reload will lose them public void savePlayerData(UUID playerID, PlayerData playerData) { - //thread won't have access to read from files (silent failure - all readlines() return null) //ensure player data is already read from file before trying to save playerData.getAccruedClaimBlocks(); playerData.getClaims(); @@ -1048,6 +1167,8 @@ public abstract class DataStore this.addDefault(defaults, Messages.ClaimExplosivesAdvertisement, "To allow explosives to destroy blocks in this land claim, use /ClaimExplosions.", null); this.addDefault(defaults, Messages.PlayerInPvPSafeZone, "That player is in a PvP safe zone.", null); this.addDefault(defaults, Messages.NoPistonsOutsideClaims, "Warning: Pistons won't move blocks outside land claims.", null); + this.addDefault(defaults, Messages.SoftMuted, "Soft-muted {0}.", "The changed player's name."); + this.addDefault(defaults, Messages.UnSoftMuted, "Un-soft-muted {0}.", "The changed player's name."); //load the config file FileConfiguration config = YamlConfiguration.loadConfiguration(new File(messagesFilePath)); diff --git a/src/me/ryanhamshire/GriefPrevention/GriefPrevention.java b/src/me/ryanhamshire/GriefPrevention/GriefPrevention.java index 48eccaf..7a59cd6 100644 --- a/src/me/ryanhamshire/GriefPrevention/GriefPrevention.java +++ b/src/me/ryanhamshire/GriefPrevention/GriefPrevention.java @@ -1910,6 +1910,32 @@ public class GriefPrevention extends JavaPlugin GriefPrevention.sendMessage(defender, TextMode.Warn, Messages.SiegeAlert, attacker.getName()); GriefPrevention.sendMessage(player, TextMode.Success, Messages.SiegeConfirmed, defender.getName()); } + else if(cmd.getName().equalsIgnoreCase("softmute")) + { + //requires one parameter + if(args.length != 1) return false; + + //find the specified player + OfflinePlayer targetPlayer = this.resolvePlayerByName(args[0], true); + if(targetPlayer == null) + { + GriefPrevention.sendMessage(player, TextMode.Err, Messages.PlayerNotFound); + return true; + } + + //toggle mute for player + boolean isMuted = this.dataStore.toggleSoftMute(targetPlayer.getUniqueId()); + if(isMuted) + { + GriefPrevention.sendMessage(player, TextMode.Success, Messages.SoftMuted, targetPlayer.getName()); + } + else + { + GriefPrevention.sendMessage(player, TextMode.Success, Messages.UnSoftMuted, targetPlayer.getName()); + } + + return true; + } return false; } diff --git a/src/me/ryanhamshire/GriefPrevention/Messages.java b/src/me/ryanhamshire/GriefPrevention/Messages.java index 3a69478..e7ad840 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, PlayerNotFound, 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 + RespectingClaims, IgnoringClaims, SuccessfulAbandon, RestoreNatureActivate, RestoreNatureAggressiveActivate, FillModeActive, TransferClaimPermission, TransferClaimMissing, TransferClaimAdminOnly, PlayerNotFound, 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 } diff --git a/src/me/ryanhamshire/GriefPrevention/PlayerEventHandler.java b/src/me/ryanhamshire/GriefPrevention/PlayerEventHandler.java index 5b457f5..d83a4f5 100644 --- a/src/me/ryanhamshire/GriefPrevention/PlayerEventHandler.java +++ b/src/me/ryanhamshire/GriefPrevention/PlayerEventHandler.java @@ -23,6 +23,7 @@ import java.util.Calendar; import java.util.Date; import java.util.HashSet; import java.util.List; +import java.util.Set; import java.util.UUID; import java.util.concurrent.ConcurrentHashMap; import java.util.regex.Matcher; @@ -92,7 +93,34 @@ class PlayerEventHandler implements Listener String message = event.getMessage(); - event.setCancelled(this.handlePlayerChat(player, message, event)); + boolean muted = this.handlePlayerChat(player, message, event); + Set recipients = event.getRecipients(); + + //muted messages go out to only the sender + if(muted) + { + recipients.clear(); + recipients.add(player); + } + + //soft muted messages go out to all soft muted players + else if(this.dataStore.isSoftMuted(player.getUniqueId())) + { + Set recipientsToKeep = new HashSet(); + for(Player recipient : recipients) + { + if(this.dataStore.isSoftMuted(recipient.getUniqueId())) + { + recipientsToKeep.add(recipient); + } + else if(recipient.hasPermission("griefprevention.eavesdrop")) + { + recipient.sendMessage("(Muted)" + player.getName() + ": " + message); + } + } + recipients.clear(); + recipients.addAll(recipientsToKeep); + } } //last chat message shown, regardless of who sent it @@ -292,10 +320,6 @@ class PlayerEventHandler implements Listener //make a log entry GriefPrevention.AddLogEntry("Muted spam from " + player.getName() + ": " + message); - //send a fake message so the player doesn't realize he's muted - //less information for spammers = less effective spam filter dodging - player.sendMessage("<" + player.getName() + "> " + message); - //cancelling the event guarantees other players don't receive the message return true; }