Java tutorial
/* * This file is part of GriefPrevention, licensed under the MIT License (MIT). * * Copyright (c) Ryan Hamshire * Copyright (c) bloodmc * Copyright (c) contributors * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ package me.ryanhamshire.griefprevention; import com.flowpowered.math.vector.Vector3d; import com.flowpowered.math.vector.Vector3i; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableSet; import com.google.common.collect.Maps; import com.google.common.io.Files; import me.ryanhamshire.griefprevention.api.claim.Claim; import me.ryanhamshire.griefprevention.api.claim.ClaimResult; import me.ryanhamshire.griefprevention.api.claim.ClaimResultType; import me.ryanhamshire.griefprevention.api.claim.ClaimType; import me.ryanhamshire.griefprevention.claim.ClaimsMode; import me.ryanhamshire.griefprevention.claim.GPClaim; import me.ryanhamshire.griefprevention.claim.GPClaimManager; import me.ryanhamshire.griefprevention.claim.GPClaimResult; import me.ryanhamshire.griefprevention.configuration.ClaimTemplateStorage; import me.ryanhamshire.griefprevention.configuration.GriefPreventionConfig; import me.ryanhamshire.griefprevention.configuration.type.DimensionConfig; import me.ryanhamshire.griefprevention.configuration.type.GlobalConfig; import me.ryanhamshire.griefprevention.configuration.type.WorldConfig; import me.ryanhamshire.griefprevention.event.GPDeleteClaimEvent; import me.ryanhamshire.griefprevention.message.CustomizableMessage; import me.ryanhamshire.griefprevention.message.Messages; import me.ryanhamshire.griefprevention.message.TextMode; import me.ryanhamshire.griefprevention.permission.GPOptions; import me.ryanhamshire.griefprevention.permission.GPPermissions; import me.ryanhamshire.griefprevention.task.SecureClaimTask; import me.ryanhamshire.griefprevention.task.SiegeCheckupTask; import me.ryanhamshire.griefprevention.util.WordFinder; import net.minecraft.item.ItemStack; import net.minecraft.util.math.ChunkPos; import ninja.leaping.configurate.commented.CommentedConfigurationNode; import ninja.leaping.configurate.hocon.HoconConfigurationLoader; import org.apache.commons.lang3.StringUtils; import org.spongepowered.api.Sponge; import org.spongepowered.api.command.CommandSource; import org.spongepowered.api.entity.living.player.Player; import org.spongepowered.api.entity.living.player.User; import org.spongepowered.api.event.cause.Cause; import org.spongepowered.api.event.cause.NamedCause; import org.spongepowered.api.scheduler.Task; import org.spongepowered.api.service.context.Context; import org.spongepowered.api.service.permission.SubjectData; import org.spongepowered.api.service.user.UserStorageService; import org.spongepowered.api.text.Text; import org.spongepowered.api.text.action.TextActions; import org.spongepowered.api.text.format.TextColor; import org.spongepowered.api.text.format.TextColors; import org.spongepowered.api.util.Tristate; import org.spongepowered.api.world.Chunk; import org.spongepowered.api.world.Location; import org.spongepowered.api.world.World; import org.spongepowered.api.world.storage.WorldProperties; import java.io.BufferedReader; import java.io.BufferedWriter; import java.io.File; import java.io.FileReader; import java.io.FileWriter; import java.io.IOException; import java.lang.ref.WeakReference; import java.net.MalformedURLException; import java.net.URL; import java.nio.charset.Charset; import java.nio.file.Path; import java.util.ArrayList; import java.util.Calendar; import java.util.EnumMap; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Optional; import java.util.Set; import java.util.UUID; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.TimeUnit; import java.util.regex.Matcher; import java.util.regex.Pattern; //singleton class which manages all GriefPrevention data (except for config options) public abstract class DataStore { // World UUID -> PlayerDataWorldManager protected final Map<UUID, GPClaimManager> claimWorldManagers = Maps.newHashMap(); // in-memory cache for claim data public static Map<UUID, GriefPreventionConfig<DimensionConfig>> dimensionConfigMap = Maps.newHashMap(); public static Map<UUID, GriefPreventionConfig<WorldConfig>> worldConfigMap = Maps.newHashMap(); public static Map<String, ClaimTemplateStorage> globalTemplates = new HashMap<>(); public static GriefPreventionConfig<GlobalConfig> globalConfig; public static Map<UUID, GPPlayerData> GLOBAL_PLAYER_DATA = Maps.newHashMap(); public static boolean USE_GLOBAL_PLAYER_STORAGE = true; // in-memory cache for messages protected EnumMap<Messages, CustomizableMessage> messages = new EnumMap<>(Messages.class); // pattern for unique user identifiers (UUIDs) protected final static Pattern uuidpattern = Pattern .compile("[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}"); // path information, for where stuff stored on disk is well... stored public final static Path dataLayerFolderPath = GriefPreventionPlugin.instance.getConfigPath(); public final static Path globalPlayerDataPath = dataLayerFolderPath.resolve("GlobalPlayerData"); final static Path messagesFilePath = dataLayerFolderPath.resolve("messages.conf"); final static Path softMuteFilePath = dataLayerFolderPath.resolve("softMute.txt"); final static Path bannedWordsFilePath = dataLayerFolderPath.resolve("bannedWords.txt"); // the latest version of the data schema implemented here protected static final int latestSchemaVersion = 2; // reading and writing the schema version to the data store abstract int getSchemaVersionFromStorage(); abstract void updateSchemaVersionInStorage(int versionToSet); // current version of the schema of data in secondary storage private int currentSchemaVersion = -1; // -1 means not determined yet // video links public static final String SURVIVAL_VIDEO_URL_RAW = "http://bit.ly/mcgpuser"; static final String CREATIVE_VIDEO_URL_RAW = "http://bit.ly/mcgpcrea"; static final String SUBDIVISION_VIDEO_URL_RAW = "http://bit.ly/mcgpsub"; public static boolean generateMessages = true; // matcher for banned words public WordFinder bannedWordFinder; // list of UUIDs which are soft-muted ConcurrentHashMap<UUID, Boolean> softMuteMap = new ConcurrentHashMap<UUID, Boolean>(); protected int getSchemaVersion() { if (this.currentSchemaVersion >= 0) { return this.currentSchemaVersion; } else { this.currentSchemaVersion = this.getSchemaVersionFromStorage(); return this.currentSchemaVersion; } } protected void setSchemaVersion(int versionToSet) { this.currentSchemaVersion = versionToSet; this.updateSchemaVersionInStorage(versionToSet); } // initialization! void initialize() throws Exception { // ensure global player data folder exists USE_GLOBAL_PLAYER_STORAGE = GriefPreventionPlugin.getGlobalConfig() .getConfig().playerdata.useGlobalPlayerDataStorage; if (USE_GLOBAL_PLAYER_STORAGE) { File globalPlayerDataFolder = globalPlayerDataPath.toFile(); if (!globalPlayerDataFolder.exists()) { globalPlayerDataFolder.mkdirs(); } } // load up all the messages from messages.hocon this.loadMessages(); this.loadBannedWords(); GriefPreventionPlugin.addLogEntry("Customizable messages loaded."); // load list of soft mutes this.loadSoftMutes(); // make a note of the data store schema version this.setSchemaVersion(latestSchemaVersion); } private void loadSoftMutes() { File softMuteFile = softMuteFilePath.toFile(); 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; GriefPreventionPlugin.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) { GriefPreventionPlugin.addLogEntry("Failed to read from the soft mute data file: " + e.toString()); e.printStackTrace(); } try { if (inStream != null) { inStream.close(); } } catch (IOException exception) { } } } public void loadBannedWords() { try { File bannedWordsFile = bannedWordsFilePath.toFile(); boolean regenerateDefaults = false; if (!bannedWordsFile.exists()) { Files.touch(bannedWordsFile); regenerateDefaults = true; } List<String> bannedWords = Files.readLines(bannedWordsFile, Charset.forName("UTF-8")); if (regenerateDefaults || bannedWords.isEmpty()) { String defaultWords = "nigger\nniggers\nniger\nnigga\nnigers\nniggas\n" + "fag\nfags\nfaggot\nfaggots\nfeggit\nfeggits\nfaggit\nfaggits\n" + "cunt\ncunts\nwhore\nwhores\nslut\nsluts\n"; Files.write(defaultWords, bannedWordsFile, Charset.forName("UTF-8")); } this.bannedWordFinder = new WordFinder(Files.readLines(bannedWordsFile, Charset.forName("UTF-8"))); } catch (Exception e) { GriefPreventionPlugin.addLogEntry("Failed to read from the banned words data file: " + e.toString()); e.printStackTrace(); this.bannedWordFinder = new WordFinder(new ArrayList<String>()); } } // updates soft mute map and data file public boolean toggleSoftMute(UUID playerID) { boolean newValue = !this.isSoftMuted(playerID); this.softMuteMap.put(playerID, newValue); this.saveSoftMutes(); return newValue; } public 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 = softMuteFilePath.toFile(); softMuteFile.createNewFile(); outStream = new BufferedWriter(new FileWriter(softMuteFile)); for (Map.Entry<UUID, Boolean> entry : softMuteMap.entrySet()) { if (entry.getValue().booleanValue()) { outStream.write(entry.getKey().toString()); outStream.newLine(); } } } // if any problem, log it catch (Exception e) { GriefPreventionPlugin.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 public void clearCachedPlayerData(WorldProperties worldProperties, UUID playerUniqueId) { this.getClaimWorldManager(worldProperties).removePlayer(playerUniqueId); } public abstract void writeClaimToStorage(GPClaim claim); public abstract void deleteClaimFromSecondaryStorage(GPClaim claim); // finds a claim by ID public Claim getClaim(WorldProperties worldProperties, UUID id) { return this.getClaimWorldManager(worldProperties).getClaimByUUID(id).orElse(null); } public void asyncSaveGlobalPlayerData(UUID playerID, GPPlayerData 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 = globalPlayerDataPath.resolve(playerID.toString() + ".ignore").toFile(); Files.write(fileContent.toString().trim().getBytes("UTF-8"), playerDataFile); } // if any problem, log it catch (Exception e) { GriefPreventionPlugin.addLogEntry("GriefPrevention: Unexpected exception saving data for player \"" + playerID.toString() + "\": " + e.getMessage()); e.printStackTrace(); } } } abstract void overrideSavePlayerData(UUID playerID, GPPlayerData playerData); // extends a claim to a new depth // respects the max depth config variable public void extendClaim(GPClaim claim, int newDepth) { if (newDepth < GriefPreventionPlugin.getActiveConfig(claim.world.getProperties()) .getConfig().claim.maxClaimDepth) { newDepth = GriefPreventionPlugin.getActiveConfig(claim.world.getProperties()) .getConfig().claim.maxClaimDepth; } if (claim.isSubdivision()) { claim = claim.parent; } // adjust to new depth Vector3d newLesserPosition = new Vector3d(claim.lesserBoundaryCorner.getX(), newDepth, claim.lesserBoundaryCorner.getZ()); claim.lesserBoundaryCorner = claim.lesserBoundaryCorner.setPosition(newLesserPosition); Vector3d newGreaterPosition = new Vector3d(claim.greaterBoundaryCorner.getX(), newDepth, claim.greaterBoundaryCorner.getZ()); claim.greaterBoundaryCorner = claim.greaterBoundaryCorner.setPosition(newGreaterPosition); for (Claim subClaim : claim.children) { GPClaim subdivision = (GPClaim) subClaim; newLesserPosition = new Vector3d(subdivision.lesserBoundaryCorner.getX(), newDepth, subdivision.lesserBoundaryCorner.getZ()); subdivision.lesserBoundaryCorner = subdivision.lesserBoundaryCorner.setPosition(newLesserPosition); newGreaterPosition = new Vector3d(subdivision.greaterBoundaryCorner.getX(), newDepth, subdivision.greaterBoundaryCorner.getZ()); subdivision.greaterBoundaryCorner = subdivision.greaterBoundaryCorner.setPosition(newGreaterPosition); } claim.updateClaimStorageData(); } // starts a siege on a claim // does NOT check siege cooldowns, see onCooldown() below public void startSiege(Player attacker, Player defender, GPClaim defenderClaim) { // fill-in the necessary SiegeData instance SiegeData siegeData = new SiegeData(attacker, defender, defenderClaim); GPPlayerData attackerData = this.getPlayerData(attacker.getWorld(), attacker.getUniqueId()); GPPlayerData defenderData = this.getPlayerData(defender.getWorld(), defender.getUniqueId()); attackerData.siegeData = siegeData; defenderData.siegeData = siegeData; defenderClaim.siegeData = siegeData; // start a task to monitor the siege // why isn't this a "repeating" task? // because depending on the status of the siege at the time the task // runs, there may or may not be a reason to run the task again SiegeCheckupTask siegeTask = new SiegeCheckupTask(siegeData); Task task = Sponge.getGame().getScheduler().createTaskBuilder().delay(30, TimeUnit.SECONDS) .execute(siegeTask).submit(GriefPreventionPlugin.instance); siegeData.checkupTaskID = task.getUniqueId(); } // ends a siege // either winnerName or loserName can be null, but not both public void endSiege(SiegeData siegeData, String winnerName, String loserName, boolean death) { boolean grantAccess = false; // determine winner and loser if (winnerName == null && loserName != null) { if (siegeData.attacker.getName().equals(loserName)) { winnerName = siegeData.defender.getName(); } else { winnerName = siegeData.attacker.getName(); } } else if (winnerName != null && loserName == null) { if (siegeData.attacker.getName().equals(winnerName)) { loserName = siegeData.defender.getName(); } else { loserName = siegeData.attacker.getName(); } } // if the attacker won, plan to open the doors for looting if (siegeData.attacker.getName().equals(winnerName)) { grantAccess = true; } GPPlayerData attackerData = this.getPlayerData(siegeData.attacker.getWorld(), siegeData.attacker.getUniqueId()); attackerData.siegeData = null; GPPlayerData defenderData = this.getPlayerData(siegeData.defender.getWorld(), siegeData.defender.getUniqueId()); defenderData.siegeData = null; defenderData.lastSiegeEndTimeStamp = System.currentTimeMillis(); // start a cooldown for this attacker/defender pair Long now = Calendar.getInstance().getTimeInMillis(); Long cooldownEnd = now + 1000 * 60 * 60; // one hour from now this.siegeCooldownRemaining.put(siegeData.attacker.getName() + "_" + siegeData.defender.getName(), cooldownEnd); // start cooldowns for every attacker/involved claim pair for (int i = 0; i < siegeData.claims.size(); i++) { GPClaim claim = siegeData.claims.get(i); claim.siegeData = null; this.siegeCooldownRemaining.put(siegeData.attacker.getName() + "_" + claim.getOwnerName(), cooldownEnd); // if doors should be opened for looting, do that now if (grantAccess) { claim.doorsOpen = true; } } // cancel the siege checkup task Sponge.getGame().getScheduler().getTaskById(siegeData.checkupTaskID).get().cancel(); // notify everyone who won and lost if (winnerName != null && loserName != null) { Sponge.getGame().getServer().getBroadcastChannel() .send(Text.of(winnerName + " defeated " + loserName + " in siege warfare!")); } // if the claim should be opened to looting if (grantAccess) { Optional<Player> winner = Sponge.getGame().getServer().getPlayer(winnerName); if (winner.isPresent()) { // notify the winner GriefPreventionPlugin.sendMessage(winner.get(), TextMode.Success, Messages.SiegeWinDoorsOpen); // schedule a task to secure the claims in about 5 minutes SecureClaimTask task = new SecureClaimTask(siegeData); Sponge.getGame().getScheduler().createTaskBuilder().delay(5, TimeUnit.MINUTES).execute(task) .submit(GriefPreventionPlugin.instance); } } // if the siege ended due to death, transfer inventory to winner if (death) { Optional<Player> winner = Sponge.getGame().getServer().getPlayer(winnerName); Optional<Player> loser = Sponge.getGame().getServer().getPlayer(loserName); if (winner.isPresent() && loser.isPresent()) { // get loser's inventory, then clear it net.minecraft.entity.player.EntityPlayerMP loserPlayer = (net.minecraft.entity.player.EntityPlayerMP) loser .get(); net.minecraft.entity.player.EntityPlayerMP winnerPlayer = (net.minecraft.entity.player.EntityPlayerMP) winner .get(); List<ItemStack> loserItems = new ArrayList<ItemStack>(); for (int i = 0; i < loserPlayer.inventory.mainInventory.length; i++) { if (loserPlayer.inventory.mainInventory[i] != null) { loserItems.add(loserPlayer.inventory.mainInventory[i]); } } loserPlayer.inventory.clear(); // try to add it to the winner's inventory Iterator<ItemStack> iterator = loserItems.iterator(); while (iterator.hasNext()) { ItemStack loserItem = iterator.next(); boolean added = winnerPlayer.inventory.addItemStackToInventory(loserItem); if (added) { iterator.remove(); } } // drop any remainder on the ground at his feet Location<World> winnerLocation = winner.get().getLocation(); for (int i = 0; i < loserItems.size(); i++) { net.minecraft.entity.item.EntityItem entity = new net.minecraft.entity.item.EntityItem( (net.minecraft.world.World) winnerLocation.getExtent(), winnerLocation.getX(), winnerLocation.getY(), winnerLocation.getZ(), loserItems.get(i)); entity.setPickupDelay(10); ((net.minecraft.world.World) winnerLocation.getExtent()).spawnEntityInWorld(entity); } } } } // timestamp for each siege cooldown to end public HashMap<String, Long> siegeCooldownRemaining = new HashMap<String, Long>(); // whether or not a sieger can siege a particular victim or claim, // considering only cooldowns public boolean onCooldown(Player attacker, Player defender, GPClaim defenderClaim) { Long cooldownEnd = null; // look for an attacker/defender cooldown if (this.siegeCooldownRemaining.get(attacker.getName() + "_" + defender.getName()) != null) { cooldownEnd = this.siegeCooldownRemaining.get(attacker.getName() + "_" + defender.getName()); if (Calendar.getInstance().getTimeInMillis() < cooldownEnd) { return true; } // if found but expired, remove it this.siegeCooldownRemaining.remove(attacker.getName() + "_" + defender.getName()); } // look for genderal defender cooldown GPPlayerData defenderData = this.getPlayerData(defender.getWorld(), defender.getUniqueId()); if (defenderData.lastSiegeEndTimeStamp > 0) { long now = System.currentTimeMillis(); if (now - defenderData.lastSiegeEndTimeStamp > 1000 * 60 * 15) // 15 minutes in milliseconds { return true; } } // look for an attacker/claim cooldown if (cooldownEnd == null && this.siegeCooldownRemaining .get(attacker.getName() + "_" + defenderClaim.getOwnerName()) != null) { cooldownEnd = this.siegeCooldownRemaining.get(attacker.getName() + "_" + defenderClaim.getOwnerName()); if (Calendar.getInstance().getTimeInMillis() < cooldownEnd) { return true; } // if found but expired, remove it this.siegeCooldownRemaining.remove(attacker.getName() + "_" + defenderClaim.getOwnerName()); } return false; } // extend a siege, if it's possible to do so public void tryExtendSiege(Player player, GPClaim claim) { GPPlayerData playerData = this.getPlayerData(player.getWorld(), player.getUniqueId()); // player must be sieged if (playerData.siegeData == null) { return; } // claim isn't already under the same siege if (playerData.siegeData.claims.contains(claim)) { return; } // admin claims can't be sieged if (claim.isAdminClaim()) { return; } // player must have some level of permission to be sieged in a claim if (claim.allowAccess(player) != null) { return; } // otherwise extend the siege playerData.siegeData.claims.add(claim); claim.siegeData = playerData.siegeData; } public ClaimResult createClaim(World world, Vector3i point1, Vector3i point2, ClaimType claimType, UUID ownerUniqueId, boolean cuboid) { GPClaimManager claimManager = this.getClaimWorldManager(world.getProperties()); ClaimResult claimResult = Claim.builder().bounds(point1, point2).world(world).type(claimType) .owner(ownerUniqueId).cuboid(cuboid).cause(GriefPreventionPlugin.pluginCause).build(); if (claimResult.successful()) { claimManager.addClaim(claimResult.getClaim().get(), true); } return claimResult; } public ClaimResult deleteAllAdminClaims(CommandSource src, World world) { GPClaimManager claimWorldManager = this.claimWorldManagers.get(world.getProperties().getUniqueId()); if (claimWorldManager == null) { return new GPClaimResult(ClaimResultType.CLAIMS_DISABLED); } List<Claim> claimsToDelete = new ArrayList<Claim>(); boolean adminClaimFound = false; for (Claim claim : claimWorldManager.getWorldClaims()) { if (claim.isAdminClaim()) { claimsToDelete.add(claim); adminClaimFound = true; } } if (!adminClaimFound) { return new GPClaimResult(ClaimResultType.CLAIM_NOT_FOUND); } GPDeleteClaimEvent event = new GPDeleteClaimEvent(ImmutableList.copyOf(claimsToDelete), Cause.of(NamedCause.source(src))); Sponge.getEventManager().post(event); if (event.isCancelled()) { return new GPClaimResult(ClaimResultType.CLAIM_EVENT_CANCELLED, event.getMessage() .orElse(Text.of("Could not delete all admin claims. A plugin has denied it."))); } for (Claim claim : claimsToDelete) { GPClaim gpClaim = (GPClaim) claim; gpClaim.removeSurfaceFluids(null); GriefPreventionPlugin.GLOBAL_SUBJECT.getSubjectData() .clearPermissions(ImmutableSet.of(claim.getContext())); claimWorldManager.deleteClaim(claim); // if in a creative mode world, delete the claim if (GriefPreventionPlugin.instance.claimModeIsActive( claim.getLesserBoundaryCorner().getExtent().getProperties(), ClaimsMode.Creative)) { GriefPreventionPlugin.instance.restoreClaim((GPClaim) claim, 0); } } return new GPClaimResult(claimsToDelete, ClaimResultType.SUCCESS); } public ClaimResult deleteClaim(Claim claim, Cause cause) { GPClaimManager claimManager = this.getClaimWorldManager(claim.getWorld().getProperties()); return claimManager.deleteClaim(claim, cause); } // deletes all claims owned by a player public void deleteClaimsForPlayer(UUID playerID) { if (DataStore.USE_GLOBAL_PLAYER_STORAGE && playerID != null) { List<Claim> claimsToDelete = new ArrayList<>(DataStore.GLOBAL_PLAYER_DATA.get(playerID).getClaims()); for (Claim claim : claimsToDelete) { ((GPClaim) claim).removeSurfaceFluids(null); GriefPreventionPlugin.GLOBAL_SUBJECT.getSubjectData() .clearPermissions(ImmutableSet.of(claim.getContext())); GPClaimManager claimWorldManager = this.claimWorldManagers .get(claim.getWorld().getProperties().getUniqueId()); claimWorldManager.deleteClaim(claim); // if in a creative mode world, delete the claim if (GriefPreventionPlugin.instance.claimModeIsActive( claim.getLesserBoundaryCorner().getExtent().getProperties(), ClaimsMode.Creative)) { GriefPreventionPlugin.instance.restoreClaim((GPClaim) claim, 0); } } return; } for (GPClaimManager claimWorldManager : this.claimWorldManagers.values()) { List<Claim> claims = claimWorldManager.getInternalPlayerClaims(playerID); if (playerID == null) { claims = claimWorldManager.getWorldClaims(); } if (claims == null) { continue; } List<Claim> claimsToDelete = new ArrayList<Claim>(); for (Claim claim : claims) { if (!claim.isAdminClaim()) { claimsToDelete.add(claim); } } for (Claim claim : claimsToDelete) { ((GPClaim) claim).removeSurfaceFluids(null); GriefPreventionPlugin.GLOBAL_SUBJECT.getSubjectData() .clearPermissions(ImmutableSet.of(claim.getContext())); claimWorldManager.deleteClaim(claim); // if in a creative mode world, delete the claim if (GriefPreventionPlugin.instance.claimModeIsActive( claim.getLesserBoundaryCorner().getExtent().getProperties(), ClaimsMode.Creative)) { GriefPreventionPlugin.instance.restoreClaim((GPClaim) claim, 0); } } } } public void loadMessages() { this.messages.clear(); // initialize defaults this.addDefault(Messages.AbandonClaimAdvertisement, "To delete another claim and free up some blocks, use /AbandonClaim."); this.addDefault(Messages.AbandonClaimMissing, "Stand in the claim you want to delete, or consider /AbandonAllClaims."); this.addDefault(Messages.AbandonSuccess, "Claim abandoned. You now have {0} available claim blocks.", "0: remaining claim blocks"); this.addDefault(Messages.AbandonOtherSuccess, "{0}'s claim has been abandoned. {0} now has {1} available claim blocks.", "0: player; 1: remaining claim blocks"); this.addDefault(Messages.Access, "Access"); this.addDefault(Messages.AccessPermission, "interact with everything except inventory containers."); this.addDefault(Messages.AdjustBlocksSuccess, "Adjusted {0}'s bonus claim blocks by {1}. New total bonus blocks: {2}.", "0: player; 1: adjustment; 2: new total"); this.addDefault(Messages.AdjustGroupBlocksSuccess, "Adjusted bonus claim blocks for players with the {0} permission by {1}. New total: {2}.", "0: permission; 1: adjustment amount; 2: new total bonus"); this.addDefault(Messages.AdminClaimsMode, "Administrative claims mode active. Any claims created will be free and editable by other administrators."); this.addDefault(Messages.AdvertiseACB, "You may use /ACB to give yourself more claim blocks."); this.addDefault(Messages.AdvertiseACandACB, "You may use /ACB to give yourself more claim blocks, or /AdminClaims to create a free administrative claim."); this.addDefault(Messages.AdvertiseAdminClaims, "You could create an administrative land claim instead using /AdminClaims, which you'd share with other administrators."); this.addDefault(Messages.AllAdminDeleted, "Deleted all administrative claims."); this.addDefault(Messages.AlreadySieging, "You're already involved in a siege."); this.addDefault(Messages.AlreadyUnderSiegeArea, "That area is already under siege. Join the party!"); this.addDefault(Messages.AlreadyUnderSiegePlayer, "{0} is already under siege. Join the party!", "0: defending player"); this.addDefault(Messages.AutoBanNotify, "Auto-banned {0}({1}). See logs for details."); this.addDefault(Messages.AutomaticClaimNotification, "This chest and nearby blocks are protected from breakage and theft."); this.addDefault(Messages.AvoidGriefClaimLand, "Prevent grief! If you claim your land, you will be grief-proof."); this.addDefault(Messages.BasicClaimsMode, "Returned to basic claim creation mode."); this.addDefault(Messages.BecomeMayor, "Subdivide your land claim and become a mayor!"); this.addDefault(Messages.BesiegedNoTeleport, "You can't teleport into a besieged area."); this.addDefault(Messages.BlockChangeFromWilderness, "Claim blocks are not allowed to be changed from wilderness."); this.addDefault(Messages.BlockClaimed, "That block has been claimed by {0}.", "0: claim owner"); this.addDefault(Messages.BlockedCommand, "The command {0} has been blocked by claim owner {1}."); this.addDefault(Messages.BlockNotClaimed, "No one has claimed this block."); this.addDefault(Messages.BlockPurchaseCost, "Each claim block costs {0}. Your balance is {1}.", "0: cost of one block; 1: player's account balance"); this.addDefault(Messages.BlockSaleConfirmation, "Deposited {0} in your account. You now have {1} available claim blocks.", "0: amount deposited; 1: remaining blocks"); this.addDefault(Messages.BlockSaleValue, "Each claim block is worth {0}. You have {1} available for sale.", "0: block value; 1: available blocks"); this.addDefault(Messages.BookAuthor, "BigScary"); this.addDefault(Messages.BookDisabledChestClaims, " On this server, placing a chest will NOT claim land for you."); this.addDefault(Messages.BookIntro, "Claim land to protect your stuff! Click the link above to learn land claims in 3 minutes or less. :)"); this.addDefault(Messages.BookLink, "Click: " + SURVIVAL_VIDEO_URL_RAW); this.addDefault(Messages.BookTitle, "How to Claim Land"); this.addDefault(Messages.BookTools, "Our claim tools are {0} and {1}.", "0: claim modification tool name; 1:claim information tool name"); this.addDefault(Messages.BookUsefulCommands, "Useful Commands:"); this.addDefault(Messages.Build, "Build"); this.addDefault(Messages.BuildPermission, "build"); this.addDefault(Messages.BuildingOutsideClaims, "Other players can build here, too. Consider creating a land claim to protect your work!"); this.addDefault(Messages.BuySellNotConfigured, "Sorry, buying and selling claim blocks is disabled."); this.addDefault(Messages.CantDeleteAdminClaim, "You don't have permission to delete administrative claims."); this.addDefault(Messages.CantDeleteBasicClaim, "You don't have permission to delete basic claims."); this.addDefault(Messages.CantFightWhileImmune, "You can't fight someone while you're protected from PvP."); this.addDefault(Messages.CantGrantThatPermission, "You can't grant a permission you don't have yourself."); this.addDefault(Messages.CantTransferAdminClaim, "You don't have permission to transfer administrative claims."); this.addDefault(Messages.ChestClaimConfirmation, "This chest is protected."); this.addDefault(Messages.ChestFull, "This chest is full."); this.addDefault(Messages.ClaimBlockLimit, "You've reached your claim block limit. You can't purchase more."); this.addDefault(Messages.ClaimCreationFailedOverClaimCountLimit, "You've reached your limit on land claims. Use /AbandonClaim to remove one before creating another."); this.addDefault(Messages.ClaimExplosivesAdvertisement, "To allow explosives to destroy blocks in this land claim, use /ClaimExplosions."); this.addDefault(Messages.ClaimFlagOverridden, "Failed to set claim flag. The flag '{0}' has been overridden by an admin.", "0: The claim flag that has been overridden."); this.addDefault(Messages.ClaimLastActive, "Claim last active {0}.", "0: The date and time when this claim was last active"); this.addDefault(Messages.ClaimResizeSuccess, "Claim resized. {0} available claim blocks remaining.", "0: remaining blocks"); this.addDefault(Messages.ClaimStart, "Claim corner set! Use the shovel again at the opposite corner to claim a rectangle of land. To cancel, put your shovel away."); this.addDefault(Messages.ClaimTooSmallForEntities, "This claim isn't big enough for that. Try enlarging it."); this.addDefault(Messages.ClaimsDisabledWorld, "Land claims are disabled in this world."); this.addDefault(Messages.ClaimsListHeader, "Claims:"); this.addDefault(Messages.ClaimsListNoPermission, "You don't have permission to get information about another player's land claims."); this.addDefault(Messages.ClearPermissionsOneClaim, "Cleared permissions in this claim. To set permission for ALL your claims, stand outside them."); this.addDefault(Messages.ClearPermsOwnerOnly, "Only the claim owner can clear all permissions."); this.addDefault(Messages.CollectivePublic, "the public", "as in 'granted the public permission to...'"); this.addDefault(Messages.CommandBannedInPvP, "You can't use that command while in PvP combat."); this.addDefault(Messages.ConfirmFluidRemoval, "Abandoning this claim will remove lava inside the claim. If you're sure, use /AbandonClaim again."); this.addDefault(Messages.Containers, "Containers"); this.addDefault(Messages.ContainersPermission, "access all containers including inventory."); this.addDefault(Messages.ContinueBlockMath, " (-{0} blocks)"); this.addDefault(Messages.CreateClaimFailOverlap, "You can't create a claim here because it would overlap your other claim. Use /abandonclaim to delete it, or use your shovel at a " + "corner to resize it."); this.addDefault(Messages.CreateClaimFailOverlapOtherPlayer, "You can't create a claim here because it would overlap {0}'s claim.", "0: other claim owner"); this.addDefault(Messages.CreateClaimFailOverlapRegion, "You can't claim all of this because you're not allowed to build here."); this.addDefault(Messages.CreateClaimFailOverlapShort, "Your selected area overlaps an existing claim."); this.addDefault(Messages.CreateClaimInsufficientBlocks, "You don't have enough blocks to claim that entire area. You need {0} more blocks.", "0: additional blocks needed"); this.addDefault(Messages.CreateClaimSuccess, "Claim created! Use /trust to share it with friends."); this.addDefault(Messages.CreateSubdivisionOverlap, "Your selected area overlaps another subdivision."); this.addDefault(Messages.CreativeBasicsVideo2, "Click for Land Claim Help: " + CREATIVE_VIDEO_URL_RAW); this.addDefault(Messages.CuboidClaimDisabled, "Now claiming in 2D mode."); this.addDefault(Messages.CuboidClaimEnabled, "Now claiming in 3D mode."); this.addDefault(Messages.DeleteAllSuccess, "Deleted all of {0}'s claims.", "0: owner's name"); this.addDefault(Messages.DeleteClaimMissing, "There's no claim here."); this.addDefault(Messages.DeleteSuccess, "Claim deleted."); this.addDefault(Messages.DeleteTopLevelClaim, "To delete a subdivision, stand inside it. Otherwise, use /AbandonTopLevelClaim to delete this claim and all subdivisions."); this.addDefault(Messages.DeletionSubdivisionWarning, "This claim includes subdivisions. If you're sure you want to delete it, use /DeleteClaim again."); this.addDefault(Messages.DonateItemsInstruction, "To give away the item(s) in your hand, left-click the chest again."); this.addDefault(Messages.DonationSuccess, "Item(s) transferred to chest!"); this.addDefault(Messages.DropUnlockAdvertisement, "Other players can't pick up your dropped items unless you /UnlockDrops first."); this.addDefault(Messages.DropUnlockConfirmation, "Unlocked your drops. Other players may now pick them up (until you die again)."); this.addDefault(Messages.EndBlockMath, " = {0} blocks left to spend"); this.addDefault(Messages.FillModeActive, "Fill mode activated with radius {0}. Right click an area to fill.", "0: fill radius"); this.addDefault(Messages.FireSpreadOutsideClaim, "Fire attempting to spread outside of claim."); this.addDefault(Messages.GrantPermissionConfirmation, "Granted {0} permission to {1} {2}.", "0: target player; 1: permission description; 2: scope (changed claims)"); this.addDefault(Messages.GrantPermissionNoClaim, "Stand inside the claim where you want to grant permission."); this.addDefault(Messages.HowToClaimRegex, "(^|.*\\W)how\\W.*\\W(claim|protect|lock)(\\W.*|$)", "This is a Java Regular Expression. Look it up before editing! It's used to tell players about the demo video when they ask how " + "to claim land."); this.addDefault(Messages.IgnoreClaimsAdvertisement, "To override, use /IgnoreClaims."); this.addDefault(Messages.IgnoreConfirmation, "You're now ignoring chat messages from that player."); this.addDefault(Messages.IgnoringClaims, "Now ignoring claims."); this.addDefault(Messages.InsufficientFunds, "You don't have enough money. You need {0}, but you only have {1}.", "0: total cost; 1: player's account balance"); this.addDefault(Messages.InvalidPermissionID, "Please specify a player name, or a permission in [brackets]."); this.addDefault(Messages.ItemNotAuthorized, "You have not been authorized to use the item {0} in this claim."); this.addDefault(Messages.LocationAllClaims, "in all your claims"); this.addDefault(Messages.LocationCurrentClaim, "in this claim"); this.addDefault(Messages.Manage, "Manage"); this.addDefault(Messages.ManageOneClaimPermissionsInstruction, "To manage permissions for a specific claim, stand inside it."); this.addDefault(Messages.ManageUniversalPermissionsInstruction, "To manage permissions for ALL your claims, stand outside them."); this.addDefault(Messages.ManagersDontUntrustManagers, "Only the claim owner can demote a manager."); this.addDefault(Messages.NewClaimTooNarrow, "This claim would be too small. Any claim must be at least {0} blocks wide.", "0: minimum claim width"); this.addDefault(Messages.NoAccessPermission, "You don't have {0}'s permission to use that.", "0: owner name. access permission controls buttons, levers, and beds"); this.addDefault(Messages.NoBedPermission, "{0} hasn't given you permission to sleep here.", "0: claim owner"); this.addDefault(Messages.NoBuildOutsideClaims, "You can't build here unless you claim some land first."); this.addDefault(Messages.NoBuildPermission, "You don't have {0}'s permission to build here.", "0: owner name"); this.addDefault(Messages.NoBuildPortalPermission, "You can't use this portal because you don't have {0}'s permission to build an exit portal in the destination land claim.", "0: Destination land claim owner's name."); this.addDefault(Messages.NoBuildPvP, "You can't build in claims during PvP combat."); this.addDefault(Messages.NoBuildUnderSiege, "This claim is under siege by {0}. No one can build here.", "0: attacker name"); this.addDefault(Messages.NoChatUntilMove, "Sorry, but you have to move a little more before you can chat. We get lots of spam bots here. :)"); this.addDefault(Messages.NoClaimDuringPvP, "You can't claim lands during PvP combat."); this.addDefault(Messages.NoContainersPermission, "You don't have {0}'s permission to use that.", "0: owner's name. containers also include crafting blocks"); this.addDefault(Messages.NoContainersSiege, "This claim is under siege by {0}. No one can access containers here right now.", "0: attacker name"); this.addDefault(Messages.NoCreateClaimPermission, "You don't have permission to claim land."); this.addDefault(Messages.NoCreativeUnClaim, "You can't unclaim this land. You can only make this claim larger or create additional claims."); this.addDefault(Messages.NoDamageClaimedEntity, "That belongs to {0}.", "0: owner name"); this.addDefault(Messages.NoDeletePermission, "You don't have permission to delete claims."); this.addDefault(Messages.NoDropsAllowed, "You can't drop items in this claim."); this.addDefault(Messages.NoEditPermission, "You don't have permission to edit this claim."); this.addDefault(Messages.NoEnterClaim, "You don't have permission to enter this claim."); this.addDefault(Messages.NoExitClaim, "You don't have permission to exit this claim."); this.addDefault(Messages.NoInteractBlockPermission, "You don't have {0}'s permission to interact with the block {1}.", "0: owner name; 1: block id"); this.addDefault(Messages.NoInteractEntityPermission, "You don't have {0}'s permission to interact with the entity {1}.", "0: owner name; 1: entity id"); this.addDefault(Messages.NoInteractItemPermission, "You don't have {0}'s permission to interact with the item {1}.", "0: owner name; 1: item id"); this.addDefault(Messages.NoInteractItemPermissionSelf, "You don't have permission to interact with the item {0}.", "0: item id"); this.addDefault(Messages.NoLavaNearOtherPlayer, "You can't place lava this close to {0}.", "0: nearby player"); this.addDefault(Messages.NoModifyDuringSiege, "Claims can't be modified while under siege."); this.addDefault(Messages.NoOwnerBuildUnderSiege, "You can't make changes while under siege."); this.addDefault(Messages.NoPermissionForCommand, "You don't have permission to do that."); this.addDefault(Messages.NoPermissionTrust, "You don't have {0}'s permission to manage permissions here.", "0: claim owner's name"); this.addDefault(Messages.NoPistonsOutsideClaims, "Warning: Pistons won't move blocks outside land claims."); this.addDefault(Messages.NoPortalFromProtectedClaim, "You do not have permission to use portals in this claim owned by {0}.", "0: claim owner's name"); this.addDefault(Messages.NoPortalToProtectedClaim, "You do not have permission to travel through this portal into the protected claim owned by {0}.", "0: claim owner's name"); this.addDefault(Messages.NoProfanity, "Please moderate your language."); this.addDefault(Messages.NoSiegeAdminClaim, "Siege is disabled in this area."); this.addDefault(Messages.NoSiegeDefenseless, "That player is defenseless. Go pick on somebody else."); this.addDefault(Messages.NoTNTDamageAboveSeaLevel, "Warning: TNT will not destroy blocks above sea level."); this.addDefault(Messages.NoTNTDamageClaims, "Warning: TNT will not destroy claimed blocks."); this.addDefault(Messages.NoTeleportFromProtectedClaim, "You do not have permission to teleport from the protected claim owned by {0}.", "0: owner of claim"); this.addDefault(Messages.NoTeleportToProtectedClaim, "You do not have permission to teleport into a protected claim owned by {0}.", "0: owner of claim"); this.addDefault(Messages.NoTeleportPvPCombat, "You can't teleport while fighting another player."); this.addDefault(Messages.NoWildernessBuckets, "You may only dump buckets inside your claim(s) or underground."); this.addDefault(Messages.NonSiegeMaterial, "That material is too tough to break."); this.addDefault(Messages.NonSiegeWorld, "Siege is disabled here."); this.addDefault(Messages.NotEnoughBlocksForSale, "You don't have that many claim blocks available for sale."); this.addDefault(Messages.NotIgnoringAnyone, "You're not ignoring anyone."); this.addDefault(Messages.NotIgnoringPlayer, "You're not ignoring that player."); this.addDefault(Messages.NotSiegableThere, "{0} isn't protected there.", "0: defending player"); this.addDefault(Messages.NotTrappedHere, "You can build here. Save yourself."); this.addDefault(Messages.NotYourClaim, "This isn't your claim."); this.addDefault(Messages.NotYourPet, "That belongs to {0} until it's given to you with /GivePet.", "0: owner name"); this.addDefault(Messages.NucleusNoSetHome, "You must be trusted in order to use /sethome here."); this.addDefault(Messages.OnlyOwnersModifyClaims, "Only {0} can modify this claim.", "0: owner name"); this.addDefault(Messages.OnlyPurchaseBlocks, "Claim blocks may only be purchased, not sold."); this.addDefault(Messages.OnlySellBlocks, "Claim blocks may only be sold, not purchased."); this.addDefault(Messages.OwnerNameForAdminClaims, "an administrator", "as in 'You don't have an administrator's permission to build here.'"); this.addDefault(Messages.OwnerNameForWildernessClaims, "a wilderness administrator", "as in 'You don't have a wilderness administrator's permission to build here.'"); this.addDefault(Messages.PermissionsPermission, "manage permissions"); this.addDefault(Messages.PetGiveawayInvalid, "Pet type {0} is invalid, only vanilla entities are supported.", "0: The entity type."); this.addDefault(Messages.PetGiveawayConfirmation, "Pet transferred."); this.addDefault(Messages.PetTransferCancellation, "Pet giveaway cancelled."); this.addDefault(Messages.PickupBlockedExplanation, "You can't pick this up unless {0} uses /UnlockDrops.", "0: The item stack's owner."); this.addDefault(Messages.PlayerInPvPSafeZone, "That player is in a PvP safe zone."); this.addDefault(Messages.PlayerNotFound2, "No player by that name has logged in recently."); this.addDefault(Messages.PlayerNotIgnorable, "You can't ignore that player."); this.addDefault(Messages.PlayerOfflineTime, " Last login: {0} days ago.", "0: number of full days since last login"); this.addDefault(Messages.PlayerTooCloseForFire, "You can't start a fire this close to {0}.", "0: other player's name"); this.addDefault(Messages.PurchaseConfirmation, "Withdrew {0} from your account. You now have {1} available claim blocks.", "0: total cost; 1: remaining blocks"); this.addDefault(Messages.PvPImmunityEnd, "Now you can fight with other players."); this.addDefault(Messages.PvPImmunityStart, "You're protected from attack by other players as long as your inventory is empty."); this.addDefault(Messages.PvPNoContainers, "You can't access containers during PvP combat."); this.addDefault(Messages.PvPNoDrop, "You can't drop items while in PvP combat."); this.addDefault(Messages.ReadyToTransferPet, "Ready to transfer! Right-click the pet you'd like to give away, or cancel with /GivePet cancel."); this.addDefault(Messages.RemainingBlocks, "You may claim up to {0} more blocks.", "0: remaining blocks"); this.addDefault(Messages.RescueAbortedMoved, "You moved! Rescue cancelled."); this.addDefault(Messages.RescuePending, "If you stay put for 10 seconds, you'll be teleported out. Please wait."); this.addDefault(Messages.ResizeClaimInsufficientArea, "The selected claim size of {0} blocks({1}x{2}) would be too small. A claim must use at least {3} total claim blocks."); this.addDefault(Messages.ResizeClaimTooNarrow, "This new claim size of {0} blocks({1}x{2}) would be too small. Claims must be at least {3} blocks wide.", "0: minimum claim width"); this.addDefault(Messages.ResizeFailOverlap, "Can't resize here because it would overlap another nearby claim."); this.addDefault(Messages.ResizeFailOverlapRegion, "You don't have permission to build there, so you can't claim that area."); this.addDefault(Messages.ResizeFailOverlapSubdivision, "You can't create a subdivision here because it would overlap another subdivision. Consider /abandonclaim to delete it, or use " + "your shovel at a corner to resize it."); this.addDefault(Messages.ResizeNeedMoreBlocks, "You don't have enough blocks for this size. You need {0} more.", "0: how many needed"); this.addDefault(Messages.ResizeStart, "Resizing claim. Use your shovel again at the new location for this corner."); this.addDefault(Messages.RespectingClaims, "Now respecting claims."); this.addDefault(Messages.RestoreNatureActivate, "Ready to restore some nature! Right click to restore nature, and use /BasicClaims to stop."); this.addDefault(Messages.RestoreNatureAggressiveActivate, "Aggressive mode activated. Do NOT use this underneath anything you want to keep! Right click to aggressively restore nature, and" + " use /BasicClaims to stop.", null); this.addDefault(Messages.RestoreNaturePlayerInChunk, "Unable to restore. {0} is in that chunk.", "0: nearby player"); this.addDefault(Messages.SeparateConfirmation, "Those players will now ignore each other in chat."); this.addDefault(Messages.SetClaimBlocksSuccess, "Updated accrued claim blocks."); this.addDefault(Messages.ShovelBasicClaimMode, "Shovel returned to basic claims mode."); this.addDefault(Messages.ShowNearbyClaims, "Found {0} land claims.", "0: Number of claims found."); this.addDefault(Messages.SiegeAlert, "You're under siege! If you log out now, you will die. You must defeat {0}, wait for him to give up, or escape.", "0: attacker name"); this.addDefault(Messages.SiegeConfirmed, "The siege has begun! If you log out now, you will die. You must defeat {0}, chase him away, or admit defeat and walk away.", "0: defender name"); this.addDefault(Messages.SiegeDoorsLockedEjection, "Looting time is up! Ejected from the claim."); this.addDefault(Messages.SiegeImmune, "That player is immune to /siege."); this.addDefault(Messages.SiegeNoContainers, "You can't access containers while involved in a siege."); this.addDefault(Messages.SiegeNoDrop, "You can't give away items while involved in a siege."); this.addDefault(Messages.SiegeNoShovel, "You can't use your shovel tool while involved in a siege."); this.addDefault(Messages.SiegeNoTeleport, "You can't teleport out of a besieged area."); this.addDefault(Messages.SiegeOnCooldown, "You're still on siege cooldown for this defender or claim. Find another victim."); this.addDefault(Messages.SiegeTooFarAway, "You're too far away to siege."); this.addDefault(Messages.SiegeWinDoorsOpen, "Congratulations! Buttons and levers are temporarily unlocked (five minutes)."); this.addDefault(Messages.SoftMuted, "Soft-muted {0}.", "0: The changed player's name."); this.addDefault(Messages.StartBlockMath, "{0} blocks from play + {1} bonus + {2} initial = {3} total."); this.addDefault(Messages.SubdivisionNoClaimFound, "No claim exists at selected corner. Please click a valid opposite corner within parent claim in order to create your subdivision."); this.addDefault(Messages.SubdivisionMode, "Subdivision mode. Use your shovel to create subdivisions in your existing claims. Use /basicclaims to exit."); this.addDefault(Messages.SubdivisionStart, "Subdivision corner set! Use your shovel at the location for the opposite corner of this new subdivision."); this.addDefault(Messages.SubdivisionSuccess, "Subdivision created! Use /trust to share it with friends."); this.addDefault(Messages.SubdivisionVideo2, "Click for Subdivision Help: " + SUBDIVISION_VIDEO_URL_RAW); this.addDefault(Messages.SuccessfulAbandon, "Claims abandoned. You now have {0} available claim blocks.", "0: remaining blocks"); this.addDefault(Messages.SurvivalBasicsVideo2, "Click for Land Claim Help: " + SURVIVAL_VIDEO_URL_RAW); this.addDefault(Messages.ThatPlayerPvPImmune, "You can't injure defenseless players."); this.addDefault(Messages.TooDeepToClaim, "This chest can't be protected because it's too deep underground. Consider moving it."); this.addDefault(Messages.TooFarAway, "That's too far away."); this.addDefault(Messages.TooManyEntitiesInClaim, "This claim has too many entities already. Try enlarging the claim or removing some animals, monsters, paintings, or minecarts."); this.addDefault(Messages.TransferClaimAdminOnly, "Only administrative claims may be transferred to a player."); this.addDefault(Messages.TransferClaimMissing, "There's no claim here. Stand in the administrative claim you want to transfer.", null); this.addDefault(Messages.TransferSuccess, "Claim transferred."); this.addDefault(Messages.TransferTopLevel, "Only top level claims (not subdivisions) may be transferred. Stand outside of the subdivision and try again."); this.addDefault(Messages.TrappedWontWorkHere, "Sorry, unable to find a safe location to teleport you to. Contact an admin, or consider /kill if you don't want to wait."); this.addDefault(Messages.TrustIndividualAllClaims, "Granted {0}'s full trust to all your claims. To unset permissions for ALL your claims, use /untrustall.", "0: untrusted player"); this.addDefault(Messages.TrustListHeader, "Explicit permissions here:"); this.addDefault(Messages.TrustListNoClaim, "Stand inside the claim you're curious about."); this.addDefault(Messages.UnIgnoreConfirmation, "You're no longer ignoring chat messages from that player."); this.addDefault(Messages.UnSeparateConfirmation, "Those players will no longer ignore each other in chat."); this.addDefault(Messages.UnSoftMuted, "Un-soft-muted {0}.", "0: The changed player's name."); this.addDefault(Messages.UnclaimCleanupWarning, "The land you've unclaimed may be changed by other players or cleaned up by administrators. If you've built something there you " + "want to keep, you should reclaim it."); this.addDefault(Messages.UnprotectedChestWarning, "This chest is NOT protected. Consider using a golden shovel to expand an existing claim or to create a new one."); this.addDefault(Messages.UntrustAllOwnerOnly, "Only the claim owner can clear all its permissions."); this.addDefault(Messages.UntrustEveryoneAllClaims, "Cleared permissions in ALL your claims. To set permissions for a single claim, stand inside it."); this.addDefault(Messages.UntrustIndividualAllClaims, "Revoked {0}'s access to ALL your claims. To set permissions for a single claim, stand inside it and use /untrust.", "0: untrusted player"); this.addDefault(Messages.UntrustIndividualSingleClaim, "Revoked {0}'s access to this claim. To unset permissions for ALL your claims, use /untrustall.", "0: untrusted player"); this.addDefault(Messages.YouHaveNoClaims, "You don't have any land claims."); // load the config file try { HoconConfigurationLoader configurationLoader = HoconConfigurationLoader.builder() .setPath(messagesFilePath).build(); CommentedConfigurationNode mainNode = configurationLoader.load(); // for each message ID for (CustomizableMessage messageData : this.messages.values()) { // read the message from the file, use default if necessary if (mainNode.getNode("Messages", messageData.id.name(), "Text").isVirtual()) { mainNode.getNode("Messages", messageData.id.name(), "Text").setValue(messageData.text); } else { messageData.text = mainNode.getNode("Messages", messageData.id.name(), "Text").getString(); } if (messageData.notes != null) { if (mainNode.getNode("Messages", messageData.id.name(), "Notes").isVirtual()) { mainNode.getNode("Messages", messageData.id.name(), "Notes").setValue(messageData.notes); } else { messageData.notes = mainNode.getNode("Messages", messageData.id.name(), "Notes") .getString(); } } } // save any changes configurationLoader.save(mainNode); } catch (Exception e) { e.printStackTrace(); GriefPreventionPlugin.addLogEntry( "Unable to write to the configuration file at \"" + DataStore.messagesFilePath + "\""); } } private void addDefault(Messages id, String text) { this.addDefault(id, text, null); } private void addDefault(Messages id, String text, String notes) { CustomizableMessage message = new CustomizableMessage(id, text, notes); this.messages.put(id, message); } public String getMessage(Messages messageID, String... args) { if (!generateMessages) { return ""; } if (this.messages.get(messageID) == null) { return null; } String message = this.messages.get(messageID).text; for (int i = 0; i < args.length; i++) { String param = args[i]; message = message.replace("{" + i + "}", param); } return message; } public Text parseMessage(Messages messageID, TextColor color, String... args) { String message = GriefPreventionPlugin.instance.dataStore.getMessage(messageID, args); Text textMessage = Text.of(color, message); List<String> urls = extractUrls(message); if (urls.isEmpty()) { return textMessage; } Iterator<String> iterator = urls.iterator(); while (iterator.hasNext()) { String url = iterator.next(); String msgPart = StringUtils.substringBefore(message, url); if (msgPart != null && !msgPart.equals("")) { try { textMessage = Text.of(color, msgPart, TextColors.GREEN, TextActions.openUrl(new URL(url)), url); } catch (MalformedURLException e) { e.printStackTrace(); return Text.of(message); } } iterator.remove(); message = StringUtils.substringAfter(message, url); } if (message != null && !message.equals("")) { textMessage = Text.of(textMessage, " ", color, message); } return textMessage; } /** * Returns a list with all links contained in the input */ public static List<String> extractUrls(String text) { List<String> containedUrls = new ArrayList<String>(); String urlRegex = "((https?|ftp|gopher|telnet|file):((//)|(\\\\))+[\\w\\d:#@%/;$()~_?\\+-=\\\\\\.&]*)"; Pattern pattern = Pattern.compile(urlRegex, Pattern.CASE_INSENSITIVE); Matcher urlMatcher = null; try { urlMatcher = pattern.matcher(text); } catch (Throwable t) { return containedUrls; } while (urlMatcher.find()) { containedUrls.add(text.substring(urlMatcher.start(0), urlMatcher.end(0))); } return containedUrls; } // used in updating the data schema from 0 to 1. // converts player names in a list to uuids protected List<String> convertNameListToUUIDList(List<String> names) { // doesn't apply after schema has been updated to version 1 if (this.getSchemaVersion() >= 1) { return names; } // list to build results List<String> resultNames = new ArrayList<String>(); for (String name : names) { // skip non-player-names (groups and "public"), leave them as-is if (name.startsWith("[") || name.equals("public")) { resultNames.add(name); continue; } // otherwise try to convert to a UUID Optional<User> player = Optional.empty(); try { player = Sponge.getGame().getServiceManager().provide(UserStorageService.class).get().get(name); } catch (Exception ex) { } // if successful, replace player name with corresponding UUID if (player.isPresent()) { resultNames.add(player.get().getUniqueId().toString()); } } return resultNames; } // gets all the claims "near" a location public Set<GPClaim> getNearbyClaims(Location<World> location) { Set<GPClaim> claims = new HashSet<>(); GPClaimManager claimWorldManager = this.getClaimWorldManager(location.getExtent().getProperties()); if (claimWorldManager == null) { return claims; } Optional<Chunk> lesserChunk = location.getExtent() .getChunkAtBlock(location.sub(50, 0, 50).getBlockPosition()); Optional<Chunk> greaterChunk = location.getExtent() .getChunkAtBlock(location.add(50, 0, 50).getBlockPosition()); if (lesserChunk.isPresent() && greaterChunk.isPresent()) { for (int chunkX = lesserChunk.get().getPosition().getX(); chunkX <= greaterChunk.get().getPosition() .getX(); chunkX++) { for (int chunkZ = lesserChunk.get().getPosition().getZ(); chunkZ <= greaterChunk.get().getPosition() .getZ(); chunkZ++) { Optional<Chunk> chunk = location.getExtent().getChunk(chunkX, 0, chunkZ); if (chunk.isPresent()) { Set<GPClaim> claimsInChunk = claimWorldManager.getChunksToClaimsMap() .get(ChunkPos.chunkXZ2Int(chunkX, chunkZ)); if (claimsInChunk != null) { claims.addAll(claimsInChunk); } } } } } return claims; } public GPClaim getClaimAtPlayer(GPPlayerData playerData, Location<World> location) { return this.getClaimAtPlayer(playerData, location, false); } public GPClaim getClaimAtPlayer(GPPlayerData playerData, Location<World> location, boolean ignoreHeight) { GPClaimManager claimManager = this.getClaimWorldManager(location.getExtent().getProperties()); return (GPClaim) claimManager.getClaimAtPlayer(playerData, location, ignoreHeight); } public GPClaim getClaimAt(Location<World> location) { return this.getClaimAt(location, false); } public GPClaim getClaimAt(Location<World> location, boolean ignoreHeight) { GPClaimManager claimManager = this.getClaimWorldManager(location.getExtent().getProperties()); return (GPClaim) claimManager.getClaimAt(location, ignoreHeight); } public GPClaim getClaimAt(Location<World> location, boolean ignoreHeight, WeakReference<Claim> cachedClaim) { GPClaimManager claimManager = this.getClaimWorldManager(location.getExtent().getProperties()); return (GPClaim) claimManager.getClaimAt(location, ignoreHeight, cachedClaim); } public GPPlayerData getPlayerData(World world, UUID playerUniqueId) { return this.getPlayerData(world.getProperties(), playerUniqueId); } public GPPlayerData getPlayerData(WorldProperties worldProperties, UUID playerUniqueId) { GPPlayerData playerData = null; GPClaimManager claimWorldManager = this.getClaimWorldManager(worldProperties); playerData = claimWorldManager.getPlayerDataMap().get(playerUniqueId); return playerData; } // retrieves player data from memory or secondary storage, as necessary // if the player has never been on the server before, this will return a // fresh player data with default values public GPPlayerData getOrCreatePlayerData(World world, UUID playerUniqueId) { return getOrCreatePlayerData(world.getProperties(), playerUniqueId); } public GPPlayerData getOrCreatePlayerData(WorldProperties worldProperties, UUID playerUniqueId) { GPClaimManager claimWorldManager = this.getClaimWorldManager(worldProperties); return claimWorldManager.getOrCreatePlayerData(playerUniqueId); } public void removePlayerData(WorldProperties worldProperties, UUID playerUniqueId) { GPClaimManager claimWorldManager = this.getClaimWorldManager(worldProperties); claimWorldManager.removePlayer(playerUniqueId); } public GPClaimManager getClaimWorldManager(WorldProperties worldProperties) { GPClaimManager claimWorldManager = null; if (worldProperties == null) { claimWorldManager = this.claimWorldManagers .get(Sponge.getServer().getDefaultWorld().get().getUniqueId()); } else { claimWorldManager = this.claimWorldManagers.get(worldProperties.getUniqueId()); } if (claimWorldManager == null) { claimWorldManager = new GPClaimManager(worldProperties); this.claimWorldManagers.put(worldProperties.getUniqueId(), claimWorldManager); } return claimWorldManager; } public void removeClaimWorldManager(WorldProperties worldProperties) { if (DataStore.USE_GLOBAL_PLAYER_STORAGE) { return; } this.claimWorldManagers.remove(worldProperties.getUniqueId()); } public void setupDefaultPermissions(World world) { Set<Context> contexts = new HashSet<>(); contexts.add(GriefPreventionPlugin.ADMIN_CLAIM_FLAG_DEFAULT_CONTEXT); contexts.add(world.getContext()); this.setFlagDefaultPermissions(contexts, GriefPreventionPlugin.getActiveConfig(world.getProperties()).getConfig().flags.getAdminDefaults()); this.setOptionDefaultPermissions(contexts); contexts = new HashSet<>(); contexts.add(GriefPreventionPlugin.BASIC_CLAIM_FLAG_DEFAULT_CONTEXT); contexts.add(world.getContext()); this.setFlagDefaultPermissions(contexts, GriefPreventionPlugin.getActiveConfig(world.getProperties()).getConfig().flags.getBasicDefaults()); this.setOptionDefaultPermissions(contexts); contexts = new HashSet<>(); contexts.add(GriefPreventionPlugin.WILDERNESS_CLAIM_FLAG_DEFAULT_CONTEXT); contexts.add(world.getContext()); this.setFlagDefaultPermissions(contexts, GriefPreventionPlugin.getActiveConfig(world.getProperties()).getConfig().flags .getWildernessDefaults()); this.setOptionDefaultPermissions(contexts); } private void setFlagDefaultPermissions(Set<Context> contexts, Map<String, Boolean> defaultFlags) { Sponge.getScheduler().createAsyncExecutor(GriefPreventionPlugin.instance.pluginContainer).execute(() -> { Map<String, Boolean> defaultPermissions = GriefPreventionPlugin.GLOBAL_SUBJECT.getTransientSubjectData() .getPermissions(contexts); if (defaultPermissions.isEmpty()) { for (Map.Entry<String, Boolean> mapEntry : defaultFlags.entrySet()) { GriefPreventionPlugin.GLOBAL_SUBJECT.getTransientSubjectData().setPermission(contexts, GPPermissions.FLAG_BASE + "." + mapEntry.getKey(), Tristate.fromBoolean(mapEntry.getValue())); } } else { // remove invalid flag entries for (String flagPermission : defaultPermissions.keySet()) { String flag = flagPermission.replace(GPPermissions.FLAG_BASE + ".", ""); if (!defaultFlags.containsKey(flag)) { GriefPreventionPlugin.GLOBAL_SUBJECT.getTransientSubjectData().setPermission(contexts, flagPermission, Tristate.UNDEFINED); } } // make sure all defaults are available for (Map.Entry<String, Boolean> mapEntry : defaultFlags.entrySet()) { String flagPermission = GPPermissions.FLAG_BASE + "." + mapEntry.getKey(); if (!defaultPermissions.keySet().contains(flagPermission)) { GriefPreventionPlugin.GLOBAL_SUBJECT.getTransientSubjectData().setPermission(contexts, flagPermission, Tristate.fromBoolean(mapEntry.getValue())); } } } }); } private void setOptionDefaultPermissions(Set<Context> contexts) { Sponge.getScheduler().createAsyncExecutor(GriefPreventionPlugin.instance.pluginContainer).execute(() -> { final SubjectData globalSubjectData = GriefPreventionPlugin.GLOBAL_SUBJECT.getTransientSubjectData(); for (Map.Entry<String, String> optionEntry : GPOptions.DEFAULT_OPTIONS.entrySet()) { globalSubjectData.setOption(contexts, optionEntry.getKey(), optionEntry.getValue()); } }); } abstract GPPlayerData getPlayerDataFromStorage(UUID playerID); public abstract void loadWorldData(World world); public abstract void unloadWorldData(WorldProperties worldProperties); abstract void loadClaimTemplates(); }