4.2
This commit is contained in:
parent
540746ad4b
commit
afe868de2f
16
plugin.yml
16
plugin.yml
|
|
@ -1,7 +1,7 @@
|
|||
name: GriefPrevention
|
||||
main: me.ryanhamshire.GriefPrevention.GriefPrevention
|
||||
softdepend: [Vault, Multiverse-Core]
|
||||
version: 3.8
|
||||
version: 4.2
|
||||
commands:
|
||||
abandonclaim:
|
||||
description: Deletes a claim.
|
||||
|
|
@ -60,6 +60,16 @@ commands:
|
|||
usage: /RestoreNature
|
||||
permission: griefprevention.restorenature
|
||||
aliases: rn
|
||||
restorenatureaggressive:
|
||||
description: Switches the shovel tool to aggressive restoration mode.
|
||||
usage: /RestoreNatureAggressive
|
||||
permission: griefprevention.restorenatureaggressive
|
||||
aliases: rna
|
||||
restorenaturefill:
|
||||
description: Switches the shovel tool to fill mode.
|
||||
usage: /RestoreNatureFill <radius>
|
||||
permission: griefprevention.restorenatureaggressive
|
||||
aliases: rnf
|
||||
basicclaims:
|
||||
description: Switches the shovel tool back to basic claims mode.
|
||||
usage: /BasicClaims
|
||||
|
|
@ -102,6 +112,7 @@ permissions:
|
|||
description: Grants all administrative functionality.
|
||||
children:
|
||||
griefprevention.restorenature: true
|
||||
griefprevention.restorenatureaggressive: true
|
||||
griefprevention.ignoreclaims: true
|
||||
griefprevention.adminclaims: true
|
||||
griefprevention.adjustclaimblocks: true
|
||||
|
|
@ -132,4 +143,7 @@ permissions:
|
|||
default: op
|
||||
griefprevention.eavesdrop:
|
||||
description: Allows a player to see whispered chat messages (/tell).
|
||||
default: op
|
||||
griefprevention.restorenatureaggressive:
|
||||
description: Grants access to /RestoreNatureAggressive and /RestoreNatureFill.
|
||||
default: op
|
||||
|
|
@ -25,6 +25,7 @@ import org.bukkit.Location;
|
|||
import org.bukkit.Material;
|
||||
import org.bukkit.OfflinePlayer;
|
||||
import org.bukkit.block.Block;
|
||||
import org.bukkit.block.BlockFace;
|
||||
import org.bukkit.block.BlockState;
|
||||
import org.bukkit.block.Chest;
|
||||
import org.bukkit.entity.Player;
|
||||
|
|
@ -41,6 +42,7 @@ import org.bukkit.event.block.BlockPistonExtendEvent;
|
|||
import org.bukkit.event.block.BlockPistonRetractEvent;
|
||||
import org.bukkit.event.block.BlockPlaceEvent;
|
||||
import org.bukkit.event.block.BlockSpreadEvent;
|
||||
import org.bukkit.event.block.SignChangeEvent;
|
||||
import org.bukkit.event.world.StructureGrowEvent;
|
||||
import org.bukkit.inventory.Inventory;
|
||||
import org.bukkit.inventory.ItemStack;
|
||||
|
|
@ -139,7 +141,7 @@ public class BlockEventHandler implements Listener
|
|||
}
|
||||
|
||||
//when a player breaks a block...
|
||||
@EventHandler(ignoreCancelled = true, priority = EventPriority.HIGHEST)
|
||||
@EventHandler(ignoreCancelled = true, priority = EventPriority.LOWEST)
|
||||
public void onBlockBreak(BlockBreakEvent breakEvent)
|
||||
{
|
||||
Player player = breakEvent.getPlayer();
|
||||
|
|
@ -177,8 +179,36 @@ public class BlockEventHandler implements Listener
|
|||
}
|
||||
}
|
||||
|
||||
//when a player places a sign...
|
||||
@EventHandler(ignoreCancelled = true)
|
||||
public void onSignChanged(SignChangeEvent event)
|
||||
{
|
||||
Player player = event.getPlayer();
|
||||
if(player == null) return;
|
||||
|
||||
StringBuilder lines = new StringBuilder();
|
||||
boolean notEmpty = false;
|
||||
for(int i = 0; i < event.getLines().length; i++)
|
||||
{
|
||||
if(event.getLine(i).length() != 0) notEmpty = true;
|
||||
lines.append(event.getLine(i) + ";");
|
||||
}
|
||||
|
||||
String signMessage = lines.toString();
|
||||
|
||||
//if not empty and wasn't the same as the last sign, log it and remember it for later
|
||||
PlayerData playerData = this.dataStore.getPlayerData(player.getName());
|
||||
if(notEmpty && playerData.lastMessage != null && !playerData.lastMessage.equals(signMessage))
|
||||
{
|
||||
GriefPrevention.AddLogEntry("[Sign Placement] <" + player.getName() + "> " + lines.toString());
|
||||
GriefPrevention.AddLogEntry("Location: " + GriefPrevention.getfriendlyLocationString(event.getBlock().getLocation()));
|
||||
|
||||
playerData.lastMessage = signMessage;
|
||||
}
|
||||
}
|
||||
|
||||
//when a player places a block...
|
||||
@EventHandler(ignoreCancelled = true, priority = EventPriority.HIGHEST)
|
||||
@EventHandler(ignoreCancelled = true, priority = EventPriority.LOWEST)
|
||||
public void onBlockPlace(BlockPlaceEvent placeEvent)
|
||||
{
|
||||
Player player = placeEvent.getPlayer();
|
||||
|
|
@ -203,6 +233,20 @@ public class BlockEventHandler implements Listener
|
|||
}
|
||||
}
|
||||
|
||||
//FEATURE: limit tree planting to grass, and dirt with more earth beneath it
|
||||
if(block.getType() == Material.SAPLING)
|
||||
{
|
||||
Block earthBlock = placeEvent.getBlockAgainst();
|
||||
if(earthBlock.getType() != Material.GRASS)
|
||||
{
|
||||
if(earthBlock.getRelative(BlockFace.DOWN).getType() == Material.AIR ||
|
||||
earthBlock.getRelative(BlockFace.DOWN).getRelative(BlockFace.DOWN).getType() == Material.AIR)
|
||||
{
|
||||
placeEvent.setCancelled(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//make sure the player is allowed to build at the location
|
||||
String noBuildReason = GriefPrevention.instance.allowBuild(player, block.getLocation());
|
||||
if(noBuildReason != null)
|
||||
|
|
@ -292,7 +336,7 @@ public class BlockEventHandler implements Listener
|
|||
}
|
||||
|
||||
//blocks "pushing" other players' blocks around (pistons)
|
||||
@EventHandler(ignoreCancelled = true, priority = EventPriority.HIGHEST)
|
||||
@EventHandler(ignoreCancelled = true, priority = EventPriority.LOWEST)
|
||||
public void onBlockPistonExtend (BlockPistonExtendEvent event)
|
||||
{
|
||||
//who owns the piston, if anyone?
|
||||
|
|
@ -316,7 +360,7 @@ public class BlockEventHandler implements Listener
|
|||
}
|
||||
|
||||
//blocks theft by pulling blocks out of a claim (again pistons)
|
||||
@EventHandler(ignoreCancelled = true, priority = EventPriority.HIGHEST)
|
||||
@EventHandler(ignoreCancelled = true, priority = EventPriority.LOWEST)
|
||||
public void onBlockPistonRetract (BlockPistonRetractEvent event)
|
||||
{
|
||||
//we only care about sticky pistons
|
||||
|
|
@ -342,21 +386,21 @@ public class BlockEventHandler implements Listener
|
|||
}
|
||||
|
||||
//blocks are ignited ONLY by flint and steel (not by being near lava, open flames, etc), unless configured otherwise
|
||||
@EventHandler(priority = EventPriority.HIGHEST)
|
||||
@EventHandler(priority = EventPriority.LOWEST)
|
||||
public void onBlockIgnite (BlockIgniteEvent igniteEvent)
|
||||
{
|
||||
if(igniteEvent.getCause() != IgniteCause.FLINT_AND_STEEL && !GriefPrevention.instance.config_fireSpreads) igniteEvent.setCancelled(true);
|
||||
}
|
||||
|
||||
//fire doesn't spread unless configured to, but other blocks still do (mushrooms and vines, for example)
|
||||
@EventHandler(priority = EventPriority.HIGHEST)
|
||||
@EventHandler(priority = EventPriority.LOWEST)
|
||||
public void onBlockSpread (BlockSpreadEvent spreadEvent)
|
||||
{
|
||||
if(spreadEvent.getSource().getType() == Material.FIRE && !GriefPrevention.instance.config_fireSpreads) spreadEvent.setCancelled(true);
|
||||
}
|
||||
|
||||
//blocks are not destroyed by fire, unless configured to do so
|
||||
@EventHandler(priority = EventPriority.HIGHEST)
|
||||
@EventHandler(priority = EventPriority.LOWEST)
|
||||
public void onBlockBurn (BlockBurnEvent burnEvent)
|
||||
{
|
||||
if(!GriefPrevention.instance.config_fireDestroys)
|
||||
|
|
@ -371,28 +415,28 @@ public class BlockEventHandler implements Listener
|
|||
}
|
||||
}
|
||||
|
||||
//ensures fluids don't flow into claims, unless out of another claim where the owner is trusted to build in the receiving claim
|
||||
@EventHandler(ignoreCancelled = true, priority = EventPriority.HIGHEST)
|
||||
//ensures fluids don't flow out of claims, unless into another claim where the owner is trusted to build
|
||||
@EventHandler(ignoreCancelled = true, priority = EventPriority.LOWEST)
|
||||
public void onBlockFromTo (BlockFromToEvent spreadEvent)
|
||||
{
|
||||
//where to?
|
||||
Block toBlock = spreadEvent.getToBlock();
|
||||
Claim toClaim = this.dataStore.getClaimAt(toBlock.getLocation(), false, null);
|
||||
//from where?
|
||||
Block fromBlock = spreadEvent.getBlock();
|
||||
Claim fromClaim = this.dataStore.getClaimAt(fromBlock.getLocation(), false, null);
|
||||
|
||||
//if in a creative world, block any spread into the wilderness
|
||||
if(GriefPrevention.instance.creativeRulesApply(toBlock.getLocation()) && toClaim == null)
|
||||
//where to?
|
||||
Block toBlock = spreadEvent.getToBlock();
|
||||
Claim toClaim = this.dataStore.getClaimAt(toBlock.getLocation(), false, fromClaim);
|
||||
|
||||
//block any spread into the wilderness
|
||||
if(fromClaim != null && toClaim == null)
|
||||
{
|
||||
spreadEvent.setCancelled(true);
|
||||
return;
|
||||
}
|
||||
|
||||
//if spreading into a claim
|
||||
if(toClaim != null)
|
||||
else if(toClaim != null)
|
||||
{
|
||||
//from where?
|
||||
Block fromBlock = spreadEvent.getBlock();
|
||||
Claim fromClaim = this.dataStore.getClaimAt(fromBlock.getLocation(), false, null);
|
||||
|
||||
//who owns the spreading block, if anyone?
|
||||
OfflinePlayer fromOwner = null;
|
||||
if(fromClaim != null)
|
||||
|
|
|
|||
|
|
@ -26,6 +26,8 @@ import java.util.Iterator;
|
|||
import java.util.Map;
|
||||
|
||||
import org.bukkit.*;
|
||||
import org.bukkit.World.Environment;
|
||||
import org.bukkit.block.Block;
|
||||
import org.bukkit.entity.Entity;
|
||||
import org.bukkit.entity.Player;
|
||||
|
||||
|
|
@ -98,6 +100,44 @@ public class Claim
|
|||
return true;
|
||||
}
|
||||
|
||||
//removes any fluids above sea level in a claim
|
||||
//exclusionClaim is another claim indicating an sub-area to be excluded from this operation
|
||||
//it may be null
|
||||
public void removeSurfaceFluids(Claim exclusionClaim)
|
||||
{
|
||||
//don't do this automatically for administrative claims
|
||||
if(this.isAdminClaim()) return;
|
||||
|
||||
Location lesser = this.getLesserBoundaryCorner();
|
||||
Location greater = this.getGreaterBoundaryCorner();
|
||||
|
||||
if(lesser.getWorld().getEnvironment() == Environment.NETHER) return; //don't clean up lava in the nether
|
||||
|
||||
int seaLevel = 0; //clean up all fluids in the end
|
||||
|
||||
//respect sea level in normal worlds
|
||||
if(lesser.getWorld().getEnvironment() == Environment.NORMAL) seaLevel = lesser.getWorld().getSeaLevel();
|
||||
|
||||
for(int x = lesser.getBlockX(); x <= greater.getBlockX(); x++)
|
||||
{
|
||||
for(int z = lesser.getBlockZ(); z <= greater.getBlockZ(); z++)
|
||||
{
|
||||
for(int y = seaLevel - 1; y <= lesser.getWorld().getMaxHeight(); y++)
|
||||
{
|
||||
//dodge the exclusion claim
|
||||
Block block = lesser.getWorld().getBlockAt(x, y, z);
|
||||
if(exclusionClaim != null && exclusionClaim.contains(block.getLocation(), true, false)) continue;
|
||||
|
||||
if(block.getType() == Material.STATIONARY_WATER || block.getType() == Material.STATIONARY_LAVA || block.getType() == Material.LAVA || block.getType() == Material.WATER)
|
||||
{
|
||||
block.setType(Material.AIR);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
//main constructor. note that only creating a claim instance does nothing - a claim must be added to the data store to be effective
|
||||
Claim(Location lesserBoundaryCorner, Location greaterBoundaryCorner, String ownerName, String [] builderNames, String [] containerNames, String [] accessorNames, String [] managerNames)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -217,11 +217,24 @@ public class DataStore
|
|||
//if that's a chest claim, delete it
|
||||
if(claim.getArea() <= areaOfDefaultClaim)
|
||||
{
|
||||
claim.removeSurfaceFluids(null);
|
||||
this.deleteClaim(claim);
|
||||
GriefPrevention.AddLogEntry(" " + playerName + "'s new player claim expired.");
|
||||
}
|
||||
}
|
||||
|
||||
if(GriefPrevention.instance.config_claims_expirationDays > 0)
|
||||
{
|
||||
Calendar earliestPermissibleLastLogin = Calendar.getInstance();
|
||||
earliestPermissibleLastLogin.add(Calendar.DATE, -GriefPrevention.instance.config_claims_expirationDays);
|
||||
|
||||
if(earliestPermissibleLastLogin.getTime().after(playerData.lastLogin))
|
||||
{
|
||||
this.deleteClaimsForPlayer(playerName, true);
|
||||
GriefPrevention.AddLogEntry(" All of " + playerName + "'s claims have expired.");
|
||||
}
|
||||
}
|
||||
|
||||
//toss that player data out of the cache, it's not needed in memory right now
|
||||
this.clearCachedPlayerData(playerName);
|
||||
}
|
||||
|
|
@ -1074,6 +1087,7 @@ public class DataStore
|
|||
//delete them one by one
|
||||
for(int i = 0; i < claimsToDelete.size(); i++)
|
||||
{
|
||||
claimsToDelete.get(i).removeSurfaceFluids(null);
|
||||
this.deleteClaim(claimsToDelete.get(i));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -21,11 +21,12 @@ package me.ryanhamshire.GriefPrevention;
|
|||
import java.util.Calendar;
|
||||
import java.util.List;
|
||||
|
||||
import org.bukkit.Location;
|
||||
import org.bukkit.Material;
|
||||
import org.bukkit.World.Environment;
|
||||
import org.bukkit.block.Block;
|
||||
import org.bukkit.entity.Animals;
|
||||
import org.bukkit.entity.Arrow;
|
||||
import org.bukkit.entity.Creeper;
|
||||
import org.bukkit.entity.Enderman;
|
||||
import org.bukkit.entity.Entity;
|
||||
import org.bukkit.entity.LivingEntity;
|
||||
|
|
@ -34,6 +35,7 @@ import org.bukkit.entity.ThrownPotion;
|
|||
import org.bukkit.entity.Vehicle;
|
||||
|
||||
import org.bukkit.event.EventHandler;
|
||||
import org.bukkit.event.EventPriority;
|
||||
import org.bukkit.event.Listener;
|
||||
import org.bukkit.event.entity.CreatureSpawnEvent;
|
||||
import org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason;
|
||||
|
|
@ -59,17 +61,17 @@ class EntityEventHandler implements Listener
|
|||
}
|
||||
|
||||
//when an entity explodes...
|
||||
@EventHandler(ignoreCancelled = true)
|
||||
@EventHandler(ignoreCancelled = true, priority = EventPriority.LOWEST)
|
||||
public void onEntityExplode(EntityExplodeEvent explodeEvent)
|
||||
{
|
||||
List<Block> blocks = explodeEvent.blockList();
|
||||
Entity entity = explodeEvent.getEntity();
|
||||
Location location = explodeEvent.getLocation();
|
||||
|
||||
//FEATURE: creepers don't destroy blocks when they explode near or above sea level
|
||||
//FEATURE: explosions don't destroy blocks when they explode near or above sea level in standard worlds
|
||||
|
||||
if(GriefPrevention.instance.config_creepersDontDestroySurface && entity instanceof Creeper)
|
||||
if(GriefPrevention.instance.config_blockSurfaceExplosions && location.getWorld().getEnvironment() == Environment.NORMAL)
|
||||
{
|
||||
if(entity.getLocation().getBlockY() > entity.getLocation().getWorld().getSeaLevel() - 7)
|
||||
if(location.getBlockY() >location.getWorld().getSeaLevel() - 7)
|
||||
{
|
||||
blocks.clear(); //explosion still happens, can damage creatures/players, but no blocks will be destroyed
|
||||
return;
|
||||
|
|
@ -77,7 +79,7 @@ class EntityEventHandler implements Listener
|
|||
}
|
||||
|
||||
//special rule for creative worlds: explosions don't destroy anything
|
||||
if(GriefPrevention.instance.creativeRulesApply(entity.getLocation()))
|
||||
if(GriefPrevention.instance.creativeRulesApply(explodeEvent.getLocation()))
|
||||
{
|
||||
blocks.clear();
|
||||
}
|
||||
|
|
@ -106,7 +108,7 @@ class EntityEventHandler implements Listener
|
|||
}
|
||||
|
||||
//when an item spawns...
|
||||
@EventHandler
|
||||
@EventHandler(priority = EventPriority.LOWEST)
|
||||
public void onItemSpawn(ItemSpawnEvent event)
|
||||
{
|
||||
//if in a creative world, cancel the event (don't drop items on the ground)
|
||||
|
|
@ -117,7 +119,7 @@ class EntityEventHandler implements Listener
|
|||
}
|
||||
|
||||
//when a creature spawns...
|
||||
@EventHandler
|
||||
@EventHandler(priority = EventPriority.LOWEST)
|
||||
public void onEntitySpawn(CreatureSpawnEvent event)
|
||||
{
|
||||
LivingEntity entity = event.getEntity();
|
||||
|
|
@ -175,7 +177,7 @@ class EntityEventHandler implements Listener
|
|||
}
|
||||
|
||||
//when an entity picks up an item
|
||||
@EventHandler
|
||||
@EventHandler(priority = EventPriority.LOWEST)
|
||||
public void onEntityPickup(EntityChangeBlockEvent event)
|
||||
{
|
||||
//FEATURE: endermen don't steal claimed blocks
|
||||
|
|
@ -193,7 +195,7 @@ class EntityEventHandler implements Listener
|
|||
}
|
||||
|
||||
//when a painting is broken
|
||||
@EventHandler(ignoreCancelled = true)
|
||||
@EventHandler(ignoreCancelled = true, priority = EventPriority.LOWEST)
|
||||
public void onPaintingBreak(PaintingBreakEvent event)
|
||||
{
|
||||
//FEATURE: claimed paintings are protected from breakage
|
||||
|
|
@ -217,11 +219,7 @@ class EntityEventHandler implements Listener
|
|||
return;
|
||||
}
|
||||
|
||||
//make sure the player has build permission here
|
||||
Claim claim = this.dataStore.getClaimAt(event.getPainting().getLocation(), false, null);
|
||||
if(claim == null) return;
|
||||
|
||||
//if the player doesn't have build permission, don't allow the breakage
|
||||
//if the player doesn't have build permission, don't allow the breakage
|
||||
Player playerRemover = (Player)entityEvent.getRemover();
|
||||
String noBuildReason = GriefPrevention.instance.allowBuild(playerRemover, event.getPainting().getLocation());
|
||||
if(noBuildReason != null)
|
||||
|
|
@ -232,7 +230,7 @@ class EntityEventHandler implements Listener
|
|||
}
|
||||
|
||||
//when a painting is placed...
|
||||
@EventHandler(ignoreCancelled = true)
|
||||
@EventHandler(ignoreCancelled = true, priority = EventPriority.LOWEST)
|
||||
public void onPaintingPlace(PaintingPlaceEvent event)
|
||||
{
|
||||
//FEATURE: similar to above, placing a painting requires build permission in the claim
|
||||
|
|
@ -247,7 +245,7 @@ class EntityEventHandler implements Listener
|
|||
}
|
||||
|
||||
//when an entity is damaged
|
||||
@EventHandler(ignoreCancelled = true)
|
||||
@EventHandler(ignoreCancelled = true, priority = EventPriority.LOWEST)
|
||||
public void onEntityDamage (EntityDamageEvent event)
|
||||
{
|
||||
//only actually interested in entities damaging entities (ignoring environmental damage)
|
||||
|
|
|
|||
|
|
@ -54,7 +54,7 @@ public class GriefPrevention extends JavaPlugin
|
|||
|
||||
//for logging to the console and log file
|
||||
private static Logger log = Logger.getLogger("Minecraft");
|
||||
|
||||
|
||||
//this handles data storage, like player and region data
|
||||
public DataStore dataStore;
|
||||
|
||||
|
|
@ -69,6 +69,7 @@ public class GriefPrevention extends JavaPlugin
|
|||
public int config_claims_blocksAccruedPerHour; //how many additional blocks players get each hour of play (can be zero)
|
||||
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_automaticClaimsForNewPlayersRadius; //how big automatic new player claims (when they place a chest) should be. 0 to disable
|
||||
public boolean config_claims_creationRequiresPermission; //whether creating claims with the shovel requires a permission
|
||||
|
|
@ -99,7 +100,7 @@ public class GriefPrevention extends JavaPlugin
|
|||
public double config_economy_claimBlocksPurchaseCost; //cost to purchase a claim block. set to zero to disable purchase.
|
||||
public double config_economy_claimBlocksSellValue; //return on a sold claim block. set to zero to disable sale.
|
||||
|
||||
public boolean config_creepersDontDestroySurface; //whether creeper explosions near or above the surface destroy blocks
|
||||
public boolean config_blockSurfaceExplosions; //whether creeper/TNT explosions near or above the surface destroy blocks
|
||||
|
||||
public boolean config_fireSpreads; //whether fire spreads outside of claims
|
||||
public boolean config_fireDestroys; //whether fire destroys blocks outside of claims
|
||||
|
|
@ -107,12 +108,17 @@ public class GriefPrevention extends JavaPlugin
|
|||
public boolean config_addItemsToClaimedChests; //whether players may add items to claimed chests by left-clicking them
|
||||
public boolean config_eavesdrop; //whether whispered messages will be visible to administrators
|
||||
|
||||
public boolean config_smartBan; //whether to ban accounts which very likely owned by a banned player
|
||||
|
||||
//reference to the economy plugin, if economy integration is enabled
|
||||
public static Economy economy = null;
|
||||
|
||||
//how far away to search from a tree trunk for its branch blocks
|
||||
public static final int TREE_RADIUS = 5;
|
||||
|
||||
//how long to wait before deciding a player is staying online or staying offline, for notication messages
|
||||
public static final int NOTIFICATION_SECONDS = 20;
|
||||
|
||||
//adds a server log entry
|
||||
public static void AddLogEntry(String entry)
|
||||
{
|
||||
|
|
@ -207,11 +213,12 @@ public class GriefPrevention extends JavaPlugin
|
|||
this.config_claims_creationRequiresPermission = config.getBoolean("GriefPrevention.Claims.CreationRequiresPermission", false);
|
||||
this.config_claims_minSize = config.getInt("GriefPrevention.Claims.MinimumSize", 10);
|
||||
this.config_claims_maxDepth = config.getInt("GriefPrevention.Claims.MaximumDepth", 0);
|
||||
this.config_claims_expirationDays = config.getInt("GriefPrevention.Claims.IdleLimitDays", 0);
|
||||
this.config_claims_trappedCooldownHours = config.getInt("GriefPrevention.Claims.TrappedCommandCooldownHours", 8);
|
||||
|
||||
this.config_spam_enabled = config.getBoolean("GriefPrevention.Spam.Enabled", true);
|
||||
this.config_spam_loginCooldownMinutes = config.getInt("GriefPrevention.Spam.LoginCooldownMinutes", 5);
|
||||
this.config_spam_warningMessage = config.getString("GriefPrevention.Spam.WarningMessage", "Please reduce your message speed. Spammers will be banned.");
|
||||
this.config_spam_loginCooldownMinutes = config.getInt("GriefPrevention.Spam.LoginCooldownMinutes", 2);
|
||||
this.config_spam_warningMessage = config.getString("GriefPrevention.Spam.WarningMessage", "Please reduce your noise level. Spammers will be banned.");
|
||||
this.config_spam_allowedIpAddresses = config.getString("GriefPrevention.Spam.AllowedIpAddresses", "1.2.3.4; 5.6.7.8");
|
||||
this.config_spam_banOffenders = config.getBoolean("GriefPrevention.Spam.BanOffenders", true);
|
||||
this.config_spam_banMessage = config.getString("GriefPrevention.Spam.BanMessage", "Banned for spam.");
|
||||
|
|
@ -228,7 +235,7 @@ public class GriefPrevention extends JavaPlugin
|
|||
this.config_economy_claimBlocksPurchaseCost = config.getDouble("GriefPrevention.Economy.ClaimBlocksPurchaseCost", 0);
|
||||
this.config_economy_claimBlocksSellValue = config.getDouble("GriefPrevention.Economy.ClaimBlocksSellValue", 0);
|
||||
|
||||
this.config_creepersDontDestroySurface = config.getBoolean("GriefPrevention.CreepersDontDestroySurface", true);
|
||||
this.config_blockSurfaceExplosions = config.getBoolean("GriefPrevention.BlockSurfaceExplosions", true);
|
||||
|
||||
this.config_fireSpreads = config.getBoolean("GriefPrevention.FireSpreads", false);
|
||||
this.config_fireDestroys = config.getBoolean("GriefPrevention.FireDestroys", false);
|
||||
|
|
@ -236,6 +243,8 @@ public class GriefPrevention extends JavaPlugin
|
|||
this.config_addItemsToClaimedChests = config.getBoolean("GriefPrevention.AddItemsToClaimedChests", true);
|
||||
this.config_eavesdrop = config.getBoolean("GriefPrevention.EavesdropEnabled", false);
|
||||
|
||||
this.config_smartBan = config.getBoolean("GriefPrevention.SmartBan", true);
|
||||
|
||||
//default for siege worlds list
|
||||
ArrayList<String> defaultSiegeWorldNames = new ArrayList<String>();
|
||||
|
||||
|
|
@ -320,6 +329,7 @@ public class GriefPrevention extends JavaPlugin
|
|||
config.set("GriefPrevention.Claims.CreationRequiresPermission", this.config_claims_creationRequiresPermission);
|
||||
config.set("GriefPrevention.Claims.MinimumSize", this.config_claims_minSize);
|
||||
config.set("GriefPrevention.Claims.MaximumDepth", this.config_claims_maxDepth);
|
||||
config.set("GriefPrevention.Claims.IdleLimitDays", this.config_claims_expirationDays);
|
||||
config.set("GriefPrevention.Claims.TrappedCommandCooldownHours", this.config_claims_trappedCooldownHours);
|
||||
|
||||
config.set("GriefPrevention.Spam.Enabled", this.config_spam_enabled);
|
||||
|
|
@ -341,7 +351,7 @@ public class GriefPrevention extends JavaPlugin
|
|||
config.set("GriefPrevention.Economy.ClaimBlocksPurchaseCost", this.config_economy_claimBlocksPurchaseCost);
|
||||
config.set("GriefPrevention.Economy.ClaimBlocksSellValue", this.config_economy_claimBlocksSellValue);
|
||||
|
||||
config.set("GriefPrevention.CreepersDontDestroySurface", this.config_creepersDontDestroySurface);
|
||||
config.set("GriefPrevention.BlockSurfaceExplosions", this.config_blockSurfaceExplosions);
|
||||
|
||||
config.set("GriefPrevention.FireSpreads", this.config_fireSpreads);
|
||||
config.set("GriefPrevention.FireDestroys", this.config_fireDestroys);
|
||||
|
|
@ -349,6 +359,8 @@ public class GriefPrevention extends JavaPlugin
|
|||
config.set("GriefPrevention.AddItemsToClaimedChests", this.config_addItemsToClaimedChests);
|
||||
config.set("GriefPrevention.EavesdropEnabled", this.config_eavesdrop);
|
||||
|
||||
config.set("GriefPrevention.SmartBan", this.config_smartBan);
|
||||
|
||||
config.set("GriefPrevention.Siege.Worlds", siegeEnabledWorldNames);
|
||||
config.set("GriefPrevention.Siege.BreakableBlocks", breakableBlocksList);
|
||||
|
||||
|
|
@ -518,6 +530,40 @@ public class GriefPrevention extends JavaPlugin
|
|||
return true;
|
||||
}
|
||||
|
||||
//restore nature aggressive mode
|
||||
else if(cmd.getName().equalsIgnoreCase("restorenatureaggressive") && player != null)
|
||||
{
|
||||
//change shovel mode
|
||||
PlayerData playerData = this.dataStore.getPlayerData(player.getName());
|
||||
playerData.shovelMode = ShovelMode.RestoreNatureAggressive;
|
||||
GriefPrevention.sendMessage(player, TextMode.Warn, "Aggressive mode activated. Do NOT use this underneath anything you want to keep! Right click to aggressively restore nature, and use /BasicClaims to stop.");
|
||||
return true;
|
||||
}
|
||||
|
||||
//restore nature fill mode
|
||||
else if(cmd.getName().equalsIgnoreCase("restorenaturefill") && player != null)
|
||||
{
|
||||
//change shovel mode
|
||||
PlayerData playerData = this.dataStore.getPlayerData(player.getName());
|
||||
playerData.shovelMode = ShovelMode.RestoreNatureFill;
|
||||
|
||||
//set radius based on arguments
|
||||
playerData.fillRadius = 2;
|
||||
if(args.length > 0)
|
||||
{
|
||||
try
|
||||
{
|
||||
playerData.fillRadius = Integer.parseInt(args[0]);
|
||||
}
|
||||
catch(Exception exception){ }
|
||||
}
|
||||
|
||||
if(playerData.fillRadius < 0) playerData.fillRadius = 2;
|
||||
|
||||
GriefPrevention.sendMessage(player, TextMode.Success, "Fill mode activated with radius " + playerData.fillRadius + ". Right-click an area to fill.");
|
||||
return true;
|
||||
}
|
||||
|
||||
//trust <player>
|
||||
else if(cmd.getName().equalsIgnoreCase("trust") && player != null)
|
||||
{
|
||||
|
|
@ -833,6 +879,17 @@ public class GriefPrevention extends JavaPlugin
|
|||
|
||||
else
|
||||
{
|
||||
//determine max purchasable blocks
|
||||
PlayerData playerData = this.dataStore.getPlayerData(player.getName());
|
||||
int maxPurchasable = GriefPrevention.instance.config_claims_maxAccruedBlocks - playerData.accruedClaimBlocks;
|
||||
|
||||
//if the player is at his max, tell him so
|
||||
if(maxPurchasable <= 0)
|
||||
{
|
||||
GriefPrevention.sendMessage(player, TextMode.Err, "You've reached your claim block limit. You can't purchase more.");
|
||||
return true;
|
||||
}
|
||||
|
||||
//try to parse number of blocks
|
||||
int blockCount;
|
||||
try
|
||||
|
|
@ -844,6 +901,12 @@ public class GriefPrevention extends JavaPlugin
|
|||
return false; //causes usage to be displayed
|
||||
}
|
||||
|
||||
//correct block count to max allowed
|
||||
if(blockCount > maxPurchasable)
|
||||
{
|
||||
blockCount = maxPurchasable;
|
||||
}
|
||||
|
||||
//if the player can't afford his purchase, send error message
|
||||
double balance = economy.getBalance(player.getName());
|
||||
double totalCost = blockCount * GriefPrevention.instance.config_economy_claimBlocksPurchaseCost;
|
||||
|
|
@ -859,8 +922,7 @@ public class GriefPrevention extends JavaPlugin
|
|||
economy.withdrawPlayer(player.getName(), totalCost);
|
||||
|
||||
//add blocks
|
||||
PlayerData playerData = this.dataStore.getPlayerData(player.getName());
|
||||
playerData.bonusClaimBlocks += blockCount;
|
||||
playerData.accruedClaimBlocks += blockCount;
|
||||
this.dataStore.savePlayerData(player.getName(), playerData);
|
||||
|
||||
//inform player
|
||||
|
|
@ -920,7 +982,7 @@ public class GriefPrevention extends JavaPlugin
|
|||
economy.depositPlayer(player.getName(), totalValue);
|
||||
|
||||
//subtract blocks
|
||||
playerData.bonusClaimBlocks -= blockCount;
|
||||
playerData.accruedClaimBlocks -= blockCount;
|
||||
this.dataStore.savePlayerData(player.getName(), playerData);
|
||||
|
||||
//inform player
|
||||
|
|
@ -978,12 +1040,24 @@ public class GriefPrevention extends JavaPlugin
|
|||
//deleting an admin claim additionally requires the adminclaims permission
|
||||
if(!claim.isAdminClaim() || player.hasPermission("griefprevention.adminclaims"))
|
||||
{
|
||||
this.dataStore.deleteClaim(claim);
|
||||
GriefPrevention.sendMessage(player, TextMode.Success, "Claim deleted.");
|
||||
GriefPrevention.AddLogEntry(player.getName() + " deleted " + claim.getOwnerName() + "'s claim at " + GriefPrevention.getfriendlyLocationString(claim.getLesserBoundaryCorner()));
|
||||
|
||||
//revert any current visualization
|
||||
Visualization.Revert(player);
|
||||
PlayerData playerData = this.dataStore.getPlayerData(player.getName());
|
||||
if(claim.children.size() > 0 && !playerData.warnedAboutMajorDeletion)
|
||||
{
|
||||
GriefPrevention.sendMessage(player, TextMode.Warn, "This claim includes subdivisions. If you're sure you want to delete it, use /DeleteClaim again.");
|
||||
playerData.warnedAboutMajorDeletion = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
claim.removeSurfaceFluids(null);
|
||||
this.dataStore.deleteClaim(claim);
|
||||
GriefPrevention.sendMessage(player, TextMode.Success, "Claim deleted.");
|
||||
GriefPrevention.AddLogEntry(player.getName() + " deleted " + claim.getOwnerName() + "'s claim at " + GriefPrevention.getfriendlyLocationString(claim.getLesserBoundaryCorner()));
|
||||
|
||||
//revert any current visualization
|
||||
Visualization.Revert(player);
|
||||
|
||||
playerData.warnedAboutMajorDeletion = false;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
|
|
@ -1266,6 +1340,7 @@ public class GriefPrevention extends JavaPlugin
|
|||
else
|
||||
{
|
||||
//delete it
|
||||
claim.removeSurfaceFluids(null);
|
||||
this.dataStore.deleteClaim(claim);
|
||||
|
||||
//tell the player how many claim blocks he has left
|
||||
|
|
@ -1619,8 +1694,8 @@ public class GriefPrevention extends JavaPlugin
|
|||
//schedule a cleanup task for later, in case the player leaves part of this tree hanging in the air
|
||||
TreeCleanupTask cleanupTask = new TreeCleanupTask(block, rootBlock, treeBlocks);
|
||||
|
||||
//20L ~ 1 second, so 5 mins = 300 seconds ~ 6000L
|
||||
GriefPrevention.instance.getServer().getScheduler().scheduleSyncDelayedTask(GriefPrevention.instance, cleanupTask, 6000L);
|
||||
//20L ~ 1 second, so 2 mins = 120 seconds ~ 2400L
|
||||
GriefPrevention.instance.getServer().getScheduler().scheduleSyncDelayedTask(GriefPrevention.instance, cleanupTask, 2400L);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
17
src/me/ryanhamshire/GriefPrevention/IpBanInfo.java
Normal file
17
src/me/ryanhamshire/GriefPrevention/IpBanInfo.java
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
package me.ryanhamshire.GriefPrevention;
|
||||
|
||||
import java.net.InetAddress;
|
||||
|
||||
public class IpBanInfo
|
||||
{
|
||||
InetAddress address;
|
||||
long expirationTimestamp;
|
||||
String bannedAccountName;
|
||||
|
||||
IpBanInfo(InetAddress address, long expirationTimestamp, String bannedAccountName)
|
||||
{
|
||||
this.address = address;
|
||||
this.expirationTimestamp = expirationTimestamp;
|
||||
this.bannedAccountName = bannedAccountName;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,66 @@
|
|||
/*
|
||||
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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package me.ryanhamshire.GriefPrevention;
|
||||
|
||||
import org.bukkit.entity.Player;
|
||||
|
||||
//this main thread task takes the output from the RestoreNatureProcessingTask\
|
||||
//and updates the world accordingly
|
||||
class JoinLeaveAnnouncementTask implements Runnable
|
||||
{
|
||||
//player joining or leaving the server
|
||||
private Player player;
|
||||
|
||||
//message to be displayed
|
||||
private String message;
|
||||
|
||||
//whether joining or leaving
|
||||
private boolean joining;
|
||||
|
||||
public JoinLeaveAnnouncementTask(Player player, String message, boolean joining)
|
||||
{
|
||||
this.player = player;
|
||||
this.message = message;
|
||||
this.joining = joining;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run()
|
||||
{
|
||||
//verify the player still has the same online/offline status
|
||||
if((this.joining && this.player.isOnline() || (!this.joining && !this.player.isOnline())))
|
||||
{
|
||||
Player players [] = GriefPrevention.instance.getServer().getOnlinePlayers();
|
||||
for(int i = 0; i < players.length; i++)
|
||||
{
|
||||
if(!players[i].equals(this.player))
|
||||
{
|
||||
players[i].sendMessage(this.message);
|
||||
}
|
||||
}
|
||||
|
||||
//if left
|
||||
if(!joining)
|
||||
{
|
||||
//drop player data from memory
|
||||
GriefPrevention.instance.dataStore.clearCachedPlayerData(this.player.getName());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -17,6 +17,7 @@
|
|||
*/
|
||||
|
||||
package me.ryanhamshire.GriefPrevention;
|
||||
import java.net.InetAddress;
|
||||
import java.util.Calendar;
|
||||
import java.util.Date;
|
||||
import java.util.Vector;
|
||||
|
|
@ -41,6 +42,9 @@ public class PlayerData
|
|||
//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;
|
||||
|
|
@ -64,7 +68,11 @@ public class PlayerData
|
|||
public Date lastLogin; //when the player last logged into the server
|
||||
public String lastMessage = ""; //the player's last chat message, or slash command complete with parameters
|
||||
public Date lastMessageTimestamp = new Date(); //last time the player sent a chat message or used a monitored slash command
|
||||
public int spamCount = 0; //number of consecutive "spams"
|
||||
public int spamCount = 0; //number of consecutive "spams"
|
||||
public boolean spamWarned = false; //whether the player recently received a warning
|
||||
|
||||
//last logout timestamp, default to long enough to trigger a join message, see player join event
|
||||
public long lastLogout = Calendar.getInstance().getTimeInMillis() - GriefPrevention.NOTIFICATION_SECONDS * 2000;
|
||||
|
||||
//visualization
|
||||
public Visualization currentVisualization = null;
|
||||
|
|
@ -86,6 +94,11 @@ public class PlayerData
|
|||
public long lastPvpTimestamp = 0;
|
||||
public String lastPvpPlayer = "";
|
||||
|
||||
//safety confirmation for deleting multi-subdivision claims
|
||||
public boolean warnedAboutMajorDeletion = false;
|
||||
|
||||
public InetAddress ipAddress;
|
||||
|
||||
PlayerData()
|
||||
{
|
||||
//default last login date value to a year ago to ensure a brand new player can log in
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@
|
|||
*/
|
||||
|
||||
package me.ryanhamshire.GriefPrevention;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Calendar;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
|
|
@ -27,7 +28,10 @@ import org.bukkit.ChatColor;
|
|||
import org.bukkit.Chunk;
|
||||
import org.bukkit.Location;
|
||||
import org.bukkit.Material;
|
||||
import org.bukkit.OfflinePlayer;
|
||||
import org.bukkit.World.Environment;
|
||||
import org.bukkit.block.Block;
|
||||
import org.bukkit.block.BlockFace;
|
||||
import org.bukkit.entity.Animals;
|
||||
import org.bukkit.entity.Boat;
|
||||
import org.bukkit.entity.Entity;
|
||||
|
|
@ -49,6 +53,12 @@ class PlayerEventHandler implements Listener
|
|||
{
|
||||
private DataStore dataStore;
|
||||
|
||||
//list of temporarily banned ip's
|
||||
private ArrayList<IpBanInfo> tempBannedIps = new ArrayList<IpBanInfo>();
|
||||
|
||||
//number of milliseconds in a day
|
||||
private final long MILLISECONDS_IN_DAY = 1000 * 60 * 60 * 24;
|
||||
|
||||
//typical constructor, yawn
|
||||
PlayerEventHandler(DataStore dataStore, GriefPrevention plugin)
|
||||
{
|
||||
|
|
@ -56,7 +66,7 @@ class PlayerEventHandler implements Listener
|
|||
}
|
||||
|
||||
//when a player chats, monitor for spam
|
||||
@EventHandler(ignoreCancelled = true, priority = EventPriority.HIGHEST)
|
||||
@EventHandler(ignoreCancelled = true, priority = EventPriority.LOWEST)
|
||||
void onPlayerChat (PlayerChatEvent event)
|
||||
{
|
||||
Player player = event.getPlayer();
|
||||
|
|
@ -74,21 +84,29 @@ class PlayerEventHandler implements Listener
|
|||
|
||||
if(!GriefPrevention.instance.config_spam_enabled) return;
|
||||
|
||||
//if the player has permission to spam, don't bother even examining the message
|
||||
if(player.hasPermission("griefprevention.spam")) return;
|
||||
|
||||
//remedy any CAPS SPAM without bothering to fault the player for it
|
||||
if(message.length() > 4 && !player.hasPermission("griefprevention.spam") && message.toUpperCase().equals(message))
|
||||
if(message.length() > 4 && message.toUpperCase().equals(message))
|
||||
{
|
||||
event.setMessage(message.toLowerCase());
|
||||
}
|
||||
|
||||
//where spam is concerned, casing isn't significant
|
||||
message = message.toLowerCase();
|
||||
|
||||
PlayerData playerData = this.dataStore.getPlayerData(player.getName());
|
||||
|
||||
boolean spam = false;
|
||||
boolean muted = false;
|
||||
|
||||
//filter IP addresses
|
||||
if(!(event instanceof PlayerCommandPreprocessEvent))
|
||||
{
|
||||
Pattern ipAddressPattern = Pattern.compile("\\d+\\.\\d+\\.\\d+\\.\\d+");
|
||||
Matcher matcher = ipAddressPattern.matcher(event.getMessage());
|
||||
|
||||
//if it looks like an IP address
|
||||
while(matcher.find())
|
||||
{
|
||||
|
|
@ -103,7 +121,7 @@ class PlayerEventHandler implements Listener
|
|||
spam = true;
|
||||
|
||||
//block message
|
||||
event.setCancelled(true);
|
||||
muted = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -112,35 +130,41 @@ class PlayerEventHandler implements Listener
|
|||
long millisecondsSinceLastMessage = (new Date()).getTime() - playerData.lastMessageTimestamp.getTime();
|
||||
|
||||
//if the message came too close to the last one
|
||||
if(millisecondsSinceLastMessage < 2000)
|
||||
if(millisecondsSinceLastMessage < 3000)
|
||||
{
|
||||
//increment the spam counter
|
||||
playerData.spamCount++;
|
||||
spam = true;
|
||||
}
|
||||
|
||||
//if it's the same as the last message
|
||||
if(message.equals(playerData.lastMessage))
|
||||
//if it's very similar to the last message
|
||||
if(this.stringsAreSimilar(message, playerData.lastMessage))
|
||||
{
|
||||
playerData.spamCount++;
|
||||
event.setCancelled(true);
|
||||
spam = true;
|
||||
muted = true;
|
||||
}
|
||||
|
||||
//if the message was mostly non-alpha-numerics, consider it a spam (probably ansi art)
|
||||
//if the message was mostly non-alpha-numerics or doesn't include much whitespace, consider it a spam (probably ansi art or random text gibberish)
|
||||
if(message.length() > 5)
|
||||
{
|
||||
int symbolsCount = 0;
|
||||
int whitespaceCount = 0;
|
||||
for(int i = 0; i < message.length(); i++)
|
||||
{
|
||||
char character = message.charAt(i);
|
||||
if(!(Character.isLetterOrDigit(character) || Character.isWhitespace(character)))
|
||||
if(!(Character.isLetterOrDigit(character)))
|
||||
{
|
||||
symbolsCount++;
|
||||
}
|
||||
}
|
||||
|
||||
if(Character.isWhitespace(character))
|
||||
{
|
||||
whitespaceCount++;
|
||||
}
|
||||
}
|
||||
|
||||
if(symbolsCount > message.length() / 2)
|
||||
if(symbolsCount > message.length() / 2 || (message.length() > 15 && whitespaceCount < message.length() / 10))
|
||||
{
|
||||
spam = true;
|
||||
playerData.spamCount++;
|
||||
|
|
@ -150,12 +174,9 @@ class PlayerEventHandler implements Listener
|
|||
//if the message was determined to be a spam, consider taking action
|
||||
if(!player.hasPermission("griefprevention.spam") && spam)
|
||||
{
|
||||
//at the fifth spam level, auto-ban (if enabled)
|
||||
if(playerData.spamCount > 4)
|
||||
//anything above level 4 for a player which has received a warning... kick or if enabled, ban
|
||||
if(playerData.spamCount > 4 && playerData.spamWarned)
|
||||
{
|
||||
event.setCancelled(true);
|
||||
GriefPrevention.AddLogEntry("Muted spam from " + player.getName() + ": " + message);
|
||||
|
||||
if(GriefPrevention.instance.config_spam_banOffenders)
|
||||
{
|
||||
//log entry
|
||||
|
|
@ -174,19 +195,36 @@ class PlayerEventHandler implements Listener
|
|||
}
|
||||
|
||||
//cancel any messages while at or above the third spam level and issue warnings
|
||||
else if(playerData.spamCount >= 3)
|
||||
//anything above level 2, mute and warn
|
||||
if(playerData.spamCount >= 3)
|
||||
{
|
||||
GriefPrevention.sendMessage(player, TextMode.Warn, GriefPrevention.instance.config_spam_warningMessage);
|
||||
event.setCancelled(true);
|
||||
GriefPrevention.AddLogEntry("Warned " + player.getName() + " about spam penalties.");
|
||||
GriefPrevention.AddLogEntry("Muted spam from " + player.getName() + ": " + message);
|
||||
muted = true;
|
||||
if(!playerData.spamWarned)
|
||||
{
|
||||
GriefPrevention.sendMessage(player, TextMode.Warn, GriefPrevention.instance.config_spam_warningMessage);
|
||||
GriefPrevention.AddLogEntry("Warned " + player.getName() + " about spam penalties.");
|
||||
playerData.spamWarned = true;
|
||||
}
|
||||
}
|
||||
|
||||
if(muted)
|
||||
{
|
||||
//cancel the event and make a log entry
|
||||
//cancelling the event guarantees players don't receive the message
|
||||
event.setCancelled(true);
|
||||
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() + "> " + event.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
//otherwise if not a spam, reset the spam counter for this player
|
||||
else
|
||||
{
|
||||
playerData.spamCount = 0;
|
||||
playerData.spamWarned = false;
|
||||
}
|
||||
|
||||
//in any case, record the timestamp of this message and also its content for next time
|
||||
|
|
@ -194,8 +232,50 @@ class PlayerEventHandler implements Listener
|
|||
playerData.lastMessage = message;
|
||||
}
|
||||
|
||||
//if two strings are 75% identical, they're too close to follow each other in the chat
|
||||
private boolean stringsAreSimilar(String message, String lastMessage)
|
||||
{
|
||||
//determine which is shorter
|
||||
String shorterString, longerString;
|
||||
if(lastMessage.length() < message.length())
|
||||
{
|
||||
shorterString = lastMessage;
|
||||
longerString = message;
|
||||
}
|
||||
else
|
||||
{
|
||||
shorterString = message;
|
||||
longerString = lastMessage;
|
||||
}
|
||||
|
||||
if(shorterString.length() <= 5) return shorterString.equals(longerString);
|
||||
|
||||
//set similarity tolerance
|
||||
int maxIdenticalCharacters = longerString.length() - longerString.length() / 4;
|
||||
|
||||
//trivial check on length
|
||||
if(shorterString.length() < maxIdenticalCharacters) return false;
|
||||
|
||||
//compare forward
|
||||
int identicalCount = 0;
|
||||
for(int i = 0; i < shorterString.length(); i++)
|
||||
{
|
||||
if(shorterString.charAt(i) == longerString.charAt(i)) identicalCount++;
|
||||
if(identicalCount > maxIdenticalCharacters) return true;
|
||||
}
|
||||
|
||||
//compare backward
|
||||
for(int i = 0; i < shorterString.length(); i++)
|
||||
{
|
||||
if(shorterString.charAt(shorterString.length() - i - 1) == longerString.charAt(longerString.length() - i - 1)) identicalCount++;
|
||||
if(identicalCount > maxIdenticalCharacters) return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
//when a player uses a slash command, monitor for spam
|
||||
@EventHandler(ignoreCancelled = true, priority = EventPriority.HIGHEST)
|
||||
@EventHandler(ignoreCancelled = true, priority = EventPriority.LOWEST)
|
||||
void onPlayerCommandPreprocess (PlayerCommandPreprocessEvent event)
|
||||
{
|
||||
if(!GriefPrevention.instance.config_spam_enabled) return;
|
||||
|
|
@ -231,31 +311,97 @@ class PlayerEventHandler implements Listener
|
|||
}
|
||||
|
||||
//when a player attempts to join the server...
|
||||
@EventHandler(ignoreCancelled = true)
|
||||
@EventHandler(priority = EventPriority.HIGHEST)
|
||||
void onPlayerLogin (PlayerLoginEvent event)
|
||||
{
|
||||
if(!GriefPrevention.instance.config_spam_enabled) return;
|
||||
|
||||
Player player = event.getPlayer();
|
||||
|
||||
//FEATURE: login cooldown to prevent login/logout spam with custom clients
|
||||
|
||||
//if allowed to join and login cooldown enabled
|
||||
if(GriefPrevention.instance.config_spam_loginCooldownMinutes > 0 && event.getResult() == Result.ALLOWED)
|
||||
//all this is anti-spam code
|
||||
if(GriefPrevention.instance.config_spam_enabled)
|
||||
{
|
||||
//determine how long since last login and cooldown remaining
|
||||
PlayerData playerData = this.dataStore.getPlayerData(player.getName());
|
||||
long millisecondsSinceLastLogin = (new Date()).getTime() - playerData.lastLogin.getTime();
|
||||
long minutesSinceLastLogin = millisecondsSinceLastLogin / 1000 / 60;
|
||||
long cooldownRemaining = GriefPrevention.instance.config_spam_loginCooldownMinutes - minutesSinceLastLogin;
|
||||
//FEATURE: login cooldown to prevent login/logout spam with custom clients
|
||||
|
||||
//if cooldown remaining and player doesn't have permission to spam
|
||||
if(cooldownRemaining > 0 && !player.hasPermission("griefprevention.spam"))
|
||||
//if allowed to join and login cooldown enabled
|
||||
if(GriefPrevention.instance.config_spam_loginCooldownMinutes > 0 && event.getResult() == Result.ALLOWED)
|
||||
{
|
||||
//DAS BOOT!
|
||||
event.setResult(Result.KICK_OTHER);
|
||||
event.setKickMessage("You must wait " + cooldownRemaining + " more minutes before logging-in again.");
|
||||
event.disallow(event.getResult(), event.getKickMessage());
|
||||
//determine how long since last login and cooldown remaining
|
||||
PlayerData playerData = this.dataStore.getPlayerData(player.getName());
|
||||
long millisecondsSinceLastLogin = (new Date()).getTime() - playerData.lastLogin.getTime();
|
||||
long minutesSinceLastLogin = millisecondsSinceLastLogin / 1000 / 60;
|
||||
long cooldownRemaining = GriefPrevention.instance.config_spam_loginCooldownMinutes - minutesSinceLastLogin;
|
||||
|
||||
//if cooldown remaining and player doesn't have permission to spam
|
||||
if(cooldownRemaining > 0 && !player.hasPermission("griefprevention.spam"))
|
||||
{
|
||||
//DAS BOOT!
|
||||
event.setResult(Result.KICK_OTHER);
|
||||
event.setKickMessage("You must wait " + cooldownRemaining + " more minutes before logging-in again.");
|
||||
event.disallow(event.getResult(), event.getKickMessage());
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//remember the player's ip address
|
||||
PlayerData playerData = this.dataStore.getPlayerData(player.getName());
|
||||
playerData.ipAddress = event.getAddress();
|
||||
|
||||
//FEATURE: auto-ban accounts who use an IP address which was very recently used by another banned account
|
||||
if(GriefPrevention.instance.config_smartBan)
|
||||
{
|
||||
//if logging-in account is banned, remember IP address for later
|
||||
long now = Calendar.getInstance().getTimeInMillis();
|
||||
if(event.getResult() == Result.KICK_BANNED)
|
||||
{
|
||||
this.tempBannedIps.add(new IpBanInfo(event.getAddress(), now + this.MILLISECONDS_IN_DAY, player.getName()));
|
||||
}
|
||||
|
||||
//otherwise if not banned
|
||||
else
|
||||
{
|
||||
//search temporarily banned IP addresses for this one
|
||||
for(int i = 0; i < this.tempBannedIps.size(); i++)
|
||||
{
|
||||
IpBanInfo info = this.tempBannedIps.get(i);
|
||||
String address = info.address.toString();
|
||||
|
||||
//eliminate any expired entries
|
||||
if(now > info.expirationTimestamp)
|
||||
{
|
||||
this.tempBannedIps.remove(i--);
|
||||
}
|
||||
|
||||
//if we find a match
|
||||
else if(address.equals(playerData.ipAddress.toString()))
|
||||
{
|
||||
//if the account associated with the IP ban has been pardoned, remove all ip bans for that ip and we're done
|
||||
OfflinePlayer bannedPlayer = GriefPrevention.instance.getServer().getOfflinePlayer(info.bannedAccountName);
|
||||
if(!bannedPlayer.isBanned())
|
||||
{
|
||||
for(int j = 0; j < this.tempBannedIps.size(); j++)
|
||||
{
|
||||
IpBanInfo info2 = this.tempBannedIps.get(j);
|
||||
if(info2.address.toString().equals(address))
|
||||
{
|
||||
OfflinePlayer bannedAccount = GriefPrevention.instance.getServer().getOfflinePlayer(info2.bannedAccountName);
|
||||
bannedAccount.setBanned(false);
|
||||
this.tempBannedIps.remove(j--);
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
//otherwise if that account is still banned, ban this account, too
|
||||
else
|
||||
{
|
||||
player.setBanned(true);
|
||||
event.setResult(Result.KICK_BANNED);
|
||||
event.disallow(event.getResult(), "");
|
||||
GriefPrevention.AddLogEntry("Auto-banned " + player.getName() + " because that account is using an IP address very recently used by banned player " + info.bannedAccountName + " (" + info.address.toString() + ").");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -270,7 +416,7 @@ class PlayerEventHandler implements Listener
|
|||
}
|
||||
|
||||
//when a player successfully joins the server...
|
||||
@EventHandler(ignoreCancelled = true)
|
||||
@EventHandler(ignoreCancelled = true, priority = EventPriority.HIGHEST)
|
||||
void onPlayerJoin(PlayerJoinEvent event)
|
||||
{
|
||||
String playerName = event.getPlayer().getName();
|
||||
|
|
@ -283,24 +429,47 @@ class PlayerEventHandler implements Listener
|
|||
|
||||
//check inventory, may need pvp protection
|
||||
GriefPrevention.instance.checkPvpProtectionNeeded(event.getPlayer());
|
||||
|
||||
//how long since the last logout?
|
||||
long elapsed = Calendar.getInstance().getTimeInMillis() - playerData.lastLogout;
|
||||
|
||||
//remember message, then silence it. may broadcast it later
|
||||
String message = event.getJoinMessage();
|
||||
event.setJoinMessage(null);
|
||||
|
||||
if(message != null && elapsed >= GriefPrevention.NOTIFICATION_SECONDS * 1000)
|
||||
{
|
||||
//start a timer for a delayed join notification message (will only show if player is still online in 30 seconds)
|
||||
JoinLeaveAnnouncementTask task = new JoinLeaveAnnouncementTask(event.getPlayer(), message, true);
|
||||
GriefPrevention.instance.getServer().getScheduler().scheduleSyncDelayedTask(GriefPrevention.instance, task, 20L * GriefPrevention.NOTIFICATION_SECONDS);
|
||||
}
|
||||
}
|
||||
|
||||
//when a player quits...
|
||||
@EventHandler
|
||||
void onPlayerQuit(PlayerQuitEvent event)
|
||||
{
|
||||
this.onPlayerDisconnect(event.getPlayer());
|
||||
}
|
||||
|
||||
//when a player gets kicked...
|
||||
@EventHandler(ignoreCancelled = true)
|
||||
@EventHandler(priority = EventPriority.HIGHEST)
|
||||
void onPlayerKicked(PlayerKickEvent event)
|
||||
{
|
||||
this.onPlayerDisconnect(event.getPlayer());
|
||||
Player player = event.getPlayer();
|
||||
PlayerData playerData = this.dataStore.getPlayerData(player.getName());
|
||||
if(player.isBanned())
|
||||
{
|
||||
long now = Calendar.getInstance().getTimeInMillis();
|
||||
this.tempBannedIps.add(new IpBanInfo(playerData.ipAddress, now + this.MILLISECONDS_IN_DAY, player.getName()));
|
||||
}
|
||||
}
|
||||
|
||||
//when a player quits...
|
||||
@EventHandler(priority = EventPriority.HIGHEST)
|
||||
void onPlayerQuit(PlayerQuitEvent event)
|
||||
{
|
||||
this.onPlayerDisconnect(event.getPlayer(), event.getQuitMessage());
|
||||
|
||||
//silence the leave message (may be broadcast later, if the player stays offline)
|
||||
event.setQuitMessage(null);
|
||||
}
|
||||
|
||||
//helper for above
|
||||
private void onPlayerDisconnect(Player player)
|
||||
private void onPlayerDisconnect(Player player, String notificationMessage)
|
||||
{
|
||||
String playerName = player.getName();
|
||||
PlayerData playerData = this.dataStore.getPlayerData(playerName);
|
||||
|
|
@ -319,15 +488,24 @@ class PlayerEventHandler implements Listener
|
|||
if(player.getHealth() > 0) player.setHealth(0); //might already be zero from above, this avoids a double death message
|
||||
}
|
||||
|
||||
//disable ignore claims mode
|
||||
playerData.ignoreClaims = false;
|
||||
//how long was the player online?
|
||||
long now = Calendar.getInstance().getTimeInMillis();
|
||||
long elapsed = now - playerData.lastLogin.getTime();
|
||||
|
||||
//drop player data from memory
|
||||
this.dataStore.clearCachedPlayerData(playerName);
|
||||
//remember logout time
|
||||
playerData.lastLogout = Calendar.getInstance().getTimeInMillis();
|
||||
|
||||
//if notification message isn't null and the player has been online for at least 30 seconds...
|
||||
if(notificationMessage != null && elapsed >= 1000 * GriefPrevention.NOTIFICATION_SECONDS)
|
||||
{
|
||||
//start a timer for a delayed leave notification message (will only show if player is still offline in 30 seconds)
|
||||
JoinLeaveAnnouncementTask task = new JoinLeaveAnnouncementTask(player, notificationMessage, false);
|
||||
GriefPrevention.instance.getServer().getScheduler().scheduleSyncDelayedTask(GriefPrevention.instance, task, 20L * GriefPrevention.NOTIFICATION_SECONDS);
|
||||
}
|
||||
}
|
||||
|
||||
//when a player drops an item
|
||||
@EventHandler(priority = EventPriority.HIGHEST)
|
||||
@EventHandler(priority = EventPriority.LOWEST)
|
||||
public void onPlayerDropItem(PlayerDropItemEvent event)
|
||||
{
|
||||
Player player = event.getPlayer();
|
||||
|
|
@ -360,7 +538,7 @@ class PlayerEventHandler implements Listener
|
|||
}
|
||||
|
||||
//when a player teleports
|
||||
@EventHandler(priority = EventPriority.HIGHEST)
|
||||
@EventHandler(priority = EventPriority.LOWEST)
|
||||
public void onPlayerTeleport(PlayerTeleportEvent event)
|
||||
{
|
||||
//FEATURE: prevent teleport abuse to win sieges
|
||||
|
|
@ -390,7 +568,7 @@ class PlayerEventHandler implements Listener
|
|||
}
|
||||
|
||||
//when a player interacts with an entity...
|
||||
@EventHandler(ignoreCancelled = true, priority = EventPriority.HIGHEST)
|
||||
@EventHandler(ignoreCancelled = true, priority = EventPriority.LOWEST)
|
||||
public void onPlayerInteractEntity(PlayerInteractEntityEvent event)
|
||||
{
|
||||
Player player = event.getPlayer();
|
||||
|
|
@ -458,7 +636,7 @@ class PlayerEventHandler implements Listener
|
|||
}
|
||||
|
||||
//when a player picks up an item...
|
||||
@EventHandler(ignoreCancelled = true, priority = EventPriority.HIGHEST)
|
||||
@EventHandler(ignoreCancelled = true, priority = EventPriority.LOWEST)
|
||||
public void onPlayerPickupItem(PlayerPickupItemEvent event)
|
||||
{
|
||||
Player player = event.getPlayer();
|
||||
|
|
@ -504,7 +682,7 @@ class PlayerEventHandler implements Listener
|
|||
}
|
||||
|
||||
//block players from entering beds they don't have permission for
|
||||
@EventHandler(ignoreCancelled = true, priority = EventPriority.HIGHEST)
|
||||
@EventHandler(ignoreCancelled = true, priority = EventPriority.LOWEST)
|
||||
public void onPlayerBedEnter (PlayerBedEnterEvent bedEvent)
|
||||
{
|
||||
if(!GriefPrevention.instance.config_claims_preventButtonsSwitches) return;
|
||||
|
|
@ -526,7 +704,7 @@ class PlayerEventHandler implements Listener
|
|||
}
|
||||
|
||||
//block use of buckets within other players' claims
|
||||
@EventHandler(ignoreCancelled = true, priority = EventPriority.HIGHEST)
|
||||
@EventHandler(ignoreCancelled = true, priority = EventPriority.LOWEST)
|
||||
public void onPlayerBucketEmpty (PlayerBucketEmptyEvent bucketEvent)
|
||||
{
|
||||
Player player = bucketEvent.getPlayer();
|
||||
|
|
@ -542,29 +720,19 @@ class PlayerEventHandler implements Listener
|
|||
return;
|
||||
}
|
||||
|
||||
//if the bucket is being used in a claim
|
||||
//if the bucket is being used in a claim, allow for dumping lava closer to other players
|
||||
Claim claim = this.dataStore.getClaimAt(block.getLocation(), false, null);
|
||||
if(claim != null)
|
||||
{
|
||||
//the claim must be at least an hour old
|
||||
long now = Calendar.getInstance().getTimeInMillis();
|
||||
long lastModified = claim.modifiedDate.getTime();
|
||||
long elapsed = now - lastModified;
|
||||
if(bucketEvent.getBucket() == Material.LAVA_BUCKET && !player.hasPermission("griefprevention.lava") && elapsed < 1000 * 60 * 60)
|
||||
{
|
||||
GriefPrevention.sendMessage(player, TextMode.Err, "You can't dump lava here because this claim was recently modified. Try again later.");
|
||||
bucketEvent.setCancelled(true);
|
||||
}
|
||||
|
||||
minLavaDistance = 3;
|
||||
}
|
||||
|
||||
//otherwise it must be underground
|
||||
//otherwise no dumping anything unless underground
|
||||
else
|
||||
{
|
||||
if(bucketEvent.getBucket() == Material.LAVA_BUCKET && block.getY() >= block.getWorld().getSeaLevel() - 5 && !player.hasPermission("griefprevention.lava"))
|
||||
if(block.getY() >= block.getWorld().getSeaLevel() - 5 && !player.hasPermission("griefprevention.lava"))
|
||||
{
|
||||
GriefPrevention.sendMessage(player, TextMode.Err, "You may only dump lava inside your claim(s) or underground.");
|
||||
GriefPrevention.sendMessage(player, TextMode.Err, "You may only dump buckets inside your claim(s) or underground.");
|
||||
bucketEvent.setCancelled(true);
|
||||
return;
|
||||
}
|
||||
|
|
@ -592,7 +760,7 @@ class PlayerEventHandler implements Listener
|
|||
}
|
||||
|
||||
//see above
|
||||
@EventHandler(ignoreCancelled = true, priority = EventPriority.HIGHEST)
|
||||
@EventHandler(ignoreCancelled = true, priority = EventPriority.LOWEST)
|
||||
public void onPlayerBucketFill (PlayerBucketFillEvent bucketEvent)
|
||||
{
|
||||
Player player = bucketEvent.getPlayer();
|
||||
|
|
@ -609,7 +777,7 @@ class PlayerEventHandler implements Listener
|
|||
}
|
||||
|
||||
//when a player interacts with the world
|
||||
@EventHandler(priority = EventPriority.HIGHEST)
|
||||
@EventHandler(priority = EventPriority.LOWEST)
|
||||
void onPlayerInteract(PlayerInteractEvent event)
|
||||
{
|
||||
Player player = event.getPlayer();
|
||||
|
|
@ -640,7 +808,7 @@ class PlayerEventHandler implements Listener
|
|||
Material clickedBlockType = clickedBlock.getType();
|
||||
|
||||
//apply rules for buttons and switches
|
||||
if(GriefPrevention.instance.config_claims_preventButtonsSwitches && (clickedBlockType == Material.STONE_BUTTON || clickedBlockType == Material.LEVER))
|
||||
if(GriefPrevention.instance.config_claims_preventButtonsSwitches && (clickedBlockType == null || clickedBlockType == Material.STONE_BUTTON || clickedBlockType == Material.LEVER))
|
||||
{
|
||||
Claim claim = this.dataStore.getClaimAt(clickedBlock.getLocation(), false, null);
|
||||
if(claim != null)
|
||||
|
|
@ -814,7 +982,7 @@ class PlayerEventHandler implements Listener
|
|||
//if the player is in restore nature mode, do only that
|
||||
String playerName = player.getName();
|
||||
playerData = this.dataStore.getPlayerData(player.getName());
|
||||
if(playerData.shovelMode == ShovelMode.RestoreNature)
|
||||
if(playerData.shovelMode == ShovelMode.RestoreNature || playerData.shovelMode == ShovelMode.RestoreNatureAggressive)
|
||||
{
|
||||
//if the clicked block is in a claim, visualize that claim and deliver an error message
|
||||
Claim claim = this.dataStore.getClaimAt(clickedBlock.getLocation(), false, playerData.lastClaim);
|
||||
|
|
@ -827,7 +995,7 @@ class PlayerEventHandler implements Listener
|
|||
return;
|
||||
}
|
||||
|
||||
//figure out which chunk to regen
|
||||
//figure out which chunk to repair
|
||||
Chunk chunk = player.getWorld().getChunkAt(clickedBlock.getLocation());
|
||||
|
||||
//check it for players, and cancel if there are any
|
||||
|
|
@ -863,9 +1031,14 @@ class PlayerEventHandler implements Listener
|
|||
|
||||
//set boundaries for processing
|
||||
int miny = clickedBlock.getY();
|
||||
if(miny > chunk.getWorld().getSeaLevel() - 10)
|
||||
|
||||
//if not in aggressive mode, extend the selection down to a little below sea level
|
||||
if(!(playerData.shovelMode == ShovelMode.RestoreNatureAggressive))
|
||||
{
|
||||
miny = chunk.getWorld().getSeaLevel() - 10;
|
||||
if(miny > chunk.getWorld().getSeaLevel() - 10)
|
||||
{
|
||||
miny = chunk.getWorld().getSeaLevel() - 10;
|
||||
}
|
||||
}
|
||||
|
||||
Location lesserBoundaryCorner = chunk.getBlock(0, 0, 0).getLocation();
|
||||
|
|
@ -873,12 +1046,112 @@ class PlayerEventHandler implements Listener
|
|||
|
||||
//create task
|
||||
//when done processing, this task will create a main thread task to actually update the world with processing results
|
||||
RestoreNatureProcessingTask task = new RestoreNatureProcessingTask(snapshots, miny, chunk.getWorld().getEnvironment(), chunk.getWorld().getBiome(lesserBoundaryCorner.getBlockX(), lesserBoundaryCorner.getBlockZ()), lesserBoundaryCorner, greaterBoundaryCorner, chunk.getWorld().getSeaLevel(), player);
|
||||
RestoreNatureProcessingTask task = new RestoreNatureProcessingTask(snapshots, miny, chunk.getWorld().getEnvironment(), chunk.getWorld().getBiome(lesserBoundaryCorner.getBlockX(), lesserBoundaryCorner.getBlockZ()), lesserBoundaryCorner, greaterBoundaryCorner, chunk.getWorld().getSeaLevel(), playerData.shovelMode == ShovelMode.RestoreNatureAggressive, player);
|
||||
GriefPrevention.instance.getServer().getScheduler().scheduleAsyncDelayedTask(GriefPrevention.instance, task);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
//if in restore nature fill mode
|
||||
if(playerData.shovelMode == ShovelMode.RestoreNatureFill)
|
||||
{
|
||||
ArrayList<Material> allowedFillBlocks = new ArrayList<Material>();
|
||||
Environment environment = clickedBlock.getWorld().getEnvironment();
|
||||
if(environment == Environment.NETHER)
|
||||
{
|
||||
allowedFillBlocks.add(Material.NETHERRACK);
|
||||
}
|
||||
else if(environment == Environment.THE_END)
|
||||
{
|
||||
allowedFillBlocks.add(Material.ENDER_STONE);
|
||||
}
|
||||
else
|
||||
{
|
||||
allowedFillBlocks.add(Material.STONE);
|
||||
allowedFillBlocks.add(Material.SAND);
|
||||
allowedFillBlocks.add(Material.SANDSTONE);
|
||||
allowedFillBlocks.add(Material.DIRT);
|
||||
allowedFillBlocks.add(Material.GRASS);
|
||||
}
|
||||
|
||||
Block centerBlock = clickedBlock;
|
||||
int maxHeight = centerBlock.getY();
|
||||
int minx = centerBlock.getX() - playerData.fillRadius;
|
||||
int maxx = centerBlock.getX() + playerData.fillRadius;
|
||||
int minz = centerBlock.getZ() - playerData.fillRadius;
|
||||
int maxz = centerBlock.getZ() + playerData.fillRadius;
|
||||
int minHeight = maxHeight - 10;
|
||||
if(minHeight < 0) minHeight = 0;
|
||||
|
||||
Claim cachedClaim = null;
|
||||
for(int x = minx; x <= maxx; x++)
|
||||
{
|
||||
for(int z = minz; z <= maxz; z++)
|
||||
{
|
||||
//circular brush
|
||||
Location location = new Location(centerBlock.getWorld(), x, centerBlock.getY(), z);
|
||||
if(location.distance(centerBlock.getLocation()) > playerData.fillRadius) continue;
|
||||
|
||||
//fill bottom to top
|
||||
for(int y = minHeight; y <= maxHeight; y++)
|
||||
{
|
||||
Block block = centerBlock.getWorld().getBlockAt(x, y, z);
|
||||
|
||||
//respect claims
|
||||
Claim claim = this.dataStore.getClaimAt(block.getLocation(), false, cachedClaim);
|
||||
if(claim != null)
|
||||
{
|
||||
cachedClaim = claim;
|
||||
break;
|
||||
}
|
||||
|
||||
//only replace air and spilling water
|
||||
if(block.getType() == Material.AIR || block.getType() == Material.STATIONARY_WATER && block.getData() != 0)
|
||||
{
|
||||
//look to neighbors for an appropriate fill block
|
||||
Block eastBlock = block.getRelative(BlockFace.EAST);
|
||||
Block westBlock = block.getRelative(BlockFace.WEST);
|
||||
Block northBlock = block.getRelative(BlockFace.NORTH);
|
||||
Block southBlock = block.getRelative(BlockFace.SOUTH);
|
||||
Block underBlock = block.getRelative(BlockFace.DOWN);
|
||||
|
||||
//first, check lateral neighbors (ideally, want to keep natural layers)
|
||||
if(allowedFillBlocks.contains(eastBlock.getType()))
|
||||
{
|
||||
block.setType(eastBlock.getType());
|
||||
}
|
||||
else if(allowedFillBlocks.contains(westBlock.getType()))
|
||||
{
|
||||
block.setType(westBlock.getType());
|
||||
}
|
||||
else if(allowedFillBlocks.contains(northBlock.getType()))
|
||||
{
|
||||
block.setType(northBlock.getType());
|
||||
}
|
||||
else if(allowedFillBlocks.contains(southBlock.getType()))
|
||||
{
|
||||
block.setType(southBlock.getType());
|
||||
}
|
||||
|
||||
//then check underneath
|
||||
else if(allowedFillBlocks.contains(underBlock.getType()))
|
||||
{
|
||||
block.setType(underBlock.getType());
|
||||
}
|
||||
|
||||
//if all else fails, use the first material listed in the acceptable fill blocks above
|
||||
else
|
||||
{
|
||||
block.setType(allowedFillBlocks.get(0));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
//if the player doesn't have claims permission, don't do anything
|
||||
if(GriefPrevention.instance.config_claims_creationRequiresPermission && !player.hasPermission("griefprevention.createclaims"))
|
||||
{
|
||||
|
|
@ -959,24 +1232,30 @@ class PlayerEventHandler implements Listener
|
|||
}
|
||||
}
|
||||
|
||||
//in creative mode, top-level claims can't be moved or resized smaller.
|
||||
//to check this, verifying the old claim's corners are inside the new claim's boundaries.
|
||||
if(!player.hasPermission("griefprevention.deleteclaims") && GriefPrevention.instance.creativeRulesApply(player.getLocation()) && playerData.claimResizing.parent == null)
|
||||
{
|
||||
Claim oldClaim = playerData.claimResizing;
|
||||
|
||||
//special rules for making a top-level claim smaller. to check this, verifying the old claim's corners are inside the new claim's boundaries.
|
||||
//rule1: in creative mode, top-level claims can't be moved or resized smaller.
|
||||
//rule2: in any mode, shrinking a claim removes any surface fluids
|
||||
Claim oldClaim = playerData.claimResizing;
|
||||
if(oldClaim.parent == null)
|
||||
{
|
||||
//temporary claim instance, just for checking contains()
|
||||
Claim newClaim = new Claim(
|
||||
new Location(oldClaim.getLesserBoundaryCorner().getWorld(), newx1, newy1, newz1),
|
||||
new Location(oldClaim.getLesserBoundaryCorner().getWorld(), newx2, newy2, newz2),
|
||||
"", new String[]{}, new String[]{}, new String[]{}, new String[]{});
|
||||
|
||||
//both greater and lesser boundary corners of the old claim must be inside the new claim
|
||||
//if the new claim is smaller
|
||||
if(!newClaim.contains(oldClaim.getLesserBoundaryCorner(), true, false) || !newClaim.contains(oldClaim.getGreaterBoundaryCorner(), true, false))
|
||||
{
|
||||
//otherwise, show an error message and stop here
|
||||
GriefPrevention.sendMessage(player, TextMode.Err, "You can't un-claim creative mode land. You can only make this claim larger or create additional claims.");
|
||||
return;
|
||||
//enforce creative mode rule
|
||||
if(!player.hasPermission("griefprevention.deleteclaims") && GriefPrevention.instance.creativeRulesApply(player.getLocation()))
|
||||
{
|
||||
GriefPrevention.sendMessage(player, TextMode.Err, "You can't un-claim creative mode land. You can only make this claim larger or create additional claims.");
|
||||
return;
|
||||
}
|
||||
|
||||
//remove surface fluids about to be unclaimed
|
||||
oldClaim.removeSurfaceFluids(newClaim);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -61,7 +61,7 @@ class PlayerRescueTask implements Runnable
|
|||
Location destination = GriefPrevention.instance.ejectPlayer(this.player);
|
||||
|
||||
//log entry, in case admins want to investigate the "trap"
|
||||
GriefPrevention.AddLogEntry("Rescued trapped player " + player.getName() + " from " + this.location.toString() + " to " + destination.toString() + ".");
|
||||
GriefPrevention.AddLogEntry("Rescued trapped player " + player.getName() + " from " + GriefPrevention.getfriendlyLocationString(this.location) + " to " + GriefPrevention.getfriendlyLocationString(destination) + ".");
|
||||
|
||||
//timestamp this successful save so that he can't use /trapped again for a while
|
||||
playerData.lastTrappedUsage = Calendar.getInstance().getTime();
|
||||
|
|
|
|||
|
|
@ -43,12 +43,13 @@ class RestoreNatureProcessingTask implements Runnable
|
|||
private Player player; //absolutely must not be accessed. not thread safe.
|
||||
private Biome biome;
|
||||
private int seaLevel;
|
||||
private boolean aggressiveMode;
|
||||
|
||||
//two lists of materials
|
||||
private ArrayList<Integer> notAllowedToHang; //natural blocks which don't naturally hang in their air
|
||||
private ArrayList<Integer> playerBlocks; //a "complete" list of player-placed blocks. MUST BE MAINTAINED as patches introduce more
|
||||
|
||||
public RestoreNatureProcessingTask(BlockSnapshot[][][] snapshots, int miny, Environment environment, Biome biome, Location lesserBoundaryCorner, Location greaterBoundaryCorner, int seaLevel, Player player)
|
||||
public RestoreNatureProcessingTask(BlockSnapshot[][][] snapshots, int miny, Environment environment, Biome biome, Location lesserBoundaryCorner, Location greaterBoundaryCorner, int seaLevel, boolean aggressiveMode, Player player)
|
||||
{
|
||||
this.snapshots = snapshots;
|
||||
this.miny = miny;
|
||||
|
|
@ -57,18 +58,24 @@ class RestoreNatureProcessingTask implements Runnable
|
|||
this.greaterBoundaryCorner = greaterBoundaryCorner;
|
||||
this.biome = biome;
|
||||
this.seaLevel = seaLevel;
|
||||
this.aggressiveMode = aggressiveMode;
|
||||
this.player = player;
|
||||
|
||||
this.notAllowedToHang = new ArrayList<Integer>();
|
||||
this.notAllowedToHang.add(Material.DIRT.getId());
|
||||
this.notAllowedToHang.add(Material.GRASS.getId());
|
||||
this.notAllowedToHang.add(Material.LONG_GRASS.getId());
|
||||
this.notAllowedToHang.add(Material.SNOW.getId());
|
||||
this.notAllowedToHang.add(Material.LOG.getId());
|
||||
|
||||
if(this.aggressiveMode)
|
||||
{
|
||||
this.notAllowedToHang.add(Material.GRASS.getId());
|
||||
this.notAllowedToHang.add(Material.STONE.getId());
|
||||
}
|
||||
|
||||
//NOTE on this list. why not make a list of natural blocks?
|
||||
//answer: better to leave a few player blocks than to remove too many natural blocks. remember we're "restoring nature"
|
||||
//a few extra player blocks can be manually removed, but it will be impossible to guess exactly which natural materials to use in replacements
|
||||
//a few extra player blocks can be manually removed, but it will be impossible to guess exactly which natural materials to use in manual repair of an overzealous block removal
|
||||
this.playerBlocks = new ArrayList<Integer>();
|
||||
this.playerBlocks.add(Material.FIRE.getId());
|
||||
this.playerBlocks.add(Material.BED_BLOCK.getId());
|
||||
|
|
@ -155,11 +162,23 @@ class RestoreNatureProcessingTask implements Runnable
|
|||
}
|
||||
|
||||
//these are unnatural in sandy biomes, but not elsewhere
|
||||
if(this.biome == Biome.DESERT || this.biome == Biome.DESERT_HILLS || this.biome == Biome.BEACH)
|
||||
if(this.biome == Biome.DESERT || this.biome == Biome.DESERT_HILLS || this.biome == Biome.BEACH || this.aggressiveMode)
|
||||
{
|
||||
this.playerBlocks.add(Material.LEAVES.getId());
|
||||
this.playerBlocks.add(Material.LOG.getId());
|
||||
}
|
||||
|
||||
//in aggressive mode, also treat these blocks as user placed, to be removed
|
||||
//this is helpful in the few cases where griefers intentionally use natural blocks to grief,
|
||||
//like a single-block tower of iron ore or a giant penis constructed with logs
|
||||
if(this.aggressiveMode)
|
||||
{
|
||||
this.playerBlocks.add(Material.IRON_ORE.getId());
|
||||
this.playerBlocks.add(Material.PUMPKIN.getId());
|
||||
this.playerBlocks.add(Material.PUMPKIN_STEM.getId());
|
||||
this.playerBlocks.add(Material.MELON_BLOCK.getId());
|
||||
this.playerBlocks.add(Material.MELON_STEM.getId());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
@ -185,6 +204,9 @@ class RestoreNatureProcessingTask implements Runnable
|
|||
//fill water depressions and fix unnatural surface ripples
|
||||
this.fixWater();
|
||||
|
||||
//remove water/lava above sea level
|
||||
this.removeDumpedFluids();
|
||||
|
||||
//schedule main thread task to apply the result to the world
|
||||
RestoreNatureExecutionTask task = new RestoreNatureExecutionTask(this.snapshots, this.miny, this.lesserBoundaryCorner, this.greaterBoundaryCorner, this.player);
|
||||
GriefPrevention.instance.getServer().getScheduler().scheduleSyncDelayedTask(GriefPrevention.instance, task);
|
||||
|
|
@ -195,6 +217,7 @@ class RestoreNatureProcessingTask implements Runnable
|
|||
int miny = this.miny;
|
||||
if(miny < 1) miny = 1;
|
||||
|
||||
//remove all player blocks
|
||||
for(int x = 1; x < snapshots.length - 1; x++)
|
||||
{
|
||||
for(int z = 1; z < snapshots[0][0].length - 1; z++)
|
||||
|
|
@ -208,7 +231,7 @@ class RestoreNatureProcessingTask implements Runnable
|
|||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void removeHanging()
|
||||
|
|
@ -225,7 +248,7 @@ class RestoreNatureProcessingTask implements Runnable
|
|||
BlockSnapshot block = snapshots[x][y][z];
|
||||
BlockSnapshot underBlock = snapshots[x][y - 1][z];
|
||||
|
||||
if(underBlock.typeId == Material.AIR.getId() || underBlock.typeId == Material.WATER.getId())
|
||||
if(underBlock.typeId == Material.AIR.getId() || underBlock.typeId == Material.STATIONARY_WATER.getId() || underBlock.typeId == Material.STATIONARY_LAVA.getId() || underBlock.typeId == Material.LEAVES.getId())
|
||||
{
|
||||
if(this.notAllowedToHang.contains(block.typeId))
|
||||
{
|
||||
|
|
@ -267,19 +290,19 @@ class RestoreNatureProcessingTask implements Runnable
|
|||
{
|
||||
for(int z = 1; z < snapshots[0][0].length - 1; z++)
|
||||
{
|
||||
int thisy = this.highestY(x, z);
|
||||
int thisy = this.highestY(x, z, false);
|
||||
if(excludedBlocks.contains(this.snapshots[x][thisy][z].typeId)) continue;
|
||||
|
||||
int righty = this.highestY(x + 1, z);
|
||||
int lefty = this.highestY(x - 1, z);
|
||||
int righty = this.highestY(x + 1, z, false);
|
||||
int lefty = this.highestY(x - 1, z, false);
|
||||
while(lefty < thisy && righty < thisy)
|
||||
{
|
||||
this.snapshots[x][thisy--][z].typeId = Material.AIR.getId();
|
||||
changed = true;
|
||||
}
|
||||
|
||||
int upy = this.highestY(x, z + 1);
|
||||
int downy = this.highestY(x, z - 1);
|
||||
int upy = this.highestY(x, z + 1, false);
|
||||
int downy = this.highestY(x, z - 1, false);
|
||||
while(upy < thisy && downy < thisy)
|
||||
{
|
||||
this.snapshots[x][thisy--][z].typeId = Material.AIR.getId();
|
||||
|
|
@ -296,7 +319,7 @@ class RestoreNatureProcessingTask implements Runnable
|
|||
{
|
||||
for(int z = 1; z < snapshots[0][0].length - 1; z++)
|
||||
{
|
||||
int y = this.highestY(x, z);
|
||||
int y = this.highestY(x, z, true);
|
||||
BlockSnapshot block = snapshots[x][y][z];
|
||||
|
||||
if(block.typeId == Material.STONE.getId() || block.typeId == Material.GRAVEL.getId() || block.typeId == Material.DIRT.getId())
|
||||
|
|
@ -454,13 +477,36 @@ class RestoreNatureProcessingTask implements Runnable
|
|||
}while(changed);
|
||||
}
|
||||
|
||||
private int highestY(int x, int z)
|
||||
private void removeDumpedFluids()
|
||||
{
|
||||
//remove any surface water or lava above sea level, presumed to be placed by players
|
||||
//sometimes, this is naturally generated. but replacing it is very easy with a bucket, so overall this is a good plan
|
||||
if(this.environment == Environment.NETHER) return;
|
||||
for(int x = 1; x < snapshots.length - 1; x++)
|
||||
{
|
||||
for(int z = 1; z < snapshots[0][0].length - 1; z++)
|
||||
{
|
||||
for(int y = this.seaLevel - 1; y < snapshots[0].length - 1; y++)
|
||||
{
|
||||
BlockSnapshot block = snapshots[x][y][z];
|
||||
if(block.typeId == Material.STATIONARY_WATER.getId() || block.typeId == Material.STATIONARY_LAVA.getId() ||
|
||||
block.typeId == Material.WATER.getId() || block.typeId == Material.LAVA.getId())
|
||||
{
|
||||
block.typeId = Material.AIR.getId();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private int highestY(int x, int z, boolean ignoreLeaves)
|
||||
{
|
||||
int y;
|
||||
for(y = snapshots[0].length - 1; y > 0; y--)
|
||||
{
|
||||
BlockSnapshot block = this.snapshots[x][y][z];
|
||||
if(block.typeId != Material.AIR.getId() &&
|
||||
if(block.typeId != Material.AIR.getId() &&
|
||||
!(ignoreLeaves && block.typeId == Material.LEAVES.getId()) &&
|
||||
!(block.typeId == Material.STATIONARY_WATER.getId() && block.data != 0) &&
|
||||
!(block.typeId == Material.STATIONARY_LAVA.getId() && block.data != 0))
|
||||
{
|
||||
|
|
|
|||
|
|
@ -24,5 +24,7 @@ enum ShovelMode
|
|||
Basic,
|
||||
Admin,
|
||||
Subdivide,
|
||||
RestoreNature
|
||||
RestoreNature,
|
||||
RestoreNatureAggressive,
|
||||
RestoreNatureFill
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user