/* GriefPrevention Server Plugin for Minecraft Copyright (C) 2011 Ryan Hamshire This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ package me.ryanhamshire.GriefPrevention; import java.net.InetAddress; import java.util.Calendar; import java.util.Date; import java.util.UUID; import java.util.Vector; import java.util.concurrent.ConcurrentHashMap; import me.ryanhamshire.GriefPrevention.Claim; import me.ryanhamshire.GriefPrevention.GriefPrevention; import me.ryanhamshire.GriefPrevention.ShovelMode; import me.ryanhamshire.GriefPrevention.SiegeData; import me.ryanhamshire.GriefPrevention.Visualization; import org.bukkit.Bukkit; import org.bukkit.Location; import org.bukkit.OfflinePlayer; import org.bukkit.entity.Player; //holds all of GriefPrevention's player-tied data public class PlayerData { //the player's ID public UUID playerID; //the player's claims private Vector claims = null; //how many claim blocks the player has earned via play time private Integer accruedClaimBlocks = null; //temporary holding area to avoid opening data files too early private int newlyAccruedClaimBlocks = 0; //where this player was the last time we checked on him for earning claim blocks public Location lastAfkCheckLocation = null; //how many claim blocks the player has been gifted by admins, or purchased via economy integration private Integer bonusClaimBlocks = null; //what "mode" the shovel is in determines what it will do when it's used public ShovelMode shovelMode = ShovelMode.Basic; //radius for restore nature fill mode int fillRadius = 0; //last place the player used the shovel, useful in creating and resizing claims, //because the player must use the shovel twice in those instances public Location lastShovelLocation = null; //the claim this player is currently resizing public Claim claimResizing = null; //the claim this player is currently subdividing public Claim claimSubdividing = null; //whether or not the player has a pending /trapped rescue public boolean pendingTrapped = false; //whether this player was recently warned about building outside land claims boolean warnedAboutBuildingOutsideClaims = false; //timestamp when last siege ended (where this player was the defender) long lastSiegeEndTimeStamp = 0; //whether the player was kicked (set and used during logout) boolean wasKicked = false; //visualization public Visualization currentVisualization = null; //anti-camping pvp protection public boolean pvpImmune = false; public long lastSpawn = 0; //ignore claims mode public boolean ignoreClaims = false; //the last claim this player was in, that we know of public Claim lastClaim = null; //siege public SiegeData siegeData = null; //pvp public long lastPvpTimestamp = 0; public String lastPvpPlayer = ""; //safety confirmation for deleting multi-subdivision claims public boolean warnedAboutMajorDeletion = false; public InetAddress ipAddress; //whether or not this player has received a message about unlocking death drops since his last death boolean receivedDropUnlockAdvertisement = false; //whether or not this player's dropped items (on death) are unlocked for other players to pick up boolean dropsAreUnlocked = false; //message to send to player after he respawns String messageOnRespawn = null; //player which a pet will be given to when it's right-clicked OfflinePlayer petGiveawayRecipient = null; //timestamp for last "you're building outside your land claims" message Long buildWarningTimestamp = null; //spot where a player can't talk, used to mute new players until they've moved a little //this is an anti-bot strategy. Location noChatLocation = null; //ignore list //true means invisible (admin-forced ignore), false means player-created ignore public ConcurrentHashMap ignoredPlayers = new ConcurrentHashMap(); public boolean ignoreListChanged = false; //profanity warning, once per play session boolean profanityWarned = false; //whether or not this player is "in" pvp combat public boolean inPvpCombat() { if(this.lastPvpTimestamp == 0) return false; long now = Calendar.getInstance().getTimeInMillis(); long elapsed = now - this.lastPvpTimestamp; if(elapsed > GriefPrevention.instance.config_pvp_combatTimeoutSeconds * 1000) //X seconds { this.lastPvpTimestamp = 0; return false; } return true; } //the number of claim blocks a player has available for claiming land public int getRemainingClaimBlocks() { int remainingBlocks = this.getAccruedClaimBlocks() + this.getBonusClaimBlocks(); for(int i = 0; i < this.getClaims().size(); i++) { Claim claim = this.getClaims().get(i); remainingBlocks -= claim.getArea(); } //add any blocks this player might have based on group membership (permissions) remainingBlocks += GriefPrevention.instance.dataStore.getGroupBonusBlocks(this.playerID); return remainingBlocks; } //don't load data from secondary storage until it's needed public int getAccruedClaimBlocks() { if(this.accruedClaimBlocks == null) this.loadDataFromSecondaryStorage(); //update claim blocks with any he has accrued during his current play session if(this.newlyAccruedClaimBlocks > 0) { int accruedLimit = this.getAccruedClaimBlocksLimit(); //if over the limit before adding blocks, leave it as-is, because the limit may have changed AFTER he accrued the blocks if(this.accruedClaimBlocks < accruedLimit) { //move any in the holding area int newTotal = this.accruedClaimBlocks + this.newlyAccruedClaimBlocks; //respect limits this.accruedClaimBlocks = Math.min(newTotal, accruedLimit); } this.newlyAccruedClaimBlocks = 0; return this.accruedClaimBlocks; } return accruedClaimBlocks; } public void setAccruedClaimBlocks(Integer accruedClaimBlocks) { this.accruedClaimBlocks = accruedClaimBlocks; this.newlyAccruedClaimBlocks = 0; } public int getBonusClaimBlocks() { if(this.bonusClaimBlocks == null) this.loadDataFromSecondaryStorage(); return bonusClaimBlocks; } public void setBonusClaimBlocks(Integer bonusClaimBlocks) { this.bonusClaimBlocks = bonusClaimBlocks; } private void loadDataFromSecondaryStorage() { //reach out to secondary storage to get any data there PlayerData storageData = GriefPrevention.instance.dataStore.getPlayerDataFromStorage(this.playerID); if(this.accruedClaimBlocks == null) { if(storageData.accruedClaimBlocks != null) { this.accruedClaimBlocks = storageData.accruedClaimBlocks; //ensure at least minimum accrued are accrued (in case of settings changes to increase initial amount) if(this.accruedClaimBlocks < GriefPrevention.instance.config_claims_initialBlocks) { this.accruedClaimBlocks = GriefPrevention.instance.config_claims_initialBlocks; } } else { this.accruedClaimBlocks = GriefPrevention.instance.config_claims_initialBlocks; } } if(this.bonusClaimBlocks == null) { if(storageData.bonusClaimBlocks != null) { this.bonusClaimBlocks = storageData.bonusClaimBlocks; } else { this.bonusClaimBlocks = 0; } } } public Vector getClaims() { if(this.claims == null) { this.claims = new Vector(); //find all the claims belonging to this player and note them for future reference DataStore dataStore = GriefPrevention.instance.dataStore; int totalClaimsArea = 0; for(int i = 0; i < dataStore.claims.size(); i++) { Claim claim = dataStore.claims.get(i); if(!claim.inDataStore) { dataStore.claims.remove(i--); continue; } if(playerID.equals(claim.ownerID)) { this.claims.add(claim); totalClaimsArea += claim.getArea(); } } //ensure player has claim blocks for his claims, and at least the minimum accrued this.loadDataFromSecondaryStorage(); //if total claimed area is more than total blocks available int totalBlocks = this.accruedClaimBlocks + this.getBonusClaimBlocks() + GriefPrevention.instance.dataStore.getGroupBonusBlocks(this.playerID); if(totalBlocks < totalClaimsArea) { OfflinePlayer player = GriefPrevention.instance.getServer().getOfflinePlayer(this.playerID); GriefPrevention.AddLogEntry(player.getName() + " has more claimed land than blocks available. Adding blocks to fix.", CustomLogEntryTypes.Debug, true); GriefPrevention.AddLogEntry("Total blocks: " + totalBlocks + " Total claimed area: " + totalClaimsArea, CustomLogEntryTypes.Debug, true); for(Claim claim : this.claims) { if(!claim.inDataStore) continue; GriefPrevention.AddLogEntry( GriefPrevention.getfriendlyLocationString(claim.getLesserBoundaryCorner()) + " // " + GriefPrevention.getfriendlyLocationString(claim.getGreaterBoundaryCorner()) + " = " + claim.getArea() , CustomLogEntryTypes.Debug, true); } //try to fix it by adding to accrued blocks this.accruedClaimBlocks = totalClaimsArea; int accruedLimit = this.getAccruedClaimBlocksLimit(); this.accruedClaimBlocks = Math.min(accruedLimit, this.accruedClaimBlocks); //if that didn't fix it, then make up the difference with bonus blocks totalBlocks = this.accruedClaimBlocks + this.getBonusClaimBlocks() + GriefPrevention.instance.dataStore.getGroupBonusBlocks(this.playerID); if(totalBlocks < totalClaimsArea) { this.bonusClaimBlocks += totalClaimsArea - totalBlocks; } } } for(int i = 0; i < this.claims.size(); i++) { if(!claims.get(i).inDataStore) { claims.remove(i--); } } return claims; } //determine limits based on permissions private int getAccruedClaimBlocksLimit() { Player player = Bukkit.getServer().getPlayer(this.playerID); //if the player isn't online, give him the benefit of any doubt //TODO: revisit and perhaps call an event for this(?) //if(player == null) return Integer.MAX_VALUE; return GriefPrevention.instance.config_claims_maxAccruedBlocks_default; } public void accrueBlocks(int howMany) { this.newlyAccruedClaimBlocks += howMany; } }