Reworked inactive claim expiration.

Cost to check a claim for inactivity greatly reduced.  Increased
frequency of checks to make inactive claims disappear closer to their
expiration times.  Enabled claim expiration for all servers (can be
disabled), added configurable exclusions with generous defaults for
players who've been playing on the server a long time and/or have
somehow earned a significant amount of bonus claim blocks.
This commit is contained in:
ryanhamshire 2016-01-15 10:06:34 -08:00
parent bed934ce09
commit fa68ba9ee8
5 changed files with 252 additions and 192 deletions

View File

@ -0,0 +1,49 @@
/*
GriefPrevention Server Plugin for Minecraft
Copyright (C) 2012 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 <http://www.gnu.org/licenses/>.
*/
package me.ryanhamshire.GriefPrevention;
import org.bukkit.Bukkit;
//asynchronously loads player data without caching it in the datastore, then
//passes those data to a claim cleanup task which might decide to delete a claim for inactivity
class CleanupUnusedClaimPreTask implements Runnable
{
private Claim claim = null;
CleanupUnusedClaimPreTask(Claim claim)
{
this.claim = claim;
}
@Override
public void run()
{
//get the data
PlayerData ownerData = GriefPrevention.instance.dataStore.getPlayerDataFromStorage(claim.ownerID);
//skip claims belonging to exempted players based on block totals in config
int bonusBlocks = ownerData.getBonusClaimBlocks();
if(bonusBlocks >= GriefPrevention.instance.config_claims_expirationExemptionBonusBlocks) return;
if(bonusBlocks + ownerData.getAccruedClaimBlocks() >= GriefPrevention.instance.config_claims_expirationExemptionTotalBlocks) return;
//pass it back to the main server thread, where it's safe to delete a claim if needed
Bukkit.getScheduler().scheduleSyncDelayedTask(GriefPrevention.instance, new CleanupUnusedClaimTask(claim, ownerData), 1L);
}
}

View File

@ -19,57 +19,22 @@
package me.ryanhamshire.GriefPrevention;
import java.util.Calendar;
import java.util.Random;
import java.util.Vector;
import org.bukkit.Chunk;
import org.bukkit.World;
class CleanupUnusedClaimTask implements Runnable
{
Claim claim;
PlayerData ownerData;
//FEATURE: automatically remove claims owned by inactive players which:
//...aren't protecting much OR
//...are a free new player claim (and the player has no other claims) OR
//...because the player has been gone a REALLY long time, and that expiration has been configured in config.yml
//runs every 1 minute in the main thread
class CleanupUnusedClaimsTask implements Runnable
CleanupUnusedClaimTask(Claim claim, PlayerData ownerData)
{
int nextClaimIndex;
CleanupUnusedClaimsTask()
{
//start scanning in a random spot
if(GriefPrevention.instance.dataStore.claims.size() == 0)
{
this.nextClaimIndex = 0;
}
else
{
Random randomNumberGenerator = new Random();
this.nextClaimIndex = randomNumberGenerator.nextInt(GriefPrevention.instance.dataStore.claims.size());
}
this.claim = claim;
this.ownerData = ownerData;
}
@Override
public void run()
{
//don't do anything when there are no claims
if(GriefPrevention.instance.dataStore.claims.size() == 0) return;
//wrap search around to beginning
if(this.nextClaimIndex >= GriefPrevention.instance.dataStore.claims.size()) this.nextClaimIndex = 0;
//decide which claim to check next
Claim claim = GriefPrevention.instance.dataStore.claims.get(this.nextClaimIndex++);
//skip administrative claims
if(claim.isAdminClaim()) return;
//track whether we do any important work which would require cleanup afterward
boolean cleanupChunks = false;
//get data for the player, especially last login timestamp
PlayerData playerData = null;
//determine area of the default chest claim
int areaOfDefaultClaim = 0;
if(GriefPrevention.instance.config_claims_automaticClaimsForNewPlayersRadius >= 0)
@ -80,17 +45,14 @@ class CleanupUnusedClaimsTask implements Runnable
//if this claim is a chest claim and those are set to expire
if(claim.getArea() <= areaOfDefaultClaim && GriefPrevention.instance.config_claims_chestClaimExpirationDays > 0)
{
playerData = GriefPrevention.instance.dataStore.getPlayerData(claim.ownerID);
//if the owner has been gone at least a week, and if he has ONLY the new player claim, it will be removed
Calendar sevenDaysAgo = Calendar.getInstance();
sevenDaysAgo.add(Calendar.DATE, -GriefPrevention.instance.config_claims_chestClaimExpirationDays);
boolean newPlayerClaimsExpired = sevenDaysAgo.getTime().after(playerData.getLastLogin());
if(newPlayerClaimsExpired && playerData.getClaims().size() == 1)
boolean newPlayerClaimsExpired = sevenDaysAgo.getTime().after(ownerData.getLastLogin());
if(newPlayerClaimsExpired && ownerData.getClaims().size() == 1)
{
claim.removeSurfaceFluids(null);
GriefPrevention.instance.dataStore.deleteClaim(claim, true, true);
cleanupChunks = true;
//if configured to do so, restore the land to natural
if(GriefPrevention.instance.creativeRulesApply(claim.getLesserBoundaryCorner()) || GriefPrevention.instance.config_claims_survivalAutoNatureRestoration)
@ -105,17 +67,16 @@ class CleanupUnusedClaimsTask implements Runnable
//if configured to always remove claims after some inactivity period without exceptions...
else if(GriefPrevention.instance.config_claims_expirationDays > 0)
{
if(playerData == null) playerData = GriefPrevention.instance.dataStore.getPlayerData(claim.ownerID);
Calendar earliestPermissibleLastLogin = Calendar.getInstance();
earliestPermissibleLastLogin.add(Calendar.DATE, -GriefPrevention.instance.config_claims_expirationDays);
if(earliestPermissibleLastLogin.getTime().after(playerData.getLastLogin()))
if(earliestPermissibleLastLogin.getTime().after(ownerData.getLastLogin()))
{
//make a copy of this player's claim list
Vector<Claim> claims = new Vector<Claim>();
for(int i = 0; i < playerData.getClaims().size(); i++)
for(int i = 0; i < ownerData.getClaims().size(); i++)
{
claims.add(playerData.getClaims().get(i));
claims.add(ownerData.getClaims().get(i));
}
//delete them
@ -128,7 +89,6 @@ class CleanupUnusedClaimsTask implements Runnable
if(GriefPrevention.instance.creativeRulesApply(claims.get(i).getLesserBoundaryCorner()) || GriefPrevention.instance.config_claims_survivalAutoNatureRestoration)
{
GriefPrevention.instance.restoreClaim(claims.get(i), 0);
cleanupChunks = true;
}
}
}
@ -143,16 +103,13 @@ class CleanupUnusedClaimsTask implements Runnable
int minInvestment = 400;
long investmentScore = claim.getPlayerInvestmentScore();
cleanupChunks = true;
if(investmentScore < minInvestment)
{
playerData = GriefPrevention.instance.dataStore.getPlayerData(claim.ownerID);
//if the owner has been gone at least a week, and if he has ONLY the new player claim, it will be removed
Calendar sevenDaysAgo = Calendar.getInstance();
sevenDaysAgo.add(Calendar.DATE, -GriefPrevention.instance.config_claims_unusedClaimExpirationDays);
boolean claimExpired = sevenDaysAgo.getTime().after(playerData.getLastLogin());
boolean claimExpired = sevenDaysAgo.getTime().after(ownerData.getLastLogin());
if(claimExpired)
{
GriefPrevention.instance.dataStore.deleteClaim(claim, true, true);
@ -163,26 +120,5 @@ class CleanupUnusedClaimsTask implements Runnable
}
}
}
if(playerData != null) GriefPrevention.instance.dataStore.clearCachedPlayerData(claim.ownerID);
//since we're potentially loading a lot of chunks to scan parts of the world where there are no players currently playing, be mindful of memory usage
if(cleanupChunks)
{
World world = claim.getLesserBoundaryCorner().getWorld();
Chunk lesserChunk = world.getChunkAt(claim.getLesserBoundaryCorner());
Chunk greaterChunk = world.getChunkAt(claim.getGreaterBoundaryCorner());
for(int x = lesserChunk.getX(); x <= greaterChunk.getX(); x++)
{
for(int z = lesserChunk.getZ(); z <= greaterChunk.getZ(); z++)
{
Chunk chunk = world.getChunkAt(x, z);
if(chunk.isLoaded())
{
chunk.unload(true, true);
}
}
}
}
}
}

View File

@ -731,6 +731,9 @@ public abstract class DataStore
int smallx, bigx, smally, bigy, smallz, bigz;
if(y1 < GriefPrevention.instance.config_claims_maxDepth) y1 = GriefPrevention.instance.config_claims_maxDepth;
if(y2 < GriefPrevention.instance.config_claims_maxDepth) y2 = GriefPrevention.instance.config_claims_maxDepth;
//determine small versus big inputs
if(x1 < x2)
{

View File

@ -0,0 +1,66 @@
/*
GriefPrevention Server Plugin for Minecraft
Copyright (C) 2012 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 <http://www.gnu.org/licenses/>.
*/
package me.ryanhamshire.GriefPrevention;
import java.util.Random;
import org.bukkit.Bukkit;
//FEATURE: automatically remove claims owned by inactive players which:
//...aren't protecting much OR
//...are a free new player claim (and the player has no other claims) OR
//...because the player has been gone a REALLY long time, and that expiration has been configured in config.yml
//runs every 1 minute in the main thread
class FindUnusedClaimsTask implements Runnable
{
int nextClaimIndex;
FindUnusedClaimsTask()
{
//start scanning in a random spot
if(GriefPrevention.instance.dataStore.claims.size() == 0)
{
this.nextClaimIndex = 0;
}
else
{
Random randomNumberGenerator = new Random();
this.nextClaimIndex = randomNumberGenerator.nextInt(GriefPrevention.instance.dataStore.claims.size());
}
}
@Override
public void run()
{
//don't do anything when there are no claims
if(GriefPrevention.instance.dataStore.claims.size() == 0) return;
//wrap search around to beginning
if(this.nextClaimIndex >= GriefPrevention.instance.dataStore.claims.size()) this.nextClaimIndex = 0;
//decide which claim to check next
Claim claim = GriefPrevention.instance.dataStore.claims.get(this.nextClaimIndex++);
//skip administrative claims
if(claim.isAdminClaim()) return;
Bukkit.getScheduler().runTaskAsynchronously(GriefPrevention.instance, new CleanupUnusedClaimPreTask(claim));
}
}

View File

@ -103,6 +103,8 @@ public class GriefPrevention extends JavaPlugin
public int config_claims_maxAccruedBlocks; //the limit on accrued blocks (over time). doesn't limit purchased or admin-gifted blocks
public int config_claims_maxDepth; //limit on how deep claims can go
public int config_claims_expirationDays; //how many days of inactivity before a player loses his claims
public int config_claims_expirationExemptionTotalBlocks; //total claim blocks amount which will exempt a player from claim expiration
public int config_claims_expirationExemptionBonusBlocks; //bonus claim blocks amount which will exempt a player from claim expiration
public int config_claims_automaticClaimsForNewPlayersRadius; //how big automatic new player claims (when they place a chest) should be. 0 to disable
public int config_claims_claimsExtendIntoGroundDistance; //how far below the shoveled block a new claim will reach
@ -311,8 +313,8 @@ public class GriefPrevention extends JavaPlugin
this.getServer().getScheduler().scheduleSyncDelayedTask(GriefPrevention.instance, task, 20L * 60 * 2);
//start recurring cleanup scan for unused claims belonging to inactive players
CleanupUnusedClaimsTask task2 = new CleanupUnusedClaimsTask();
this.getServer().getScheduler().scheduleSyncRepeatingTask(this, task2, 20L * 60 * 2, 20L * 60 * 5);
FindUnusedClaimsTask task2 = new FindUnusedClaimsTask();
this.getServer().getScheduler().scheduleSyncRepeatingTask(this, task2, 20L * 60, 20L * 60);
//register for events
PluginManager pluginManager = this.getServer().getPluginManager();
@ -525,7 +527,9 @@ public class GriefPrevention extends JavaPlugin
this.config_claims_maxDepth = config.getInt("GriefPrevention.Claims.MaximumDepth", 0);
this.config_claims_chestClaimExpirationDays = config.getInt("GriefPrevention.Claims.Expiration.ChestClaimDays", 7);
this.config_claims_unusedClaimExpirationDays = config.getInt("GriefPrevention.Claims.Expiration.UnusedClaimDays", 14);
this.config_claims_expirationDays = config.getInt("GriefPrevention.Claims.Expiration.AllClaimDays", 0);
this.config_claims_expirationDays = config.getInt("GriefPrevention.Claims.Expiration.AllClaims.DaysInactive", 60);
this.config_claims_expirationExemptionTotalBlocks = config.getInt("GriefPrevention.Claims.Expiration.AllClaims.ExceptWhenOwnerHasTotalClaimBlocks", 10000);
this.config_claims_expirationExemptionBonusBlocks = config.getInt("GriefPrevention.Claims.Expiration.AllClaims.ExceptWhenOwnerHasBonusClaimBlocks", 5000);
this.config_claims_survivalAutoNatureRestoration = config.getBoolean("GriefPrevention.Claims.Expiration.AutomaticNatureRestoration.SurvivalWorlds", false);
this.config_claims_maxClaimsPerPlayer = config.getInt("GriefPrevention.Claims.MaximumNumberOfClaimsPerPlayer", 0);
this.config_claims_respectWorldGuard = config.getBoolean("GriefPrevention.Claims.CreationRequiresWorldGuardBuildPermission", true);
@ -760,7 +764,9 @@ public class GriefPrevention extends JavaPlugin
outConfig.set("GriefPrevention.Claims.ModificationTool", this.config_claims_modificationTool.name());
outConfig.set("GriefPrevention.Claims.Expiration.ChestClaimDays", this.config_claims_chestClaimExpirationDays);
outConfig.set("GriefPrevention.Claims.Expiration.UnusedClaimDays", this.config_claims_unusedClaimExpirationDays);
outConfig.set("GriefPrevention.Claims.Expiration.AllClaimDays", this.config_claims_expirationDays);
outConfig.set("GriefPrevention.Claims.Expiration.AllClaims.DaysInactive", this.config_claims_expirationDays);
outConfig.set("GriefPrevention.Claims.Expiration.AllClaims.ExceptWhenOwnerHasTotalClaimBlocks", this.config_claims_expirationExemptionTotalBlocks);
outConfig.set("GriefPrevention.Claims.Expiration.AllClaims.ExceptWhenOwnerHasBonusClaimBlocks", this.config_claims_expirationExemptionBonusBlocks);
outConfig.set("GriefPrevention.Claims.Expiration.AutomaticNatureRestoration.SurvivalWorlds", this.config_claims_survivalAutoNatureRestoration);
outConfig.set("GriefPrevention.Claims.MaximumNumberOfClaimsPerPlayer", this.config_claims_maxClaimsPerPlayer);
outConfig.set("GriefPrevention.Claims.CreationRequiresWorldGuardBuildPermission", this.config_claims_respectWorldGuard);