Refactor to maven standard layout (#270)
This commit is contained in:
@@ -0,0 +1,132 @@
|
||||
package me.ryanhamshire.GriefPrevention;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.ChunkSnapshot;
|
||||
import org.bukkit.Material;
|
||||
import org.bukkit.World.Environment;
|
||||
import org.bukkit.block.Biome;
|
||||
|
||||
//automatically extends a claim downward based on block types detected
|
||||
class AutoExtendClaimTask implements Runnable
|
||||
{
|
||||
private Claim claim;
|
||||
private ArrayList<ChunkSnapshot> chunks;
|
||||
private Environment worldType;
|
||||
|
||||
public AutoExtendClaimTask(Claim claim, ArrayList<ChunkSnapshot> chunks, Environment worldType)
|
||||
{
|
||||
this.claim = claim;
|
||||
this.chunks = chunks;
|
||||
this.worldType = worldType;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run()
|
||||
{
|
||||
int newY = this.getLowestBuiltY();
|
||||
if(newY < this.claim.getLesserBoundaryCorner().getBlockY())
|
||||
{
|
||||
Bukkit.getScheduler().runTask(GriefPrevention.instance, new ExecuteExtendClaimTask(claim, newY));
|
||||
}
|
||||
}
|
||||
|
||||
private int getLowestBuiltY()
|
||||
{
|
||||
int y = this.claim.getLesserBoundaryCorner().getBlockY();
|
||||
|
||||
if(this.yTooSmall(y)) return y;
|
||||
|
||||
try
|
||||
{
|
||||
for(ChunkSnapshot chunk : this.chunks)
|
||||
{
|
||||
Biome biome = chunk.getBiome(0, 0);
|
||||
ArrayList<Material> playerBlockIDs = RestoreNatureProcessingTask.getPlayerBlocks(this.worldType, biome);
|
||||
|
||||
boolean ychanged = true;
|
||||
while(!this.yTooSmall(y) && ychanged)
|
||||
{
|
||||
ychanged = false;
|
||||
for(int x = 0; x < 16; x++)
|
||||
{
|
||||
for(int z = 0; z < 16; z++)
|
||||
{
|
||||
Material blockType = chunk.getBlockType(x, y, z);
|
||||
while(!this.yTooSmall(y) && playerBlockIDs.contains(blockType))
|
||||
{
|
||||
ychanged = true;
|
||||
blockType = chunk.getBlockType(x, --y, z);
|
||||
}
|
||||
|
||||
if(this.yTooSmall(y)) return y;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(this.yTooSmall(y)) return y;
|
||||
}
|
||||
}
|
||||
catch (NoSuchMethodError e)
|
||||
{
|
||||
GriefPrevention.instance.getLogger().severe("You are running an outdated build of Craftbukkit/Spigot/Paper. Please update.");
|
||||
for(ChunkSnapshot chunk : this.chunks)
|
||||
{
|
||||
Biome biome = chunk.getBiome(0, 0);
|
||||
ArrayList<Material> playerBlockIDs = RestoreNatureProcessingTask.getPlayerBlocks(this.worldType, biome);
|
||||
|
||||
boolean ychanged = true;
|
||||
while(!this.yTooSmall(y) && ychanged)
|
||||
{
|
||||
ychanged = false;
|
||||
for(int x = 0; x < 16; x++)
|
||||
{
|
||||
for(int z = 0; z < 16; z++)
|
||||
{
|
||||
int blockType = chunk.getBlockTypeId(x, y, z);
|
||||
while(!this.yTooSmall(y) && playerBlockIDs.contains(Material.getMaterial(blockType)))
|
||||
{
|
||||
ychanged = true;
|
||||
blockType = chunk.getBlockTypeId(x, --y, z);
|
||||
}
|
||||
|
||||
if(this.yTooSmall(y)) return y;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(this.yTooSmall(y)) return y;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
return y;
|
||||
}
|
||||
|
||||
private boolean yTooSmall(int y)
|
||||
{
|
||||
return y == 0 || y <= GriefPrevention.instance.config_claims_maxDepth;
|
||||
}
|
||||
|
||||
//runs in the main execution thread, where it can safely change claims and save those changes
|
||||
private class ExecuteExtendClaimTask implements Runnable
|
||||
{
|
||||
private Claim claim;
|
||||
private int newY;
|
||||
|
||||
public ExecuteExtendClaimTask(Claim claim, int newY)
|
||||
{
|
||||
this.claim = claim;
|
||||
this.newY = newY;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run()
|
||||
{
|
||||
GriefPrevention.instance.dataStore.extendClaim(claim, newY);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,856 @@
|
||||
/*
|
||||
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.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
|
||||
import org.bukkit.ChatColor;
|
||||
import org.bukkit.Location;
|
||||
import org.bukkit.Material;
|
||||
import org.bukkit.World.Environment;
|
||||
import org.bukkit.block.Block;
|
||||
import org.bukkit.block.BlockFace;
|
||||
import org.bukkit.block.BlockState;
|
||||
import org.bukkit.block.Hopper;
|
||||
import org.bukkit.entity.Item;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.entity.minecart.HopperMinecart;
|
||||
import org.bukkit.event.EventHandler;
|
||||
import org.bukkit.event.EventPriority;
|
||||
import org.bukkit.event.Listener;
|
||||
import org.bukkit.event.block.BlockBreakEvent;
|
||||
import org.bukkit.event.block.BlockBurnEvent;
|
||||
import org.bukkit.event.block.BlockDispenseEvent;
|
||||
import org.bukkit.event.block.BlockFormEvent;
|
||||
import org.bukkit.event.block.BlockFromToEvent;
|
||||
import org.bukkit.event.block.BlockIgniteEvent;
|
||||
import org.bukkit.event.block.BlockIgniteEvent.IgniteCause;
|
||||
import org.bukkit.event.block.BlockMultiPlaceEvent;
|
||||
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.inventory.InventoryPickupItemEvent;
|
||||
import org.bukkit.event.world.StructureGrowEvent;
|
||||
import org.bukkit.inventory.InventoryHolder;
|
||||
import org.bukkit.inventory.ItemStack;
|
||||
import org.bukkit.material.Dispenser;
|
||||
import org.bukkit.metadata.MetadataValue;
|
||||
|
||||
//event handlers related to blocks
|
||||
public class BlockEventHandler implements Listener
|
||||
{
|
||||
//convenience reference to singleton datastore
|
||||
private DataStore dataStore;
|
||||
|
||||
private ArrayList<Material> trashBlocks;
|
||||
|
||||
//constructor
|
||||
public BlockEventHandler(DataStore dataStore)
|
||||
{
|
||||
this.dataStore = dataStore;
|
||||
|
||||
//create the list of blocks which will not trigger a warning when they're placed outside of land claims
|
||||
this.trashBlocks = new ArrayList<Material>();
|
||||
this.trashBlocks.add(Material.COBBLESTONE);
|
||||
this.trashBlocks.add(Material.TORCH);
|
||||
this.trashBlocks.add(Material.DIRT);
|
||||
this.trashBlocks.add(Material.SAPLING);
|
||||
this.trashBlocks.add(Material.GRAVEL);
|
||||
this.trashBlocks.add(Material.SAND);
|
||||
this.trashBlocks.add(Material.TNT);
|
||||
this.trashBlocks.add(Material.WORKBENCH);
|
||||
}
|
||||
|
||||
//when a player breaks a block...
|
||||
@EventHandler(ignoreCancelled = true, priority = EventPriority.LOWEST)
|
||||
public void onBlockBreak(BlockBreakEvent breakEvent)
|
||||
{
|
||||
Player player = breakEvent.getPlayer();
|
||||
Block block = breakEvent.getBlock();
|
||||
|
||||
//make sure the player is allowed to break at the location
|
||||
String noBuildReason = GriefPrevention.instance.allowBreak(player, block, block.getLocation(), breakEvent);
|
||||
if(noBuildReason != null)
|
||||
{
|
||||
GriefPrevention.sendMessage(player, TextMode.Err, noBuildReason);
|
||||
breakEvent.setCancelled(true);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
//when a player places a sign...
|
||||
@EventHandler(ignoreCancelled = true)
|
||||
public void onSignChanged(SignChangeEvent event)
|
||||
{
|
||||
//send sign content to online administrators
|
||||
if(!GriefPrevention.instance.config_signNotifications) return;
|
||||
|
||||
Player player = event.getPlayer();
|
||||
if(player == null) return;
|
||||
|
||||
StringBuilder lines = new StringBuilder(" placed a sign @ " + GriefPrevention.getfriendlyLocationString(event.getBlock().getLocation()));
|
||||
boolean notEmpty = false;
|
||||
for(int i = 0; i < event.getLines().length; i++)
|
||||
{
|
||||
String withoutSpaces = event.getLine(i).replace(" ", "");
|
||||
if(!withoutSpaces.isEmpty())
|
||||
{
|
||||
notEmpty = true;
|
||||
lines.append("\n " + event.getLine(i));
|
||||
}
|
||||
}
|
||||
|
||||
String signMessage = lines.toString();
|
||||
|
||||
//prevent signs with blocked IP addresses
|
||||
if(!player.hasPermission("griefprevention.spam") && GriefPrevention.instance.containsBlockedIP(signMessage))
|
||||
{
|
||||
event.setCancelled(true);
|
||||
return;
|
||||
}
|
||||
|
||||
PlayerData playerData = this.dataStore.getPlayerData(player.getUniqueId());
|
||||
//if not empty and wasn't the same as the last sign, log it and remember it for later
|
||||
//This has been temporarily removed since `signMessage` includes location, not just the message. Waste of memory IMO
|
||||
//if(notEmpty && (playerData.lastSignMessage == null || !playerData.lastSignMessage.equals(signMessage)))
|
||||
if (notEmpty)
|
||||
{
|
||||
GriefPrevention.AddLogEntry(player.getName() + lines.toString().replace("\n ", ";"), null);
|
||||
PlayerEventHandler.makeSocialLogEntry(player.getName(), signMessage);
|
||||
//playerData.lastSignMessage = signMessage;
|
||||
|
||||
if(!player.hasPermission("griefprevention.eavesdropsigns"))
|
||||
{
|
||||
@SuppressWarnings("unchecked")
|
||||
Collection<Player> players = (Collection<Player>)GriefPrevention.instance.getServer().getOnlinePlayers();
|
||||
for(Player otherPlayer : players)
|
||||
{
|
||||
if(otherPlayer.hasPermission("griefprevention.eavesdropsigns"))
|
||||
{
|
||||
otherPlayer.sendMessage(ChatColor.GRAY + player.getName() + signMessage);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//when a player places multiple blocks...
|
||||
@EventHandler(ignoreCancelled = true, priority = EventPriority.HIGHEST)
|
||||
public void onBlocksPlace(BlockMultiPlaceEvent placeEvent)
|
||||
{
|
||||
Player player = placeEvent.getPlayer();
|
||||
|
||||
//don't track in worlds where claims are not enabled
|
||||
if(!GriefPrevention.instance.claimsEnabledForWorld(placeEvent.getBlock().getWorld())) return;
|
||||
|
||||
//make sure the player is allowed to build at the location
|
||||
for(BlockState block : placeEvent.getReplacedBlockStates())
|
||||
{
|
||||
String noBuildReason = GriefPrevention.instance.allowBuild(player, block.getLocation(), block.getType());
|
||||
if(noBuildReason != null)
|
||||
{
|
||||
GriefPrevention.sendMessage(player, TextMode.Err, noBuildReason);
|
||||
placeEvent.setCancelled(true);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//when a player places a block...
|
||||
@SuppressWarnings("null")
|
||||
@EventHandler(ignoreCancelled = true, priority = EventPriority.HIGH)
|
||||
public void onBlockPlace(BlockPlaceEvent placeEvent)
|
||||
{
|
||||
Player player = placeEvent.getPlayer();
|
||||
Block block = placeEvent.getBlock();
|
||||
|
||||
//FEATURE: limit fire placement, to prevent PvP-by-fire
|
||||
|
||||
//if placed block is fire and pvp is off, apply rules for proximity to other players
|
||||
if(block.getType() == Material.FIRE && (!GriefPrevention.instance.pvpRulesApply(block.getWorld()) || !GriefPrevention.instance.config_pvp_allowFireNearPlayers))
|
||||
{
|
||||
List<Player> players = block.getWorld().getPlayers();
|
||||
for(int i = 0; i < players.size(); i++)
|
||||
{
|
||||
Player otherPlayer = players.get(i);
|
||||
Location location = otherPlayer.getLocation();
|
||||
if(!otherPlayer.equals(player) && location.distanceSquared(block.getLocation()) < 9)
|
||||
{
|
||||
GriefPrevention.sendMessage(player, TextMode.Err, Messages.PlayerTooCloseForFire2);
|
||||
placeEvent.setCancelled(true);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//don't track in worlds where claims are not enabled
|
||||
if(!GriefPrevention.instance.claimsEnabledForWorld(placeEvent.getBlock().getWorld())) return;
|
||||
|
||||
//make sure the player is allowed to build at the location
|
||||
String noBuildReason = GriefPrevention.instance.allowBuild(player, block.getLocation(), block.getType());
|
||||
if(noBuildReason != null)
|
||||
{
|
||||
GriefPrevention.sendMessage(player, TextMode.Err, noBuildReason);
|
||||
placeEvent.setCancelled(true);
|
||||
return;
|
||||
}
|
||||
|
||||
//if the block is being placed within or under an existing claim
|
||||
PlayerData playerData = this.dataStore.getPlayerData(player.getUniqueId());
|
||||
Claim claim = this.dataStore.getClaimAt(block.getLocation(), true, playerData.lastClaim);
|
||||
if(claim != null)
|
||||
{
|
||||
playerData.lastClaim = claim;
|
||||
|
||||
//warn about TNT not destroying claimed blocks
|
||||
if(block.getType() == Material.TNT && !claim.areExplosivesAllowed && playerData.siegeData == null)
|
||||
{
|
||||
GriefPrevention.sendMessage(player, TextMode.Warn, Messages.NoTNTDamageClaims);
|
||||
GriefPrevention.sendMessage(player, TextMode.Instr, Messages.ClaimExplosivesAdvertisement);
|
||||
}
|
||||
|
||||
//if the player has permission for the claim and he's placing UNDER the claim
|
||||
if(block.getY() <= claim.lesserBoundaryCorner.getBlockY() && claim.allowBuild(player, block.getType()) == null)
|
||||
{
|
||||
//extend the claim downward
|
||||
this.dataStore.extendClaim(claim, block.getY() - GriefPrevention.instance.config_claims_claimsExtendIntoGroundDistance);
|
||||
}
|
||||
|
||||
//allow for a build warning in the future
|
||||
playerData.warnedAboutBuildingOutsideClaims = false;
|
||||
}
|
||||
|
||||
//FEATURE: automatically create a claim when a player who has no claims places a chest
|
||||
|
||||
//otherwise if there's no claim, the player is placing a chest, and new player automatic claims are enabled
|
||||
else if(GriefPrevention.instance.config_claims_automaticClaimsForNewPlayersRadius > -1 && player.hasPermission("griefprevention.createclaims") && block.getType() == Material.CHEST)
|
||||
{
|
||||
//if the chest is too deep underground, don't create the claim and explain why
|
||||
if(GriefPrevention.instance.config_claims_preventTheft && block.getY() < GriefPrevention.instance.config_claims_maxDepth)
|
||||
{
|
||||
GriefPrevention.sendMessage(player, TextMode.Warn, Messages.TooDeepToClaim);
|
||||
return;
|
||||
}
|
||||
|
||||
int radius = GriefPrevention.instance.config_claims_automaticClaimsForNewPlayersRadius;
|
||||
|
||||
//if the player doesn't have any claims yet, automatically create a claim centered at the chest
|
||||
if(playerData.getClaims().size() == 0)
|
||||
{
|
||||
//radius == 0 means protect ONLY the chest
|
||||
if(GriefPrevention.instance.config_claims_automaticClaimsForNewPlayersRadius == 0)
|
||||
{
|
||||
this.dataStore.createClaim(block.getWorld(), block.getX(), block.getX(), block.getY(), block.getY(), block.getZ(), block.getZ(), player.getUniqueId(), null, null, player);
|
||||
GriefPrevention.sendMessage(player, TextMode.Success, Messages.ChestClaimConfirmation);
|
||||
}
|
||||
|
||||
//otherwise, create a claim in the area around the chest
|
||||
else
|
||||
{
|
||||
//if failure due to insufficient claim blocks available
|
||||
if(playerData.getRemainingClaimBlocks() < 1)
|
||||
{
|
||||
GriefPrevention.sendMessage(player, TextMode.Warn, Messages.NoEnoughBlocksForChestClaim);
|
||||
return;
|
||||
}
|
||||
|
||||
//as long as the automatic claim overlaps another existing claim, shrink it
|
||||
//note that since the player had permission to place the chest, at the very least, the automatic claim will include the chest
|
||||
CreateClaimResult result = null;
|
||||
while(radius >= 0)
|
||||
{
|
||||
int area = (radius * 2 + 1) * (radius * 2 + 1);
|
||||
if(playerData.getRemainingClaimBlocks() >= area)
|
||||
{
|
||||
result = this.dataStore.createClaim(
|
||||
block.getWorld(),
|
||||
block.getX() - radius, block.getX() + radius,
|
||||
block.getY() - GriefPrevention.instance.config_claims_claimsExtendIntoGroundDistance, block.getY(),
|
||||
block.getZ() - radius, block.getZ() + radius,
|
||||
player.getUniqueId(),
|
||||
null, null,
|
||||
player);
|
||||
|
||||
if(result.succeeded) break;
|
||||
}
|
||||
|
||||
radius--;
|
||||
}
|
||||
|
||||
if(result != null && result.succeeded)
|
||||
{
|
||||
//notify and explain to player
|
||||
GriefPrevention.sendMessage(player, TextMode.Success, Messages.AutomaticClaimNotification);
|
||||
|
||||
//show the player the protected area
|
||||
Visualization visualization = Visualization.FromClaim(result.claim, block.getY(), VisualizationType.Claim, player.getLocation());
|
||||
Visualization.Apply(player, visualization);
|
||||
}
|
||||
}
|
||||
|
||||
GriefPrevention.sendMessage(player, TextMode.Instr, Messages.SurvivalBasicsVideo2, DataStore.SURVIVAL_VIDEO_URL);
|
||||
}
|
||||
|
||||
//check to see if this chest is in a claim, and warn when it isn't
|
||||
if(GriefPrevention.instance.config_claims_preventTheft && this.dataStore.getClaimAt(block.getLocation(), false, playerData.lastClaim) == null)
|
||||
{
|
||||
GriefPrevention.sendMessage(player, TextMode.Warn, Messages.UnprotectedChestWarning);
|
||||
}
|
||||
}
|
||||
|
||||
//FEATURE: limit wilderness tree planting to grass, or dirt with more blocks beneath it
|
||||
else if(block.getType() == Material.SAPLING && GriefPrevention.instance.config_blockSkyTrees && GriefPrevention.instance.claimsEnabledForWorld(player.getWorld()))
|
||||
{
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//FEATURE: warn players when they're placing non-trash blocks outside of their claimed areas
|
||||
else if(!this.trashBlocks.contains(block.getType()) && GriefPrevention.instance.claimsEnabledForWorld(block.getWorld()))
|
||||
{
|
||||
if(!playerData.warnedAboutBuildingOutsideClaims && !player.hasPermission("griefprevention.adminclaims")
|
||||
&& player.hasPermission("griefprevention.createclaims") && ((playerData.lastClaim == null
|
||||
&& playerData.getClaims().size() == 0) || (playerData.lastClaim != null
|
||||
&& playerData.lastClaim.isNear(player.getLocation(), 15))))
|
||||
{
|
||||
Long now = null;
|
||||
if(playerData.buildWarningTimestamp == null || (now = System.currentTimeMillis()) - playerData.buildWarningTimestamp > 600000) //10 minute cooldown
|
||||
{
|
||||
GriefPrevention.sendMessage(player, TextMode.Warn, Messages.BuildingOutsideClaims);
|
||||
playerData.warnedAboutBuildingOutsideClaims = true;
|
||||
|
||||
if(now == null) now = System.currentTimeMillis();
|
||||
playerData.buildWarningTimestamp = now;
|
||||
|
||||
if(playerData.getClaims().size() < 2)
|
||||
{
|
||||
GriefPrevention.sendMessage(player, TextMode.Instr, Messages.SurvivalBasicsVideo2, DataStore.SURVIVAL_VIDEO_URL);
|
||||
}
|
||||
|
||||
if(playerData.lastClaim != null)
|
||||
{
|
||||
Visualization visualization = Visualization.FromClaim(playerData.lastClaim, block.getY(), VisualizationType.Claim, player.getLocation());
|
||||
Visualization.Apply(player, visualization);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//warn players when they place TNT above sea level, since it doesn't destroy blocks there
|
||||
if( GriefPrevention.instance.config_blockSurfaceOtherExplosions && block.getType() == Material.TNT &&
|
||||
block.getWorld().getEnvironment() != Environment.NETHER &&
|
||||
block.getY() > GriefPrevention.instance.getSeaLevel(block.getWorld()) - 5 &&
|
||||
claim == null &&
|
||||
playerData.siegeData == null)
|
||||
{
|
||||
GriefPrevention.sendMessage(player, TextMode.Warn, Messages.NoTNTDamageAboveSeaLevel);
|
||||
}
|
||||
|
||||
//warn players about disabled pistons outside of land claims
|
||||
if( GriefPrevention.instance.config_pistonsInClaimsOnly &&
|
||||
(block.getType() == Material.PISTON_BASE || block.getType() == Material.PISTON_STICKY_BASE) &&
|
||||
claim == null )
|
||||
{
|
||||
GriefPrevention.sendMessage(player, TextMode.Warn, Messages.NoPistonsOutsideClaims);
|
||||
}
|
||||
|
||||
//limit active blocks in creative mode worlds
|
||||
if(!player.hasPermission("griefprevention.adminclaims") && GriefPrevention.instance.creativeRulesApply(block.getLocation()) && isActiveBlock(block))
|
||||
{
|
||||
String noPlaceReason = claim.allowMoreActiveBlocks();
|
||||
if(noPlaceReason != null)
|
||||
{
|
||||
GriefPrevention.sendMessage(player, TextMode.Err, noPlaceReason);
|
||||
placeEvent.setCancelled(true);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static boolean isActiveBlock(Block block)
|
||||
{
|
||||
return isActiveBlock(block.getType());
|
||||
}
|
||||
|
||||
static boolean isActiveBlock(BlockState state)
|
||||
{
|
||||
return isActiveBlock(state.getType());
|
||||
}
|
||||
|
||||
static boolean isActiveBlock(Material type)
|
||||
{
|
||||
if(type == Material.HOPPER || type == Material.BEACON || type == Material.MOB_SPAWNER) return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
//blocks "pushing" other players' blocks around (pistons)
|
||||
@EventHandler(ignoreCancelled = true, priority = EventPriority.LOWEST)
|
||||
public void onBlockPistonExtend (BlockPistonExtendEvent event)
|
||||
{
|
||||
//pushing down is ALWAYS safe
|
||||
if(event.getDirection() == BlockFace.DOWN) return;
|
||||
|
||||
//don't track in worlds where claims are not enabled
|
||||
if(!GriefPrevention.instance.claimsEnabledForWorld(event.getBlock().getWorld())) return;
|
||||
|
||||
Block pistonBlock = event.getBlock();
|
||||
List<Block> blocks = event.getBlocks();
|
||||
|
||||
//if no blocks moving, then only check to make sure we're not pushing into a claim from outside
|
||||
//this avoids pistons breaking non-solids just inside a claim, like torches, doors, and touchplates
|
||||
if(blocks.size() == 0)
|
||||
{
|
||||
Block invadedBlock = pistonBlock.getRelative(event.getDirection());
|
||||
|
||||
//pushing "air" is harmless
|
||||
if(invadedBlock.getType() == Material.AIR) return;
|
||||
|
||||
if( this.dataStore.getClaimAt(pistonBlock.getLocation(), false, null) == null &&
|
||||
this.dataStore.getClaimAt(invadedBlock.getLocation(), false, null) != null)
|
||||
{
|
||||
event.setCancelled(true);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
//who owns the piston, if anyone?
|
||||
String pistonClaimOwnerName = "_";
|
||||
Claim claim = this.dataStore.getClaimAt(event.getBlock().getLocation(), false, null);
|
||||
if(claim != null) pistonClaimOwnerName = claim.getOwnerName();
|
||||
|
||||
//if pistons are limited to same-claim block movement
|
||||
if(GriefPrevention.instance.config_pistonsInClaimsOnly)
|
||||
{
|
||||
//if piston is not in a land claim, cancel event
|
||||
if(claim == null)
|
||||
{
|
||||
event.setCancelled(true);
|
||||
return;
|
||||
}
|
||||
|
||||
for(Block pushedBlock : event.getBlocks())
|
||||
{
|
||||
//if pushing blocks located outside the land claim it lives in, cancel the event
|
||||
if(!claim.contains(pushedBlock.getLocation(), false, false))
|
||||
{
|
||||
event.setCancelled(true);
|
||||
return;
|
||||
}
|
||||
|
||||
//if pushing a block inside the claim out of the claim, cancel the event
|
||||
//reason: could push into another land claim, don't want to spend CPU checking for that
|
||||
//reason: push ice out, place torch, get water outside the claim
|
||||
if(!claim.contains(pushedBlock.getRelative(event.getDirection()).getLocation(), false, false))
|
||||
{
|
||||
event.setCancelled(true);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//otherwise, consider ownership of piston and EACH pushed block
|
||||
else
|
||||
{
|
||||
//which blocks are being pushed?
|
||||
Claim cachedClaim = claim;
|
||||
for(int i = 0; i < blocks.size(); i++)
|
||||
{
|
||||
//if ANY of the pushed blocks are owned by someone other than the piston owner, cancel the event
|
||||
Block block = blocks.get(i);
|
||||
claim = this.dataStore.getClaimAt(block.getLocation(), false, cachedClaim);
|
||||
if(claim != null)
|
||||
{
|
||||
cachedClaim = claim;
|
||||
if(!claim.getOwnerName().equals(pistonClaimOwnerName))
|
||||
{
|
||||
event.setCancelled(true);
|
||||
pistonBlock.getWorld().createExplosion(pistonBlock.getLocation(), 0);
|
||||
pistonBlock.getWorld().dropItem(pistonBlock.getLocation(), new ItemStack(pistonBlock.getType()));
|
||||
pistonBlock.setType(Material.AIR);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//if any of the blocks are being pushed into a claim from outside, cancel the event
|
||||
for(int i = 0; i < blocks.size(); i++)
|
||||
{
|
||||
Block block = blocks.get(i);
|
||||
Claim originalClaim = this.dataStore.getClaimAt(block.getLocation(), false, cachedClaim);
|
||||
String originalOwnerName = "";
|
||||
if(originalClaim != null)
|
||||
{
|
||||
cachedClaim = originalClaim;
|
||||
originalOwnerName = originalClaim.getOwnerName();
|
||||
}
|
||||
|
||||
Claim newClaim = this.dataStore.getClaimAt(block.getRelative(event.getDirection()).getLocation(), false, cachedClaim);
|
||||
String newOwnerName = "";
|
||||
if(newClaim != null)
|
||||
{
|
||||
newOwnerName = newClaim.getOwnerName();
|
||||
}
|
||||
|
||||
//if pushing this block will change ownership, cancel the event and take away the piston (for performance reasons)
|
||||
if(!newOwnerName.equals(originalOwnerName) && !newOwnerName.isEmpty())
|
||||
{
|
||||
event.setCancelled(true);
|
||||
pistonBlock.getWorld().createExplosion(pistonBlock.getLocation(), 0);
|
||||
pistonBlock.getWorld().dropItem(pistonBlock.getLocation(), new ItemStack(pistonBlock.getType()));
|
||||
pistonBlock.setType(Material.AIR);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//blocks theft by pulling blocks out of a claim (again pistons)
|
||||
@EventHandler(ignoreCancelled = true, priority = EventPriority.LOWEST)
|
||||
public void onBlockPistonRetract (BlockPistonRetractEvent event)
|
||||
{
|
||||
//pulling up is always safe
|
||||
if(event.getDirection() == BlockFace.UP) return;
|
||||
|
||||
try
|
||||
{
|
||||
//don't track in worlds where claims are not enabled
|
||||
if(!GriefPrevention.instance.claimsEnabledForWorld(event.getBlock().getWorld())) return;
|
||||
|
||||
//if pistons limited to only pulling blocks which are in the same claim the piston is in
|
||||
if(GriefPrevention.instance.config_pistonsInClaimsOnly)
|
||||
{
|
||||
//if piston not in a land claim, cancel event
|
||||
Claim pistonClaim = this.dataStore.getClaimAt(event.getBlock().getLocation(), false, null);
|
||||
if(pistonClaim == null && !event.getBlocks().isEmpty())
|
||||
{
|
||||
event.setCancelled(true);
|
||||
return;
|
||||
}
|
||||
|
||||
for(Block movedBlock : event.getBlocks())
|
||||
{
|
||||
//if pulled block isn't in the same land claim, cancel the event
|
||||
if(!pistonClaim.contains(movedBlock.getLocation(), false, false))
|
||||
{
|
||||
event.setCancelled(true);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//otherwise, consider ownership of both piston and block
|
||||
else
|
||||
{
|
||||
//who owns the piston, if anyone?
|
||||
String pistonOwnerName = "_";
|
||||
Block block = event.getBlock();
|
||||
Location pistonLocation = block.getLocation();
|
||||
Claim pistonClaim = this.dataStore.getClaimAt(pistonLocation, false, null);
|
||||
if(pistonClaim != null) pistonOwnerName = pistonClaim.getOwnerName();
|
||||
|
||||
String movingBlockOwnerName = "_";
|
||||
for(Block movedBlock : event.getBlocks())
|
||||
{
|
||||
//who owns the moving block, if anyone?
|
||||
Claim movingBlockClaim = this.dataStore.getClaimAt(movedBlock.getLocation(), false, pistonClaim);
|
||||
if(movingBlockClaim != null) movingBlockOwnerName = movingBlockClaim.getOwnerName();
|
||||
|
||||
//if there are owners for the blocks, they must be the same player
|
||||
//otherwise cancel the event
|
||||
if(!pistonOwnerName.equals(movingBlockOwnerName))
|
||||
{
|
||||
event.setCancelled(true);
|
||||
block.getWorld().createExplosion(block.getLocation(), 0);
|
||||
block.getWorld().dropItem(block.getLocation(), new ItemStack(Material.PISTON_STICKY_BASE));
|
||||
block.setType(Material.AIR);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
catch(NoSuchMethodError exception)
|
||||
{
|
||||
GriefPrevention.AddLogEntry("Your server is running an outdated version of 1.8 which has a griefing vulnerability. Update your server (reruns buildtools.jar to get an updated server JAR file) to ensure players can't steal claimed blocks using pistons.");
|
||||
}
|
||||
}
|
||||
|
||||
//blocks are ignited ONLY by flint and steel (not by being near lava, open flames, etc), unless configured otherwise
|
||||
@EventHandler(priority = EventPriority.LOWEST)
|
||||
public void onBlockIgnite (BlockIgniteEvent igniteEvent)
|
||||
{
|
||||
//don't track in worlds where claims are not enabled
|
||||
if(!GriefPrevention.instance.claimsEnabledForWorld(igniteEvent.getBlock().getWorld())) return;
|
||||
|
||||
|
||||
if(!GriefPrevention.instance.config_fireSpreads && igniteEvent.getCause() != IgniteCause.FLINT_AND_STEEL && igniteEvent.getCause() != IgniteCause.LIGHTNING)
|
||||
{
|
||||
igniteEvent.setCancelled(true);
|
||||
}
|
||||
}
|
||||
|
||||
//fire doesn't spread unless configured to, but other blocks still do (mushrooms and vines, for example)
|
||||
@EventHandler(priority = EventPriority.LOWEST)
|
||||
public void onBlockSpread (BlockSpreadEvent spreadEvent)
|
||||
{
|
||||
if(spreadEvent.getSource().getType() != Material.FIRE) return;
|
||||
|
||||
//don't track in worlds where claims are not enabled
|
||||
if(!GriefPrevention.instance.claimsEnabledForWorld(spreadEvent.getBlock().getWorld())) return;
|
||||
|
||||
if(!GriefPrevention.instance.config_fireSpreads)
|
||||
{
|
||||
spreadEvent.setCancelled(true);
|
||||
|
||||
Block underBlock = spreadEvent.getSource().getRelative(BlockFace.DOWN);
|
||||
if(underBlock.getType() != Material.NETHERRACK)
|
||||
{
|
||||
spreadEvent.getSource().setType(Material.AIR);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
//never spread into a claimed area, regardless of settings
|
||||
if(this.dataStore.getClaimAt(spreadEvent.getBlock().getLocation(), false, null) != null)
|
||||
{
|
||||
spreadEvent.setCancelled(true);
|
||||
|
||||
//if the source of the spread is not fire on netherrack, put out that source fire to save cpu cycles
|
||||
Block source = spreadEvent.getSource();
|
||||
if(source.getRelative(BlockFace.DOWN).getType() != Material.NETHERRACK)
|
||||
{
|
||||
source.setType(Material.AIR);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//blocks are not destroyed by fire, unless configured to do so
|
||||
@EventHandler(priority = EventPriority.LOWEST)
|
||||
public void onBlockBurn (BlockBurnEvent burnEvent)
|
||||
{
|
||||
//don't track in worlds where claims are not enabled
|
||||
if(!GriefPrevention.instance.claimsEnabledForWorld(burnEvent.getBlock().getWorld())) return;
|
||||
|
||||
if(!GriefPrevention.instance.config_fireDestroys)
|
||||
{
|
||||
burnEvent.setCancelled(true);
|
||||
Block block = burnEvent.getBlock();
|
||||
Block [] adjacentBlocks = new Block []
|
||||
{
|
||||
block.getRelative(BlockFace.UP),
|
||||
block.getRelative(BlockFace.DOWN),
|
||||
block.getRelative(BlockFace.NORTH),
|
||||
block.getRelative(BlockFace.SOUTH),
|
||||
block.getRelative(BlockFace.EAST),
|
||||
block.getRelative(BlockFace.WEST)
|
||||
};
|
||||
|
||||
//pro-actively put out any fires adjacent the burning block, to reduce future processing here
|
||||
for(int i = 0; i < adjacentBlocks.length; i++)
|
||||
{
|
||||
Block adjacentBlock = adjacentBlocks[i];
|
||||
if(adjacentBlock.getType() == Material.FIRE && adjacentBlock.getRelative(BlockFace.DOWN).getType() != Material.NETHERRACK)
|
||||
{
|
||||
adjacentBlock.setType(Material.AIR);
|
||||
}
|
||||
}
|
||||
|
||||
Block aboveBlock = block.getRelative(BlockFace.UP);
|
||||
if(aboveBlock.getType() == Material.FIRE)
|
||||
{
|
||||
aboveBlock.setType(Material.AIR);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
//never burn claimed blocks, regardless of settings
|
||||
if(this.dataStore.getClaimAt(burnEvent.getBlock().getLocation(), false, null) != null)
|
||||
{
|
||||
burnEvent.setCancelled(true);
|
||||
}
|
||||
}
|
||||
|
||||
//ensures fluids don't flow into land claims from outside
|
||||
private Claim lastSpreadClaim = null;
|
||||
@EventHandler(ignoreCancelled = true, priority = EventPriority.LOWEST)
|
||||
public void onBlockFromTo (BlockFromToEvent spreadEvent)
|
||||
{
|
||||
//always allow fluids to flow straight down
|
||||
if(spreadEvent.getFace() == BlockFace.DOWN) return;
|
||||
|
||||
//don't track in worlds where claims are not enabled
|
||||
if(!GriefPrevention.instance.claimsEnabledForWorld(spreadEvent.getBlock().getWorld())) return;
|
||||
|
||||
//where to?
|
||||
Block toBlock = spreadEvent.getToBlock();
|
||||
Location toLocation = toBlock.getLocation();
|
||||
Claim toClaim = this.dataStore.getClaimAt(toLocation, false, lastSpreadClaim);
|
||||
|
||||
//if into a land claim, it must be from the same land claim
|
||||
if(toClaim != null)
|
||||
{
|
||||
this.lastSpreadClaim = toClaim;
|
||||
if(!toClaim.contains(spreadEvent.getBlock().getLocation(), false, true))
|
||||
{
|
||||
//exception: from parent into subdivision
|
||||
if(toClaim.parent == null || !toClaim.parent.contains(spreadEvent.getBlock().getLocation(), false, false))
|
||||
{
|
||||
spreadEvent.setCancelled(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//otherwise if creative mode world, don't flow
|
||||
else if(GriefPrevention.instance.creativeRulesApply(toLocation))
|
||||
{
|
||||
spreadEvent.setCancelled(true);
|
||||
}
|
||||
}
|
||||
|
||||
@EventHandler(ignoreCancelled = true, priority = EventPriority.LOWEST)
|
||||
public void onForm(BlockFormEvent event)
|
||||
{
|
||||
Block block = event.getBlock();
|
||||
Location location = block.getLocation();
|
||||
|
||||
if(GriefPrevention.instance.creativeRulesApply(location))
|
||||
{
|
||||
Material type = block.getType();
|
||||
if(type == Material.COBBLESTONE || type == Material.OBSIDIAN || type == Material.STATIONARY_LAVA || type == Material.STATIONARY_WATER)
|
||||
{
|
||||
Claim claim = GriefPrevention.instance.dataStore.getClaimAt(location, false, null);
|
||||
if(claim == null)
|
||||
{
|
||||
event.setCancelled(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//ensures dispensers can't be used to dispense a block(like water or lava) or item across a claim boundary
|
||||
@EventHandler(ignoreCancelled = true, priority = EventPriority.LOWEST)
|
||||
public void onDispense(BlockDispenseEvent dispenseEvent)
|
||||
{
|
||||
//don't track in worlds where claims are not enabled
|
||||
if(!GriefPrevention.instance.claimsEnabledForWorld(dispenseEvent.getBlock().getWorld())) return;
|
||||
|
||||
//from where?
|
||||
Block fromBlock = dispenseEvent.getBlock();
|
||||
@SuppressWarnings("deprecation")
|
||||
Dispenser dispenser = new Dispenser(Material.DISPENSER, fromBlock.getData());
|
||||
|
||||
//to where?
|
||||
Block toBlock = fromBlock.getRelative(dispenser.getFacing());
|
||||
Claim fromClaim = this.dataStore.getClaimAt(fromBlock.getLocation(), false, null);
|
||||
Claim toClaim = this.dataStore.getClaimAt(toBlock.getLocation(), false, fromClaim);
|
||||
|
||||
//into wilderness is NOT OK in creative mode worlds
|
||||
Material materialDispensed = dispenseEvent.getItem().getType();
|
||||
if((materialDispensed == Material.WATER_BUCKET || materialDispensed == Material.LAVA_BUCKET) && GriefPrevention.instance.creativeRulesApply(dispenseEvent.getBlock().getLocation()) && toClaim == null)
|
||||
{
|
||||
dispenseEvent.setCancelled(true);
|
||||
return;
|
||||
}
|
||||
|
||||
//wilderness to wilderness is OK
|
||||
if(fromClaim == null && toClaim == null) return;
|
||||
|
||||
//within claim is OK
|
||||
if(fromClaim == toClaim) return;
|
||||
|
||||
//everything else is NOT OK
|
||||
dispenseEvent.setCancelled(true);
|
||||
}
|
||||
|
||||
@EventHandler(ignoreCancelled = true)
|
||||
public void onTreeGrow (StructureGrowEvent growEvent)
|
||||
{
|
||||
//only take these potentially expensive steps if configured to do so
|
||||
if(!GriefPrevention.instance.config_limitTreeGrowth) return;
|
||||
|
||||
//don't track in worlds where claims are not enabled
|
||||
if(!GriefPrevention.instance.claimsEnabledForWorld(growEvent.getWorld())) return;
|
||||
|
||||
Location rootLocation = growEvent.getLocation();
|
||||
Claim rootClaim = this.dataStore.getClaimAt(rootLocation, false, null);
|
||||
String rootOwnerName = null;
|
||||
|
||||
//who owns the spreading block, if anyone?
|
||||
if(rootClaim != null)
|
||||
{
|
||||
//tree growth in subdivisions is dependent on who owns the top level claim
|
||||
if(rootClaim.parent != null) rootClaim = rootClaim.parent;
|
||||
|
||||
//if an administrative claim, just let the tree grow where it wants
|
||||
if(rootClaim.isAdminClaim()) return;
|
||||
|
||||
//otherwise, note the owner of the claim
|
||||
rootOwnerName = rootClaim.getOwnerName();
|
||||
}
|
||||
|
||||
//for each block growing
|
||||
for(int i = 0; i < growEvent.getBlocks().size(); i++)
|
||||
{
|
||||
BlockState block = growEvent.getBlocks().get(i);
|
||||
Claim blockClaim = this.dataStore.getClaimAt(block.getLocation(), false, rootClaim);
|
||||
|
||||
//if it's growing into a claim
|
||||
if(blockClaim != null)
|
||||
{
|
||||
//if there's no owner for the new tree, or the owner for the new tree is different from the owner of the claim
|
||||
if(rootOwnerName == null || !rootOwnerName.equals(blockClaim.getOwnerName()))
|
||||
{
|
||||
growEvent.getBlocks().remove(i--);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@EventHandler(ignoreCancelled = true)
|
||||
public void onInventoryPickupItem (InventoryPickupItemEvent event)
|
||||
{
|
||||
//prevent hoppers from picking-up items dropped by players on death
|
||||
|
||||
InventoryHolder holder = event.getInventory().getHolder();
|
||||
if(holder instanceof HopperMinecart || holder instanceof Hopper)
|
||||
{
|
||||
Item item = event.getItem();
|
||||
List<MetadataValue> data = item.getMetadata("GP_ITEMOWNER");
|
||||
|
||||
//if this is marked as belonging to a player
|
||||
if(data != null && data.size() > 0)
|
||||
{
|
||||
//don't allow the pickup
|
||||
event.setCancelled(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
/*
|
||||
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.Location;
|
||||
import org.bukkit.Material;
|
||||
|
||||
//basically, just a few data points from a block conveniently encapsulated in a class
|
||||
//this is used only by the RestoreNature code
|
||||
public class BlockSnapshot
|
||||
{
|
||||
public Location location;
|
||||
public Material typeId;
|
||||
public byte data;
|
||||
|
||||
public BlockSnapshot(Location location, Material typeId, byte data)
|
||||
{
|
||||
this.location = location;
|
||||
this.typeId = typeId;
|
||||
this.data = data;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
/*
|
||||
GriefPrevention Server Plugin for Minecraft
|
||||
Copyright (C) 2016 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;
|
||||
|
||||
//sends a message to all online players
|
||||
//used to send delayed messages, for example a quit message after the player has been gone a while
|
||||
class BroadcastMessageTask implements Runnable
|
||||
{
|
||||
private String message;
|
||||
|
||||
public BroadcastMessageTask(String message)
|
||||
{
|
||||
this.message = message;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run()
|
||||
{
|
||||
Bukkit.getServer().broadcastMessage(this.message);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
/*
|
||||
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.Location;
|
||||
import org.bukkit.Material;
|
||||
import org.bukkit.block.Block;
|
||||
import org.bukkit.block.BlockFace;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.metadata.FixedMetadataValue;
|
||||
import org.bukkit.scheduler.BukkitRunnable;
|
||||
|
||||
//players can be "trapped" in a portal frame if they don't have permission to break
|
||||
//solid blocks blocking them from exiting the frame
|
||||
//if that happens, we detect the problem and send them back through the portal.
|
||||
class CheckForPortalTrapTask extends BukkitRunnable
|
||||
{
|
||||
GriefPrevention instance;
|
||||
//player who recently teleported via nether portal
|
||||
private Player player;
|
||||
|
||||
//where to send the player back to if he hasn't left the portal frame
|
||||
private Location returnLocation;
|
||||
|
||||
public CheckForPortalTrapTask(Player player, GriefPrevention plugin, Location locationToReturn)
|
||||
{
|
||||
this.player = player;
|
||||
this.instance = plugin;
|
||||
this.returnLocation = locationToReturn;
|
||||
player.setMetadata("GP_PORTALRESCUE", new FixedMetadataValue(instance, locationToReturn));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run()
|
||||
{
|
||||
if(player.isOnline() && player.getPortalCooldown() >= 10)
|
||||
{
|
||||
instance.AddLogEntry("Rescued " + player.getName() + " from a nether portal.\nTeleported from " + player.getLocation().toString() + " to " + ((Location)player.getMetadata("GP_PORTALRESCUE").get(0).value()).toString(), CustomLogEntryTypes.Debug);
|
||||
player.teleport(returnLocation);
|
||||
player.removeMetadata("GP_PORTALRESCUE", instance);
|
||||
}
|
||||
instance.portalReturnTaskMap.remove(player.getUniqueId());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,969 @@
|
||||
/*
|
||||
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.*;
|
||||
|
||||
import org.bukkit.*;
|
||||
import org.bukkit.World.Environment;
|
||||
import org.bukkit.block.Block;
|
||||
import org.bukkit.block.BlockState;
|
||||
import org.bukkit.entity.Entity;
|
||||
import org.bukkit.entity.Player;
|
||||
|
||||
//represents a player claim
|
||||
//creating an instance doesn't make an effective claim
|
||||
//only claims which have been added to the datastore have any effect
|
||||
public class Claim
|
||||
{
|
||||
//two locations, which together define the boundaries of the claim
|
||||
//note that the upper Y value is always ignored, because claims ALWAYS extend up to the sky
|
||||
Location lesserBoundaryCorner;
|
||||
Location greaterBoundaryCorner;
|
||||
|
||||
//modification date. this comes from the file timestamp during load, and is updated with runtime changes
|
||||
public Date modifiedDate;
|
||||
|
||||
//id number. unique to this claim, never changes.
|
||||
Long id = null;
|
||||
|
||||
//ownerID. for admin claims, this is NULL
|
||||
//use getOwnerName() to get a friendly name (will be "an administrator" for admin claims)
|
||||
public UUID ownerID;
|
||||
|
||||
//list of players who (beyond the claim owner) have permission to grant permissions in this claim
|
||||
public ArrayList<String> managers = new ArrayList<String>();
|
||||
|
||||
//permissions for this claim, see ClaimPermission class
|
||||
private HashMap<String, ClaimPermission> playerIDToClaimPermissionMap = new HashMap<String, ClaimPermission>();
|
||||
|
||||
//whether or not this claim is in the data store
|
||||
//if a claim instance isn't in the data store, it isn't "active" - players can't interract with it
|
||||
//why keep this? so that claims which have been removed from the data store can be correctly
|
||||
//ignored even though they may have references floating around
|
||||
public boolean inDataStore = false;
|
||||
|
||||
public boolean areExplosivesAllowed = false;
|
||||
|
||||
//parent claim
|
||||
//only used for claim subdivisions. top level claims have null here
|
||||
public Claim parent = null;
|
||||
|
||||
// intended for subclaims - they inherit no permissions
|
||||
private boolean inheritNothing = false;
|
||||
|
||||
//children (subdivisions)
|
||||
//note subdivisions themselves never have children
|
||||
public ArrayList<Claim> children = new ArrayList<Claim>();
|
||||
|
||||
//information about a siege involving this claim. null means no siege is impacting this claim
|
||||
public SiegeData siegeData = null;
|
||||
|
||||
//following a siege, buttons/levers are unlocked temporarily. this represents that state
|
||||
public boolean doorsOpen = false;
|
||||
|
||||
//whether or not this is an administrative claim
|
||||
//administrative claims are created and maintained by players with the griefprevention.adminclaims permission.
|
||||
public boolean isAdminClaim()
|
||||
{
|
||||
if(this.parent != null) return this.parent.isAdminClaim();
|
||||
|
||||
return (this.ownerID == null);
|
||||
}
|
||||
|
||||
//accessor for ID
|
||||
public Long getID()
|
||||
{
|
||||
return this.id;
|
||||
}
|
||||
|
||||
//basic constructor, just notes the creation time
|
||||
//see above declarations for other defaults
|
||||
Claim()
|
||||
{
|
||||
this.modifiedDate = Calendar.getInstance().getTime();
|
||||
}
|
||||
|
||||
//players may only siege someone when he's not in an admin claim
|
||||
//and when he has some level of permission in the claim
|
||||
public boolean canSiege(Player defender)
|
||||
{
|
||||
if(this.isAdminClaim()) return false;
|
||||
|
||||
if(this.allowAccess(defender) != null) return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
//removes any lava 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 for administrative claims
|
||||
if(this.isAdminClaim()) return;
|
||||
|
||||
//don't do it for very large claims
|
||||
if(this.getArea() > 10000) return;
|
||||
|
||||
//only in creative mode worlds
|
||||
if(!GriefPrevention.instance.creativeRulesApply(this.lesserBoundaryCorner)) 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 = GriefPrevention.instance.getSeaLevel(lesser.getWorld());
|
||||
|
||||
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_LAVA || block.getType() == Material.WATER || block.getType() == Material.STATIONARY_WATER || block.getType() == Material.LAVA)
|
||||
{
|
||||
block.setType(Material.AIR);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//determines whether or not a claim has surface lava
|
||||
//used to warn players when they abandon their claims about automatic fluid cleanup
|
||||
boolean hasSurfaceFluids()
|
||||
{
|
||||
Location lesser = this.getLesserBoundaryCorner();
|
||||
Location greater = this.getGreaterBoundaryCorner();
|
||||
|
||||
//don't bother for very large claims, too expensive
|
||||
if(this.getArea() > 10000) return false;
|
||||
|
||||
int seaLevel = 0; //clean up all fluids in the end
|
||||
|
||||
//respect sea level in normal worlds
|
||||
if(lesser.getWorld().getEnvironment() == Environment.NORMAL) seaLevel = GriefPrevention.instance.getSeaLevel(lesser.getWorld());
|
||||
|
||||
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(block.getType() == Material.STATIONARY_LAVA || block.getType() == Material.WATER || block.getType() == Material.STATIONARY_WATER || block.getType() == Material.LAVA)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
//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, UUID ownerID, List<String> builderIDs, List<String> containerIDs, List<String> accessorIDs, List<String> managerIDs, boolean inheritNothing, Long id)
|
||||
{
|
||||
//modification date
|
||||
this.modifiedDate = Calendar.getInstance().getTime();
|
||||
|
||||
//id
|
||||
this.id = id;
|
||||
|
||||
//store corners
|
||||
this.lesserBoundaryCorner = lesserBoundaryCorner;
|
||||
this.greaterBoundaryCorner = greaterBoundaryCorner;
|
||||
|
||||
//owner
|
||||
this.ownerID = ownerID;
|
||||
|
||||
//other permissions
|
||||
for(String builderID : builderIDs)
|
||||
{
|
||||
if(builderID != null && !builderID.isEmpty())
|
||||
{
|
||||
this.playerIDToClaimPermissionMap.put(builderID, ClaimPermission.Build);
|
||||
}
|
||||
}
|
||||
|
||||
for(String containerID : containerIDs)
|
||||
{
|
||||
if(containerID != null && !containerID.isEmpty())
|
||||
{
|
||||
this.playerIDToClaimPermissionMap.put(containerID, ClaimPermission.Inventory);
|
||||
}
|
||||
}
|
||||
|
||||
for(String accessorID : accessorIDs)
|
||||
{
|
||||
if(accessorID != null && !accessorID.isEmpty())
|
||||
{
|
||||
this.playerIDToClaimPermissionMap.put(accessorID, ClaimPermission.Access);
|
||||
}
|
||||
}
|
||||
|
||||
for(String managerID : managerIDs)
|
||||
{
|
||||
if(managerID != null && !managerID.isEmpty())
|
||||
{
|
||||
this.managers.add(managerID);
|
||||
}
|
||||
}
|
||||
|
||||
this.inheritNothing = inheritNothing;
|
||||
}
|
||||
|
||||
Claim(Location lesserBoundaryCorner, Location greaterBoundaryCorner, UUID ownerID, List<String> builderIDs, List<String> containerIDs, List<String> accessorIDs, List<String> managerIDs, Long id)
|
||||
{
|
||||
this(lesserBoundaryCorner, greaterBoundaryCorner, ownerID, builderIDs, containerIDs, accessorIDs, managerIDs, false, id);
|
||||
}
|
||||
|
||||
//measurements. all measurements are in blocks
|
||||
public int getArea()
|
||||
{
|
||||
int claimWidth = this.greaterBoundaryCorner.getBlockX() - this.lesserBoundaryCorner.getBlockX() + 1;
|
||||
int claimHeight = this.greaterBoundaryCorner.getBlockZ() - this.lesserBoundaryCorner.getBlockZ() + 1;
|
||||
|
||||
return claimWidth * claimHeight;
|
||||
}
|
||||
|
||||
public int getWidth()
|
||||
{
|
||||
return this.greaterBoundaryCorner.getBlockX() - this.lesserBoundaryCorner.getBlockX() + 1;
|
||||
}
|
||||
|
||||
public int getHeight()
|
||||
{
|
||||
return this.greaterBoundaryCorner.getBlockZ() - this.lesserBoundaryCorner.getBlockZ() + 1;
|
||||
}
|
||||
|
||||
public boolean getSubclaimRestrictions()
|
||||
{
|
||||
return inheritNothing;
|
||||
}
|
||||
|
||||
public void setSubclaimRestrictions(boolean inheritNothing)
|
||||
{
|
||||
this.inheritNothing = inheritNothing;
|
||||
}
|
||||
|
||||
//distance check for claims, distance in this case is a band around the outside of the claim rather then euclidean distance
|
||||
public boolean isNear(Location location, int howNear)
|
||||
{
|
||||
Claim claim = new Claim
|
||||
(new Location(this.lesserBoundaryCorner.getWorld(), this.lesserBoundaryCorner.getBlockX() - howNear, this.lesserBoundaryCorner.getBlockY(), this.lesserBoundaryCorner.getBlockZ() - howNear),
|
||||
new Location(this.greaterBoundaryCorner.getWorld(), this.greaterBoundaryCorner.getBlockX() + howNear, this.greaterBoundaryCorner.getBlockY(), this.greaterBoundaryCorner.getBlockZ() + howNear),
|
||||
null, new ArrayList<String>(), new ArrayList<String>(), new ArrayList<String>(), new ArrayList<String>(), null);
|
||||
|
||||
return claim.contains(location, false, true);
|
||||
}
|
||||
|
||||
//permissions. note administrative "public" claims have different rules than other claims
|
||||
//all of these return NULL when a player has permission, or a String error message when the player doesn't have permission
|
||||
public String allowEdit(Player player)
|
||||
{
|
||||
//if we don't know who's asking, always say no (i've been told some mods can make this happen somehow)
|
||||
if(player == null) return "";
|
||||
|
||||
//special cases...
|
||||
|
||||
//admin claims need adminclaims permission only.
|
||||
if(this.isAdminClaim())
|
||||
{
|
||||
if(player.hasPermission("griefprevention.adminclaims")) return null;
|
||||
}
|
||||
|
||||
//anyone with deleteclaims permission can modify non-admin claims at any time
|
||||
else
|
||||
{
|
||||
if(player.hasPermission("griefprevention.deleteclaims")) return null;
|
||||
}
|
||||
|
||||
//no resizing, deleting, and so forth while under siege
|
||||
if(player.getUniqueId().equals(this.ownerID))
|
||||
{
|
||||
if(this.siegeData != null)
|
||||
{
|
||||
return GriefPrevention.instance.dataStore.getMessage(Messages.NoModifyDuringSiege);
|
||||
}
|
||||
|
||||
//otherwise, owners can do whatever
|
||||
return null;
|
||||
}
|
||||
|
||||
//permission inheritance for subdivisions
|
||||
if(this.parent != null)
|
||||
{
|
||||
if (player.getUniqueId().equals(this.parent.ownerID))
|
||||
return null;
|
||||
if (!inheritNothing)
|
||||
return this.parent.allowEdit(player);
|
||||
}
|
||||
|
||||
//error message if all else fails
|
||||
return GriefPrevention.instance.dataStore.getMessage(Messages.OnlyOwnersModifyClaims, this.getOwnerName());
|
||||
}
|
||||
|
||||
private List<Material> placeableFarmingBlocksList = Arrays.asList(
|
||||
Material.PUMPKIN_STEM,
|
||||
Material.CROPS,
|
||||
Material.MELON_STEM,
|
||||
Material.CARROT,
|
||||
Material.POTATO,
|
||||
Material.NETHER_WARTS,
|
||||
Material.BEETROOT_BLOCK);
|
||||
|
||||
private boolean placeableForFarming(Material material)
|
||||
{
|
||||
return this.placeableFarmingBlocksList.contains(material);
|
||||
}
|
||||
|
||||
//build permission check
|
||||
public String allowBuild(Player player, Material material)
|
||||
{
|
||||
//if we don't know who's asking, always say no (i've been told some mods can make this happen somehow)
|
||||
if(player == null) return "";
|
||||
|
||||
//when a player tries to build in a claim, if he's under siege, the siege may extend to include the new claim
|
||||
GriefPrevention.instance.dataStore.tryExtendSiege(player, this);
|
||||
|
||||
//admin claims can always be modified by admins, no exceptions
|
||||
if(this.isAdminClaim())
|
||||
{
|
||||
if(player.hasPermission("griefprevention.adminclaims")) return null;
|
||||
}
|
||||
|
||||
//no building while under siege
|
||||
if(this.siegeData != null)
|
||||
{
|
||||
return GriefPrevention.instance.dataStore.getMessage(Messages.NoBuildUnderSiege, this.siegeData.attacker.getName());
|
||||
}
|
||||
|
||||
//no building while in pvp combat
|
||||
PlayerData playerData = GriefPrevention.instance.dataStore.getPlayerData(player.getUniqueId());
|
||||
if(playerData.inPvpCombat())
|
||||
{
|
||||
return GriefPrevention.instance.dataStore.getMessage(Messages.NoBuildPvP);
|
||||
}
|
||||
|
||||
//owners can make changes, or admins with ignore claims mode enabled
|
||||
if(player.getUniqueId().equals(this.ownerID) || GriefPrevention.instance.dataStore.getPlayerData(player.getUniqueId()).ignoreClaims) return null;
|
||||
|
||||
//anyone with explicit build permission can make changes
|
||||
if(this.hasExplicitPermission(player, ClaimPermission.Build)) return null;
|
||||
|
||||
//also everyone is a member of the "public", so check for public permission
|
||||
ClaimPermission permissionLevel = this.playerIDToClaimPermissionMap.get("public");
|
||||
if(ClaimPermission.Build == permissionLevel) return null;
|
||||
|
||||
//allow for farming with /containertrust permission
|
||||
if(this.allowContainers(player) == null)
|
||||
{
|
||||
//do allow for farming, if player has /containertrust permission
|
||||
if(this.placeableForFarming(material))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
//subdivision permission inheritance
|
||||
if(this.parent != null)
|
||||
{
|
||||
if (player.getUniqueId().equals(this.parent.ownerID))
|
||||
return null;
|
||||
if (!inheritNothing)
|
||||
return this.parent.allowBuild(player, material);
|
||||
}
|
||||
|
||||
//failure message for all other cases
|
||||
String reason = GriefPrevention.instance.dataStore.getMessage(Messages.NoBuildPermission, this.getOwnerName());
|
||||
if(player.hasPermission("griefprevention.ignoreclaims"))
|
||||
reason += " " + GriefPrevention.instance.dataStore.getMessage(Messages.IgnoreClaimsAdvertisement);
|
||||
|
||||
return reason;
|
||||
}
|
||||
|
||||
private boolean hasExplicitPermission(Player player, ClaimPermission level)
|
||||
{
|
||||
String playerID = player.getUniqueId().toString();
|
||||
Set<String> keys = this.playerIDToClaimPermissionMap.keySet();
|
||||
Iterator<String> iterator = keys.iterator();
|
||||
while(iterator.hasNext())
|
||||
{
|
||||
String identifier = iterator.next();
|
||||
if(playerID.equalsIgnoreCase(identifier) && this.playerIDToClaimPermissionMap.get(identifier) == level) return true;
|
||||
|
||||
else if(identifier.startsWith("[") && identifier.endsWith("]"))
|
||||
{
|
||||
//drop the brackets
|
||||
String permissionIdentifier = identifier.substring(1, identifier.length() - 1);
|
||||
|
||||
//defensive coding
|
||||
if(permissionIdentifier == null || permissionIdentifier.isEmpty()) continue;
|
||||
|
||||
//check permission
|
||||
if(player.hasPermission(permissionIdentifier) && this.playerIDToClaimPermissionMap.get(identifier) == level) return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
//break permission check
|
||||
public String allowBreak(Player player, Material material)
|
||||
{
|
||||
//if under siege, some blocks will be breakable
|
||||
if(this.siegeData != null || this.doorsOpen)
|
||||
{
|
||||
boolean breakable = false;
|
||||
|
||||
//search for block type in list of breakable blocks
|
||||
for(int i = 0; i < GriefPrevention.instance.config_siege_blocks.size(); i++)
|
||||
{
|
||||
Material breakableMaterial = GriefPrevention.instance.config_siege_blocks.get(i);
|
||||
if(breakableMaterial == material)
|
||||
{
|
||||
breakable = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
//custom error messages for siege mode
|
||||
if(!breakable)
|
||||
{
|
||||
return GriefPrevention.instance.dataStore.getMessage(Messages.NonSiegeMaterial);
|
||||
}
|
||||
else if(player.getUniqueId().equals(this.ownerID))
|
||||
{
|
||||
return GriefPrevention.instance.dataStore.getMessage(Messages.NoOwnerBuildUnderSiege);
|
||||
}
|
||||
else
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
//if not under siege, build rules apply
|
||||
return this.allowBuild(player, material);
|
||||
}
|
||||
|
||||
//access permission check
|
||||
public String allowAccess(Player player)
|
||||
{
|
||||
//following a siege where the defender lost, the claim will allow everyone access for a time
|
||||
if(this.doorsOpen) return null;
|
||||
|
||||
//admin claims need adminclaims permission only.
|
||||
if(this.isAdminClaim())
|
||||
{
|
||||
if(player.hasPermission("griefprevention.adminclaims")) return null;
|
||||
}
|
||||
|
||||
//claim owner and admins in ignoreclaims mode have access
|
||||
if(player.getUniqueId().equals(this.ownerID) || GriefPrevention.instance.dataStore.getPlayerData(player.getUniqueId()).ignoreClaims) return null;
|
||||
|
||||
//look for explicit individual access, inventory, or build permission
|
||||
if(this.hasExplicitPermission(player, ClaimPermission.Access)) return null;
|
||||
if(this.hasExplicitPermission(player, ClaimPermission.Inventory)) return null;
|
||||
if(this.hasExplicitPermission(player, ClaimPermission.Build)) return null;
|
||||
|
||||
//also check for public permission
|
||||
ClaimPermission permissionLevel = this.playerIDToClaimPermissionMap.get("public");
|
||||
if(ClaimPermission.Build == permissionLevel || ClaimPermission.Inventory == permissionLevel || ClaimPermission.Access == permissionLevel) return null;
|
||||
|
||||
//permission inheritance for subdivisions
|
||||
if(this.parent != null)
|
||||
{
|
||||
if (player.getUniqueId().equals(this.parent.ownerID))
|
||||
return null;
|
||||
if (!inheritNothing)
|
||||
return this.parent.allowAccess(player);
|
||||
}
|
||||
|
||||
//catch-all error message for all other cases
|
||||
String reason = GriefPrevention.instance.dataStore.getMessage(Messages.NoAccessPermission, this.getOwnerName());
|
||||
if(player.hasPermission("griefprevention.ignoreclaims"))
|
||||
reason += " " + GriefPrevention.instance.dataStore.getMessage(Messages.IgnoreClaimsAdvertisement);
|
||||
return reason;
|
||||
}
|
||||
|
||||
//inventory permission check
|
||||
public String allowContainers(Player player)
|
||||
{
|
||||
//if we don't know who's asking, always say no (i've been told some mods can make this happen somehow)
|
||||
if(player == null) return "";
|
||||
|
||||
//trying to access inventory in a claim may extend an existing siege to include this claim
|
||||
GriefPrevention.instance.dataStore.tryExtendSiege(player, this);
|
||||
|
||||
//if under siege, nobody accesses containers
|
||||
if(this.siegeData != null)
|
||||
{
|
||||
return GriefPrevention.instance.dataStore.getMessage(Messages.NoContainersSiege, siegeData.attacker.getName());
|
||||
}
|
||||
|
||||
//owner and administrators in ignoreclaims mode have access
|
||||
if(player.getUniqueId().equals(this.ownerID) || GriefPrevention.instance.dataStore.getPlayerData(player.getUniqueId()).ignoreClaims) return null;
|
||||
|
||||
//admin claims need adminclaims permission only.
|
||||
if(this.isAdminClaim())
|
||||
{
|
||||
if(player.hasPermission("griefprevention.adminclaims")) return null;
|
||||
}
|
||||
|
||||
//check for explicit individual container or build permission
|
||||
if(this.hasExplicitPermission(player, ClaimPermission.Inventory)) return null;
|
||||
if(this.hasExplicitPermission(player, ClaimPermission.Build)) return null;
|
||||
|
||||
//check for public container or build permission
|
||||
ClaimPermission permissionLevel = this.playerIDToClaimPermissionMap.get("public");
|
||||
if(ClaimPermission.Build == permissionLevel || ClaimPermission.Inventory == permissionLevel) return null;
|
||||
|
||||
//permission inheritance for subdivisions
|
||||
if(this.parent != null)
|
||||
{
|
||||
if (player.getUniqueId().equals(this.parent.ownerID))
|
||||
return null;
|
||||
if (!inheritNothing)
|
||||
return this.parent.allowContainers(player);
|
||||
}
|
||||
|
||||
//error message for all other cases
|
||||
String reason = GriefPrevention.instance.dataStore.getMessage(Messages.NoContainersPermission, this.getOwnerName());
|
||||
if(player.hasPermission("griefprevention.ignoreclaims"))
|
||||
reason += " " + GriefPrevention.instance.dataStore.getMessage(Messages.IgnoreClaimsAdvertisement);
|
||||
return reason;
|
||||
}
|
||||
|
||||
//grant permission check, relatively simple
|
||||
public String allowGrantPermission(Player player)
|
||||
{
|
||||
//if we don't know who's asking, always say no (i've been told some mods can make this happen somehow)
|
||||
if(player == null) return "";
|
||||
|
||||
//anyone who can modify the claim can do this
|
||||
if(this.allowEdit(player) == null) return null;
|
||||
|
||||
//anyone who's in the managers (/PermissionTrust) list can do this
|
||||
for(int i = 0; i < this.managers.size(); i++)
|
||||
{
|
||||
String managerID = this.managers.get(i);
|
||||
if(player.getUniqueId().toString().equals(managerID)) return null;
|
||||
|
||||
else if(managerID.startsWith("[") && managerID.endsWith("]"))
|
||||
{
|
||||
managerID = managerID.substring(1, managerID.length() - 1);
|
||||
if(managerID == null || managerID.isEmpty()) continue;
|
||||
if(player.hasPermission(managerID)) return null;
|
||||
}
|
||||
}
|
||||
|
||||
//permission inheritance for subdivisions
|
||||
if(this.parent != null)
|
||||
{
|
||||
if (player.getUniqueId().equals(this.parent.ownerID))
|
||||
return null;
|
||||
if (!inheritNothing)
|
||||
return this.parent.allowGrantPermission(player);
|
||||
}
|
||||
|
||||
//generic error message
|
||||
String reason = GriefPrevention.instance.dataStore.getMessage(Messages.NoPermissionTrust, this.getOwnerName());
|
||||
if(player.hasPermission("griefprevention.ignoreclaims"))
|
||||
reason += " " + GriefPrevention.instance.dataStore.getMessage(Messages.IgnoreClaimsAdvertisement);
|
||||
return reason;
|
||||
}
|
||||
|
||||
//grants a permission for a player or the public
|
||||
public void setPermission(String playerID, ClaimPermission permissionLevel)
|
||||
{
|
||||
this.playerIDToClaimPermissionMap.put(playerID.toLowerCase(), permissionLevel);
|
||||
}
|
||||
|
||||
//revokes a permission for a player or the public
|
||||
public void dropPermission(String playerID)
|
||||
{
|
||||
this.playerIDToClaimPermissionMap.remove(playerID.toLowerCase());
|
||||
|
||||
for(Claim child : this.children)
|
||||
{
|
||||
child.dropPermission(playerID);
|
||||
}
|
||||
}
|
||||
|
||||
//clears all permissions (except owner of course)
|
||||
public void clearPermissions()
|
||||
{
|
||||
this.playerIDToClaimPermissionMap.clear();
|
||||
this.managers.clear();
|
||||
|
||||
for(Claim child : this.children)
|
||||
{
|
||||
child.clearPermissions();
|
||||
}
|
||||
}
|
||||
|
||||
//gets ALL permissions
|
||||
//useful for making copies of permissions during a claim resize and listing all permissions in a claim
|
||||
public void getPermissions(ArrayList<String> builders, ArrayList<String> containers, ArrayList<String> accessors, ArrayList<String> managers)
|
||||
{
|
||||
//loop through all the entries in the hash map
|
||||
Iterator<Map.Entry<String, ClaimPermission>> mappingsIterator = this.playerIDToClaimPermissionMap.entrySet().iterator();
|
||||
while(mappingsIterator.hasNext())
|
||||
{
|
||||
Map.Entry<String, ClaimPermission> entry = mappingsIterator.next();
|
||||
|
||||
//build up a list for each permission level
|
||||
if(entry.getValue() == ClaimPermission.Build)
|
||||
{
|
||||
builders.add(entry.getKey());
|
||||
}
|
||||
else if(entry.getValue() == ClaimPermission.Inventory)
|
||||
{
|
||||
containers.add(entry.getKey());
|
||||
}
|
||||
else
|
||||
{
|
||||
accessors.add(entry.getKey());
|
||||
}
|
||||
}
|
||||
|
||||
//managers are handled a little differently
|
||||
for(int i = 0; i < this.managers.size(); i++)
|
||||
{
|
||||
managers.add(this.managers.get(i));
|
||||
}
|
||||
}
|
||||
|
||||
//returns a copy of the location representing lower x, y, z limits
|
||||
public Location getLesserBoundaryCorner()
|
||||
{
|
||||
return this.lesserBoundaryCorner.clone();
|
||||
}
|
||||
|
||||
//returns a copy of the location representing upper x, y, z limits
|
||||
//NOTE: remember upper Y will always be ignored, all claims always extend to the sky
|
||||
public Location getGreaterBoundaryCorner()
|
||||
{
|
||||
return this.greaterBoundaryCorner.clone();
|
||||
}
|
||||
|
||||
//returns a friendly owner name (for admin claims, returns "an administrator" as the owner)
|
||||
public String getOwnerName()
|
||||
{
|
||||
if(this.parent != null)
|
||||
return this.parent.getOwnerName();
|
||||
|
||||
if(this.ownerID == null)
|
||||
return GriefPrevention.instance.dataStore.getMessage(Messages.OwnerNameForAdminClaims);
|
||||
|
||||
return GriefPrevention.lookupPlayerName(this.ownerID);
|
||||
}
|
||||
|
||||
//whether or not a location is in a claim
|
||||
//ignoreHeight = true means location UNDER the claim will return TRUE
|
||||
//excludeSubdivisions = true means that locations inside subdivisions of the claim will return FALSE
|
||||
public boolean contains(Location location, boolean ignoreHeight, boolean excludeSubdivisions)
|
||||
{
|
||||
//not in the same world implies false
|
||||
if(!location.getWorld().equals(this.lesserBoundaryCorner.getWorld())) return false;
|
||||
|
||||
double x = location.getX();
|
||||
double y = location.getY();
|
||||
double z = location.getZ();
|
||||
|
||||
//main check
|
||||
boolean inClaim = (ignoreHeight || y >= this.lesserBoundaryCorner.getY()) &&
|
||||
x >= this.lesserBoundaryCorner.getX() &&
|
||||
x < this.greaterBoundaryCorner.getX() + 1 &&
|
||||
z >= this.lesserBoundaryCorner.getZ() &&
|
||||
z < this.greaterBoundaryCorner.getZ() + 1;
|
||||
|
||||
if(!inClaim) return false;
|
||||
|
||||
//additional check for subdivisions
|
||||
//you're only in a subdivision when you're also in its parent claim
|
||||
//NOTE: if a player creates subdivions then resizes the parent claim, it's possible that
|
||||
//a subdivision can reach outside of its parent's boundaries. so this check is important!
|
||||
if(this.parent != null)
|
||||
{
|
||||
return this.parent.contains(location, ignoreHeight, false);
|
||||
}
|
||||
|
||||
//code to exclude subdivisions in this check
|
||||
else if(excludeSubdivisions)
|
||||
{
|
||||
//search all subdivisions to see if the location is in any of them
|
||||
for(int i = 0; i < this.children.size(); i++)
|
||||
{
|
||||
//if we find such a subdivision, return false
|
||||
if(this.children.get(i).contains(location, ignoreHeight, true))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//otherwise yes
|
||||
return true;
|
||||
}
|
||||
|
||||
//whether or not two claims overlap
|
||||
//used internally to prevent overlaps when creating claims
|
||||
boolean overlaps(Claim otherClaim)
|
||||
{
|
||||
//NOTE: if trying to understand this makes your head hurt, don't feel bad - it hurts mine too.
|
||||
//try drawing pictures to visualize test cases.
|
||||
|
||||
if(!this.lesserBoundaryCorner.getWorld().equals(otherClaim.getLesserBoundaryCorner().getWorld())) return false;
|
||||
|
||||
//first, check the corners of this claim aren't inside any existing claims
|
||||
if(otherClaim.contains(this.lesserBoundaryCorner, true, false)) return true;
|
||||
if(otherClaim.contains(this.greaterBoundaryCorner, true, false)) return true;
|
||||
if(otherClaim.contains(new Location(this.lesserBoundaryCorner.getWorld(), this.lesserBoundaryCorner.getBlockX(), 0, this.greaterBoundaryCorner.getBlockZ()), true, false)) return true;
|
||||
if(otherClaim.contains(new Location(this.lesserBoundaryCorner.getWorld(), this.greaterBoundaryCorner.getBlockX(), 0, this.lesserBoundaryCorner.getBlockZ()), true, false)) return true;
|
||||
|
||||
//verify that no claim's lesser boundary point is inside this new claim, to cover the "existing claim is entirely inside new claim" case
|
||||
if(this.contains(otherClaim.getLesserBoundaryCorner(), true, false)) return true;
|
||||
|
||||
//verify this claim doesn't band across an existing claim, either horizontally or vertically
|
||||
if( this.getLesserBoundaryCorner().getBlockZ() <= otherClaim.getGreaterBoundaryCorner().getBlockZ() &&
|
||||
this.getLesserBoundaryCorner().getBlockZ() >= otherClaim.getLesserBoundaryCorner().getBlockZ() &&
|
||||
this.getLesserBoundaryCorner().getBlockX() < otherClaim.getLesserBoundaryCorner().getBlockX() &&
|
||||
this.getGreaterBoundaryCorner().getBlockX() > otherClaim.getGreaterBoundaryCorner().getBlockX() )
|
||||
return true;
|
||||
|
||||
if( this.getGreaterBoundaryCorner().getBlockZ() <= otherClaim.getGreaterBoundaryCorner().getBlockZ() &&
|
||||
this.getGreaterBoundaryCorner().getBlockZ() >= otherClaim.getLesserBoundaryCorner().getBlockZ() &&
|
||||
this.getLesserBoundaryCorner().getBlockX() < otherClaim.getLesserBoundaryCorner().getBlockX() &&
|
||||
this.getGreaterBoundaryCorner().getBlockX() > otherClaim.getGreaterBoundaryCorner().getBlockX() )
|
||||
return true;
|
||||
|
||||
if( this.getLesserBoundaryCorner().getBlockX() <= otherClaim.getGreaterBoundaryCorner().getBlockX() &&
|
||||
this.getLesserBoundaryCorner().getBlockX() >= otherClaim.getLesserBoundaryCorner().getBlockX() &&
|
||||
this.getLesserBoundaryCorner().getBlockZ() < otherClaim.getLesserBoundaryCorner().getBlockZ() &&
|
||||
this.getGreaterBoundaryCorner().getBlockZ() > otherClaim.getGreaterBoundaryCorner().getBlockZ() )
|
||||
return true;
|
||||
|
||||
if( this.getGreaterBoundaryCorner().getBlockX() <= otherClaim.getGreaterBoundaryCorner().getBlockX() &&
|
||||
this.getGreaterBoundaryCorner().getBlockX() >= otherClaim.getLesserBoundaryCorner().getBlockX() &&
|
||||
this.getLesserBoundaryCorner().getBlockZ() < otherClaim.getLesserBoundaryCorner().getBlockZ() &&
|
||||
this.getGreaterBoundaryCorner().getBlockZ() > otherClaim.getGreaterBoundaryCorner().getBlockZ() )
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
//whether more entities may be added to a claim
|
||||
public String allowMoreEntities(boolean remove)
|
||||
{
|
||||
if(this.parent != null) return this.parent.allowMoreEntities(remove);
|
||||
|
||||
//this rule only applies to creative mode worlds
|
||||
if(!GriefPrevention.instance.creativeRulesApply(this.getLesserBoundaryCorner())) return null;
|
||||
|
||||
//admin claims aren't restricted
|
||||
if(this.isAdminClaim()) return null;
|
||||
|
||||
//don't apply this rule to very large claims
|
||||
if(this.getArea() > 10000) return null;
|
||||
|
||||
//determine maximum allowable entity count, based on claim size
|
||||
int maxEntities = this.getArea() / 50;
|
||||
if(maxEntities == 0) return GriefPrevention.instance.dataStore.getMessage(Messages.ClaimTooSmallForEntities);
|
||||
|
||||
//count current entities (ignoring players)
|
||||
int totalEntities = 0;
|
||||
ArrayList<Chunk> chunks = this.getChunks();
|
||||
for(Chunk chunk : chunks)
|
||||
{
|
||||
Entity [] entities = chunk.getEntities();
|
||||
for(int i = 0; i < entities.length; i++)
|
||||
{
|
||||
Entity entity = entities[i];
|
||||
if(!(entity instanceof Player) && this.contains(entity.getLocation(), false, false))
|
||||
{
|
||||
totalEntities++;
|
||||
if(remove && totalEntities > maxEntities) entity.remove();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(totalEntities >= maxEntities) return GriefPrevention.instance.dataStore.getMessage(Messages.TooManyEntitiesInClaim);
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public String allowMoreActiveBlocks()
|
||||
{
|
||||
if(this.parent != null) return this.parent.allowMoreActiveBlocks();
|
||||
|
||||
//determine maximum allowable entity count, based on claim size
|
||||
int maxActives = this.getArea() / 100;
|
||||
if(maxActives == 0) return GriefPrevention.instance.dataStore.getMessage(Messages.ClaimTooSmallForActiveBlocks);
|
||||
|
||||
//count current actives
|
||||
int totalActives = 0;
|
||||
ArrayList<Chunk> chunks = this.getChunks();
|
||||
for(Chunk chunk : chunks)
|
||||
{
|
||||
BlockState [] actives = chunk.getTileEntities();
|
||||
for(int i = 0; i < actives.length; i++)
|
||||
{
|
||||
BlockState active = actives[i];
|
||||
if(BlockEventHandler.isActiveBlock(active))
|
||||
{
|
||||
if(this.contains(active.getLocation(), false, false))
|
||||
{
|
||||
totalActives++;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(totalActives >= maxActives) return GriefPrevention.instance.dataStore.getMessage(Messages.TooManyActiveBlocksInClaim);
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
//implements a strict ordering of claims, used to keep the claims collection sorted for faster searching
|
||||
boolean greaterThan(Claim otherClaim)
|
||||
{
|
||||
Location thisCorner = this.getLesserBoundaryCorner();
|
||||
Location otherCorner = otherClaim.getLesserBoundaryCorner();
|
||||
|
||||
if(thisCorner.getBlockX() > otherCorner.getBlockX()) return true;
|
||||
|
||||
if(thisCorner.getBlockX() < otherCorner.getBlockX()) return false;
|
||||
|
||||
if(thisCorner.getBlockZ() > otherCorner.getBlockZ()) return true;
|
||||
|
||||
if(thisCorner.getBlockZ() < otherCorner.getBlockZ()) return false;
|
||||
|
||||
return thisCorner.getWorld().getName().compareTo(otherCorner.getWorld().getName()) < 0;
|
||||
}
|
||||
|
||||
@SuppressWarnings("deprecation")
|
||||
long getPlayerInvestmentScore()
|
||||
{
|
||||
//decide which blocks will be considered player placed
|
||||
Location lesserBoundaryCorner = this.getLesserBoundaryCorner();
|
||||
ArrayList<Material> playerBlocks = RestoreNatureProcessingTask.getPlayerBlocks(lesserBoundaryCorner.getWorld().getEnvironment(), lesserBoundaryCorner.getBlock().getBiome());
|
||||
|
||||
//scan the claim for player placed blocks
|
||||
double score = 0;
|
||||
|
||||
boolean creativeMode = GriefPrevention.instance.creativeRulesApply(lesserBoundaryCorner);
|
||||
|
||||
for(int x = this.lesserBoundaryCorner.getBlockX(); x <= this.greaterBoundaryCorner.getBlockX(); x++)
|
||||
{
|
||||
for(int z = this.lesserBoundaryCorner.getBlockZ(); z <= this.greaterBoundaryCorner.getBlockZ(); z++)
|
||||
{
|
||||
int y = this.lesserBoundaryCorner.getBlockY();
|
||||
for(; y < GriefPrevention.instance.getSeaLevel(this.lesserBoundaryCorner.getWorld()) - 5; y++)
|
||||
{
|
||||
Block block = this.lesserBoundaryCorner.getWorld().getBlockAt(x, y, z);
|
||||
if(playerBlocks.contains(block.getType()))
|
||||
{
|
||||
if(block.getType() == Material.CHEST && !creativeMode)
|
||||
{
|
||||
score += 10;
|
||||
}
|
||||
else
|
||||
{
|
||||
score += .5;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for(; y < this.lesserBoundaryCorner.getWorld().getMaxHeight(); y++)
|
||||
{
|
||||
Block block = this.lesserBoundaryCorner.getWorld().getBlockAt(x, y, z);
|
||||
if(playerBlocks.contains(block.getType()))
|
||||
{
|
||||
if(block.getType() == Material.CHEST && !creativeMode)
|
||||
{
|
||||
score += 10;
|
||||
}
|
||||
else if(creativeMode && (block.getType() == Material.LAVA || block.getType() == Material.STATIONARY_LAVA))
|
||||
{
|
||||
score -= 10;
|
||||
}
|
||||
else
|
||||
{
|
||||
score += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return (long)score;
|
||||
}
|
||||
|
||||
public ArrayList<Chunk> getChunks()
|
||||
{
|
||||
ArrayList<Chunk> chunks = new ArrayList<Chunk>();
|
||||
|
||||
World world = this.getLesserBoundaryCorner().getWorld();
|
||||
Chunk lesserChunk = this.getLesserBoundaryCorner().getChunk();
|
||||
Chunk greaterChunk = this.getGreaterBoundaryCorner().getChunk();
|
||||
|
||||
for(int x = lesserChunk.getX(); x <= greaterChunk.getX(); x++)
|
||||
{
|
||||
for(int z = lesserChunk.getZ(); z <= greaterChunk.getZ(); z++)
|
||||
{
|
||||
chunks.add(world.getChunkAt(x, z));
|
||||
}
|
||||
}
|
||||
|
||||
return chunks;
|
||||
}
|
||||
|
||||
ArrayList<Long> getChunkHashes()
|
||||
{
|
||||
ArrayList<Long> hashes = new ArrayList<Long>();
|
||||
int smallX = this.getLesserBoundaryCorner().getBlockX() >> 4;
|
||||
int smallZ = this.getLesserBoundaryCorner().getBlockZ() >> 4;
|
||||
int largeX = this.getGreaterBoundaryCorner().getBlockX() >> 4;
|
||||
int largeZ = this.getGreaterBoundaryCorner().getBlockZ() >> 4;
|
||||
|
||||
for(int x = smallX; x <= largeX; x++)
|
||||
{
|
||||
for(int z = smallZ; z <= largeZ; z++)
|
||||
{
|
||||
hashes.add(DataStore.getChunkHash(x, z));
|
||||
}
|
||||
}
|
||||
|
||||
return hashes;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
/*
|
||||
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;
|
||||
|
||||
//basic enum stuff
|
||||
public enum ClaimPermission
|
||||
{
|
||||
Build,
|
||||
Inventory,
|
||||
Access
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
package me.ryanhamshire.GriefPrevention;
|
||||
|
||||
public enum ClaimsMode
|
||||
{
|
||||
Survival,
|
||||
Creative,
|
||||
Disabled,
|
||||
SurvivalRequiringClaims
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
/*
|
||||
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;
|
||||
import org.bukkit.OfflinePlayer;
|
||||
|
||||
//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);
|
||||
OfflinePlayer ownerInfo = Bukkit.getServer().getOfflinePlayer(claim.ownerID);
|
||||
|
||||
//expiration code uses last logout timestamp to decide whether to expire claims
|
||||
//don't expire claims for online players
|
||||
if(ownerInfo.isOnline()) return;
|
||||
if(ownerInfo.getLastPlayed() <= 0) return;
|
||||
|
||||
GriefPrevention.AddLogEntry("Looking for expired claims. Checking data for " + claim.ownerID.toString(), CustomLogEntryTypes.Debug, true);
|
||||
|
||||
//skip claims belonging to exempted players based on block totals in config
|
||||
int bonusBlocks = ownerData.getBonusClaimBlocks();
|
||||
if(bonusBlocks >= GriefPrevention.instance.config_claims_expirationExemptionBonusBlocks || bonusBlocks + ownerData.getAccruedClaimBlocks() >= GriefPrevention.instance.config_claims_expirationExemptionTotalBlocks)
|
||||
{
|
||||
GriefPrevention.AddLogEntry("Player exempt from claim expiration based on claim block counts vs. config file settings.", CustomLogEntryTypes.Debug, true);
|
||||
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, ownerInfo), 1L);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,137 @@
|
||||
/*
|
||||
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.Calendar;
|
||||
import java.util.Date;
|
||||
import java.util.Vector;
|
||||
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.OfflinePlayer;
|
||||
|
||||
import me.ryanhamshire.GriefPrevention.events.ClaimExpirationEvent;
|
||||
|
||||
class CleanupUnusedClaimTask implements Runnable
|
||||
{
|
||||
Claim claim;
|
||||
PlayerData ownerData;
|
||||
OfflinePlayer ownerInfo;
|
||||
|
||||
CleanupUnusedClaimTask(Claim claim, PlayerData ownerData, OfflinePlayer ownerInfo)
|
||||
{
|
||||
this.claim = claim;
|
||||
this.ownerData = ownerData;
|
||||
this.ownerInfo = ownerInfo;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run()
|
||||
{
|
||||
//see if any other plugins don't want this claim deleted
|
||||
ClaimExpirationEvent event = new ClaimExpirationEvent(this.claim);
|
||||
Bukkit.getPluginManager().callEvent(event);
|
||||
if(event.isCancelled()) return;
|
||||
|
||||
//determine area of the default chest claim
|
||||
int areaOfDefaultClaim = 0;
|
||||
if(GriefPrevention.instance.config_claims_automaticClaimsForNewPlayersRadius >= 0)
|
||||
{
|
||||
areaOfDefaultClaim = (int)Math.pow(GriefPrevention.instance.config_claims_automaticClaimsForNewPlayersRadius * 2 + 1, 2);
|
||||
}
|
||||
|
||||
//if this claim is a chest claim and those are set to expire
|
||||
if(claim.getArea() <= areaOfDefaultClaim && GriefPrevention.instance.config_claims_chestClaimExpirationDays > 0)
|
||||
{
|
||||
//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(new Date(ownerInfo.getLastPlayed()));
|
||||
if(newPlayerClaimsExpired && ownerData.getClaims().size() == 1)
|
||||
{
|
||||
claim.removeSurfaceFluids(null);
|
||||
GriefPrevention.instance.dataStore.deleteClaim(claim, true, true);
|
||||
|
||||
//if configured to do so, restore the land to natural
|
||||
if(GriefPrevention.instance.creativeRulesApply(claim.getLesserBoundaryCorner()) || GriefPrevention.instance.config_claims_survivalAutoNatureRestoration)
|
||||
{
|
||||
GriefPrevention.instance.restoreClaim(claim, 0);
|
||||
}
|
||||
|
||||
GriefPrevention.AddLogEntry(" " + claim.getOwnerName() + "'s new player claim expired.", CustomLogEntryTypes.AdminActivity);
|
||||
}
|
||||
}
|
||||
|
||||
//if configured to always remove claims after some inactivity period without exceptions...
|
||||
else if(GriefPrevention.instance.config_claims_expirationDays > 0)
|
||||
{
|
||||
Calendar earliestPermissibleLastLogin = Calendar.getInstance();
|
||||
earliestPermissibleLastLogin.add(Calendar.DATE, -GriefPrevention.instance.config_claims_expirationDays);
|
||||
|
||||
if(earliestPermissibleLastLogin.getTime().after(new Date(ownerInfo.getLastPlayed())))
|
||||
{
|
||||
//make a copy of this player's claim list
|
||||
Vector<Claim> claims = new Vector<Claim>();
|
||||
for(int i = 0; i < ownerData.getClaims().size(); i++)
|
||||
{
|
||||
claims.add(ownerData.getClaims().get(i));
|
||||
}
|
||||
|
||||
//delete them
|
||||
GriefPrevention.instance.dataStore.deleteClaimsForPlayer(claim.ownerID, true);
|
||||
GriefPrevention.AddLogEntry(" All of " + claim.getOwnerName() + "'s claims have expired.", CustomLogEntryTypes.AdminActivity);
|
||||
|
||||
for(int i = 0; i < claims.size(); i++)
|
||||
{
|
||||
//if configured to do so, restore the land to natural
|
||||
if(GriefPrevention.instance.creativeRulesApply(claims.get(i).getLesserBoundaryCorner()) || GriefPrevention.instance.config_claims_survivalAutoNatureRestoration)
|
||||
{
|
||||
GriefPrevention.instance.restoreClaim(claims.get(i), 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
else if(GriefPrevention.instance.config_claims_unusedClaimExpirationDays > 0 && GriefPrevention.instance.creativeRulesApply(claim.getLesserBoundaryCorner()))
|
||||
{
|
||||
//avoid scanning large claims and administrative claims
|
||||
if(claim.isAdminClaim() || claim.getWidth() > 25 || claim.getHeight() > 25) return;
|
||||
|
||||
//otherwise scan the claim content
|
||||
int minInvestment = 400;
|
||||
|
||||
long investmentScore = claim.getPlayerInvestmentScore();
|
||||
|
||||
if(investmentScore < minInvestment)
|
||||
{
|
||||
//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(new Date(ownerInfo.getLastPlayed()));
|
||||
if(claimExpired)
|
||||
{
|
||||
GriefPrevention.instance.dataStore.deleteClaim(claim, true, true);
|
||||
GriefPrevention.AddLogEntry("Removed " + claim.getOwnerName() + "'s unused claim @ " + GriefPrevention.getfriendlyLocationString(claim.getLesserBoundaryCorner()), CustomLogEntryTypes.AdminActivity);
|
||||
|
||||
//restore the claim area to natural state
|
||||
GriefPrevention.instance.restoreClaim(claim, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
package me.ryanhamshire.GriefPrevention;
|
||||
|
||||
enum CommandCategory
|
||||
{
|
||||
Chat,
|
||||
Whisper,
|
||||
None
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
/*
|
||||
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;
|
||||
|
||||
public class CreateClaimResult
|
||||
{
|
||||
//whether or not the creation succeeded (it would fail if the new claim overlapped another existing claim)
|
||||
public boolean succeeded;
|
||||
|
||||
//when succeeded, this is a reference to the new claim
|
||||
//when failed, this is a reference to the pre-existing, conflicting claim
|
||||
public Claim claim;
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
/*
|
||||
GriefPrevention Server Plugin for Minecraft
|
||||
Copyright (C) 2015 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;
|
||||
|
||||
public enum CustomLogEntryTypes
|
||||
{
|
||||
SocialActivity,
|
||||
SuspiciousActivity,
|
||||
AdminActivity,
|
||||
Debug,
|
||||
Exception,
|
||||
MutedChat
|
||||
}
|
||||
@@ -0,0 +1,183 @@
|
||||
/*
|
||||
GriefPrevention Server Plugin for Minecraft
|
||||
Copyright (C) 2015 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.io.File;
|
||||
import java.nio.charset.Charset;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.Calendar;
|
||||
import java.util.Date;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import org.bukkit.scheduler.BukkitScheduler;
|
||||
|
||||
import com.google.common.io.Files;
|
||||
|
||||
class CustomLogger
|
||||
{
|
||||
private final SimpleDateFormat timestampFormat = new SimpleDateFormat("HH:mm");
|
||||
private final SimpleDateFormat filenameFormat = new SimpleDateFormat("yyyy_MM_dd");
|
||||
private final String logFolderPath = DataStore.dataLayerFolderPath + File.separator + "Logs";
|
||||
private final int secondsBetweenWrites = 300;
|
||||
|
||||
//stringbuilder is not thread safe, stringbuffer is
|
||||
private StringBuffer queuedEntries = new StringBuffer();
|
||||
|
||||
CustomLogger()
|
||||
{
|
||||
//ensure log folder exists
|
||||
File logFolder = new File(this.logFolderPath);
|
||||
logFolder.mkdirs();
|
||||
|
||||
//delete any outdated log files immediately
|
||||
this.DeleteExpiredLogs();
|
||||
|
||||
//unless disabled, schedule recurring tasks
|
||||
int daysToKeepLogs = GriefPrevention.instance.config_logs_daysToKeep;
|
||||
if(daysToKeepLogs > 0)
|
||||
{
|
||||
BukkitScheduler scheduler = GriefPrevention.instance.getServer().getScheduler();
|
||||
final long ticksPerSecond = 20L;
|
||||
final long ticksPerDay = ticksPerSecond * 60 * 60 * 24;
|
||||
scheduler.runTaskTimerAsynchronously(GriefPrevention.instance, new EntryWriter(), this.secondsBetweenWrites * ticksPerSecond, this.secondsBetweenWrites * ticksPerSecond);
|
||||
scheduler.runTaskTimerAsynchronously(GriefPrevention.instance, new ExpiredLogRemover(), ticksPerDay, ticksPerDay);
|
||||
}
|
||||
}
|
||||
|
||||
private static final Pattern inlineFormatterPattern = Pattern.compile("§.");
|
||||
void AddEntry(String entry, CustomLogEntryTypes entryType)
|
||||
{
|
||||
//if disabled, do nothing
|
||||
int daysToKeepLogs = GriefPrevention.instance.config_logs_daysToKeep;
|
||||
if(daysToKeepLogs == 0) return;
|
||||
|
||||
//if entry type is not enabled, do nothing
|
||||
if(!this.isEnabledType(entryType)) return;
|
||||
|
||||
//otherwise write to the in-memory buffer, after removing formatters
|
||||
Matcher matcher = inlineFormatterPattern.matcher(entry);
|
||||
entry = matcher.replaceAll("");
|
||||
String timestamp = this.timestampFormat.format(new Date());
|
||||
this.queuedEntries.append(timestamp + " " + entry + "\n");
|
||||
}
|
||||
|
||||
private boolean isEnabledType(CustomLogEntryTypes entryType)
|
||||
{
|
||||
if(entryType == CustomLogEntryTypes.Exception) return true;
|
||||
if(entryType == CustomLogEntryTypes.SocialActivity && !GriefPrevention.instance.config_logs_socialEnabled) return false;
|
||||
if(entryType == CustomLogEntryTypes.SuspiciousActivity && !GriefPrevention.instance.config_logs_suspiciousEnabled) return false;
|
||||
if(entryType == CustomLogEntryTypes.AdminActivity && !GriefPrevention.instance.config_logs_adminEnabled) return false;
|
||||
if(entryType == CustomLogEntryTypes.Debug && !GriefPrevention.instance.config_logs_debugEnabled) return false;
|
||||
if(entryType == CustomLogEntryTypes.MutedChat && !GriefPrevention.instance.config_logs_mutedChatEnabled) return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void WriteEntries()
|
||||
{
|
||||
try
|
||||
{
|
||||
//if nothing to write, stop here
|
||||
if(this.queuedEntries.length() == 0) return;
|
||||
|
||||
//determine filename based on date
|
||||
String filename = this.filenameFormat.format(new Date()) + ".log";
|
||||
String filepath = this.logFolderPath + File.separator + filename;
|
||||
File logFile = new File(filepath);
|
||||
|
||||
//dump content
|
||||
Files.append(this.queuedEntries.toString(), logFile, Charset.forName("UTF-8"));
|
||||
|
||||
//in case of a failure to write the above due to exception,
|
||||
//the unwritten entries will remain the buffer for the next write to retry
|
||||
this.queuedEntries.setLength(0);
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
private void DeleteExpiredLogs()
|
||||
{
|
||||
try
|
||||
{
|
||||
//get list of log files
|
||||
File logFolder = new File(this.logFolderPath);
|
||||
File [] files = logFolder.listFiles();
|
||||
|
||||
//delete any created before x days ago
|
||||
int daysToKeepLogs = GriefPrevention.instance.config_logs_daysToKeep;
|
||||
Calendar expirationBoundary = Calendar.getInstance();
|
||||
expirationBoundary.add(Calendar.DATE, -daysToKeepLogs);
|
||||
for(int i = 0; i < files.length; i++)
|
||||
{
|
||||
File file = files[i];
|
||||
if(file.isDirectory()) continue; //skip any folders
|
||||
|
||||
String filename = file.getName().replace(".log", "");
|
||||
String [] dateParts = filename.split("_"); //format is yyyy_MM_dd
|
||||
if(dateParts.length != 3) continue;
|
||||
|
||||
try
|
||||
{
|
||||
int year = Integer.parseInt(dateParts[0]);
|
||||
int month = Integer.parseInt(dateParts[1]) - 1;
|
||||
int day = Integer.parseInt(dateParts[2]);
|
||||
|
||||
Calendar filedate = Calendar.getInstance();
|
||||
filedate.set(year, month, day);
|
||||
if(filedate.before(expirationBoundary))
|
||||
{
|
||||
file.delete();
|
||||
}
|
||||
}
|
||||
catch(NumberFormatException e)
|
||||
{
|
||||
//throw this away - effectively ignoring any files without the correct filename format
|
||||
GriefPrevention.AddLogEntry("Ignoring an unexpected file in the abridged logs folder: " + file.getName(), CustomLogEntryTypes.Debug, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
//transfers the internal buffer to a log file
|
||||
private class EntryWriter implements Runnable
|
||||
{
|
||||
@Override
|
||||
public void run()
|
||||
{
|
||||
WriteEntries();
|
||||
}
|
||||
}
|
||||
|
||||
private class ExpiredLogRemover implements Runnable
|
||||
{
|
||||
@Override
|
||||
public void run()
|
||||
{
|
||||
DeleteExpiredLogs();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
/*
|
||||
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;
|
||||
|
||||
public class CustomizableMessage
|
||||
{
|
||||
public Messages id;
|
||||
public String text;
|
||||
public String notes;
|
||||
|
||||
public CustomizableMessage(Messages id, String text, String notes)
|
||||
{
|
||||
this.id = id;
|
||||
this.text = text;
|
||||
this.notes = notes;
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,737 @@
|
||||
/*
|
||||
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.io.PrintWriter;
|
||||
import java.io.StringWriter;
|
||||
import java.sql.*;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.*;
|
||||
import java.util.Date;
|
||||
|
||||
import org.bukkit.*;
|
||||
|
||||
//manages data stored in the file system
|
||||
public class DatabaseDataStore extends DataStore
|
||||
{
|
||||
private Connection databaseConnection = null;
|
||||
|
||||
private String databaseUrl;
|
||||
private String userName;
|
||||
private String password;
|
||||
|
||||
private String updateNameSQL;
|
||||
private String insertClaimSQL;
|
||||
private String deleteClaimSQL;
|
||||
private String getPlayerDataSQL;
|
||||
private String deletePlayerDataSQL;
|
||||
private String insertPlayerDataSQL;
|
||||
private String insertNextClaimIdSQL;
|
||||
private String deleteGroupBonusSQL;
|
||||
private String insertSchemaVerSQL;
|
||||
private String deleteNextClaimIdSQL;
|
||||
private String deleteSchemaVersionSQL;
|
||||
private String selectSchemaVersionSQL;
|
||||
|
||||
DatabaseDataStore(String url, String userName, String password) throws Exception
|
||||
{
|
||||
this.databaseUrl = url;
|
||||
this.userName = userName;
|
||||
this.password = password;
|
||||
|
||||
this.initialize();
|
||||
}
|
||||
|
||||
@Override
|
||||
void initialize() throws Exception
|
||||
{
|
||||
try
|
||||
{
|
||||
//load the java driver for mySQL
|
||||
Class.forName("com.mysql.jdbc.Driver");
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
GriefPrevention.AddLogEntry("ERROR: Unable to load Java's mySQL database driver. Check to make sure you've installed it properly.");
|
||||
throw e;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
this.refreshDataConnection();
|
||||
}
|
||||
catch(Exception e2)
|
||||
{
|
||||
GriefPrevention.AddLogEntry("ERROR: Unable to connect to database. Check your config file settings.");
|
||||
throw e2;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
//ensure the data tables exist
|
||||
Statement statement = databaseConnection.createStatement();
|
||||
|
||||
statement.execute("CREATE TABLE IF NOT EXISTS griefprevention_nextclaimid (nextid INT(15));");
|
||||
|
||||
statement.execute("CREATE TABLE IF NOT EXISTS griefprevention_claimdata (id INT(15), owner VARCHAR(50), lessercorner VARCHAR(100), greatercorner VARCHAR(100), builders TEXT, containers TEXT, accessors TEXT, managers TEXT, inheritnothing BOOLEAN, parentid INT(15));");
|
||||
|
||||
statement.execute("CREATE TABLE IF NOT EXISTS griefprevention_playerdata (name VARCHAR(50), lastlogin DATETIME, accruedblocks INT(15), bonusblocks INT(15));");
|
||||
|
||||
statement.execute("CREATE TABLE IF NOT EXISTS griefprevention_schemaversion (version INT(15));");
|
||||
|
||||
statement.execute("ALTER TABLE griefprevention_claimdata MODIFY builders TEXT;");
|
||||
statement.execute("ALTER TABLE griefprevention_claimdata MODIFY containers TEXT;");
|
||||
statement.execute("ALTER TABLE griefprevention_claimdata MODIFY accessors TEXT;");
|
||||
statement.execute("ALTER TABLE griefprevention_claimdata MODIFY managers TEXT;");
|
||||
|
||||
//if the next claim id table is empty, this is a brand new database which will write using the latest schema
|
||||
//otherwise, schema version is determined by schemaversion table (or =0 if table is empty, see getSchemaVersion())
|
||||
ResultSet results = statement.executeQuery("SELECT * FROM griefprevention_nextclaimid;");
|
||||
if(!results.next())
|
||||
{
|
||||
this.setSchemaVersion(latestSchemaVersion);
|
||||
}
|
||||
}
|
||||
catch(Exception e3)
|
||||
{
|
||||
GriefPrevention.AddLogEntry("ERROR: Unable to create the necessary database table. Details:");
|
||||
GriefPrevention.AddLogEntry(e3.getMessage());
|
||||
e3.printStackTrace();
|
||||
throw e3;
|
||||
}
|
||||
|
||||
this.updateNameSQL = "UPDATE griefprevention_playerdata SET name = ? WHERE name = ?;";
|
||||
this.insertClaimSQL = "INSERT INTO griefprevention_claimdata (id, owner, lessercorner, greatercorner, builders, containers, accessors, managers, inheritnothing, parentid) VALUES(?,?,?,?,?,?,?,?,?,?);";
|
||||
this.deleteClaimSQL = "DELETE FROM griefprevention_claimdata WHERE id=?;";
|
||||
this.getPlayerDataSQL = "SELECT * FROM griefprevention_playerdata WHERE name=?;";
|
||||
this.deletePlayerDataSQL = "DELETE FROM griefprevention_playerdata WHERE name=?;";
|
||||
this.insertPlayerDataSQL = "INSERT INTO griefprevention_playerdata (name, lastlogin, accruedblocks, bonusblocks) VALUES (?,?,?,?);";
|
||||
this.insertNextClaimIdSQL = "INSERT INTO griefprevention_nextclaimid VALUES (?);";
|
||||
this.deleteGroupBonusSQL = "DELETE FROM griefprevention_playerdata WHERE name=?;";
|
||||
this.insertSchemaVerSQL = "INSERT INTO griefprevention_schemaversion VALUES (?)";
|
||||
this.deleteNextClaimIdSQL = "DELETE FROM griefprevention_nextclaimid;";
|
||||
this.deleteSchemaVersionSQL = "DELETE FROM griefprevention_schemaversion;";
|
||||
this.selectSchemaVersionSQL = "SELECT * FROM griefprevention_schemaversion;";
|
||||
|
||||
//load group data into memory
|
||||
Statement statement = databaseConnection.createStatement();
|
||||
ResultSet results = statement.executeQuery("SELECT * FROM griefprevention_playerdata;");
|
||||
|
||||
while(results.next())
|
||||
{
|
||||
String name = results.getString("name");
|
||||
|
||||
//ignore non-groups. all group names start with a dollar sign.
|
||||
if(!name.startsWith("$")) continue;
|
||||
|
||||
String groupName = name.substring(1);
|
||||
if(groupName == null || groupName.isEmpty()) continue; //defensive coding, avoid unlikely cases
|
||||
|
||||
int groupBonusBlocks = results.getInt("bonusblocks");
|
||||
|
||||
this.permissionToBonusBlocksMap.put(groupName, groupBonusBlocks);
|
||||
}
|
||||
|
||||
//load next claim number into memory
|
||||
results = statement.executeQuery("SELECT * FROM griefprevention_nextclaimid;");
|
||||
|
||||
//if there's nothing yet, add it
|
||||
if(!results.next())
|
||||
{
|
||||
statement.execute("INSERT INTO griefprevention_nextclaimid VALUES(0);");
|
||||
this.nextClaimID = (long)0;
|
||||
}
|
||||
|
||||
//otherwise load it
|
||||
else
|
||||
{
|
||||
this.nextClaimID = results.getLong("nextid");
|
||||
}
|
||||
|
||||
if(this.getSchemaVersion() == 0)
|
||||
{
|
||||
try
|
||||
{
|
||||
this.refreshDataConnection();
|
||||
|
||||
//pull ALL player data from the database
|
||||
statement = this.databaseConnection.createStatement();
|
||||
results = statement.executeQuery("SELECT * FROM griefprevention_playerdata;");
|
||||
|
||||
//make a list of changes to be made
|
||||
HashMap<String, UUID> changes = new HashMap<String, UUID>();
|
||||
|
||||
ArrayList<String> namesToConvert = new ArrayList<String>();
|
||||
while(results.next())
|
||||
{
|
||||
//get the id
|
||||
String playerName = results.getString("name");
|
||||
|
||||
//add to list of names to convert to UUID
|
||||
namesToConvert.add(playerName);
|
||||
}
|
||||
|
||||
//resolve and cache as many as possible through various means
|
||||
try
|
||||
{
|
||||
UUIDFetcher fetcher = new UUIDFetcher(namesToConvert);
|
||||
fetcher.call();
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
GriefPrevention.AddLogEntry("Failed to resolve a batch of names to UUIDs. Details:" + e.getMessage());
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
//reset results cursor
|
||||
results.beforeFirst();
|
||||
|
||||
//for each result
|
||||
while(results.next())
|
||||
{
|
||||
//get the id
|
||||
String playerName = results.getString("name");
|
||||
|
||||
//try to convert player name to UUID
|
||||
try
|
||||
{
|
||||
UUID playerID = UUIDFetcher.getUUIDOf(playerName);
|
||||
|
||||
//if successful, update the playerdata row by replacing the player's name with the player's UUID
|
||||
if(playerID != null)
|
||||
{
|
||||
changes.put(playerName, playerID);
|
||||
}
|
||||
}
|
||||
//otherwise leave it as-is. no harm done - it won't be requested by name, and this update only happens once.
|
||||
catch(Exception ex){ }
|
||||
}
|
||||
|
||||
//refresh data connection in case data migration took a long time
|
||||
this.refreshDataConnection();
|
||||
|
||||
for(String name : changes.keySet())
|
||||
{
|
||||
try (PreparedStatement updateStmnt = this.databaseConnection.prepareStatement(this.getUpdateNameSQL())) {
|
||||
updateStmnt.setString(1, changes.get(name).toString());
|
||||
updateStmnt.setString(2, name);
|
||||
updateStmnt.executeUpdate();
|
||||
}
|
||||
catch(SQLException e)
|
||||
{
|
||||
GriefPrevention.AddLogEntry("Unable to convert player data for " + name + ". Skipping.");
|
||||
GriefPrevention.AddLogEntry(e.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
catch(SQLException e)
|
||||
{
|
||||
GriefPrevention.AddLogEntry("Unable to convert player data. Details:");
|
||||
GriefPrevention.AddLogEntry(e.getMessage());
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
if(this.getSchemaVersion() <= 2)
|
||||
{
|
||||
statement = this.databaseConnection.createStatement();
|
||||
statement.execute("ALTER TABLE griefprevention_claimdata ADD inheritNothing BOOLEAN DEFAULT 0 AFTER managers;");
|
||||
}
|
||||
|
||||
//load claims data into memory
|
||||
|
||||
results = statement.executeQuery("SELECT * FROM griefprevention_claimdata;");
|
||||
|
||||
ArrayList<Claim> claimsToRemove = new ArrayList<Claim>();
|
||||
ArrayList<Claim> subdivisionsToLoad = new ArrayList<Claim>();
|
||||
List<World> validWorlds = Bukkit.getServer().getWorlds();
|
||||
|
||||
Long claimID = null;
|
||||
while(results.next())
|
||||
{
|
||||
try
|
||||
{
|
||||
//problematic claims will be removed from secondary storage, and never added to in-memory data store
|
||||
boolean removeClaim = false;
|
||||
|
||||
long parentId = results.getLong("parentid");
|
||||
claimID = results.getLong("id");
|
||||
boolean inheritNothing = results.getBoolean("inheritNothing");
|
||||
Location lesserBoundaryCorner = null;
|
||||
Location greaterBoundaryCorner = null;
|
||||
String lesserCornerString = "(location not available)";
|
||||
try
|
||||
{
|
||||
lesserCornerString = results.getString("lessercorner");
|
||||
lesserBoundaryCorner = this.locationFromString(lesserCornerString, validWorlds);
|
||||
String greaterCornerString = results.getString("greatercorner");
|
||||
greaterBoundaryCorner = this.locationFromString(greaterCornerString, validWorlds);
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
if(e.getMessage() != null && e.getMessage().contains("World not found"))
|
||||
{
|
||||
GriefPrevention.AddLogEntry("Failed to load a claim (ID:" + claimID.toString() + ") because its world isn't loaded (yet?). Please delete the claim or contact the GriefPrevention developer with information about which plugin(s) you're using to load or create worlds. " + lesserCornerString);
|
||||
continue;
|
||||
}
|
||||
else
|
||||
{
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
String ownerName = results.getString("owner");
|
||||
UUID ownerID = null;
|
||||
if(ownerName.isEmpty() || ownerName.startsWith("--"))
|
||||
{
|
||||
ownerID = null; //administrative land claim or subdivision
|
||||
}
|
||||
else if(this.getSchemaVersion() < 1)
|
||||
{
|
||||
try
|
||||
{
|
||||
ownerID = UUIDFetcher.getUUIDOf(ownerName);
|
||||
}
|
||||
catch(Exception ex)
|
||||
{
|
||||
GriefPrevention.AddLogEntry("This owner name did not convert to a UUID: " + ownerName + ".");
|
||||
GriefPrevention.AddLogEntry(" Converted land claim to administrative @ " + lesserBoundaryCorner.toString());
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
try
|
||||
{
|
||||
ownerID = UUID.fromString(ownerName);
|
||||
}
|
||||
catch(Exception ex)
|
||||
{
|
||||
GriefPrevention.AddLogEntry("This owner entry is not a UUID: " + ownerName + ".");
|
||||
GriefPrevention.AddLogEntry(" Converted land claim to administrative @ " + lesserBoundaryCorner.toString());
|
||||
}
|
||||
}
|
||||
|
||||
String buildersString = results.getString("builders");
|
||||
List<String> builderNames = Arrays.asList(buildersString.split(";"));
|
||||
builderNames = this.convertNameListToUUIDList(builderNames);
|
||||
|
||||
String containersString = results.getString("containers");
|
||||
List<String> containerNames = Arrays.asList(containersString.split(";"));
|
||||
containerNames = this.convertNameListToUUIDList(containerNames);
|
||||
|
||||
String accessorsString = results.getString("accessors");
|
||||
List<String> accessorNames = Arrays.asList(accessorsString.split(";"));
|
||||
accessorNames = this.convertNameListToUUIDList(accessorNames);
|
||||
|
||||
String managersString = results.getString("managers");
|
||||
List<String> managerNames = Arrays.asList(managersString.split(";"));
|
||||
managerNames = this.convertNameListToUUIDList(managerNames);
|
||||
Claim claim = new Claim(lesserBoundaryCorner, greaterBoundaryCorner, ownerID, builderNames, containerNames, accessorNames, managerNames, inheritNothing, claimID);
|
||||
|
||||
if(removeClaim)
|
||||
{
|
||||
claimsToRemove.add(claim);
|
||||
}
|
||||
else if(parentId == -1)
|
||||
{
|
||||
//top level claim
|
||||
this.addClaim(claim, false);
|
||||
}
|
||||
else
|
||||
{
|
||||
//subdivision
|
||||
subdivisionsToLoad.add(claim);
|
||||
}
|
||||
}
|
||||
catch(SQLException e)
|
||||
{
|
||||
GriefPrevention.AddLogEntry("Unable to load a claim. Details: " + e.getMessage() + " ... " + results.toString());
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
//add subdivisions to their parent claims
|
||||
for(Claim childClaim : subdivisionsToLoad)
|
||||
{
|
||||
//find top level claim parent
|
||||
Claim topLevelClaim = this.getClaimAt(childClaim.getLesserBoundaryCorner(), true, null);
|
||||
|
||||
if(topLevelClaim == null)
|
||||
{
|
||||
claimsToRemove.add(childClaim);
|
||||
GriefPrevention.AddLogEntry("Removing orphaned claim subdivision: " + childClaim.getLesserBoundaryCorner().toString());
|
||||
continue;
|
||||
}
|
||||
|
||||
//add this claim to the list of children of the current top level claim
|
||||
childClaim.parent = topLevelClaim;
|
||||
topLevelClaim.children.add(childClaim);
|
||||
childClaim.inDataStore = true;
|
||||
}
|
||||
|
||||
for(Claim claim : claimsToRemove)
|
||||
{
|
||||
this.deleteClaimFromSecondaryStorage(claim);
|
||||
}
|
||||
|
||||
if(this.getSchemaVersion() <= 2)
|
||||
{
|
||||
this.refreshDataConnection();
|
||||
statement = this.databaseConnection.createStatement();
|
||||
statement.execute("DELETE FROM griefprevention_claimdata WHERE id='-1';");
|
||||
}
|
||||
|
||||
super.initialize();
|
||||
}
|
||||
|
||||
@Override
|
||||
synchronized void writeClaimToStorage(Claim claim) //see datastore.cs. this will ALWAYS be a top level claim
|
||||
{
|
||||
try
|
||||
{
|
||||
this.refreshDataConnection();
|
||||
|
||||
//wipe out any existing data about this claim
|
||||
this.deleteClaimFromSecondaryStorage(claim);
|
||||
|
||||
//write claim data to the database
|
||||
this.writeClaimData(claim);
|
||||
}
|
||||
catch(SQLException e)
|
||||
{
|
||||
GriefPrevention.AddLogEntry("Unable to save data for claim at " + this.locationToString(claim.lesserBoundaryCorner) + ". Details:");
|
||||
GriefPrevention.AddLogEntry(e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
//actually writes claim data to the database
|
||||
synchronized private void writeClaimData(Claim claim) throws SQLException
|
||||
{
|
||||
String lesserCornerString = this.locationToString(claim.getLesserBoundaryCorner());
|
||||
String greaterCornerString = this.locationToString(claim.getGreaterBoundaryCorner());
|
||||
String owner = "";
|
||||
if(claim.ownerID != null) owner = claim.ownerID.toString();
|
||||
|
||||
ArrayList<String> builders = new ArrayList<String>();
|
||||
ArrayList<String> containers = new ArrayList<String>();
|
||||
ArrayList<String> accessors = new ArrayList<String>();
|
||||
ArrayList<String> managers = new ArrayList<String>();
|
||||
|
||||
claim.getPermissions(builders, containers, accessors, managers);
|
||||
|
||||
String buildersString = this.storageStringBuilder(builders);
|
||||
String containersString = this.storageStringBuilder(containers);
|
||||
String accessorsString = this.storageStringBuilder(accessors);
|
||||
String managersString = this.storageStringBuilder(managers);
|
||||
boolean inheritNothing = claim.getSubclaimRestrictions();
|
||||
long parentId = claim.parent == null ? -1 : claim.parent.id;
|
||||
|
||||
try (PreparedStatement insertStmt = this.databaseConnection.prepareStatement(this.getInsertClaimSQL())) {
|
||||
|
||||
insertStmt.setLong(1, claim.id);
|
||||
insertStmt.setString(2, owner);
|
||||
insertStmt.setString(3, lesserCornerString);
|
||||
insertStmt.setString(4, greaterCornerString);
|
||||
insertStmt.setString(5, buildersString);
|
||||
insertStmt.setString(6, containersString);
|
||||
insertStmt.setString(7, accessorsString);
|
||||
insertStmt.setString(8, managersString);
|
||||
insertStmt.setBoolean(9, inheritNothing);
|
||||
insertStmt.setLong(10, parentId);
|
||||
insertStmt.executeUpdate();
|
||||
}
|
||||
catch(SQLException e)
|
||||
{
|
||||
GriefPrevention.AddLogEntry("Unable to save data for claim at " + this.locationToString(claim.lesserBoundaryCorner) + ". Details:");
|
||||
GriefPrevention.AddLogEntry(e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
//deletes a claim from the database
|
||||
@Override
|
||||
synchronized void deleteClaimFromSecondaryStorage(Claim claim)
|
||||
{
|
||||
try(PreparedStatement deleteStmnt = this.databaseConnection.prepareStatement(this.getDeleteClaimSQL())) {
|
||||
deleteStmnt.setLong(1, claim.id);
|
||||
deleteStmnt.executeUpdate();
|
||||
}
|
||||
catch(SQLException e)
|
||||
{
|
||||
GriefPrevention.AddLogEntry("Unable to delete data for claim " + claim.id + ". Details:");
|
||||
GriefPrevention.AddLogEntry(e.getMessage());
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
synchronized PlayerData getPlayerDataFromStorage(UUID playerID)
|
||||
{
|
||||
PlayerData playerData = new PlayerData();
|
||||
playerData.playerID = playerID;
|
||||
|
||||
try (PreparedStatement selectStmnt = this.databaseConnection.prepareStatement( this.getGetPlayerDataSQL()))
|
||||
{
|
||||
selectStmnt.setString(1, playerID.toString());
|
||||
ResultSet results = selectStmnt.executeQuery();
|
||||
|
||||
//if data for this player exists, use it
|
||||
if(results.next())
|
||||
{
|
||||
playerData.setAccruedClaimBlocks(results.getInt("accruedblocks"));
|
||||
playerData.setBonusClaimBlocks(results.getInt("bonusblocks"));
|
||||
}
|
||||
}
|
||||
catch(SQLException e)
|
||||
{
|
||||
StringWriter errors = new StringWriter();
|
||||
e.printStackTrace(new PrintWriter(errors));
|
||||
GriefPrevention.AddLogEntry(playerID + " " + errors.toString(), CustomLogEntryTypes.Exception);
|
||||
}
|
||||
|
||||
return playerData;
|
||||
}
|
||||
|
||||
//saves changes to player data. MUST be called after you're done making changes, otherwise a reload will lose them
|
||||
@Override
|
||||
public void overrideSavePlayerData(UUID playerID, PlayerData playerData)
|
||||
{
|
||||
//never save data for the "administrative" account. an empty string for player name indicates administrative account
|
||||
if(playerID == null) return;
|
||||
|
||||
this.savePlayerData(playerID.toString(), playerData);
|
||||
}
|
||||
|
||||
private void savePlayerData(String playerID, PlayerData playerData)
|
||||
{
|
||||
try (PreparedStatement deleteStmnt = this.databaseConnection.prepareStatement(this.getDeletePlayerDataSQL());
|
||||
PreparedStatement insertStmnt = this.databaseConnection.prepareStatement(this.getInsertPlayerDataSQL())) {
|
||||
OfflinePlayer player = Bukkit.getOfflinePlayer(UUID.fromString(playerID));
|
||||
|
||||
SimpleDateFormat sqlFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
|
||||
String dateString = sqlFormat.format(new Date(player.getLastPlayed()));
|
||||
deleteStmnt.setString(1, playerID);
|
||||
deleteStmnt.executeUpdate();
|
||||
|
||||
insertStmnt.setString(1, playerID);
|
||||
insertStmnt.setString(2, dateString);
|
||||
insertStmnt.setInt(3, playerData.getAccruedClaimBlocks());
|
||||
insertStmnt.setInt(4, playerData.getBonusClaimBlocks());
|
||||
insertStmnt.executeUpdate();
|
||||
}
|
||||
catch(SQLException e)
|
||||
{
|
||||
StringWriter errors = new StringWriter();
|
||||
e.printStackTrace(new PrintWriter(errors));
|
||||
GriefPrevention.AddLogEntry(playerID + " " + errors.toString(), CustomLogEntryTypes.Exception);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
synchronized void incrementNextClaimID()
|
||||
{
|
||||
this.setNextClaimID(this.nextClaimID + 1);
|
||||
}
|
||||
|
||||
//sets the next claim ID. used by incrementNextClaimID() above, and also while migrating data from a flat file data store
|
||||
synchronized void setNextClaimID(long nextID)
|
||||
{
|
||||
this.nextClaimID = nextID;
|
||||
|
||||
try (PreparedStatement deleteStmnt = this.databaseConnection.prepareStatement(this.getDeleteNextClaimIdSQL());
|
||||
PreparedStatement insertStmnt = this.databaseConnection.prepareStatement(this.getInsertNextClaimIdSQL())) {
|
||||
deleteStmnt.execute();
|
||||
insertStmnt.setLong(1, nextID);
|
||||
insertStmnt.executeUpdate();
|
||||
}
|
||||
catch(SQLException e)
|
||||
{
|
||||
GriefPrevention.AddLogEntry("Unable to set next claim ID to " + nextID + ". Details:");
|
||||
GriefPrevention.AddLogEntry(e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
//updates the database with a group's bonus blocks
|
||||
@Override
|
||||
synchronized void saveGroupBonusBlocks(String groupName, int currentValue)
|
||||
{
|
||||
//group bonus blocks are stored in the player data table, with player name = $groupName
|
||||
try (PreparedStatement deleteStmnt = this.databaseConnection.prepareStatement(this.getDeleteGroupBonusSQL());
|
||||
PreparedStatement insertStmnt = this.databaseConnection.prepareStatement(this.getInsertPlayerDataSQL())) {
|
||||
SimpleDateFormat sqlFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
|
||||
String dateString = sqlFormat.format(new Date());
|
||||
deleteStmnt.setString(1, '$' + groupName);
|
||||
deleteStmnt.executeUpdate();
|
||||
|
||||
insertStmnt.setString(1, '$' + groupName);
|
||||
insertStmnt.setString(2, dateString);
|
||||
insertStmnt.setInt(3, 0);
|
||||
insertStmnt.setInt(4, currentValue);
|
||||
insertStmnt.executeUpdate();
|
||||
}
|
||||
catch(SQLException e)
|
||||
{
|
||||
GriefPrevention.AddLogEntry("Unable to save data for group " + groupName + ". Details:");
|
||||
GriefPrevention.AddLogEntry(e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
synchronized void close()
|
||||
{
|
||||
if(this.databaseConnection != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
if(!this.databaseConnection.isClosed())
|
||||
{
|
||||
this.databaseConnection.close();
|
||||
}
|
||||
}
|
||||
catch(SQLException e){};
|
||||
}
|
||||
|
||||
this.databaseConnection = null;
|
||||
}
|
||||
|
||||
private synchronized void refreshDataConnection() throws SQLException
|
||||
{
|
||||
if(this.databaseConnection == null || !this.databaseConnection.isValid(3))
|
||||
{
|
||||
if(this.databaseConnection != null && !this.databaseConnection.isClosed())
|
||||
{
|
||||
this.databaseConnection.close();
|
||||
}
|
||||
|
||||
//set username/pass properties
|
||||
Properties connectionProps = new Properties();
|
||||
connectionProps.put("user", this.userName);
|
||||
connectionProps.put("password", this.password);
|
||||
connectionProps.put("autoReconnect", "true");
|
||||
connectionProps.put("maxReconnects", String.valueOf(Integer.MAX_VALUE));
|
||||
|
||||
//establish connection
|
||||
this.databaseConnection = DriverManager.getConnection(this.databaseUrl, connectionProps);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int getSchemaVersionFromStorage()
|
||||
{
|
||||
try (PreparedStatement selectStmnt = this.databaseConnection.prepareStatement(this.getSelectSchemaVersionSQL())) {
|
||||
ResultSet results = selectStmnt.executeQuery();
|
||||
|
||||
//if there's nothing yet, assume 0 and add it
|
||||
if(!results.next())
|
||||
{
|
||||
this.setSchemaVersion(0);
|
||||
return 0;
|
||||
}
|
||||
//otherwise return the value that's in the table
|
||||
else
|
||||
{
|
||||
return results.getInt("version");
|
||||
}
|
||||
}
|
||||
catch(SQLException e)
|
||||
{
|
||||
GriefPrevention.AddLogEntry("Unable to retrieve schema version from database. Details:");
|
||||
GriefPrevention.AddLogEntry(e.getMessage());
|
||||
e.printStackTrace();
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void updateSchemaVersionInStorage(int versionToSet)
|
||||
{
|
||||
try (PreparedStatement deleteStmnt = this.databaseConnection.prepareStatement(this.getDeleteSchemaVersionSQL());
|
||||
PreparedStatement insertStmnt = this.databaseConnection.prepareStatement(this.getInsertSchemaVerSQL())) {
|
||||
deleteStmnt.execute();
|
||||
|
||||
insertStmnt.setInt(1, versionToSet);
|
||||
insertStmnt.executeUpdate();
|
||||
}
|
||||
catch(SQLException e)
|
||||
{
|
||||
GriefPrevention.AddLogEntry("Unable to set next schema version to " + versionToSet + ". Details:");
|
||||
GriefPrevention.AddLogEntry(e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Concats an array to a string divided with the ; sign
|
||||
* @param input Arraylist with strings to concat
|
||||
* @return String with all values from input array
|
||||
*/
|
||||
private String storageStringBuilder(ArrayList<String> input) {
|
||||
String output = "";
|
||||
for(String string : input) {
|
||||
output += string + ";";
|
||||
}
|
||||
return output;
|
||||
}
|
||||
|
||||
public String getUpdateNameSQL() {
|
||||
return updateNameSQL;
|
||||
}
|
||||
|
||||
public String getInsertClaimSQL() {
|
||||
return insertClaimSQL;
|
||||
}
|
||||
|
||||
public String getDeleteClaimSQL() {
|
||||
return deleteClaimSQL;
|
||||
}
|
||||
|
||||
public String getGetPlayerDataSQL() {
|
||||
return getPlayerDataSQL;
|
||||
}
|
||||
|
||||
public String getDeletePlayerDataSQL() {
|
||||
return deletePlayerDataSQL;
|
||||
}
|
||||
|
||||
public String getInsertPlayerDataSQL() {
|
||||
return insertPlayerDataSQL;
|
||||
}
|
||||
|
||||
public String getInsertNextClaimIdSQL() {
|
||||
return insertNextClaimIdSQL;
|
||||
}
|
||||
|
||||
public String getDeleteGroupBonusSQL() {
|
||||
return deleteGroupBonusSQL;
|
||||
}
|
||||
|
||||
public String getInsertSchemaVerSQL() {
|
||||
return insertSchemaVerSQL;
|
||||
}
|
||||
|
||||
public String getDeleteNextClaimIdSQL() {
|
||||
return deleteNextClaimIdSQL;
|
||||
}
|
||||
|
||||
public String getDeleteSchemaVersionSQL() {
|
||||
return deleteSchemaVersionSQL;
|
||||
}
|
||||
|
||||
public String getSelectSchemaVersionSQL() {
|
||||
return selectSchemaVersionSQL;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,128 @@
|
||||
/*
|
||||
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.Collection;
|
||||
|
||||
import me.ryanhamshire.GriefPrevention.events.AccrueClaimBlocksEvent;
|
||||
import org.bukkit.entity.Player;
|
||||
|
||||
//FEATURE: give players claim blocks for playing, as long as they're not away from their computer
|
||||
|
||||
//runs every 5 minutes in the main thread, grants blocks per hour / 12 to each online player who appears to be actively playing
|
||||
class DeliverClaimBlocksTask implements Runnable
|
||||
{
|
||||
private Player player;
|
||||
private GriefPrevention instance;
|
||||
private int idleThresholdSquared;
|
||||
|
||||
public DeliverClaimBlocksTask(Player player, GriefPrevention instance)
|
||||
{
|
||||
this.player = player;
|
||||
this.instance = instance;
|
||||
this.idleThresholdSquared = instance.config_claims_accruedIdleThreshold * instance.config_claims_accruedIdleThreshold;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run()
|
||||
{
|
||||
//if no player specified, this task will create a player-specific task for each online player, scheduled one tick apart
|
||||
if(this.player == null)
|
||||
{
|
||||
@SuppressWarnings("unchecked")
|
||||
Collection<Player> players = (Collection<Player>)GriefPrevention.instance.getServer().getOnlinePlayers();
|
||||
|
||||
long i = 0;
|
||||
for(Player onlinePlayer : players)
|
||||
{
|
||||
DeliverClaimBlocksTask newTask = new DeliverClaimBlocksTask(onlinePlayer, instance);
|
||||
instance.getServer().getScheduler().scheduleSyncDelayedTask(instance, newTask, i++);
|
||||
}
|
||||
|
||||
return; //tasks started for each player
|
||||
}
|
||||
|
||||
//deliver claim blocks to the specified player
|
||||
if(!this.player.isOnline())
|
||||
{
|
||||
return; //player is not online to receive claim blocks
|
||||
}
|
||||
|
||||
DataStore dataStore = instance.dataStore;
|
||||
PlayerData playerData = dataStore.getPlayerData(player.getUniqueId());
|
||||
|
||||
// check if player is idle. considered idle if
|
||||
// in vehicle or is in water (pushed by water)
|
||||
// or has not moved at least defined blocks since last check
|
||||
boolean isIdle = false;
|
||||
try
|
||||
{
|
||||
isIdle = player.isInsideVehicle() || player.getLocation().getBlock().isLiquid() ||
|
||||
!(playerData.lastAfkCheckLocation == null || playerData.lastAfkCheckLocation.distanceSquared(player.getLocation()) > idleThresholdSquared);
|
||||
}
|
||||
catch(IllegalArgumentException ignore) //can't measure distance when to/from are different worlds
|
||||
{
|
||||
}
|
||||
|
||||
//remember current location for next time
|
||||
playerData.lastAfkCheckLocation = player.getLocation();
|
||||
|
||||
try
|
||||
{
|
||||
//determine how fast blocks accrue for this player //RoboMWM: addons determine this instead
|
||||
int accrualRate = instance.config_claims_blocksAccruedPerHour_default;
|
||||
|
||||
//determine idle accrual rate when idle
|
||||
if (isIdle)
|
||||
{
|
||||
if (instance.config_claims_accruedIdlePercent <= 0)
|
||||
{
|
||||
GriefPrevention.AddLogEntry(player.getName() + " wasn't active enough to accrue claim blocks this round.", CustomLogEntryTypes.Debug, true);
|
||||
return; //idle accrual percentage is disabled
|
||||
}
|
||||
|
||||
accrualRate = (int) (accrualRate * (instance.config_claims_accruedIdlePercent / 100.0D));
|
||||
}
|
||||
|
||||
//fire event for addons
|
||||
AccrueClaimBlocksEvent event = new AccrueClaimBlocksEvent(player, accrualRate, isIdle);
|
||||
instance.getServer().getPluginManager().callEvent(event);
|
||||
if (event.isCancelled())
|
||||
{
|
||||
GriefPrevention.AddLogEntry(player.getName() + " claim block delivery was canceled by another plugin.", CustomLogEntryTypes.Debug, true);
|
||||
return; //event was cancelled
|
||||
}
|
||||
|
||||
//set actual accrual
|
||||
accrualRate = event.getBlocksToAccrue();
|
||||
if (accrualRate < 0) accrualRate = 0;
|
||||
playerData.accrueBlocks(accrualRate);
|
||||
GriefPrevention.AddLogEntry("Delivering " + event.getBlocksToAccrue() + " blocks to " + player.getName(), CustomLogEntryTypes.Debug, true);
|
||||
|
||||
//intentionally NOT saving data here to reduce overall secondary storage access frequency
|
||||
//many other operations will cause this player's data to save, including his eventual logout
|
||||
//dataStore.savePlayerData(player.getUniqueIdentifier(), playerData);
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
GriefPrevention.AddLogEntry("Problem delivering claim blocks to player " + player.getName() + ":");
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,149 @@
|
||||
/*
|
||||
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.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import org.bukkit.Material;
|
||||
import org.bukkit.World;
|
||||
import org.bukkit.entity.Boat;
|
||||
import org.bukkit.entity.Entity;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.entity.Vehicle;
|
||||
|
||||
//FEATURE: creative mode worlds get a regular entity cleanup
|
||||
|
||||
//this main thread task revisits the location of a partially chopped tree from several minutes ago
|
||||
//if any part of the tree is still there and nothing else has been built in its place, remove the remaining parts
|
||||
class EntityCleanupTask implements Runnable
|
||||
{
|
||||
//where to start cleaning in the list of entities
|
||||
private double percentageStart;
|
||||
|
||||
public EntityCleanupTask(double percentageStart)
|
||||
{
|
||||
this.percentageStart = percentageStart;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run()
|
||||
{
|
||||
ArrayList<World> worlds = new ArrayList<World>();
|
||||
for(World world : GriefPrevention.instance.getServer().getWorlds())
|
||||
{
|
||||
if(GriefPrevention.instance.config_claims_worldModes.get(world) == ClaimsMode.Creative)
|
||||
{
|
||||
worlds.add(world);
|
||||
}
|
||||
}
|
||||
|
||||
for(int i = 0; i < worlds.size(); i++)
|
||||
{
|
||||
World world = worlds.get(i);
|
||||
|
||||
List<Entity> entities = world.getEntities();
|
||||
|
||||
//starting and stopping point. each execution of the task scans 10% of the server's (loaded) entities
|
||||
int j = (int)(entities.size() * this.percentageStart);
|
||||
int k = (int)(entities.size() * (this.percentageStart + .1));
|
||||
Claim cachedClaim = null;
|
||||
for(; j < entities.size() && j < k; j++)
|
||||
{
|
||||
Entity entity = entities.get(j);
|
||||
|
||||
boolean remove = false;
|
||||
if(entity instanceof Boat) //boats must be occupied
|
||||
{
|
||||
Boat boat = (Boat)entity;
|
||||
if(boat.isEmpty()) remove = true;
|
||||
}
|
||||
|
||||
else if(entity instanceof Vehicle)
|
||||
{
|
||||
Vehicle vehicle = (Vehicle)entity;
|
||||
|
||||
//minecarts in motion must be occupied by a player
|
||||
if(vehicle.getVelocity().lengthSquared() != 0)
|
||||
{
|
||||
if(vehicle.isEmpty() || !(vehicle.getPassenger() instanceof Player))
|
||||
{
|
||||
remove = true;
|
||||
}
|
||||
}
|
||||
|
||||
//stationary carts must be on rails
|
||||
else
|
||||
{
|
||||
Material material = world.getBlockAt(vehicle.getLocation()).getType();
|
||||
if(material != Material.RAILS && material != Material.POWERED_RAIL && material != Material.DETECTOR_RAIL)
|
||||
{
|
||||
remove = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//all non-player entities must be in claims
|
||||
else if(!(entity instanceof Player))
|
||||
{
|
||||
Claim claim = GriefPrevention.instance.dataStore.getClaimAt(entity.getLocation(), false, cachedClaim);
|
||||
if(claim != null)
|
||||
{
|
||||
cachedClaim = claim;
|
||||
}
|
||||
else
|
||||
{
|
||||
remove = true;
|
||||
}
|
||||
}
|
||||
|
||||
if(remove)
|
||||
{
|
||||
entity.remove();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//starting and stopping point. each execution of the task scans 5% of the server's claims
|
||||
List<Claim> claims = GriefPrevention.instance.dataStore.claims;
|
||||
int j = (int)(claims.size() * this.percentageStart);
|
||||
int k = (int)(claims.size() * (this.percentageStart + .05));
|
||||
for(; j < claims.size() && j < k; j++)
|
||||
{
|
||||
Claim claim = claims.get(j);
|
||||
|
||||
//if it's a creative mode claim
|
||||
if(GriefPrevention.instance.creativeRulesApply(claim.getLesserBoundaryCorner()))
|
||||
{
|
||||
//check its entity count and remove any extras
|
||||
claim.allowMoreEntities(true);
|
||||
}
|
||||
}
|
||||
|
||||
//schedule the next run of this task, in 3 minutes (20L is approximately 1 second)
|
||||
double nextRunPercentageStart = this.percentageStart + .05;
|
||||
if(nextRunPercentageStart > .99)
|
||||
{
|
||||
nextRunPercentageStart = 0;
|
||||
}
|
||||
|
||||
EntityCleanupTask task = new EntityCleanupTask(nextRunPercentageStart);
|
||||
GriefPrevention.instance.getServer().getScheduler().scheduleSyncDelayedTask(GriefPrevention.instance, task, 20L * 60 * 1);
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,78 @@
|
||||
/*
|
||||
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.entity.Player;
|
||||
import org.bukkit.inventory.EquipmentSlot;
|
||||
|
||||
//tells a player about how many claim blocks he has, etc
|
||||
//implemented as a task so that it can be delayed
|
||||
//otherwise, it's spammy when players mouse-wheel past the shovel in their hot bars
|
||||
class EquipShovelProcessingTask implements Runnable
|
||||
{
|
||||
//player data
|
||||
private Player player;
|
||||
|
||||
public EquipShovelProcessingTask(Player player)
|
||||
{
|
||||
this.player = player;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run()
|
||||
{
|
||||
//if he's not holding the golden shovel anymore, do nothing
|
||||
if(GriefPrevention.instance.getItemInHand(player, EquipmentSlot.HAND).getType() != GriefPrevention.instance.config_claims_modificationTool) return;
|
||||
|
||||
PlayerData playerData = GriefPrevention.instance.dataStore.getPlayerData(player.getUniqueId());
|
||||
|
||||
//reset any work he might have been doing
|
||||
playerData.lastShovelLocation = null;
|
||||
playerData.claimResizing = null;
|
||||
|
||||
//always reset to basic claims mode
|
||||
if(playerData.shovelMode != ShovelMode.Basic)
|
||||
{
|
||||
playerData.shovelMode = ShovelMode.Basic;
|
||||
GriefPrevention.sendMessage(player, TextMode.Info, Messages.ShovelBasicClaimMode);
|
||||
}
|
||||
|
||||
//tell him how many claim blocks he has available
|
||||
int remainingBlocks = playerData.getRemainingClaimBlocks();
|
||||
GriefPrevention.sendMessage(player, TextMode.Instr, Messages.RemainingBlocks, String.valueOf(remainingBlocks));
|
||||
|
||||
//link to a video demo of land claiming, based on world type
|
||||
if(GriefPrevention.instance.creativeRulesApply(player.getLocation()))
|
||||
{
|
||||
GriefPrevention.sendMessage(player, TextMode.Instr, Messages.CreativeBasicsVideo2, DataStore.CREATIVE_VIDEO_URL);
|
||||
}
|
||||
else if(GriefPrevention.instance.claimsEnabledForWorld(player.getLocation().getWorld()))
|
||||
{
|
||||
GriefPrevention.sendMessage(player, TextMode.Instr, Messages.SurvivalBasicsVideo2, DataStore.SURVIVAL_VIDEO_URL);
|
||||
}
|
||||
|
||||
//if standing in a claim owned by the player, visualize it
|
||||
Claim claim = GriefPrevention.instance.dataStore.getClaimAt(player.getLocation(), true, playerData.lastClaim);
|
||||
if(claim != null && claim.allowEdit(player) == null)
|
||||
{
|
||||
playerData.lastClaim = claim;
|
||||
Visualization.Apply(player, Visualization.FromClaim(claim, player.getEyeLocation().getBlockY(), VisualizationType.Claim, player.getLocation()));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,931 @@
|
||||
/*
|
||||
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.io.*;
|
||||
import java.nio.charset.Charset;
|
||||
import java.text.DateFormat;
|
||||
import java.text.ParseException;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.regex.Matcher;
|
||||
|
||||
import org.bukkit.*;
|
||||
import org.bukkit.configuration.InvalidConfigurationException;
|
||||
import org.bukkit.configuration.file.YamlConfiguration;
|
||||
|
||||
import com.google.common.io.Files;
|
||||
|
||||
//manages data stored in the file system
|
||||
public class FlatFileDataStore extends DataStore
|
||||
{
|
||||
private final static String claimDataFolderPath = dataLayerFolderPath + File.separator + "ClaimData";
|
||||
private final static String nextClaimIdFilePath = claimDataFolderPath + File.separator + "_nextClaimID";
|
||||
private final static String schemaVersionFilePath = dataLayerFolderPath + File.separator + "_schemaVersion";
|
||||
|
||||
static boolean hasData()
|
||||
{
|
||||
File claimsDataFolder = new File(claimDataFolderPath);
|
||||
|
||||
return claimsDataFolder.exists();
|
||||
}
|
||||
|
||||
//initialization!
|
||||
FlatFileDataStore() throws Exception
|
||||
{
|
||||
this.initialize();
|
||||
}
|
||||
|
||||
@Override
|
||||
void initialize() throws Exception
|
||||
{
|
||||
//ensure data folders exist
|
||||
boolean newDataStore = false;
|
||||
File playerDataFolder = new File(playerDataFolderPath);
|
||||
File claimDataFolder = new File(claimDataFolderPath);
|
||||
if(!playerDataFolder.exists() || !claimDataFolder.exists())
|
||||
{
|
||||
newDataStore = true;
|
||||
playerDataFolder.mkdirs();
|
||||
claimDataFolder.mkdirs();
|
||||
}
|
||||
|
||||
//if there's no data yet, then anything written will use the schema implemented by this code
|
||||
if(newDataStore)
|
||||
{
|
||||
this.setSchemaVersion(DataStore.latestSchemaVersion);
|
||||
}
|
||||
|
||||
//load group data into memory
|
||||
File [] files = playerDataFolder.listFiles();
|
||||
for(int i = 0; i < files.length; i++)
|
||||
{
|
||||
File file = files[i];
|
||||
if(!file.isFile()) continue; //avoids folders
|
||||
|
||||
//all group data files start with a dollar sign. ignoring the rest, which are player data files.
|
||||
if(!file.getName().startsWith("$")) continue;
|
||||
|
||||
String groupName = file.getName().substring(1);
|
||||
if(groupName == null || groupName.isEmpty()) continue; //defensive coding, avoid unlikely cases
|
||||
|
||||
BufferedReader inStream = null;
|
||||
try
|
||||
{
|
||||
inStream = new BufferedReader(new FileReader(file.getAbsolutePath()));
|
||||
String line = inStream.readLine();
|
||||
|
||||
int groupBonusBlocks = Integer.parseInt(line);
|
||||
|
||||
this.permissionToBonusBlocksMap.put(groupName, groupBonusBlocks);
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
StringWriter errors = new StringWriter();
|
||||
e.printStackTrace(new PrintWriter(errors));
|
||||
GriefPrevention.AddLogEntry(errors.toString(), CustomLogEntryTypes.Exception);
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
if(inStream != null) inStream.close();
|
||||
}
|
||||
catch(IOException exception) {}
|
||||
}
|
||||
|
||||
//load next claim number from file
|
||||
File nextClaimIdFile = new File(nextClaimIdFilePath);
|
||||
if(nextClaimIdFile.exists())
|
||||
{
|
||||
BufferedReader inStream = null;
|
||||
try
|
||||
{
|
||||
inStream = new BufferedReader(new FileReader(nextClaimIdFile.getAbsolutePath()));
|
||||
|
||||
//read the id
|
||||
String line = inStream.readLine();
|
||||
|
||||
//try to parse into a long value
|
||||
this.nextClaimID = Long.parseLong(line);
|
||||
}
|
||||
catch(Exception e){ }
|
||||
|
||||
try
|
||||
{
|
||||
if(inStream != null) inStream.close();
|
||||
}
|
||||
catch(IOException exception) {}
|
||||
}
|
||||
|
||||
//if converting up from schema version 0, rename player data files using UUIDs instead of player names
|
||||
//get a list of all the files in the claims data folder
|
||||
if(this.getSchemaVersion() == 0)
|
||||
{
|
||||
files = playerDataFolder.listFiles();
|
||||
ArrayList<String> namesToConvert = new ArrayList<String>();
|
||||
for(File playerFile : files)
|
||||
{
|
||||
namesToConvert.add(playerFile.getName());
|
||||
}
|
||||
|
||||
//resolve and cache as many as possible through various means
|
||||
try
|
||||
{
|
||||
UUIDFetcher fetcher = new UUIDFetcher(namesToConvert);
|
||||
fetcher.call();
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
GriefPrevention.AddLogEntry("Failed to resolve a batch of names to UUIDs. Details:" + e.getMessage());
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
//rename files
|
||||
for(File playerFile : files)
|
||||
{
|
||||
String currentFilename = playerFile.getName();
|
||||
|
||||
//if corrected casing and a record already exists using the correct casing, skip this one
|
||||
String correctedCasing = UUIDFetcher.correctedNames.get(currentFilename);
|
||||
if(correctedCasing != null && !currentFilename.equals(correctedCasing))
|
||||
{
|
||||
File correctedCasingFile = new File(playerDataFolder.getPath() + File.separator + correctedCasing);
|
||||
if(correctedCasingFile.exists())
|
||||
{
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
//try to convert player name to UUID
|
||||
UUID playerID = null;
|
||||
try
|
||||
{
|
||||
playerID = UUIDFetcher.getUUIDOf(currentFilename);
|
||||
|
||||
//if successful, rename the file using the UUID
|
||||
if(playerID != null)
|
||||
{
|
||||
playerFile.renameTo(new File(playerDataFolder, playerID.toString()));
|
||||
}
|
||||
}
|
||||
catch(Exception ex){ }
|
||||
}
|
||||
}
|
||||
|
||||
//load claims data into memory
|
||||
//get a list of all the files in the claims data folder
|
||||
files = claimDataFolder.listFiles();
|
||||
|
||||
if(this.getSchemaVersion() <= 1)
|
||||
{
|
||||
this.loadClaimData_Legacy(files);
|
||||
}
|
||||
else
|
||||
{
|
||||
this.loadClaimData(files);
|
||||
}
|
||||
|
||||
super.initialize();
|
||||
}
|
||||
|
||||
void loadClaimData_Legacy(File [] files) throws Exception
|
||||
{
|
||||
List<World> validWorlds = Bukkit.getServer().getWorlds();
|
||||
|
||||
for(int i = 0; i < files.length; i++)
|
||||
{
|
||||
if(files[i].isFile()) //avoids folders
|
||||
{
|
||||
//skip any file starting with an underscore, to avoid special files not representing land claims
|
||||
if(files[i].getName().startsWith("_")) continue;
|
||||
|
||||
//the filename is the claim ID. try to parse it
|
||||
long claimID;
|
||||
|
||||
try
|
||||
{
|
||||
claimID = Long.parseLong(files[i].getName());
|
||||
}
|
||||
|
||||
//because some older versions used a different file name pattern before claim IDs were introduced,
|
||||
//those files need to be "converted" by renaming them to a unique ID
|
||||
catch(Exception e)
|
||||
{
|
||||
claimID = this.nextClaimID;
|
||||
this.incrementNextClaimID();
|
||||
File newFile = new File(claimDataFolderPath + File.separator + String.valueOf(this.nextClaimID));
|
||||
files[i].renameTo(newFile);
|
||||
files[i] = newFile;
|
||||
}
|
||||
|
||||
BufferedReader inStream = null;
|
||||
String lesserCornerString = "";
|
||||
try
|
||||
{
|
||||
Claim topLevelClaim = null;
|
||||
|
||||
inStream = new BufferedReader(new FileReader(files[i].getAbsolutePath()));
|
||||
String line = inStream.readLine();
|
||||
|
||||
while(line != null)
|
||||
{
|
||||
//skip any SUB:### lines from previous versions
|
||||
if(line.toLowerCase().startsWith("sub:"))
|
||||
{
|
||||
line = inStream.readLine();
|
||||
}
|
||||
|
||||
//skip any UUID lines from previous versions
|
||||
Matcher match = uuidpattern.matcher(line.trim());
|
||||
if(match.find())
|
||||
{
|
||||
line = inStream.readLine();
|
||||
}
|
||||
|
||||
//first line is lesser boundary corner location
|
||||
lesserCornerString = line;
|
||||
Location lesserBoundaryCorner = this.locationFromString(lesserCornerString, validWorlds);
|
||||
|
||||
//second line is greater boundary corner location
|
||||
line = inStream.readLine();
|
||||
Location greaterBoundaryCorner = this.locationFromString(line, validWorlds);
|
||||
|
||||
//third line is owner name
|
||||
line = inStream.readLine();
|
||||
String ownerName = line;
|
||||
UUID ownerID = null;
|
||||
if(ownerName.isEmpty() || ownerName.startsWith("--"))
|
||||
{
|
||||
ownerID = null; //administrative land claim or subdivision
|
||||
}
|
||||
else if(this.getSchemaVersion() == 0)
|
||||
{
|
||||
try
|
||||
{
|
||||
ownerID = UUIDFetcher.getUUIDOf(ownerName);
|
||||
}
|
||||
catch(Exception ex)
|
||||
{
|
||||
GriefPrevention.AddLogEntry("Couldn't resolve this name to a UUID: " + ownerName + ".");
|
||||
GriefPrevention.AddLogEntry(" Converted land claim to administrative @ " + lesserBoundaryCorner.toString());
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
try
|
||||
{
|
||||
ownerID = UUID.fromString(ownerName);
|
||||
}
|
||||
catch(Exception ex)
|
||||
{
|
||||
GriefPrevention.AddLogEntry("Error - this is not a valid UUID: " + ownerName + ".");
|
||||
GriefPrevention.AddLogEntry(" Converted land claim to administrative @ " + lesserBoundaryCorner.toString());
|
||||
}
|
||||
}
|
||||
|
||||
//fourth line is list of builders
|
||||
line = inStream.readLine();
|
||||
List<String> builderNames = Arrays.asList(line.split(";"));
|
||||
builderNames = this.convertNameListToUUIDList(builderNames);
|
||||
|
||||
//fifth line is list of players who can access containers
|
||||
line = inStream.readLine();
|
||||
List<String> containerNames = Arrays.asList(line.split(";"));
|
||||
containerNames = this.convertNameListToUUIDList(containerNames);
|
||||
|
||||
//sixth line is list of players who can use buttons and switches
|
||||
line = inStream.readLine();
|
||||
List<String> accessorNames = Arrays.asList(line.split(";"));
|
||||
accessorNames = this.convertNameListToUUIDList(accessorNames);
|
||||
|
||||
//seventh line is list of players who can grant permissions
|
||||
line = inStream.readLine();
|
||||
if(line == null) line = "";
|
||||
List<String> managerNames = Arrays.asList(line.split(";"));
|
||||
managerNames = this.convertNameListToUUIDList(managerNames);
|
||||
|
||||
//skip any remaining extra lines, until the "===" string, indicating the end of this claim or subdivision
|
||||
line = inStream.readLine();
|
||||
while(line != null && !line.contains("==="))
|
||||
line = inStream.readLine();
|
||||
|
||||
//build a claim instance from those data
|
||||
//if this is the first claim loaded from this file, it's the top level claim
|
||||
if(topLevelClaim == null)
|
||||
{
|
||||
//instantiate
|
||||
topLevelClaim = new Claim(lesserBoundaryCorner, greaterBoundaryCorner, ownerID, builderNames, containerNames, accessorNames, managerNames, claimID);
|
||||
|
||||
topLevelClaim.modifiedDate = new Date(files[i].lastModified());
|
||||
this.addClaim(topLevelClaim, false);
|
||||
}
|
||||
|
||||
//otherwise there's already a top level claim, so this must be a subdivision of that top level claim
|
||||
else
|
||||
{
|
||||
Claim subdivision = new Claim(lesserBoundaryCorner, greaterBoundaryCorner, null, builderNames, containerNames, accessorNames, managerNames, null);
|
||||
|
||||
subdivision.modifiedDate = new Date(files[i].lastModified());
|
||||
subdivision.parent = topLevelClaim;
|
||||
topLevelClaim.children.add(subdivision);
|
||||
subdivision.inDataStore = true;
|
||||
}
|
||||
|
||||
//move up to the first line in the next subdivision
|
||||
line = inStream.readLine();
|
||||
}
|
||||
|
||||
inStream.close();
|
||||
}
|
||||
|
||||
//if there's any problem with the file's content, log an error message and skip it
|
||||
catch(Exception e)
|
||||
{
|
||||
if(e.getMessage() != null && e.getMessage().contains("World not found"))
|
||||
{
|
||||
GriefPrevention.AddLogEntry("Failed to load a claim " + files[i].getName() + " because its world isn't loaded (yet?). Please delete the claim file or contact the GriefPrevention developer with information about which plugin(s) you're using to load or create worlds. " + lesserCornerString);
|
||||
inStream.close();
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
StringWriter errors = new StringWriter();
|
||||
e.printStackTrace(new PrintWriter(errors));
|
||||
GriefPrevention.AddLogEntry(files[i].getName() + " " + errors.toString(), CustomLogEntryTypes.Exception);
|
||||
}
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
if(inStream != null) inStream.close();
|
||||
}
|
||||
catch(IOException exception) {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void loadClaimData(File [] files) throws Exception
|
||||
{
|
||||
ConcurrentHashMap<Claim, Long> orphans = new ConcurrentHashMap<Claim, Long>();
|
||||
for(int i = 0; i < files.length; i++)
|
||||
{
|
||||
if(files[i].isFile()) //avoids folders
|
||||
{
|
||||
//skip any file starting with an underscore, to avoid special files not representing land claims
|
||||
if(files[i].getName().startsWith("_")) continue;
|
||||
|
||||
//delete any which don't end in .yml
|
||||
if(!files[i].getName().endsWith(".yml"))
|
||||
{
|
||||
files[i].delete();
|
||||
continue;
|
||||
}
|
||||
|
||||
//the filename is the claim ID. try to parse it
|
||||
long claimID;
|
||||
|
||||
try
|
||||
{
|
||||
claimID = Long.parseLong(files[i].getName().split("\\.")[0]);
|
||||
}
|
||||
|
||||
//because some older versions used a different file name pattern before claim IDs were introduced,
|
||||
//those files need to be "converted" by renaming them to a unique ID
|
||||
catch(Exception e)
|
||||
{
|
||||
claimID = this.nextClaimID;
|
||||
this.incrementNextClaimID();
|
||||
File newFile = new File(claimDataFolderPath + File.separator + String.valueOf(this.nextClaimID) + ".yml");
|
||||
files[i].renameTo(newFile);
|
||||
files[i] = newFile;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
ArrayList<Long> out_parentID = new ArrayList<Long>(); //hacky output parameter
|
||||
Claim claim = this.loadClaim(files[i], out_parentID, claimID);
|
||||
if(out_parentID.size() == 0 || out_parentID.get(0) == -1)
|
||||
{
|
||||
this.addClaim(claim, false);
|
||||
}
|
||||
else
|
||||
{
|
||||
orphans.put(claim, out_parentID.get(0));
|
||||
}
|
||||
}
|
||||
|
||||
//if there's any problem with the file's content, log an error message and skip it
|
||||
catch(Exception e)
|
||||
{
|
||||
if(e.getMessage() != null && e.getMessage().contains("World not found"))
|
||||
{
|
||||
files[i].delete();
|
||||
}
|
||||
else
|
||||
{
|
||||
StringWriter errors = new StringWriter();
|
||||
e.printStackTrace(new PrintWriter(errors));
|
||||
GriefPrevention.AddLogEntry(files[i].getName() + " " + errors.toString(), CustomLogEntryTypes.Exception);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//link children to parents
|
||||
for(Claim child : orphans.keySet())
|
||||
{
|
||||
Claim parent = this.getClaim(orphans.get(child));
|
||||
if(parent != null)
|
||||
{
|
||||
child.parent = parent;
|
||||
this.addClaim(child, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Claim loadClaim(File file, ArrayList<Long> out_parentID, long claimID) throws IOException, InvalidConfigurationException, Exception
|
||||
{
|
||||
List<String> lines = Files.readLines(file, Charset.forName("UTF-8"));
|
||||
StringBuilder builder = new StringBuilder();
|
||||
for(String line : lines)
|
||||
{
|
||||
builder.append(line).append('\n');
|
||||
}
|
||||
|
||||
return this.loadClaim(builder.toString(), out_parentID, file.lastModified(), claimID, Bukkit.getServer().getWorlds());
|
||||
}
|
||||
|
||||
Claim loadClaim(String input, ArrayList<Long> out_parentID, long lastModifiedDate, long claimID, List<World> validWorlds) throws InvalidConfigurationException, Exception
|
||||
{
|
||||
Claim claim = null;
|
||||
YamlConfiguration yaml = new YamlConfiguration();
|
||||
yaml.loadFromString(input);
|
||||
|
||||
//boundaries
|
||||
Location lesserBoundaryCorner = this.locationFromString(yaml.getString("Lesser Boundary Corner"), validWorlds);
|
||||
Location greaterBoundaryCorner = this.locationFromString(yaml.getString("Greater Boundary Corner"), validWorlds);
|
||||
|
||||
//owner
|
||||
String ownerIdentifier = yaml.getString("Owner");
|
||||
UUID ownerID = null;
|
||||
if(!ownerIdentifier.isEmpty())
|
||||
{
|
||||
try
|
||||
{
|
||||
ownerID = UUID.fromString(ownerIdentifier);
|
||||
}
|
||||
catch(Exception ex)
|
||||
{
|
||||
GriefPrevention.AddLogEntry("Error - this is not a valid UUID: " + ownerIdentifier + ".");
|
||||
GriefPrevention.AddLogEntry(" Converted land claim to administrative @ " + lesserBoundaryCorner.toString());
|
||||
}
|
||||
}
|
||||
|
||||
List<String> builders = yaml.getStringList("Builders");
|
||||
|
||||
List<String> containers = yaml.getStringList("Containers");
|
||||
|
||||
List<String> accessors = yaml.getStringList("Accessors");
|
||||
|
||||
List<String> managers = yaml.getStringList("Managers");
|
||||
|
||||
boolean inheritNothing = yaml.getBoolean("inheritNothing");
|
||||
|
||||
out_parentID.add(yaml.getLong("Parent Claim ID", -1L));
|
||||
|
||||
//instantiate
|
||||
claim = new Claim(lesserBoundaryCorner, greaterBoundaryCorner, ownerID, builders, containers, accessors, managers, inheritNothing, claimID);
|
||||
claim.modifiedDate = new Date(lastModifiedDate);
|
||||
claim.id = claimID;
|
||||
|
||||
return claim;
|
||||
}
|
||||
|
||||
String getYamlForClaim(Claim claim)
|
||||
{
|
||||
YamlConfiguration yaml = new YamlConfiguration();
|
||||
|
||||
//boundaries
|
||||
yaml.set("Lesser Boundary Corner", this.locationToString(claim.lesserBoundaryCorner));
|
||||
yaml.set("Greater Boundary Corner", this.locationToString(claim.greaterBoundaryCorner));
|
||||
|
||||
//owner
|
||||
String ownerID = "";
|
||||
if(claim.ownerID != null) ownerID = claim.ownerID.toString();
|
||||
yaml.set("Owner", ownerID);
|
||||
|
||||
ArrayList<String> builders = new ArrayList<String>();
|
||||
ArrayList<String> containers = new ArrayList<String>();
|
||||
ArrayList<String> accessors = new ArrayList<String>();
|
||||
ArrayList<String> managers = new ArrayList<String>();
|
||||
claim.getPermissions(builders, containers, accessors, managers);
|
||||
|
||||
yaml.set("Builders", builders);
|
||||
yaml.set("Containers", containers);
|
||||
yaml.set("Accessors", accessors);
|
||||
yaml.set("Managers", managers);
|
||||
|
||||
Long parentID = -1L;
|
||||
if(claim.parent != null)
|
||||
{
|
||||
parentID = claim.parent.id;
|
||||
}
|
||||
|
||||
yaml.set("Parent Claim ID", parentID);
|
||||
|
||||
yaml.set("inheritNothing", claim.getSubclaimRestrictions());
|
||||
|
||||
return yaml.saveToString();
|
||||
}
|
||||
|
||||
@Override
|
||||
synchronized void writeClaimToStorage(Claim claim)
|
||||
{
|
||||
String claimID = String.valueOf(claim.id);
|
||||
|
||||
String yaml = this.getYamlForClaim(claim);
|
||||
|
||||
try
|
||||
{
|
||||
//open the claim's file
|
||||
File claimFile = new File(claimDataFolderPath + File.separator + claimID + ".yml");
|
||||
claimFile.createNewFile();
|
||||
Files.write(yaml.getBytes("UTF-8"), claimFile);
|
||||
}
|
||||
|
||||
//if any problem, log it
|
||||
catch(Exception e)
|
||||
{
|
||||
StringWriter errors = new StringWriter();
|
||||
e.printStackTrace(new PrintWriter(errors));
|
||||
GriefPrevention.AddLogEntry(claimID + " " + errors.toString(), CustomLogEntryTypes.Exception);
|
||||
}
|
||||
}
|
||||
|
||||
//deletes a claim from the file system
|
||||
@Override
|
||||
synchronized void deleteClaimFromSecondaryStorage(Claim claim)
|
||||
{
|
||||
String claimID = String.valueOf(claim.id);
|
||||
|
||||
//remove from disk
|
||||
File claimFile = new File(claimDataFolderPath + File.separator + claimID + ".yml");
|
||||
if(claimFile.exists() && !claimFile.delete())
|
||||
{
|
||||
GriefPrevention.AddLogEntry("Error: Unable to delete claim file \"" + claimFile.getAbsolutePath() + "\".");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
synchronized PlayerData getPlayerDataFromStorage(UUID playerID)
|
||||
{
|
||||
File playerFile = new File(playerDataFolderPath + File.separator + playerID.toString());
|
||||
|
||||
PlayerData playerData = new PlayerData();
|
||||
playerData.playerID = playerID;
|
||||
|
||||
//if it exists as a file, read the file
|
||||
if(playerFile.exists())
|
||||
{
|
||||
boolean needRetry = false;
|
||||
int retriesRemaining = 5;
|
||||
Exception latestException = null;
|
||||
do
|
||||
{
|
||||
try
|
||||
{
|
||||
needRetry = false;
|
||||
|
||||
//read the file content and immediately close it
|
||||
List<String> lines = Files.readLines(playerFile, Charset.forName("UTF-8"));
|
||||
Iterator<String> iterator = lines.iterator();
|
||||
|
||||
|
||||
iterator.next();
|
||||
//first line is last login timestamp //RoboMWM - not using this anymore
|
||||
//
|
||||
// //convert that to a date and store it
|
||||
// DateFormat dateFormat = new SimpleDateFormat("yyyy.MM.dd.HH.mm.ss");
|
||||
// try
|
||||
// {
|
||||
// playerData.setLastLogin(dateFormat.parse(lastLoginTimestampString));
|
||||
// }
|
||||
// catch(ParseException parseException)
|
||||
// {
|
||||
// GriefPrevention.AddLogEntry("Unable to load last login for \"" + playerFile.getName() + "\".");
|
||||
// playerData.setLastLogin(null);
|
||||
// }
|
||||
|
||||
//second line is accrued claim blocks
|
||||
String accruedBlocksString = iterator.next();
|
||||
|
||||
//convert that to a number and store it
|
||||
playerData.setAccruedClaimBlocks(Integer.parseInt(accruedBlocksString));
|
||||
|
||||
//third line is any bonus claim blocks granted by administrators
|
||||
String bonusBlocksString = iterator.next();
|
||||
|
||||
//convert that to a number and store it
|
||||
playerData.setBonusClaimBlocks(Integer.parseInt(bonusBlocksString));
|
||||
|
||||
//fourth line is a double-semicolon-delimited list of claims, which is currently ignored
|
||||
//String claimsString = inStream.readLine();
|
||||
//iterator.next();
|
||||
}
|
||||
|
||||
//if there's any problem with the file's content, retry up to 5 times with 5 milliseconds between
|
||||
catch(Exception e)
|
||||
{
|
||||
latestException = e;
|
||||
needRetry = true;
|
||||
retriesRemaining--;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
if(needRetry) Thread.sleep(5);
|
||||
}
|
||||
catch(InterruptedException exception) {}
|
||||
|
||||
}while(needRetry && retriesRemaining >= 0);
|
||||
|
||||
//if last attempt failed, log information about the problem
|
||||
if(needRetry)
|
||||
{
|
||||
StringWriter errors = new StringWriter();
|
||||
latestException.printStackTrace(new PrintWriter(errors));
|
||||
GriefPrevention.AddLogEntry(playerID + " " + errors.toString(), CustomLogEntryTypes.Exception);
|
||||
}
|
||||
}
|
||||
|
||||
return playerData;
|
||||
}
|
||||
|
||||
//saves changes to player data. MUST be called after you're done making changes, otherwise a reload will lose them
|
||||
@Override
|
||||
public void overrideSavePlayerData(UUID playerID, PlayerData playerData)
|
||||
{
|
||||
//never save data for the "administrative" account. null for claim owner ID indicates administrative account
|
||||
if(playerID == null) return;
|
||||
|
||||
StringBuilder fileContent = new StringBuilder();
|
||||
try
|
||||
{
|
||||
//first line is last login timestamp //RoboMWM - no longer storing/using
|
||||
//if(playerData.getLastLogin() == null) playerData.setLastLogin(new Date());
|
||||
//DateFormat dateFormat = new SimpleDateFormat("yyyy.MM.dd.HH.mm.ss");
|
||||
//fileContent.append(dateFormat.format(playerData.getLastLogin()));
|
||||
fileContent.append("\n");
|
||||
|
||||
//second line is accrued claim blocks
|
||||
fileContent.append(String.valueOf(playerData.getAccruedClaimBlocks()));
|
||||
fileContent.append("\n");
|
||||
|
||||
//third line is bonus claim blocks
|
||||
fileContent.append(String.valueOf(playerData.getBonusClaimBlocks()));
|
||||
fileContent.append("\n");
|
||||
|
||||
//fourth line is blank
|
||||
fileContent.append("\n");
|
||||
|
||||
//write data to file
|
||||
File playerDataFile = new File(playerDataFolderPath + File.separator + playerID.toString());
|
||||
Files.write(fileContent.toString().getBytes("UTF-8"), playerDataFile);
|
||||
}
|
||||
|
||||
//if any problem, log it
|
||||
catch(Exception e)
|
||||
{
|
||||
GriefPrevention.AddLogEntry("GriefPrevention: Unexpected exception saving data for player \"" + playerID.toString() + "\": " + e.getMessage());
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
synchronized void incrementNextClaimID()
|
||||
{
|
||||
//increment in memory
|
||||
this.nextClaimID++;
|
||||
|
||||
BufferedWriter outStream = null;
|
||||
|
||||
try
|
||||
{
|
||||
//open the file and write the new value
|
||||
File nextClaimIdFile = new File(nextClaimIdFilePath);
|
||||
nextClaimIdFile.createNewFile();
|
||||
outStream = new BufferedWriter(new FileWriter(nextClaimIdFile));
|
||||
|
||||
outStream.write(String.valueOf(this.nextClaimID));
|
||||
}
|
||||
|
||||
//if any problem, log it
|
||||
catch(Exception e)
|
||||
{
|
||||
GriefPrevention.AddLogEntry("Unexpected exception saving next claim ID: " + e.getMessage());
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
//close the file
|
||||
try
|
||||
{
|
||||
if(outStream != null) outStream.close();
|
||||
}
|
||||
catch(IOException exception) {}
|
||||
}
|
||||
|
||||
//grants a group (players with a specific permission) bonus claim blocks as long as they're still members of the group
|
||||
@Override
|
||||
synchronized void saveGroupBonusBlocks(String groupName, int currentValue)
|
||||
{
|
||||
//write changes to file to ensure they don't get lost
|
||||
BufferedWriter outStream = null;
|
||||
try
|
||||
{
|
||||
//open the group's file
|
||||
File groupDataFile = new File(playerDataFolderPath + File.separator + "$" + groupName);
|
||||
groupDataFile.createNewFile();
|
||||
outStream = new BufferedWriter(new FileWriter(groupDataFile));
|
||||
|
||||
//first line is number of bonus blocks
|
||||
outStream.write(String.valueOf(currentValue));
|
||||
outStream.newLine();
|
||||
}
|
||||
|
||||
//if any problem, log it
|
||||
catch(Exception e)
|
||||
{
|
||||
GriefPrevention.AddLogEntry("Unexpected exception saving data for group \"" + groupName + "\": " + e.getMessage());
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
//close the file
|
||||
if(outStream != null)
|
||||
{
|
||||
outStream.close();
|
||||
}
|
||||
}
|
||||
catch(IOException exception){}
|
||||
}
|
||||
|
||||
synchronized void migrateData(DatabaseDataStore databaseStore)
|
||||
{
|
||||
//migrate claims
|
||||
for(int i = 0; i < this.claims.size(); i++)
|
||||
{
|
||||
Claim claim = this.claims.get(i);
|
||||
databaseStore.addClaim(claim, true);
|
||||
for(Claim child : claim.children)
|
||||
{
|
||||
databaseStore.addClaim(child, true);
|
||||
}
|
||||
}
|
||||
|
||||
//migrate groups
|
||||
Iterator<String> groupNamesEnumerator = this.permissionToBonusBlocksMap.keySet().iterator();
|
||||
while(groupNamesEnumerator.hasNext())
|
||||
{
|
||||
String groupName = groupNamesEnumerator.next();
|
||||
databaseStore.saveGroupBonusBlocks(groupName, this.permissionToBonusBlocksMap.get(groupName));
|
||||
}
|
||||
|
||||
//migrate players
|
||||
File playerDataFolder = new File(playerDataFolderPath);
|
||||
File [] files = playerDataFolder.listFiles();
|
||||
for(int i = 0; i < files.length; i++)
|
||||
{
|
||||
File file = files[i];
|
||||
if(!file.isFile()) continue; //avoids folders
|
||||
if(file.isHidden()) continue; //avoid hidden files, which are likely not created by GriefPrevention
|
||||
|
||||
//all group data files start with a dollar sign. ignoring those, already handled above
|
||||
if(file.getName().startsWith("$")) continue;
|
||||
|
||||
//ignore special files
|
||||
if(file.getName().startsWith("_")) continue;
|
||||
if(file.getName().endsWith(".ignore")) continue;
|
||||
|
||||
UUID playerID = UUID.fromString(file.getName());
|
||||
databaseStore.savePlayerData(playerID, this.getPlayerData(playerID));
|
||||
this.clearCachedPlayerData(playerID);
|
||||
}
|
||||
|
||||
//migrate next claim ID
|
||||
if(this.nextClaimID > databaseStore.nextClaimID)
|
||||
{
|
||||
databaseStore.setNextClaimID(this.nextClaimID);
|
||||
}
|
||||
|
||||
//rename player and claim data folders so the migration won't run again
|
||||
int i = 0;
|
||||
File claimsBackupFolder;
|
||||
File playersBackupFolder;
|
||||
do
|
||||
{
|
||||
String claimsFolderBackupPath = claimDataFolderPath;
|
||||
if(i > 0) claimsFolderBackupPath += String.valueOf(i);
|
||||
claimsBackupFolder = new File(claimsFolderBackupPath);
|
||||
|
||||
String playersFolderBackupPath = playerDataFolderPath;
|
||||
if(i > 0) playersFolderBackupPath += String.valueOf(i);
|
||||
playersBackupFolder = new File(playersFolderBackupPath);
|
||||
i++;
|
||||
} while(claimsBackupFolder.exists() || playersBackupFolder.exists());
|
||||
|
||||
File claimsFolder = new File(claimDataFolderPath);
|
||||
File playersFolder = new File(playerDataFolderPath);
|
||||
|
||||
claimsFolder.renameTo(claimsBackupFolder);
|
||||
playersFolder.renameTo(playersBackupFolder);
|
||||
|
||||
GriefPrevention.AddLogEntry("Backed your file system data up to " + claimsBackupFolder.getName() + " and " + playersBackupFolder.getName() + ".");
|
||||
GriefPrevention.AddLogEntry("If your migration encountered any problems, you can restore those data with a quick copy/paste.");
|
||||
GriefPrevention.AddLogEntry("When you're satisfied that all your data have been safely migrated, consider deleting those folders.");
|
||||
}
|
||||
|
||||
@Override
|
||||
synchronized void close() { }
|
||||
|
||||
@Override
|
||||
int getSchemaVersionFromStorage()
|
||||
{
|
||||
File schemaVersionFile = new File(schemaVersionFilePath);
|
||||
if(schemaVersionFile.exists())
|
||||
{
|
||||
BufferedReader inStream = null;
|
||||
int schemaVersion = 0;
|
||||
try
|
||||
{
|
||||
inStream = new BufferedReader(new FileReader(schemaVersionFile.getAbsolutePath()));
|
||||
|
||||
//read the version number
|
||||
String line = inStream.readLine();
|
||||
|
||||
//try to parse into an int value
|
||||
schemaVersion = Integer.parseInt(line);
|
||||
}
|
||||
catch(Exception e){ }
|
||||
|
||||
try
|
||||
{
|
||||
if(inStream != null) inStream.close();
|
||||
}
|
||||
catch(IOException exception) {}
|
||||
|
||||
return schemaVersion;
|
||||
}
|
||||
else
|
||||
{
|
||||
this.updateSchemaVersionInStorage(0);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
void updateSchemaVersionInStorage(int versionToSet)
|
||||
{
|
||||
BufferedWriter outStream = null;
|
||||
|
||||
try
|
||||
{
|
||||
//open the file and write the new value
|
||||
File schemaVersionFile = new File(schemaVersionFilePath);
|
||||
schemaVersionFile.createNewFile();
|
||||
outStream = new BufferedWriter(new FileWriter(schemaVersionFile));
|
||||
|
||||
outStream.write(String.valueOf(versionToSet));
|
||||
}
|
||||
|
||||
//if any problem, log it
|
||||
catch(Exception e)
|
||||
{
|
||||
GriefPrevention.AddLogEntry("Unexpected exception saving schema version: " + e.getMessage());
|
||||
}
|
||||
|
||||
//close the file
|
||||
try
|
||||
{
|
||||
if(outStream != null) outStream.close();
|
||||
}
|
||||
catch(IOException exception) {}
|
||||
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,85 @@
|
||||
package me.ryanhamshire.GriefPrevention;
|
||||
|
||||
import java.io.File;
|
||||
import java.nio.charset.Charset;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
import com.google.common.io.Files;
|
||||
|
||||
//loads ignore data from file into a hash map
|
||||
class IgnoreLoaderThread extends Thread
|
||||
{
|
||||
private UUID playerToLoad;
|
||||
private ConcurrentHashMap<UUID, Boolean> destinationMap;
|
||||
|
||||
IgnoreLoaderThread(UUID playerToLoad, ConcurrentHashMap<UUID, Boolean> destinationMap)
|
||||
{
|
||||
this.playerToLoad = playerToLoad;
|
||||
this.destinationMap = destinationMap;
|
||||
this.setPriority(MIN_PRIORITY);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run()
|
||||
{
|
||||
File ignoreFile = new File(DataStore.playerDataFolderPath + File.separator + this.playerToLoad + ".ignore");
|
||||
|
||||
//if the file doesn't exist, there's nothing to do here
|
||||
if(!ignoreFile.exists()) return;
|
||||
|
||||
boolean needRetry = false;
|
||||
int retriesRemaining = 5;
|
||||
Exception latestException = null;
|
||||
do
|
||||
{
|
||||
try
|
||||
{
|
||||
needRetry = false;
|
||||
|
||||
//read the file content and immediately close it
|
||||
List<String> lines = Files.readLines(ignoreFile, Charset.forName("UTF-8"));
|
||||
|
||||
//each line is one ignore. asterisks indicate administrative ignores
|
||||
for(String line : lines)
|
||||
{
|
||||
boolean adminIgnore = false;
|
||||
if(line.startsWith("*"))
|
||||
{
|
||||
adminIgnore = true;
|
||||
line = line.substring(1);
|
||||
}
|
||||
try
|
||||
{
|
||||
UUID ignoredUUID = UUID.fromString(line);
|
||||
this.destinationMap.put(ignoredUUID, adminIgnore);
|
||||
}
|
||||
catch(IllegalArgumentException e){} //if a bad UUID, ignore the line
|
||||
}
|
||||
}
|
||||
|
||||
//if there's any problem with the file's content, retry up to 5 times with 5 milliseconds between
|
||||
catch(Exception e)
|
||||
{
|
||||
latestException = e;
|
||||
needRetry = true;
|
||||
retriesRemaining--;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
if(needRetry) Thread.sleep(5);
|
||||
}
|
||||
catch(InterruptedException exception) {}
|
||||
|
||||
}while(needRetry && retriesRemaining >= 0);
|
||||
|
||||
//if last attempt failed, log information about the problem
|
||||
if(needRetry)
|
||||
{
|
||||
GriefPrevention.AddLogEntry("Retry attempts exhausted. Unable to load ignore data for player \"" + playerToLoad.toString() + "\": " + latestException.toString());
|
||||
latestException.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
/*
|
||||
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.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,54 @@
|
||||
/*
|
||||
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.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
//ordered list of material info objects, for fast searching
|
||||
public class MaterialCollection
|
||||
{
|
||||
Set<MaterialInfo> materials = new HashSet<MaterialInfo>();
|
||||
|
||||
void Add(MaterialInfo material)
|
||||
{
|
||||
this.materials.add(material);
|
||||
}
|
||||
|
||||
boolean Contains(MaterialInfo material)
|
||||
{
|
||||
return this.materials.contains(material);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString()
|
||||
{
|
||||
return materials.toString();
|
||||
}
|
||||
|
||||
public int size()
|
||||
{
|
||||
return this.materials.size();
|
||||
}
|
||||
|
||||
public void clear()
|
||||
{
|
||||
this.materials.clear();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,96 @@
|
||||
/*
|
||||
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;
|
||||
|
||||
//represents a material or collection of materials
|
||||
|
||||
import org.bukkit.Material;
|
||||
|
||||
public class MaterialInfo
|
||||
{
|
||||
Material typeID;
|
||||
byte data;
|
||||
boolean allDataValues;
|
||||
String description;
|
||||
|
||||
public MaterialInfo(Material typeID, byte data, String description)
|
||||
{
|
||||
this.typeID = typeID;
|
||||
this.data = data;
|
||||
this.allDataValues = false;
|
||||
this.description = description;
|
||||
}
|
||||
|
||||
public MaterialInfo(Material typeID, String description)
|
||||
{
|
||||
this.typeID = typeID;
|
||||
this.data = 0;
|
||||
this.allDataValues = true;
|
||||
this.description = description;
|
||||
}
|
||||
|
||||
private MaterialInfo(Material typeID, byte data, boolean allDataValues, String description)
|
||||
{
|
||||
this.typeID = typeID;
|
||||
this.data = data;
|
||||
this.allDataValues = allDataValues;
|
||||
this.description = description;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString()
|
||||
{
|
||||
String returnValue = String.valueOf(this.typeID) + ":" + (this.allDataValues?"*":String.valueOf(this.data));
|
||||
if(this.description != null) returnValue += ":" + this.description;
|
||||
|
||||
return returnValue;
|
||||
}
|
||||
|
||||
public static MaterialInfo fromString(String string)
|
||||
{
|
||||
if(string == null || string.isEmpty()) return null;
|
||||
|
||||
String [] parts = string.split(":");
|
||||
if(parts.length < 3) return null;
|
||||
|
||||
try
|
||||
{
|
||||
Material typeID = Material.matchMaterial(parts[0]);
|
||||
|
||||
byte data;
|
||||
boolean allDataValues;
|
||||
if(parts[1].equals("*"))
|
||||
{
|
||||
allDataValues = true;
|
||||
data = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
allDataValues = false;
|
||||
data = Byte.parseByte(parts[1]);
|
||||
}
|
||||
|
||||
return new MaterialInfo(typeID, data, allDataValues, parts[2]);
|
||||
}
|
||||
catch(NumberFormatException exception)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,256 @@
|
||||
/*
|
||||
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;
|
||||
|
||||
public enum Messages
|
||||
{
|
||||
RespectingClaims,
|
||||
IgnoringClaims,
|
||||
SuccessfulAbandon,
|
||||
RestoreNatureActivate,
|
||||
RestoreNatureAggressiveActivate,
|
||||
FillModeActive,
|
||||
TransferClaimPermission,
|
||||
TransferClaimMissing,
|
||||
TransferClaimAdminOnly,
|
||||
PlayerNotFound2,
|
||||
TransferTopLevel,
|
||||
TransferSuccess,
|
||||
TrustListNoClaim,
|
||||
ClearPermsOwnerOnly,
|
||||
UntrustIndividualAllClaims,
|
||||
UntrustEveryoneAllClaims,
|
||||
NoPermissionTrust,
|
||||
ClearPermissionsOneClaim,
|
||||
UntrustIndividualSingleClaim,
|
||||
OnlySellBlocks,
|
||||
BlockPurchaseCost,
|
||||
ClaimBlockLimit,
|
||||
InsufficientFunds,
|
||||
PurchaseConfirmation,
|
||||
OnlyPurchaseBlocks,
|
||||
BlockSaleValue,
|
||||
NotEnoughBlocksForSale,
|
||||
BlockSaleConfirmation,
|
||||
AdminClaimsMode,
|
||||
BasicClaimsMode,
|
||||
SubdivisionMode,
|
||||
SubdivisionVideo2,
|
||||
DeleteClaimMissing,
|
||||
DeletionSubdivisionWarning,
|
||||
DeleteSuccess,
|
||||
CantDeleteAdminClaim,
|
||||
DeleteAllSuccess,
|
||||
NoDeletePermission,
|
||||
AllAdminDeleted,
|
||||
AdjustBlocksSuccess,
|
||||
NotTrappedHere,
|
||||
RescuePending,
|
||||
NonSiegeWorld,
|
||||
AlreadySieging,
|
||||
NotSiegableThere,
|
||||
SiegeTooFarAway,
|
||||
NoSiegeYourself,
|
||||
NoSiegeDefenseless,
|
||||
AlreadyUnderSiegePlayer,
|
||||
AlreadyUnderSiegeArea,
|
||||
NoSiegeAdminClaim,
|
||||
SiegeOnCooldown,
|
||||
SiegeAlert,
|
||||
SiegeConfirmed,
|
||||
AbandonClaimMissing,
|
||||
NotYourClaim,
|
||||
DeleteTopLevelClaim,
|
||||
AbandonSuccess,
|
||||
CantGrantThatPermission,
|
||||
GrantPermissionNoClaim,
|
||||
GrantPermissionConfirmation,
|
||||
ManageUniversalPermissionsInstruction,
|
||||
ManageOneClaimPermissionsInstruction,
|
||||
CollectivePublic,
|
||||
BuildPermission,
|
||||
ContainersPermission,
|
||||
AccessPermission,
|
||||
PermissionsPermission,
|
||||
LocationCurrentClaim,
|
||||
LocationAllClaims,
|
||||
PvPImmunityStart,
|
||||
SiegeNoDrop,
|
||||
DonateItemsInstruction,
|
||||
ChestFull,
|
||||
DonationSuccess,
|
||||
PlayerTooCloseForFire2,
|
||||
TooDeepToClaim,
|
||||
ChestClaimConfirmation,
|
||||
AutomaticClaimNotification,
|
||||
UnprotectedChestWarning,
|
||||
ThatPlayerPvPImmune,
|
||||
CantFightWhileImmune,
|
||||
NoDamageClaimedEntity,
|
||||
ShovelBasicClaimMode,
|
||||
RemainingBlocks,
|
||||
CreativeBasicsVideo2,
|
||||
SurvivalBasicsVideo2,
|
||||
TrappedChatKeyword,
|
||||
TrappedInstructions,
|
||||
PvPNoDrop,
|
||||
SiegeNoTeleport,
|
||||
BesiegedNoTeleport,
|
||||
SiegeNoContainers,
|
||||
PvPNoContainers,
|
||||
PvPImmunityEnd,
|
||||
NoBedPermission,
|
||||
NoWildernessBuckets,
|
||||
NoLavaNearOtherPlayer,
|
||||
TooFarAway,
|
||||
BlockNotClaimed,
|
||||
BlockClaimed,
|
||||
SiegeNoShovel,
|
||||
RestoreNaturePlayerInChunk,
|
||||
NoCreateClaimPermission,
|
||||
ResizeNeedMoreBlocks,
|
||||
NoCreativeUnClaim,
|
||||
ClaimResizeSuccess,
|
||||
ResizeFailOverlap,
|
||||
ResizeStart,
|
||||
ResizeFailOverlapSubdivision,
|
||||
SubdivisionStart,
|
||||
CreateSubdivisionOverlap,
|
||||
SubdivisionSuccess,
|
||||
CreateClaimFailOverlap,
|
||||
CreateClaimFailOverlapOtherPlayer,
|
||||
ClaimsDisabledWorld,
|
||||
ClaimStart,
|
||||
NewClaimTooNarrow,
|
||||
CreateClaimInsufficientBlocks,
|
||||
AbandonClaimAdvertisement,
|
||||
CreateClaimFailOverlapShort,
|
||||
CreateClaimSuccess,
|
||||
SiegeWinDoorsOpen,
|
||||
RescueAbortedMoved,
|
||||
SiegeDoorsLockedEjection,
|
||||
NoModifyDuringSiege,
|
||||
OnlyOwnersModifyClaims,
|
||||
NoBuildUnderSiege,
|
||||
NoBuildPvP,
|
||||
NoBuildPermission,
|
||||
NonSiegeMaterial,
|
||||
NoOwnerBuildUnderSiege,
|
||||
NoAccessPermission,
|
||||
NoContainersSiege,
|
||||
NoContainersPermission,
|
||||
OwnerNameForAdminClaims,
|
||||
ClaimTooSmallForEntities,
|
||||
TooManyEntitiesInClaim,
|
||||
YouHaveNoClaims,
|
||||
ConfirmFluidRemoval,
|
||||
AutoBanNotify,
|
||||
AdjustGroupBlocksSuccess,
|
||||
InvalidPermissionID,
|
||||
HowToClaimRegex,
|
||||
NoBuildOutsideClaims,
|
||||
PlayerOfflineTime,
|
||||
BuildingOutsideClaims,
|
||||
TrappedWontWorkHere,
|
||||
CommandBannedInPvP,
|
||||
UnclaimCleanupWarning,
|
||||
BuySellNotConfigured,
|
||||
NoTeleportPvPCombat,
|
||||
NoTNTDamageAboveSeaLevel,
|
||||
NoTNTDamageClaims,
|
||||
IgnoreClaimsAdvertisement,
|
||||
NoPermissionForCommand,
|
||||
ClaimsListNoPermission,
|
||||
ExplosivesDisabled,
|
||||
ExplosivesEnabled,
|
||||
ClaimExplosivesAdvertisement,
|
||||
PlayerInPvPSafeZone,
|
||||
NoPistonsOutsideClaims,
|
||||
SoftMuted,
|
||||
UnSoftMuted,
|
||||
DropUnlockAdvertisement,
|
||||
PickupBlockedExplanation,
|
||||
DropUnlockConfirmation,
|
||||
DropUnlockOthersConfirmation,
|
||||
AdvertiseACandACB,
|
||||
AdvertiseAdminClaims,
|
||||
AdvertiseACB,
|
||||
NotYourPet,
|
||||
PetGiveawayConfirmation,
|
||||
PetTransferCancellation,
|
||||
ReadyToTransferPet,
|
||||
AvoidGriefClaimLand,
|
||||
BecomeMayor,
|
||||
ClaimCreationFailedOverClaimCountLimit,
|
||||
CreateClaimFailOverlapRegion,
|
||||
ResizeFailOverlapRegion,
|
||||
NoBuildPortalPermission,
|
||||
ShowNearbyClaims,
|
||||
NoChatUntilMove,
|
||||
SiegeImmune,
|
||||
SetClaimBlocksSuccess,
|
||||
IgnoreConfirmation,
|
||||
NotIgnoringPlayer,
|
||||
UnIgnoreConfirmation,
|
||||
SeparateConfirmation,
|
||||
UnSeparateConfirmation,
|
||||
NotIgnoringAnyone,
|
||||
TrustListHeader,
|
||||
Manage,
|
||||
Build,
|
||||
Containers,
|
||||
Access,
|
||||
HasSubclaimRestriction,
|
||||
StartBlockMath,
|
||||
ClaimsListHeader,
|
||||
ContinueBlockMath,
|
||||
EndBlockMath,
|
||||
NoClaimDuringPvP,
|
||||
UntrustAllOwnerOnly,
|
||||
ManagersDontUntrustManagers,
|
||||
BookAuthor,
|
||||
BookTitle,
|
||||
BookIntro,
|
||||
BookDisabledChestClaims,
|
||||
BookUsefulCommands,
|
||||
BookLink,
|
||||
BookTools,
|
||||
ResizeClaimTooNarrow,
|
||||
ResizeClaimInsufficientArea,
|
||||
NoProfanity,
|
||||
PlayerNotIgnorable,
|
||||
NoEnoughBlocksForChestClaim,
|
||||
IsIgnoringYou,
|
||||
MustHoldModificationToolForThat,
|
||||
StandInClaimToResize,
|
||||
ClaimsExtendToSky,
|
||||
ClaimsAutoExtendDownward,
|
||||
MinimumRadius,
|
||||
RadiusRequiresGoldenShovel,
|
||||
ClaimTooSmallForActiveBlocks,
|
||||
TooManyActiveBlocksInClaim,
|
||||
ConsoleOnlyCommand,
|
||||
WorldNotFound,
|
||||
AdjustBlocksAllSuccess,
|
||||
TooMuchIpOverlap,
|
||||
StandInSubclaim,
|
||||
SubclaimRestricted,
|
||||
SubclaimUnrestricted,
|
||||
NetherPortalTrapDetectionMessage
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
package me.ryanhamshire.GriefPrevention;
|
||||
|
||||
import java.util.UUID;
|
||||
import org.bukkit.Location;
|
||||
import org.bukkit.inventory.ItemStack;
|
||||
|
||||
class PendingItemProtection
|
||||
{
|
||||
public Location location;
|
||||
public UUID owner;
|
||||
long expirationTimestamp;
|
||||
ItemStack itemStack;
|
||||
|
||||
public PendingItemProtection(Location location, UUID owner, long expirationTimestamp, ItemStack itemStack)
|
||||
{
|
||||
this.location = location;
|
||||
this.owner = owner;
|
||||
this.expirationTimestamp = expirationTimestamp;
|
||||
this.itemStack = itemStack;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,352 @@
|
||||
/*
|
||||
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 java.net.InetAddress;
|
||||
import java.util.Calendar;
|
||||
import java.util.Date;
|
||||
import java.util.UUID;
|
||||
import java.util.Vector;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
import me.ryanhamshire.GriefPrevention.Claim;
|
||||
import me.ryanhamshire.GriefPrevention.GriefPrevention;
|
||||
import me.ryanhamshire.GriefPrevention.ShovelMode;
|
||||
import me.ryanhamshire.GriefPrevention.SiegeData;
|
||||
import me.ryanhamshire.GriefPrevention.Visualization;
|
||||
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.Location;
|
||||
import org.bukkit.OfflinePlayer;
|
||||
import org.bukkit.entity.Player;
|
||||
|
||||
//holds all of GriefPrevention's player-tied data
|
||||
public class PlayerData
|
||||
{
|
||||
//the player's ID
|
||||
public UUID playerID;
|
||||
|
||||
//the player's claims
|
||||
private Vector<Claim> claims = null;
|
||||
|
||||
//how many claim blocks the player has earned via play time
|
||||
private Integer accruedClaimBlocks = null;
|
||||
|
||||
//temporary holding area to avoid opening data files too early
|
||||
private int newlyAccruedClaimBlocks = 0;
|
||||
|
||||
//where this player was the last time we checked on him for earning claim blocks
|
||||
public Location lastAfkCheckLocation = null;
|
||||
|
||||
//how many claim blocks the player has been gifted by admins, or purchased via economy integration
|
||||
private Integer bonusClaimBlocks = null;
|
||||
|
||||
//what "mode" the shovel is in determines what it will do when it's used
|
||||
public ShovelMode shovelMode = ShovelMode.Basic;
|
||||
|
||||
//radius for restore nature fill mode
|
||||
int fillRadius = 0;
|
||||
|
||||
//last place the player used the shovel, useful in creating and resizing claims,
|
||||
//because the player must use the shovel twice in those instances
|
||||
public Location lastShovelLocation = null;
|
||||
|
||||
//the claim this player is currently resizing
|
||||
public Claim claimResizing = null;
|
||||
|
||||
//the claim this player is currently subdividing
|
||||
public Claim claimSubdividing = null;
|
||||
|
||||
//whether or not the player has a pending /trapped rescue
|
||||
public boolean pendingTrapped = false;
|
||||
|
||||
//whether this player was recently warned about building outside land claims
|
||||
boolean warnedAboutBuildingOutsideClaims = false;
|
||||
|
||||
//timestamp when last siege ended (where this player was the defender)
|
||||
long lastSiegeEndTimeStamp = 0;
|
||||
|
||||
//whether the player was kicked (set and used during logout)
|
||||
boolean wasKicked = false;
|
||||
|
||||
//visualization
|
||||
public Visualization currentVisualization = null;
|
||||
|
||||
//anti-camping pvp protection
|
||||
public boolean pvpImmune = false;
|
||||
public long lastSpawn = 0;
|
||||
|
||||
//ignore claims mode
|
||||
public boolean ignoreClaims = false;
|
||||
|
||||
//the last claim this player was in, that we know of
|
||||
public Claim lastClaim = null;
|
||||
|
||||
//siege
|
||||
public SiegeData siegeData = null;
|
||||
|
||||
//pvp
|
||||
public long lastPvpTimestamp = 0;
|
||||
public String lastPvpPlayer = "";
|
||||
|
||||
//safety confirmation for deleting multi-subdivision claims
|
||||
public boolean warnedAboutMajorDeletion = false;
|
||||
|
||||
public InetAddress ipAddress;
|
||||
|
||||
//for addons to set per-player claim limits. Any negative value will use config's value
|
||||
private int AccruedClaimBlocksLimit = -1;
|
||||
|
||||
//whether or not this player has received a message about unlocking death drops since his last death
|
||||
boolean receivedDropUnlockAdvertisement = false;
|
||||
|
||||
//whether or not this player's dropped items (on death) are unlocked for other players to pick up
|
||||
boolean dropsAreUnlocked = false;
|
||||
|
||||
//message to send to player after he respawns
|
||||
String messageOnRespawn = null;
|
||||
|
||||
//player which a pet will be given to when it's right-clicked
|
||||
OfflinePlayer petGiveawayRecipient = null;
|
||||
|
||||
//timestamp for last "you're building outside your land claims" message
|
||||
Long buildWarningTimestamp = null;
|
||||
|
||||
//spot where a player can't talk, used to mute new players until they've moved a little
|
||||
//this is an anti-bot strategy.
|
||||
Location noChatLocation = null;
|
||||
|
||||
//ignore list
|
||||
//true means invisible (admin-forced ignore), false means player-created ignore
|
||||
public ConcurrentHashMap<UUID, Boolean> ignoredPlayers = new ConcurrentHashMap<UUID, Boolean>();
|
||||
public boolean ignoreListChanged = false;
|
||||
|
||||
//profanity warning, once per play session
|
||||
boolean profanityWarned = false;
|
||||
|
||||
//whether or not this player is "in" pvp combat
|
||||
public boolean inPvpCombat()
|
||||
{
|
||||
if(this.lastPvpTimestamp == 0) return false;
|
||||
|
||||
long now = Calendar.getInstance().getTimeInMillis();
|
||||
|
||||
long elapsed = now - this.lastPvpTimestamp;
|
||||
|
||||
if(elapsed > GriefPrevention.instance.config_pvp_combatTimeoutSeconds * 1000) //X seconds
|
||||
{
|
||||
this.lastPvpTimestamp = 0;
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
//the number of claim blocks a player has available for claiming land
|
||||
public int getRemainingClaimBlocks()
|
||||
{
|
||||
int remainingBlocks = this.getAccruedClaimBlocks() + this.getBonusClaimBlocks() + GriefPrevention.instance.dataStore.getGroupBonusBlocks(this.playerID);
|
||||
for(int i = 0; i < this.getClaims().size(); i++)
|
||||
{
|
||||
Claim claim = this.getClaims().get(i);
|
||||
remainingBlocks -= claim.getArea();
|
||||
}
|
||||
|
||||
return remainingBlocks;
|
||||
}
|
||||
|
||||
//don't load data from secondary storage until it's needed
|
||||
public int getAccruedClaimBlocks()
|
||||
{
|
||||
if(this.accruedClaimBlocks == null) this.loadDataFromSecondaryStorage();
|
||||
|
||||
//update claim blocks with any he has accrued during his current play session
|
||||
if(this.newlyAccruedClaimBlocks > 0)
|
||||
{
|
||||
int accruedLimit = this.getAccruedClaimBlocksLimit();
|
||||
|
||||
//if over the limit before adding blocks, leave it as-is, because the limit may have changed AFTER he accrued the blocks
|
||||
if(this.accruedClaimBlocks < accruedLimit)
|
||||
{
|
||||
//move any in the holding area
|
||||
int newTotal = this.accruedClaimBlocks + this.newlyAccruedClaimBlocks;
|
||||
|
||||
//respect limits
|
||||
this.accruedClaimBlocks = Math.min(newTotal, accruedLimit);
|
||||
}
|
||||
|
||||
this.newlyAccruedClaimBlocks = 0;
|
||||
return this.accruedClaimBlocks;
|
||||
}
|
||||
|
||||
return accruedClaimBlocks;
|
||||
}
|
||||
|
||||
public void setAccruedClaimBlocks(Integer accruedClaimBlocks)
|
||||
{
|
||||
this.accruedClaimBlocks = accruedClaimBlocks;
|
||||
this.newlyAccruedClaimBlocks = 0;
|
||||
}
|
||||
|
||||
public int getBonusClaimBlocks()
|
||||
{
|
||||
if(this.bonusClaimBlocks == null) this.loadDataFromSecondaryStorage();
|
||||
return bonusClaimBlocks;
|
||||
}
|
||||
|
||||
public void setBonusClaimBlocks(Integer bonusClaimBlocks)
|
||||
{
|
||||
this.bonusClaimBlocks = bonusClaimBlocks;
|
||||
}
|
||||
|
||||
private void loadDataFromSecondaryStorage()
|
||||
{
|
||||
//reach out to secondary storage to get any data there
|
||||
PlayerData storageData = GriefPrevention.instance.dataStore.getPlayerDataFromStorage(this.playerID);
|
||||
|
||||
if(this.accruedClaimBlocks == null)
|
||||
{
|
||||
if(storageData.accruedClaimBlocks != null)
|
||||
{
|
||||
this.accruedClaimBlocks = storageData.accruedClaimBlocks;
|
||||
|
||||
//ensure at least minimum accrued are accrued (in case of settings changes to increase initial amount)
|
||||
if(GriefPrevention.instance.config_advanced_fixNegativeClaimblockAmounts && (this.accruedClaimBlocks < GriefPrevention.instance.config_claims_initialBlocks))
|
||||
{
|
||||
this.accruedClaimBlocks = GriefPrevention.instance.config_claims_initialBlocks;
|
||||
}
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
this.accruedClaimBlocks = GriefPrevention.instance.config_claims_initialBlocks;
|
||||
}
|
||||
}
|
||||
|
||||
if(this.bonusClaimBlocks == null)
|
||||
{
|
||||
if(storageData.bonusClaimBlocks != null)
|
||||
{
|
||||
this.bonusClaimBlocks = storageData.bonusClaimBlocks;
|
||||
}
|
||||
else
|
||||
{
|
||||
this.bonusClaimBlocks = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public Vector<Claim> getClaims()
|
||||
{
|
||||
if(this.claims == null)
|
||||
{
|
||||
this.claims = new Vector<Claim>();
|
||||
|
||||
//find all the claims belonging to this player and note them for future reference
|
||||
DataStore dataStore = GriefPrevention.instance.dataStore;
|
||||
int totalClaimsArea = 0;
|
||||
for(int i = 0; i < dataStore.claims.size(); i++)
|
||||
{
|
||||
Claim claim = dataStore.claims.get(i);
|
||||
if(!claim.inDataStore)
|
||||
{
|
||||
dataStore.claims.remove(i--);
|
||||
continue;
|
||||
}
|
||||
if(playerID.equals(claim.ownerID))
|
||||
{
|
||||
this.claims.add(claim);
|
||||
totalClaimsArea += claim.getArea();
|
||||
}
|
||||
}
|
||||
|
||||
//ensure player has claim blocks for his claims, and at least the minimum accrued
|
||||
this.loadDataFromSecondaryStorage();
|
||||
|
||||
//if total claimed area is more than total blocks available
|
||||
int totalBlocks = this.accruedClaimBlocks + this.getBonusClaimBlocks() + GriefPrevention.instance.dataStore.getGroupBonusBlocks(this.playerID);
|
||||
if(GriefPrevention.instance.config_advanced_fixNegativeClaimblockAmounts && totalBlocks < totalClaimsArea)
|
||||
{
|
||||
OfflinePlayer player = GriefPrevention.instance.getServer().getOfflinePlayer(this.playerID);
|
||||
GriefPrevention.AddLogEntry(player.getName() + " has more claimed land than blocks available. Adding blocks to fix.", CustomLogEntryTypes.Debug, true);
|
||||
GriefPrevention.AddLogEntry(player.getName() + " Accrued blocks: " + this.getAccruedClaimBlocks() + " Bonus blocks: " + this.getBonusClaimBlocks(), CustomLogEntryTypes.Debug, true);
|
||||
GriefPrevention.AddLogEntry("Total blocks: " + totalBlocks + " Total claimed area: " + totalClaimsArea, CustomLogEntryTypes.Debug, true);
|
||||
for(Claim claim : this.claims)
|
||||
{
|
||||
if(!claim.inDataStore) continue;
|
||||
GriefPrevention.AddLogEntry(
|
||||
GriefPrevention.getfriendlyLocationString(claim.getLesserBoundaryCorner()) + " // "
|
||||
+ GriefPrevention.getfriendlyLocationString(claim.getGreaterBoundaryCorner()) + " = "
|
||||
+ claim.getArea()
|
||||
, CustomLogEntryTypes.Debug, true);
|
||||
}
|
||||
|
||||
//try to fix it by adding to accrued blocks
|
||||
this.accruedClaimBlocks = totalClaimsArea; //Set accrued blocks to equal total claims
|
||||
int accruedLimit = this.getAccruedClaimBlocksLimit();
|
||||
this.accruedClaimBlocks = Math.min(accruedLimit, this.accruedClaimBlocks); //set accrued blocks to maximum limit, if it's smaller
|
||||
GriefPrevention.AddLogEntry("New accrued blocks: " + this.accruedClaimBlocks, CustomLogEntryTypes.Debug, true);
|
||||
|
||||
//Recalculate total blocks (accrued + bonus + permission group bonus)
|
||||
totalBlocks = this.accruedClaimBlocks + this.getBonusClaimBlocks() + GriefPrevention.instance.dataStore.getGroupBonusBlocks(this.playerID);
|
||||
GriefPrevention.AddLogEntry("New total blocks: " + totalBlocks, CustomLogEntryTypes.Debug, true);
|
||||
|
||||
//if that didn't fix it, then make up the difference with bonus blocks
|
||||
if(totalBlocks < totalClaimsArea)
|
||||
{
|
||||
int bonusBlocksToAdd = totalClaimsArea - totalBlocks;
|
||||
this.bonusClaimBlocks += bonusBlocksToAdd;
|
||||
GriefPrevention.AddLogEntry("Accrued blocks weren't enough. Adding " + bonusBlocksToAdd + " bonus blocks.", CustomLogEntryTypes.Debug, true);
|
||||
}
|
||||
GriefPrevention.AddLogEntry(player.getName() + " Accrued blocks: " + this.getAccruedClaimBlocks() + " Bonus blocks: " + this.getBonusClaimBlocks() + " Group Bonus Blocks: " + GriefPrevention.instance.dataStore.getGroupBonusBlocks(this.playerID), CustomLogEntryTypes.Debug, true);
|
||||
//Recalculate total blocks (accrued + bonus + permission group bonus)
|
||||
totalBlocks = this.accruedClaimBlocks + this.getBonusClaimBlocks() + GriefPrevention.instance.dataStore.getGroupBonusBlocks(this.playerID);
|
||||
GriefPrevention.AddLogEntry("Total blocks: " + totalBlocks + " Total claimed area: " + totalClaimsArea, CustomLogEntryTypes.Debug, true);
|
||||
GriefPrevention.AddLogEntry("Remaining claim blocks to use: " + this.getRemainingClaimBlocks() + " (should be 0)", CustomLogEntryTypes.Debug, true);
|
||||
}
|
||||
}
|
||||
|
||||
for(int i = 0; i < this.claims.size(); i++)
|
||||
{
|
||||
if(!claims.get(i).inDataStore)
|
||||
{
|
||||
claims.remove(i--);
|
||||
}
|
||||
}
|
||||
|
||||
return claims;
|
||||
}
|
||||
|
||||
//Limit can be changed by addons
|
||||
public int getAccruedClaimBlocksLimit()
|
||||
{
|
||||
if (this.AccruedClaimBlocksLimit < 0)
|
||||
return GriefPrevention.instance.config_claims_maxAccruedBlocks_default;
|
||||
return this.AccruedClaimBlocksLimit;
|
||||
}
|
||||
|
||||
public void setAccruedClaimBlocksLimit(int limit)
|
||||
{
|
||||
this.AccruedClaimBlocksLimit = limit;
|
||||
}
|
||||
|
||||
public void accrueBlocks(int howMany)
|
||||
{
|
||||
this.newlyAccruedClaimBlocks += howMany;
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,71 @@
|
||||
/*
|
||||
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 me.ryanhamshire.GriefPrevention.events.PlayerKickBanEvent;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.entity.Player;
|
||||
|
||||
//kicks or bans a player
|
||||
//need a task for this because async threads (like the chat event handlers) can't kick or ban.
|
||||
//but they CAN schedule a task to run in the main thread to do that job
|
||||
class PlayerKickBanTask implements Runnable
|
||||
{
|
||||
//player to kick or ban
|
||||
private Player player;
|
||||
|
||||
//message to send player.
|
||||
private String reason;
|
||||
|
||||
//source of ban
|
||||
private String source;
|
||||
|
||||
//whether to ban
|
||||
private boolean ban;
|
||||
|
||||
public PlayerKickBanTask(Player player, String reason, String source, boolean ban)
|
||||
{
|
||||
this.player = player;
|
||||
this.reason = reason;
|
||||
this.source = source;
|
||||
this.ban = ban;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run()
|
||||
{
|
||||
PlayerKickBanEvent kickBanEvent = new PlayerKickBanEvent(player, reason, source, ban);
|
||||
Bukkit.getPluginManager().callEvent(kickBanEvent);
|
||||
|
||||
if (kickBanEvent.isCancelled())
|
||||
{
|
||||
return; // cancelled by a plugin
|
||||
}
|
||||
|
||||
if(this.ban)
|
||||
{
|
||||
//ban
|
||||
GriefPrevention.banPlayer(this.player, this.reason, this.source);
|
||||
}
|
||||
else if(this.player.isOnline())
|
||||
{
|
||||
this.player.kickPlayer(this.reason);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
/*
|
||||
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.Location;
|
||||
import org.bukkit.entity.Player;
|
||||
|
||||
//tries to rescue a trapped player from a claim where he doesn't have permission to save himself
|
||||
//related to the /trapped slash command
|
||||
//this does run in the main thread, so it's okay to make non-thread-safe calls
|
||||
class PlayerRescueTask implements Runnable
|
||||
{
|
||||
//original location where /trapped was used
|
||||
private Location location;
|
||||
|
||||
//rescue destination, may be decided at instantiation or at execution
|
||||
private Location destination;
|
||||
|
||||
//player data
|
||||
private Player player;
|
||||
|
||||
public PlayerRescueTask(Player player, Location location, Location destination)
|
||||
{
|
||||
this.player = player;
|
||||
this.location = location;
|
||||
this.destination = destination;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run()
|
||||
{
|
||||
//if he logged out, don't do anything
|
||||
if(!player.isOnline()) return;
|
||||
|
||||
//he no longer has a pending /trapped slash command, so he can try to use it again now
|
||||
PlayerData playerData = GriefPrevention.instance.dataStore.getPlayerData(player.getUniqueId());
|
||||
playerData.pendingTrapped = false;
|
||||
|
||||
//if the player moved three or more blocks from where he used /trapped, admonish him and don't save him
|
||||
if(player.getLocation().distance(this.location) > 3)
|
||||
{
|
||||
GriefPrevention.sendMessage(player, TextMode.Err, Messages.RescueAbortedMoved);
|
||||
return;
|
||||
}
|
||||
|
||||
//otherwise find a place to teleport him
|
||||
if(this.destination == null)
|
||||
{
|
||||
this.destination = GriefPrevention.instance.ejectPlayer(this.player);
|
||||
}
|
||||
else
|
||||
{
|
||||
player.teleport(this.destination);
|
||||
}
|
||||
|
||||
//log entry, in case admins want to investigate the "trap"
|
||||
GriefPrevention.AddLogEntry("Rescued trapped player " + player.getName() + " from " + GriefPrevention.getfriendlyLocationString(this.location) + " to " + GriefPrevention.getfriendlyLocationString(this.destination) + ".");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
/*
|
||||
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.entity.Player;
|
||||
|
||||
//sends a message to a player
|
||||
//used to send delayed messages, for example help text triggered by a player's chat
|
||||
class PvPImmunityValidationTask implements Runnable
|
||||
{
|
||||
private Player player;
|
||||
|
||||
public PvPImmunityValidationTask(Player player)
|
||||
{
|
||||
this.player = player;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run()
|
||||
{
|
||||
if(!player.isOnline()) return;
|
||||
|
||||
PlayerData playerData = GriefPrevention.instance.dataStore.getPlayerData(player.getUniqueId());
|
||||
if(!playerData.pvpImmune) return;
|
||||
|
||||
//check the player's inventory for anything
|
||||
if(!GriefPrevention.isInventoryEmpty(player))
|
||||
{
|
||||
//if found, cancel invulnerability and notify
|
||||
playerData.pvpImmune = false;
|
||||
GriefPrevention.sendMessage(player, TextMode.Warn, Messages.PvPImmunityEnd);
|
||||
}
|
||||
else
|
||||
{
|
||||
//otherwise check again in one minute
|
||||
GriefPrevention.instance.getServer().getScheduler().scheduleSyncDelayedTask(GriefPrevention.instance, this, 1200L);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,130 @@
|
||||
/*
|
||||
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.ArrayList;
|
||||
|
||||
import org.bukkit.Chunk;
|
||||
import org.bukkit.Location;
|
||||
import org.bukkit.Material;
|
||||
import org.bukkit.block.Block;
|
||||
import org.bukkit.block.BlockFace;
|
||||
import org.bukkit.entity.Animals;
|
||||
import org.bukkit.entity.Entity;
|
||||
import org.bukkit.entity.Hanging;
|
||||
import org.bukkit.entity.Player;
|
||||
|
||||
//this main thread task takes the output from the RestoreNatureProcessingTask\
|
||||
//and updates the world accordingly
|
||||
class RestoreNatureExecutionTask implements Runnable
|
||||
{
|
||||
//results from processing thread
|
||||
//will be applied to the world
|
||||
private BlockSnapshot[][][] snapshots;
|
||||
|
||||
//boundaries for changes
|
||||
private int miny;
|
||||
private Location lesserCorner;
|
||||
private Location greaterCorner;
|
||||
|
||||
//player who should be notified about the result (will see a visualization when the restoration is complete)
|
||||
private Player player;
|
||||
|
||||
public RestoreNatureExecutionTask(BlockSnapshot[][][] snapshots, int miny, Location lesserCorner, Location greaterCorner, Player player)
|
||||
{
|
||||
this.snapshots = snapshots;
|
||||
this.miny = miny;
|
||||
this.lesserCorner = lesserCorner;
|
||||
this.greaterCorner = greaterCorner;
|
||||
this.player = player;
|
||||
}
|
||||
|
||||
@SuppressWarnings("deprecation")
|
||||
@Override
|
||||
public void run()
|
||||
{
|
||||
//apply changes to the world, but ONLY to unclaimed blocks
|
||||
//note that the edge of the results is not applied (the 1-block-wide band around the outside of the chunk)
|
||||
//those data were sent to the processing thread for referernce purposes, but aren't part of the area selected for restoration
|
||||
Claim cachedClaim = null;
|
||||
for(int x = 1; x < this.snapshots.length - 1; x++)
|
||||
{
|
||||
for(int z = 1; z < this.snapshots[0][0].length - 1; z++)
|
||||
{
|
||||
for(int y = this.miny; y < this.snapshots[0].length; y++)
|
||||
{
|
||||
BlockSnapshot blockUpdate = this.snapshots[x][y][z];
|
||||
Block currentBlock = blockUpdate.location.getBlock();
|
||||
if(blockUpdate.typeId != currentBlock.getType()|| blockUpdate.data != currentBlock.getData())
|
||||
{
|
||||
Claim claim = GriefPrevention.instance.dataStore.getClaimAt(blockUpdate.location, false, cachedClaim);
|
||||
if(claim != null)
|
||||
{
|
||||
cachedClaim = claim;
|
||||
break;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
currentBlock.setType(blockUpdate.typeId, false);
|
||||
currentBlock.setData(blockUpdate.data, false);
|
||||
}
|
||||
catch(IllegalArgumentException e)
|
||||
{
|
||||
//just don't update this block and continue trying to update other blocks
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//clean up any entities in the chunk, ensure no players are suffocated
|
||||
Chunk chunk = this.lesserCorner.getChunk();
|
||||
Entity [] entities = chunk.getEntities();
|
||||
for(int i = 0; i < entities.length; i++)
|
||||
{
|
||||
Entity entity = entities[i];
|
||||
if(!(entity instanceof Player || entity instanceof Animals))
|
||||
{
|
||||
//hanging entities (paintings, item frames) are protected when they're in land claims
|
||||
if(!(entity instanceof Hanging) || GriefPrevention.instance.dataStore.getClaimAt(entity.getLocation(), false, null) == null)
|
||||
{
|
||||
//everything else is removed
|
||||
entity.remove();
|
||||
}
|
||||
}
|
||||
|
||||
//for players, always ensure there's air where the player is standing
|
||||
else
|
||||
{
|
||||
Block feetBlock = entity.getLocation().getBlock();
|
||||
feetBlock.setType(Material.AIR);
|
||||
feetBlock.getRelative(BlockFace.UP).setType(Material.AIR);
|
||||
}
|
||||
}
|
||||
|
||||
//show visualization to player who started the restoration
|
||||
if(player != null)
|
||||
{
|
||||
Claim claim = new Claim(lesserCorner, greaterCorner, null, new ArrayList<String>(), new ArrayList<String>(), new ArrayList<String>(), new ArrayList<String>(), null);
|
||||
Visualization visualization = Visualization.FromClaim(claim, player.getLocation().getBlockY(), VisualizationType.RestoreNature, player.getLocation());
|
||||
Visualization.Apply(player, visualization);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,810 @@
|
||||
/*
|
||||
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.ArrayList;
|
||||
|
||||
import org.bukkit.Location;
|
||||
import org.bukkit.Material;
|
||||
import org.bukkit.World.Environment;
|
||||
import org.bukkit.block.Biome;
|
||||
import org.bukkit.entity.Player;
|
||||
|
||||
//non-main-thread task which processes world data to repair the unnatural
|
||||
//after processing is complete, creates a main thread task to make the necessary changes to the world
|
||||
class RestoreNatureProcessingTask implements Runnable
|
||||
{
|
||||
//world information captured from the main thread
|
||||
//will be updated and sent back to main thread to be applied to the world
|
||||
private BlockSnapshot[][][] snapshots;
|
||||
|
||||
//other information collected from the main thread.
|
||||
//not to be updated, only to be passed back to main thread to provide some context about the operation
|
||||
private int miny;
|
||||
private Environment environment;
|
||||
private Location lesserBoundaryCorner;
|
||||
private Location greaterBoundaryCorner;
|
||||
private Player player; //absolutely must not be accessed. not thread safe.
|
||||
private Biome biome;
|
||||
private boolean creativeMode;
|
||||
private int seaLevel;
|
||||
private boolean aggressiveMode;
|
||||
|
||||
//two lists of materials
|
||||
private ArrayList<Material> notAllowedToHang; //natural blocks which don't naturally hang in their air
|
||||
private ArrayList<Material> playerBlocks; //a "complete" list of player-placed blocks. MUST BE MAINTAINED as patches introduce more
|
||||
|
||||
@SuppressWarnings("deprecation")
|
||||
public RestoreNatureProcessingTask(BlockSnapshot[][][] snapshots, int miny, Environment environment, Biome biome, Location lesserBoundaryCorner, Location greaterBoundaryCorner, int seaLevel, boolean aggressiveMode, boolean creativeMode, Player player)
|
||||
{
|
||||
this.snapshots = snapshots;
|
||||
this.miny = miny;
|
||||
if(this.miny < 0) this.miny = 0;
|
||||
this.environment = environment;
|
||||
this.lesserBoundaryCorner = lesserBoundaryCorner;
|
||||
this.greaterBoundaryCorner = greaterBoundaryCorner;
|
||||
this.biome = biome;
|
||||
this.seaLevel = seaLevel;
|
||||
this.aggressiveMode = aggressiveMode;
|
||||
this.player = player;
|
||||
this.creativeMode = creativeMode;
|
||||
|
||||
this.notAllowedToHang = new ArrayList<Material>();
|
||||
this.notAllowedToHang.add(Material.DIRT);
|
||||
this.notAllowedToHang.add(Material.LONG_GRASS);
|
||||
this.notAllowedToHang.add(Material.SNOW);
|
||||
this.notAllowedToHang.add(Material.LOG);
|
||||
|
||||
if(this.aggressiveMode)
|
||||
{
|
||||
this.notAllowedToHang.add(Material.GRASS);
|
||||
this.notAllowedToHang.add(Material.STONE);
|
||||
}
|
||||
|
||||
this.playerBlocks = new ArrayList<Material>();
|
||||
this.playerBlocks.addAll(RestoreNatureProcessingTask.getPlayerBlocks(this.environment, this.biome));
|
||||
|
||||
//in aggressive or creative world 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 melons
|
||||
if(this.aggressiveMode || this.creativeMode)
|
||||
{
|
||||
this.playerBlocks.add(Material.IRON_ORE);
|
||||
this.playerBlocks.add(Material.GOLD_ORE);
|
||||
this.playerBlocks.add(Material.DIAMOND_ORE);
|
||||
this.playerBlocks.add(Material.MELON_BLOCK);
|
||||
this.playerBlocks.add(Material.MELON_STEM);
|
||||
this.playerBlocks.add(Material.BEDROCK);
|
||||
this.playerBlocks.add(Material.COAL_ORE);
|
||||
this.playerBlocks.add(Material.PUMPKIN);
|
||||
this.playerBlocks.add(Material.PUMPKIN_STEM);
|
||||
this.playerBlocks.add(Material.MELON);
|
||||
}
|
||||
|
||||
if(this.aggressiveMode)
|
||||
{
|
||||
this.playerBlocks.add(Material.LEAVES);
|
||||
this.playerBlocks.add(Material.LEAVES_2);
|
||||
this.playerBlocks.add(Material.LOG);
|
||||
this.playerBlocks.add(Material.LOG_2);
|
||||
this.playerBlocks.add(Material.VINE);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run()
|
||||
{
|
||||
//order is important!
|
||||
|
||||
//remove sandstone which appears to be unnatural
|
||||
this.removeSandstone();
|
||||
|
||||
//remove any blocks which are definitely player placed
|
||||
this.removePlayerBlocks();
|
||||
|
||||
//reduce large outcroppings of stone, sandstone
|
||||
this.reduceStone();
|
||||
|
||||
//reduce logs, except in jungle biomes
|
||||
this.reduceLogs();
|
||||
|
||||
//remove natural blocks which are unnaturally hanging in the air
|
||||
this.removeHanging();
|
||||
|
||||
//remove natural blocks which are unnaturally stacked high
|
||||
this.removeWallsAndTowers();
|
||||
|
||||
//fill unnatural thin trenches and single-block potholes
|
||||
this.fillHolesAndTrenches();
|
||||
|
||||
//fill water depressions and fix unnatural surface ripples
|
||||
this.fixWater();
|
||||
|
||||
//remove water/lava above sea level
|
||||
this.removeDumpedFluids();
|
||||
|
||||
//cover surface stone and gravel with sand or grass, as the biome requires
|
||||
this.coverSurfaceStone();
|
||||
|
||||
//remove any player-placed leaves
|
||||
this.removePlayerLeaves();
|
||||
|
||||
//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);
|
||||
}
|
||||
|
||||
@SuppressWarnings("deprecation")
|
||||
private void removePlayerLeaves()
|
||||
{
|
||||
if(this.seaLevel < 1) 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; y++)
|
||||
{
|
||||
//note: see minecraft wiki data values for leaves
|
||||
BlockSnapshot block = snapshots[x][y][z];
|
||||
if(block.typeId == Material.LEAVES && (block.data & 0x4) != 0)
|
||||
{
|
||||
block.typeId = Material.AIR;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//converts sandstone adjacent to sand to sand, and any other sandstone to air
|
||||
@SuppressWarnings("deprecation")
|
||||
private void removeSandstone()
|
||||
{
|
||||
for(int x = 1; x < snapshots.length - 1; x++)
|
||||
{
|
||||
for(int z = 1; z < snapshots[0][0].length - 1; z++)
|
||||
{
|
||||
for(int y = snapshots[0].length - 2; y > miny; y--)
|
||||
{
|
||||
if(snapshots[x][y][z].typeId != Material.SANDSTONE) continue;
|
||||
|
||||
BlockSnapshot leftBlock = this.snapshots[x + 1][y][z];
|
||||
BlockSnapshot rightBlock = this.snapshots[x - 1][y][z];
|
||||
BlockSnapshot upBlock = this.snapshots[x][y][z + 1];
|
||||
BlockSnapshot downBlock = this.snapshots[x][y][z - 1];
|
||||
BlockSnapshot underBlock = this.snapshots[x][y - 1][z];
|
||||
BlockSnapshot aboveBlock = this.snapshots[x][y + 1][z];
|
||||
|
||||
//skip blocks which may cause a cave-in
|
||||
if(aboveBlock.typeId == Material.SAND && underBlock.typeId == Material.AIR) continue;
|
||||
|
||||
//count adjacent non-air/non-leaf blocks
|
||||
if( leftBlock.typeId == Material.SAND ||
|
||||
rightBlock.typeId == Material.SAND ||
|
||||
upBlock.typeId == Material.SAND ||
|
||||
downBlock.typeId == Material.SAND ||
|
||||
aboveBlock.typeId == Material.SAND ||
|
||||
underBlock.typeId == Material.SAND)
|
||||
{
|
||||
snapshots[x][y][z].typeId = Material.SAND;
|
||||
}
|
||||
else
|
||||
{
|
||||
snapshots[x][y][z].typeId = Material.AIR;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("deprecation")
|
||||
private void reduceStone()
|
||||
{
|
||||
if(this.seaLevel < 1) return;
|
||||
|
||||
for(int x = 1; x < snapshots.length - 1; x++)
|
||||
{
|
||||
for(int z = 1; z < snapshots[0][0].length - 1; z++)
|
||||
{
|
||||
int thisy = this.highestY(x, z, true);
|
||||
|
||||
while(thisy > this.seaLevel - 1 && (this.snapshots[x][thisy][z].typeId == Material.STONE || this.snapshots[x][thisy][z].typeId == Material.SANDSTONE))
|
||||
{
|
||||
BlockSnapshot leftBlock = this.snapshots[x + 1][thisy][z];
|
||||
BlockSnapshot rightBlock = this.snapshots[x - 1][thisy][z];
|
||||
BlockSnapshot upBlock = this.snapshots[x][thisy][z + 1];
|
||||
BlockSnapshot downBlock = this.snapshots[x][thisy][z - 1];
|
||||
|
||||
//count adjacent non-air/non-leaf blocks
|
||||
byte adjacentBlockCount = 0;
|
||||
if(leftBlock.typeId != Material.AIR && leftBlock.typeId != Material.LEAVES && leftBlock.typeId != Material.VINE)
|
||||
{
|
||||
adjacentBlockCount++;
|
||||
}
|
||||
if(rightBlock.typeId != Material.AIR && rightBlock.typeId != Material.LEAVES && rightBlock.typeId != Material.VINE)
|
||||
{
|
||||
adjacentBlockCount++;
|
||||
}
|
||||
if(downBlock.typeId != Material.AIR && downBlock.typeId != Material.LEAVES && downBlock.typeId != Material.VINE)
|
||||
{
|
||||
adjacentBlockCount++;
|
||||
}
|
||||
if(upBlock.typeId != Material.AIR && upBlock.typeId != Material.LEAVES && upBlock.typeId != Material.VINE)
|
||||
{
|
||||
adjacentBlockCount++;
|
||||
}
|
||||
|
||||
if(adjacentBlockCount < 3)
|
||||
{
|
||||
this.snapshots[x][thisy][z].typeId = Material.AIR;
|
||||
}
|
||||
|
||||
thisy--;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("deprecation")
|
||||
private void reduceLogs()
|
||||
{
|
||||
if(this.seaLevel < 1) return;
|
||||
|
||||
boolean jungleBiome = this.biome == Biome.JUNGLE || this.biome == Biome.JUNGLE_HILLS;
|
||||
|
||||
//scan all blocks above sea level
|
||||
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; y++)
|
||||
{
|
||||
BlockSnapshot block = snapshots[x][y][z];
|
||||
|
||||
//skip non-logs
|
||||
if(block.typeId != Material.LOG) continue;
|
||||
if(block.typeId != Material.LOG_2) continue;
|
||||
|
||||
//if in jungle biome, skip jungle logs
|
||||
if(jungleBiome && block.data == 3) continue;
|
||||
|
||||
//examine adjacent blocks for logs
|
||||
BlockSnapshot leftBlock = this.snapshots[x + 1][y][z];
|
||||
BlockSnapshot rightBlock = this.snapshots[x - 1][y][z];
|
||||
BlockSnapshot upBlock = this.snapshots[x][y][z + 1];
|
||||
BlockSnapshot downBlock = this.snapshots[x][y][z - 1];
|
||||
|
||||
//if any, remove the log
|
||||
if(leftBlock.typeId == Material.LOG || rightBlock.typeId == Material.LOG || upBlock.typeId == Material.LOG || downBlock.typeId == Material.LOG)
|
||||
{
|
||||
this.snapshots[x][y][z].typeId = Material.AIR;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("deprecation")
|
||||
private void removePlayerBlocks()
|
||||
{
|
||||
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++)
|
||||
{
|
||||
for(int y = miny; y < snapshots[0].length - 1; y++)
|
||||
{
|
||||
BlockSnapshot block = snapshots[x][y][z];
|
||||
if(this.playerBlocks.contains(block.typeId))
|
||||
{
|
||||
block.typeId = Material.AIR;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("deprecation")
|
||||
private void removeHanging()
|
||||
{
|
||||
int miny = this.miny;
|
||||
if(miny < 1) miny = 1;
|
||||
|
||||
for(int x = 1; x < snapshots.length - 1; x++)
|
||||
{
|
||||
for(int z = 1; z < snapshots[0][0].length - 1; z++)
|
||||
{
|
||||
for(int y = miny; y < snapshots[0].length - 1; y++)
|
||||
{
|
||||
BlockSnapshot block = snapshots[x][y][z];
|
||||
BlockSnapshot underBlock = snapshots[x][y - 1][z];
|
||||
|
||||
if(underBlock.typeId == Material.AIR || underBlock.typeId == Material.STATIONARY_WATER || underBlock.typeId == Material.STATIONARY_LAVA || underBlock.typeId == Material.LEAVES)
|
||||
{
|
||||
if(this.notAllowedToHang.contains(block.typeId))
|
||||
{
|
||||
block.typeId = Material.AIR;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("deprecation")
|
||||
private void removeWallsAndTowers()
|
||||
{
|
||||
Material [] excludedBlocksArray = new Material []
|
||||
{
|
||||
Material.CACTUS,
|
||||
Material.LONG_GRASS,
|
||||
Material.RED_MUSHROOM,
|
||||
Material.BROWN_MUSHROOM,
|
||||
Material.DEAD_BUSH,
|
||||
Material.SAPLING,
|
||||
Material.YELLOW_FLOWER,
|
||||
Material.RED_ROSE,
|
||||
Material.SUGAR_CANE_BLOCK,
|
||||
Material.VINE,
|
||||
Material.PUMPKIN,
|
||||
Material.WATER_LILY,
|
||||
Material.LEAVES
|
||||
};
|
||||
|
||||
ArrayList<Material> excludedBlocks = new ArrayList<Material>();
|
||||
for(int i = 0; i < excludedBlocksArray.length; i++) excludedBlocks.add(excludedBlocksArray[i]);
|
||||
|
||||
boolean changed;
|
||||
do
|
||||
{
|
||||
changed = false;
|
||||
for(int x = 1; x < snapshots.length - 1; x++)
|
||||
{
|
||||
for(int z = 1; z < snapshots[0][0].length - 1; 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, false);
|
||||
int lefty = this.highestY(x - 1, z, false);
|
||||
while(lefty < thisy && righty < thisy)
|
||||
{
|
||||
this.snapshots[x][thisy--][z].typeId = Material.AIR;
|
||||
changed = true;
|
||||
}
|
||||
|
||||
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;
|
||||
changed = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}while(changed);
|
||||
}
|
||||
|
||||
@SuppressWarnings("deprecation")
|
||||
private void coverSurfaceStone()
|
||||
{
|
||||
for(int x = 1; x < snapshots.length - 1; x++)
|
||||
{
|
||||
for(int z = 1; z < snapshots[0][0].length - 1; z++)
|
||||
{
|
||||
int y = this.highestY(x, z, true);
|
||||
BlockSnapshot block = snapshots[x][y][z];
|
||||
|
||||
if(block.typeId == Material.STONE || block.typeId == Material.GRAVEL || block.typeId == Material.SOIL || block.typeId == Material.DIRT || block.typeId == Material.SANDSTONE)
|
||||
{
|
||||
if(this.biome == Biome.DESERT || this.biome == Biome.DESERT_HILLS || this.biome == Biome.BEACHES)
|
||||
{
|
||||
this.snapshots[x][y][z].typeId = Material.SAND;
|
||||
}
|
||||
else
|
||||
{
|
||||
this.snapshots[x][y][z].typeId = Material.GRASS;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("deprecation")
|
||||
private void fillHolesAndTrenches()
|
||||
{
|
||||
ArrayList<Material> fillableBlocks = new ArrayList<Material>();
|
||||
fillableBlocks.add(Material.AIR);
|
||||
fillableBlocks.add(Material.STATIONARY_WATER);
|
||||
fillableBlocks.add(Material.STATIONARY_LAVA);
|
||||
fillableBlocks.add(Material.LONG_GRASS);
|
||||
|
||||
ArrayList<Material> notSuitableForFillBlocks = new ArrayList<Material>();
|
||||
notSuitableForFillBlocks.add(Material.LONG_GRASS);
|
||||
notSuitableForFillBlocks.add(Material.CACTUS);
|
||||
notSuitableForFillBlocks.add(Material.STATIONARY_WATER);
|
||||
notSuitableForFillBlocks.add(Material.STATIONARY_LAVA);
|
||||
notSuitableForFillBlocks.add(Material.LOG);
|
||||
notSuitableForFillBlocks.add(Material.LOG_2);
|
||||
|
||||
boolean changed;
|
||||
do
|
||||
{
|
||||
changed = false;
|
||||
for(int x = 1; x < snapshots.length - 1; x++)
|
||||
{
|
||||
for(int z = 1; z < snapshots[0][0].length - 1; z++)
|
||||
{
|
||||
for(int y = 0; y < snapshots[0].length - 1; y++)
|
||||
{
|
||||
BlockSnapshot block = this.snapshots[x][y][z];
|
||||
if(!fillableBlocks.contains(block.typeId)) continue;
|
||||
|
||||
BlockSnapshot leftBlock = this.snapshots[x + 1][y][z];
|
||||
BlockSnapshot rightBlock = this.snapshots[x - 1][y][z];
|
||||
|
||||
if(!fillableBlocks.contains(leftBlock.typeId) && !fillableBlocks.contains(rightBlock.typeId))
|
||||
{
|
||||
if(!notSuitableForFillBlocks.contains(rightBlock.typeId))
|
||||
{
|
||||
block.typeId = rightBlock.typeId;
|
||||
changed = true;
|
||||
}
|
||||
}
|
||||
|
||||
BlockSnapshot upBlock = this.snapshots[x][y][z + 1];
|
||||
BlockSnapshot downBlock = this.snapshots[x][y][z - 1];
|
||||
|
||||
if(!fillableBlocks.contains(upBlock.typeId) && !fillableBlocks.contains(downBlock.typeId))
|
||||
{
|
||||
if(!notSuitableForFillBlocks.contains(downBlock.typeId))
|
||||
{
|
||||
block.typeId = downBlock.typeId;
|
||||
changed = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}while(changed);
|
||||
}
|
||||
|
||||
@SuppressWarnings("deprecation")
|
||||
private void fixWater()
|
||||
{
|
||||
int miny = this.miny;
|
||||
if(miny < 1) miny = 1;
|
||||
|
||||
boolean changed;
|
||||
|
||||
//remove hanging water or lava
|
||||
for(int x = 1; x < snapshots.length - 1; x++)
|
||||
{
|
||||
for(int z = 1; z < snapshots[0][0].length - 1; z++)
|
||||
{
|
||||
for(int y = miny; y < snapshots[0].length - 1; y++)
|
||||
{
|
||||
BlockSnapshot block = this.snapshots[x][y][z];
|
||||
BlockSnapshot underBlock = this.snapshots[x][y][z];
|
||||
if(block.typeId == Material.STATIONARY_WATER || block.typeId == Material.STATIONARY_LAVA)
|
||||
{
|
||||
if(underBlock.typeId == Material.AIR || (underBlock.data != 0))
|
||||
{
|
||||
block.typeId = Material.AIR;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//fill water depressions
|
||||
do
|
||||
{
|
||||
changed = false;
|
||||
for(int y = Math.max(this.seaLevel - 10, 0); y <= this.seaLevel; y++)
|
||||
{
|
||||
for(int x = 1; x < snapshots.length - 1; x++)
|
||||
{
|
||||
for(int z = 1; z < snapshots[0][0].length - 1; z++)
|
||||
{
|
||||
BlockSnapshot block = snapshots[x][y][z];
|
||||
|
||||
//only consider air blocks and flowing water blocks for upgrade to water source blocks
|
||||
if(block.typeId == Material.AIR || (block.typeId == Material.STATIONARY_WATER && block.data != 0))
|
||||
{
|
||||
BlockSnapshot leftBlock = this.snapshots[x + 1][y][z];
|
||||
BlockSnapshot rightBlock = this.snapshots[x - 1][y][z];
|
||||
BlockSnapshot upBlock = this.snapshots[x][y][z + 1];
|
||||
BlockSnapshot downBlock = this.snapshots[x][y][z - 1];
|
||||
BlockSnapshot underBlock = this.snapshots[x][y - 1][z];
|
||||
|
||||
//block underneath MUST be source water
|
||||
if(underBlock.typeId != Material.STATIONARY_WATER || underBlock.data != 0) continue;
|
||||
|
||||
//count adjacent source water blocks
|
||||
byte adjacentSourceWaterCount = 0;
|
||||
if(leftBlock.typeId == Material.STATIONARY_WATER && leftBlock.data == 0)
|
||||
{
|
||||
adjacentSourceWaterCount++;
|
||||
}
|
||||
if(rightBlock.typeId == Material.STATIONARY_WATER && rightBlock.data == 0)
|
||||
{
|
||||
adjacentSourceWaterCount++;
|
||||
}
|
||||
if(upBlock.typeId == Material.STATIONARY_WATER && upBlock.data == 0)
|
||||
{
|
||||
adjacentSourceWaterCount++;
|
||||
}
|
||||
if(downBlock.typeId == Material.STATIONARY_WATER && downBlock.data == 0)
|
||||
{
|
||||
adjacentSourceWaterCount++;
|
||||
}
|
||||
|
||||
//at least two adjacent blocks must be source water
|
||||
if(adjacentSourceWaterCount >= 2)
|
||||
{
|
||||
block.typeId = Material.STATIONARY_WATER;
|
||||
block.data = 0;
|
||||
changed = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}while(changed);
|
||||
}
|
||||
|
||||
@SuppressWarnings("deprecation")
|
||||
private void removeDumpedFluids()
|
||||
{
|
||||
if(this.seaLevel < 1) return;
|
||||
|
||||
//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 || block.typeId == Material.STATIONARY_LAVA ||
|
||||
block.typeId == Material.WATER || block.typeId == Material.LAVA)
|
||||
{
|
||||
block.typeId = Material.AIR;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("deprecation")
|
||||
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 &&
|
||||
!(ignoreLeaves && block.typeId == Material.SNOW) &&
|
||||
!(ignoreLeaves && block.typeId == Material.LEAVES) &&
|
||||
!(ignoreLeaves && block.typeId == Material.LEAVES_2) &&
|
||||
!(block.typeId == Material.STATIONARY_WATER) &&
|
||||
!(block.typeId == Material.WATER) &&
|
||||
!(block.typeId == Material.LAVA) &&
|
||||
!(block.typeId == Material.STATIONARY_LAVA))
|
||||
{
|
||||
return y;
|
||||
}
|
||||
}
|
||||
|
||||
return y;
|
||||
}
|
||||
|
||||
@SuppressWarnings("deprecation")
|
||||
static ArrayList<Material> getPlayerBlocks(Environment environment, Biome biome)
|
||||
{
|
||||
//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 manual repair of an overzealous block removal
|
||||
ArrayList<Material> playerBlocks = new ArrayList<Material>();
|
||||
playerBlocks.add(Material.FIRE);
|
||||
playerBlocks.add(Material.BED_BLOCK);
|
||||
playerBlocks.add(Material.WOOD);
|
||||
playerBlocks.add(Material.BOOKSHELF);
|
||||
playerBlocks.add(Material.BREWING_STAND);
|
||||
playerBlocks.add(Material.BRICK);
|
||||
playerBlocks.add(Material.COBBLESTONE);
|
||||
playerBlocks.add(Material.GLASS);
|
||||
playerBlocks.add(Material.LAPIS_BLOCK);
|
||||
playerBlocks.add(Material.DISPENSER);
|
||||
playerBlocks.add(Material.NOTE_BLOCK);
|
||||
playerBlocks.add(Material.POWERED_RAIL);
|
||||
playerBlocks.add(Material.DETECTOR_RAIL);
|
||||
playerBlocks.add(Material.PISTON_STICKY_BASE);
|
||||
playerBlocks.add(Material.PISTON_BASE);
|
||||
playerBlocks.add(Material.PISTON_EXTENSION);
|
||||
playerBlocks.add(Material.WOOL);
|
||||
playerBlocks.add(Material.PISTON_MOVING_PIECE);
|
||||
playerBlocks.add(Material.GOLD_BLOCK);
|
||||
playerBlocks.add(Material.IRON_BLOCK);
|
||||
playerBlocks.add(Material.DOUBLE_STEP);
|
||||
playerBlocks.add(Material.STEP);
|
||||
playerBlocks.add(Material.CROPS);
|
||||
playerBlocks.add(Material.TNT);
|
||||
playerBlocks.add(Material.MOSSY_COBBLESTONE);
|
||||
playerBlocks.add(Material.TORCH);
|
||||
playerBlocks.add(Material.FIRE);
|
||||
playerBlocks.add(Material.WOOD_STAIRS);
|
||||
playerBlocks.add(Material.CHEST);
|
||||
playerBlocks.add(Material.REDSTONE_WIRE);
|
||||
playerBlocks.add(Material.DIAMOND_BLOCK);
|
||||
playerBlocks.add(Material.WORKBENCH);
|
||||
playerBlocks.add(Material.FURNACE);
|
||||
playerBlocks.add(Material.BURNING_FURNACE);
|
||||
playerBlocks.add(Material.WOODEN_DOOR);
|
||||
playerBlocks.add(Material.SIGN_POST);
|
||||
playerBlocks.add(Material.LADDER);
|
||||
playerBlocks.add(Material.RAILS);
|
||||
playerBlocks.add(Material.COBBLESTONE_STAIRS);
|
||||
playerBlocks.add(Material.WALL_SIGN);
|
||||
playerBlocks.add(Material.STONE_PLATE);
|
||||
playerBlocks.add(Material.LEVER);
|
||||
playerBlocks.add(Material.IRON_DOOR_BLOCK);
|
||||
playerBlocks.add(Material.WOOD_PLATE);
|
||||
playerBlocks.add(Material.REDSTONE_TORCH_ON);
|
||||
playerBlocks.add(Material.REDSTONE_TORCH_OFF);
|
||||
playerBlocks.add(Material.STONE_BUTTON);
|
||||
playerBlocks.add(Material.SNOW_BLOCK);
|
||||
playerBlocks.add(Material.JUKEBOX);
|
||||
playerBlocks.add(Material.FENCE);
|
||||
playerBlocks.add(Material.PORTAL);
|
||||
playerBlocks.add(Material.JACK_O_LANTERN);
|
||||
playerBlocks.add(Material.CAKE_BLOCK);
|
||||
playerBlocks.add(Material.DIODE_BLOCK_ON);
|
||||
playerBlocks.add(Material.DIODE_BLOCK_OFF);
|
||||
playerBlocks.add(Material.TRAP_DOOR);
|
||||
playerBlocks.add(Material.SMOOTH_BRICK);
|
||||
playerBlocks.add(Material.HUGE_MUSHROOM_1);
|
||||
playerBlocks.add(Material.HUGE_MUSHROOM_2);
|
||||
playerBlocks.add(Material.IRON_FENCE);
|
||||
playerBlocks.add(Material.THIN_GLASS);
|
||||
playerBlocks.add(Material.MELON_STEM);
|
||||
playerBlocks.add(Material.FENCE_GATE);
|
||||
playerBlocks.add(Material.BRICK_STAIRS);
|
||||
playerBlocks.add(Material.SMOOTH_STAIRS);
|
||||
playerBlocks.add(Material.ENCHANTMENT_TABLE);
|
||||
playerBlocks.add(Material.BREWING_STAND);
|
||||
playerBlocks.add(Material.CAULDRON);
|
||||
playerBlocks.add(Material.DIODE_BLOCK_ON);
|
||||
playerBlocks.add(Material.DIODE_BLOCK_ON);
|
||||
playerBlocks.add(Material.WEB);
|
||||
playerBlocks.add(Material.SPONGE);
|
||||
playerBlocks.add(Material.GRAVEL);
|
||||
playerBlocks.add(Material.EMERALD_BLOCK);
|
||||
playerBlocks.add(Material.SANDSTONE);
|
||||
playerBlocks.add(Material.WOOD_STEP);
|
||||
playerBlocks.add(Material.WOOD_DOUBLE_STEP);
|
||||
playerBlocks.add(Material.ENDER_CHEST);
|
||||
playerBlocks.add(Material.SANDSTONE_STAIRS);
|
||||
playerBlocks.add(Material.SPRUCE_WOOD_STAIRS);
|
||||
playerBlocks.add(Material.JUNGLE_WOOD_STAIRS);
|
||||
playerBlocks.add(Material.COMMAND);
|
||||
playerBlocks.add(Material.BEACON);
|
||||
playerBlocks.add(Material.COBBLE_WALL);
|
||||
playerBlocks.add(Material.FLOWER_POT);
|
||||
playerBlocks.add(Material.CARROT);
|
||||
playerBlocks.add(Material.POTATO);
|
||||
playerBlocks.add(Material.WOOD_BUTTON);
|
||||
playerBlocks.add(Material.SKULL);
|
||||
playerBlocks.add(Material.ANVIL);
|
||||
playerBlocks.add(Material.SPONGE);
|
||||
playerBlocks.add(Material.DOUBLE_STONE_SLAB2);
|
||||
playerBlocks.add(Material.STAINED_GLASS);
|
||||
playerBlocks.add(Material.STAINED_GLASS_PANE);
|
||||
playerBlocks.add(Material.BANNER);
|
||||
playerBlocks.add(Material.STANDING_BANNER);
|
||||
playerBlocks.add(Material.ACACIA_STAIRS);
|
||||
playerBlocks.add(Material.BIRCH_WOOD_STAIRS);
|
||||
playerBlocks.add(Material.DARK_OAK_STAIRS);
|
||||
playerBlocks.add(Material.TRAPPED_CHEST);
|
||||
playerBlocks.add(Material.GOLD_PLATE);
|
||||
playerBlocks.add(Material.IRON_PLATE);
|
||||
playerBlocks.add(Material.REDSTONE_COMPARATOR_OFF);
|
||||
playerBlocks.add(Material.REDSTONE_COMPARATOR_ON);
|
||||
playerBlocks.add(Material.DAYLIGHT_DETECTOR);
|
||||
playerBlocks.add(Material.DAYLIGHT_DETECTOR_INVERTED);
|
||||
playerBlocks.add(Material.REDSTONE_BLOCK);
|
||||
playerBlocks.add(Material.HOPPER);
|
||||
playerBlocks.add(Material.QUARTZ_BLOCK);
|
||||
playerBlocks.add(Material.QUARTZ_STAIRS);
|
||||
playerBlocks.add(Material.DROPPER);
|
||||
playerBlocks.add(Material.SLIME_BLOCK);
|
||||
playerBlocks.add(Material.IRON_TRAPDOOR);
|
||||
playerBlocks.add(Material.PRISMARINE);
|
||||
playerBlocks.add(Material.HAY_BLOCK);
|
||||
playerBlocks.add(Material.CARPET);
|
||||
playerBlocks.add(Material.SEA_LANTERN);
|
||||
playerBlocks.add(Material.RED_SANDSTONE_STAIRS);
|
||||
playerBlocks.add(Material.STONE_SLAB2);
|
||||
playerBlocks.add(Material.ACACIA_FENCE);
|
||||
playerBlocks.add(Material.ACACIA_FENCE_GATE);
|
||||
playerBlocks.add(Material.BIRCH_FENCE);
|
||||
playerBlocks.add(Material.BIRCH_FENCE_GATE);
|
||||
playerBlocks.add(Material.DARK_OAK_FENCE);
|
||||
playerBlocks.add(Material.DARK_OAK_FENCE_GATE);
|
||||
playerBlocks.add(Material.JUNGLE_FENCE);
|
||||
playerBlocks.add(Material.JUNGLE_FENCE_GATE);
|
||||
playerBlocks.add(Material.SPRUCE_FENCE);
|
||||
playerBlocks.add(Material.SPRUCE_FENCE_GATE);
|
||||
playerBlocks.add(Material.ACACIA_DOOR);
|
||||
playerBlocks.add(Material.SPRUCE_DOOR);
|
||||
playerBlocks.add(Material.DARK_OAK_DOOR);
|
||||
playerBlocks.add(Material.JUNGLE_DOOR);
|
||||
playerBlocks.add(Material.BIRCH_DOOR);
|
||||
playerBlocks.add(Material.COAL_BLOCK);
|
||||
playerBlocks.add(Material.REDSTONE_LAMP_OFF);
|
||||
playerBlocks.add(Material.REDSTONE_LAMP_ON);
|
||||
playerBlocks.add(Material.PURPUR_BLOCK);
|
||||
playerBlocks.add(Material.PURPUR_SLAB);
|
||||
playerBlocks.add(Material.PURPUR_DOUBLE_SLAB);
|
||||
playerBlocks.add(Material.PURPUR_PILLAR);
|
||||
playerBlocks.add(Material.PURPUR_STAIRS);
|
||||
playerBlocks.add(Material.NETHER_WART_BLOCK);
|
||||
playerBlocks.add(Material.RED_NETHER_BRICK);
|
||||
playerBlocks.add(Material.BONE_BLOCK);
|
||||
|
||||
//these are unnatural in the standard world, but not in the nether
|
||||
if(environment != Environment.NETHER)
|
||||
{
|
||||
playerBlocks.add(Material.NETHERRACK);
|
||||
playerBlocks.add(Material.SOUL_SAND);
|
||||
playerBlocks.add(Material.GLOWSTONE);
|
||||
playerBlocks.add(Material.NETHER_BRICK);
|
||||
playerBlocks.add(Material.NETHER_FENCE);
|
||||
playerBlocks.add(Material.NETHER_BRICK_STAIRS);
|
||||
playerBlocks.add(Material.MAGMA);
|
||||
}
|
||||
|
||||
//these are unnatural in the standard and nether worlds, but not in the end
|
||||
if(environment != Environment.THE_END)
|
||||
{
|
||||
playerBlocks.add(Material.OBSIDIAN);
|
||||
playerBlocks.add(Material.ENDER_STONE);
|
||||
playerBlocks.add(Material.ENDER_PORTAL_FRAME);
|
||||
playerBlocks.add(Material.CHORUS_PLANT);
|
||||
playerBlocks.add(Material.CHORUS_FLOWER);
|
||||
}
|
||||
|
||||
//these are unnatural in sandy biomes, but not elsewhere
|
||||
if(biome == Biome.DESERT || biome == Biome.DESERT_HILLS || biome == Biome.BEACHES || environment != Environment.NORMAL)
|
||||
{
|
||||
playerBlocks.add(Material.LEAVES);
|
||||
playerBlocks.add(Material.LEAVES_2);
|
||||
playerBlocks.add(Material.LOG);
|
||||
playerBlocks.add(Material.LOG_2);
|
||||
}
|
||||
|
||||
return playerBlocks;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
/*
|
||||
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.Collection;
|
||||
|
||||
import org.bukkit.entity.Player;
|
||||
|
||||
//secures a claim after a siege looting window has closed
|
||||
class SecureClaimTask implements Runnable
|
||||
{
|
||||
private SiegeData siegeData;
|
||||
|
||||
public SecureClaimTask(SiegeData siegeData)
|
||||
{
|
||||
this.siegeData = siegeData;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run()
|
||||
{
|
||||
//for each claim involved in this siege
|
||||
for(int i = 0; i < this.siegeData.claims.size(); i++)
|
||||
{
|
||||
//lock the doors
|
||||
Claim claim = this.siegeData.claims.get(i);
|
||||
claim.doorsOpen = false;
|
||||
|
||||
//eject bad guys
|
||||
@SuppressWarnings("unchecked")
|
||||
Collection<Player> onlinePlayers = (Collection<Player>)GriefPrevention.instance.getServer().getOnlinePlayers();
|
||||
for(Player player : onlinePlayers)
|
||||
{
|
||||
if(claim.contains(player.getLocation(), false, false) && claim.allowAccess(player) != null)
|
||||
{
|
||||
GriefPrevention.sendMessage(player, TextMode.Err, Messages.SiegeDoorsLockedEjection);
|
||||
GriefPrevention.instance.ejectPlayer(player);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
/*
|
||||
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.ChatColor;
|
||||
import org.bukkit.entity.Player;
|
||||
|
||||
//sends a message to a player
|
||||
//used to send delayed messages, for example help text triggered by a player's chat
|
||||
class SendPlayerMessageTask implements Runnable
|
||||
{
|
||||
private Player player;
|
||||
private ChatColor color;
|
||||
private String message;
|
||||
|
||||
public SendPlayerMessageTask(Player player, ChatColor color, String message)
|
||||
{
|
||||
this.player = player;
|
||||
this.color = color;
|
||||
this.message = message;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run()
|
||||
{
|
||||
if(player == null)
|
||||
{
|
||||
GriefPrevention.AddLogEntry(color + message);
|
||||
return;
|
||||
}
|
||||
|
||||
//if the player is dead, save it for after his respawn
|
||||
if(this.player.isDead())
|
||||
{
|
||||
PlayerData playerData = GriefPrevention.instance.dataStore.getPlayerData(this.player.getUniqueId());
|
||||
playerData.messageOnRespawn = this.color + this.message;
|
||||
}
|
||||
|
||||
//otherwise send it immediately
|
||||
else
|
||||
{
|
||||
GriefPrevention.sendMessage(this.player, this.color, this.message);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
/*
|
||||
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;
|
||||
|
||||
//enumeration for golden shovel modes
|
||||
public enum ShovelMode
|
||||
{
|
||||
Basic,
|
||||
Admin,
|
||||
Subdivide,
|
||||
RestoreNature,
|
||||
RestoreNatureAggressive,
|
||||
RestoreNatureFill
|
||||
}
|
||||
@@ -0,0 +1,110 @@
|
||||
/*
|
||||
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.entity.Player;
|
||||
|
||||
//checks to see whether or not a siege should end based on the locations of the players
|
||||
//for example, defender escaped or attacker gave up and left
|
||||
class SiegeCheckupTask implements Runnable
|
||||
{
|
||||
private SiegeData siegeData;
|
||||
|
||||
public SiegeCheckupTask(SiegeData siegeData)
|
||||
{
|
||||
this.siegeData = siegeData;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run()
|
||||
{
|
||||
DataStore dataStore = GriefPrevention.instance.dataStore;
|
||||
Player defender = this.siegeData.defender;
|
||||
Player attacker = this.siegeData.attacker;
|
||||
|
||||
//where is the defender?
|
||||
Claim defenderClaim = dataStore.getClaimAt(defender.getLocation(), false, null);
|
||||
|
||||
//if this is a new claim and he has some permission there, extend the siege to include it
|
||||
if(defenderClaim != null)
|
||||
{
|
||||
String noAccessReason = defenderClaim.allowAccess(defender);
|
||||
if(defenderClaim.canSiege(defender) && noAccessReason == null)
|
||||
{
|
||||
this.siegeData.claims.add(defenderClaim);
|
||||
defenderClaim.siegeData = this.siegeData;
|
||||
}
|
||||
}
|
||||
|
||||
//determine who's close enough to the siege area to be considered "still here"
|
||||
boolean attackerRemains = this.playerRemains(attacker);
|
||||
boolean defenderRemains = this.playerRemains(defender);
|
||||
|
||||
//if they're both here, just plan to come check again later
|
||||
if(attackerRemains && defenderRemains)
|
||||
{
|
||||
this.scheduleAnotherCheck();
|
||||
}
|
||||
|
||||
//otherwise attacker wins if the defender runs away
|
||||
else if(attackerRemains && !defenderRemains)
|
||||
{
|
||||
dataStore.endSiege(this.siegeData, attacker.getName(), defender.getName(), null);
|
||||
}
|
||||
|
||||
//or defender wins if the attacker leaves
|
||||
else if(!attackerRemains && defenderRemains)
|
||||
{
|
||||
dataStore.endSiege(this.siegeData, defender.getName(), attacker.getName(), null);
|
||||
}
|
||||
|
||||
//if they both left, but are still close together, the battle continues (check again later)
|
||||
else if(attacker.getWorld().equals(defender.getWorld()) && attacker.getLocation().distanceSquared(defender.getLocation()) < 2500) //50-block radius for chasing
|
||||
{
|
||||
this.scheduleAnotherCheck();
|
||||
}
|
||||
|
||||
//otherwise they both left and aren't close to each other, so call the attacker the winner (defender escaped, possibly after a chase)
|
||||
else
|
||||
{
|
||||
dataStore.endSiege(this.siegeData, attacker.getName(), defender.getName(), null);
|
||||
}
|
||||
}
|
||||
|
||||
//a player has to be within 25 blocks of the edge of a besieged claim to be considered still in the fight
|
||||
private boolean playerRemains(Player player)
|
||||
{
|
||||
for(int i = 0; i < this.siegeData.claims.size(); i++)
|
||||
{
|
||||
Claim claim = this.siegeData.claims.get(i);
|
||||
if(claim.isNear(player.getLocation(), 25))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
//schedules another checkup later
|
||||
private void scheduleAnotherCheck()
|
||||
{
|
||||
this.siegeData.checkupTaskID = GriefPrevention.instance.getServer().getScheduler().scheduleSyncDelayedTask(GriefPrevention.instance, this, 20L * 30);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
/*
|
||||
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.ArrayList;
|
||||
|
||||
import org.bukkit.entity.Player;
|
||||
|
||||
//information about an ongoing siege
|
||||
public class SiegeData
|
||||
{
|
||||
public Player defender;
|
||||
public Player attacker;
|
||||
public ArrayList<Claim> claims;
|
||||
public int checkupTaskID;
|
||||
|
||||
public SiegeData(Player attacker, Player defender, Claim claim)
|
||||
{
|
||||
this.defender = defender;
|
||||
this.attacker = attacker;
|
||||
this.claims = new ArrayList<Claim>();
|
||||
this.claims.add(claim);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,269 @@
|
||||
package me.ryanhamshire.GriefPrevention;
|
||||
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.ConcurrentLinkedQueue;
|
||||
|
||||
class SpamDetector
|
||||
{
|
||||
//last chat message shown and its timestamp, regardless of who sent it
|
||||
private String lastChatMessage = "";
|
||||
private long lastChatMessageTimestamp = 0;
|
||||
|
||||
//number of identical chat messages in a row
|
||||
private int duplicateMessageCount = 0;
|
||||
|
||||
//data for individual chatters
|
||||
ConcurrentHashMap<UUID, ChatterData> dataStore = new ConcurrentHashMap<UUID, ChatterData>();
|
||||
private ChatterData getChatterData(UUID chatterID)
|
||||
{
|
||||
ChatterData data = this.dataStore.get(chatterID);
|
||||
if(data == null)
|
||||
{
|
||||
data = new ChatterData();
|
||||
this.dataStore.put(chatterID, data);
|
||||
}
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
SpamAnalysisResult AnalyzeMessage(UUID chatterID, String message, long timestamp)
|
||||
{
|
||||
SpamAnalysisResult result = new SpamAnalysisResult();
|
||||
result.finalMessage = message;
|
||||
|
||||
//remedy any CAPS SPAM, exception for very short messages which could be emoticons like =D or XD
|
||||
if(message.length() > 4 && this.stringsAreSimilar(message.toUpperCase(), message))
|
||||
{
|
||||
message = message.toLowerCase();
|
||||
result.finalMessage = message;
|
||||
}
|
||||
|
||||
boolean spam = false;
|
||||
ChatterData chatterData = this.getChatterData(chatterID);
|
||||
|
||||
//mute if total volume of text from this player is too high
|
||||
if(message.length() > 50 && chatterData.getTotalRecentLength(timestamp) > 200)
|
||||
{
|
||||
spam = true;
|
||||
result.muteReason = "too much chat sent in 10 seconds";
|
||||
chatterData.spamLevel++;
|
||||
}
|
||||
|
||||
//always mute an exact match to the last chat message
|
||||
if(result.finalMessage.equals(this.lastChatMessage) && timestamp - this.lastChatMessageTimestamp < 2000)
|
||||
{
|
||||
chatterData.spamLevel += ++this.duplicateMessageCount;
|
||||
spam = true;
|
||||
result.muteReason = "repeat message";
|
||||
}
|
||||
else
|
||||
{
|
||||
this.lastChatMessage = message;
|
||||
this.lastChatMessageTimestamp = timestamp;
|
||||
this.duplicateMessageCount = 0;
|
||||
}
|
||||
|
||||
//check message content and timing
|
||||
long millisecondsSinceLastMessage = timestamp - chatterData.lastMessageTimestamp;
|
||||
|
||||
//if the message came too close to the last one
|
||||
if(millisecondsSinceLastMessage < 1500)
|
||||
{
|
||||
//increment the spam counter
|
||||
chatterData.spamLevel++;
|
||||
spam = true;
|
||||
}
|
||||
|
||||
//if it's exactly the same as the last message from the same player and within 30 seconds
|
||||
if(result.muteReason == null && millisecondsSinceLastMessage < 30000 && result.finalMessage.equalsIgnoreCase(chatterData.lastMessage))
|
||||
{
|
||||
chatterData.spamLevel++;
|
||||
spam = true;
|
||||
result.muteReason = "repeat message";
|
||||
}
|
||||
|
||||
//if it's very similar to the last message from the same player and within 10 seconds of that message
|
||||
if(result.muteReason == null && millisecondsSinceLastMessage < 10000 && this.stringsAreSimilar(message.toLowerCase(), chatterData.lastMessage.toLowerCase()))
|
||||
{
|
||||
chatterData.spamLevel++;
|
||||
spam = true;
|
||||
if(chatterData.spamLevel > 2)
|
||||
{
|
||||
result.muteReason = "similar message";
|
||||
}
|
||||
}
|
||||
|
||||
//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(result.muteReason == null && 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)))
|
||||
{
|
||||
symbolsCount++;
|
||||
}
|
||||
|
||||
if(Character.isWhitespace(character))
|
||||
{
|
||||
whitespaceCount++;
|
||||
}
|
||||
}
|
||||
|
||||
if(symbolsCount > message.length() / 2 || (message.length() > 15 && whitespaceCount < message.length() / 10))
|
||||
{
|
||||
spam = true;
|
||||
if(chatterData.spamLevel > 0) result.muteReason = "gibberish";
|
||||
chatterData.spamLevel++;
|
||||
}
|
||||
}
|
||||
|
||||
//very short messages close together are spam
|
||||
if(result.muteReason == null && message.length() < 5 && millisecondsSinceLastMessage < 3000)
|
||||
{
|
||||
spam = true;
|
||||
chatterData.spamLevel++;
|
||||
}
|
||||
|
||||
//if the message was determined to be a spam, consider taking action
|
||||
if(spam)
|
||||
{
|
||||
//anything above level 8 for a player which has received a warning... kick or if enabled, ban
|
||||
if(chatterData.spamLevel > 8 && chatterData.spamWarned)
|
||||
{
|
||||
result.shouldBanChatter = true;
|
||||
}
|
||||
|
||||
else if(chatterData.spamLevel >= 4)
|
||||
{
|
||||
if(!chatterData.spamWarned)
|
||||
{
|
||||
chatterData.spamWarned = true;
|
||||
result.shouldWarnChatter = true;
|
||||
}
|
||||
|
||||
if(result.muteReason == null)
|
||||
{
|
||||
result.muteReason = "too-frequent text";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//otherwise if not a spam, reduce the spam level for this player
|
||||
else
|
||||
{
|
||||
chatterData.spamLevel = 0;
|
||||
chatterData.spamWarned = false;
|
||||
}
|
||||
|
||||
chatterData.AddMessage(message, timestamp);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
//if two strings are 75% identical, they're too close to follow each other in the chat
|
||||
private boolean stringsAreSimilar(String message, String lastMessage)
|
||||
{
|
||||
//ignore differences in only punctuation and whitespace
|
||||
message = message.replaceAll("[^\\p{Alpha}]", "");
|
||||
lastMessage = lastMessage.replaceAll("[^\\p{Alpha}]", "");
|
||||
|
||||
//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;
|
||||
int i;
|
||||
for(i = 0; i < shorterString.length(); i++)
|
||||
{
|
||||
if(shorterString.charAt(i) == longerString.charAt(i)) identicalCount++;
|
||||
if(identicalCount > maxIdenticalCharacters) return true;
|
||||
}
|
||||
|
||||
//compare backward
|
||||
int j;
|
||||
for(j = 0; j < shorterString.length() - i; j++)
|
||||
{
|
||||
if(shorterString.charAt(shorterString.length() - j - 1) == longerString.charAt(longerString.length() - j - 1)) identicalCount++;
|
||||
if(identicalCount > maxIdenticalCharacters) return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
class SpamAnalysisResult
|
||||
{
|
||||
String finalMessage;
|
||||
boolean shouldWarnChatter = false;
|
||||
boolean shouldBanChatter = false;
|
||||
String muteReason;
|
||||
}
|
||||
|
||||
class ChatterData
|
||||
{
|
||||
public String lastMessage = ""; //the player's last chat message, or slash command complete with parameters
|
||||
public long lastMessageTimestamp; //last time the player sent a chat message or used a monitored slash command
|
||||
public int spamLevel = 0; //number of consecutive "spams"
|
||||
public boolean spamWarned = false; //whether the player has received a warning recently
|
||||
|
||||
//all recent message lengths and their total
|
||||
private ConcurrentLinkedQueue<LengthTimestampPair> recentMessageLengths = new ConcurrentLinkedQueue<LengthTimestampPair>();
|
||||
private int recentTotalLength = 0;
|
||||
|
||||
public void AddMessage(String message, long timestamp)
|
||||
{
|
||||
int length = message.length();
|
||||
this.recentMessageLengths.add(new LengthTimestampPair(length, timestamp));
|
||||
this.recentTotalLength += length;
|
||||
|
||||
this.lastMessage = message;
|
||||
this.lastMessageTimestamp = timestamp;
|
||||
}
|
||||
|
||||
public int getTotalRecentLength(long timestamp)
|
||||
{
|
||||
LengthTimestampPair oldestPair = this.recentMessageLengths.peek();
|
||||
while(oldestPair != null && timestamp - oldestPair.timestamp > 10000)
|
||||
{
|
||||
this.recentMessageLengths.poll();
|
||||
this.recentTotalLength -= oldestPair.length;
|
||||
oldestPair = this.recentMessageLengths.peek();
|
||||
}
|
||||
|
||||
return this.recentTotalLength;
|
||||
}
|
||||
}
|
||||
|
||||
class LengthTimestampPair
|
||||
{
|
||||
public long timestamp;
|
||||
public int length;
|
||||
|
||||
public LengthTimestampPair(int length, long timestamp)
|
||||
{
|
||||
this.length = length;
|
||||
this.timestamp = timestamp;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
/*
|
||||
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.ChatColor;
|
||||
|
||||
//just a few constants for chat color codes
|
||||
public class TextMode
|
||||
{
|
||||
final static ChatColor Info = ChatColor.AQUA;
|
||||
final static ChatColor Instr = ChatColor.YELLOW;
|
||||
final static ChatColor Warn = ChatColor.GOLD;
|
||||
final static ChatColor Err = ChatColor.RED;
|
||||
final static ChatColor Success = ChatColor.GREEN;
|
||||
}
|
||||
@@ -0,0 +1,224 @@
|
||||
//BIG THANKS to EvilMidget38 for providing this handy UUID lookup tool to the Bukkit community! :)
|
||||
|
||||
package me.ryanhamshire.GriefPrevention;
|
||||
|
||||
import com.google.common.base.Charsets;
|
||||
import org.bukkit.OfflinePlayer;
|
||||
import org.json.simple.JSONArray;
|
||||
import org.json.simple.JSONObject;
|
||||
import org.json.simple.parser.JSONParser;
|
||||
|
||||
import java.io.InputStreamReader;
|
||||
import java.io.OutputStream;
|
||||
import java.net.HttpURLConnection;
|
||||
import java.net.URL;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.*;
|
||||
|
||||
class UUIDFetcher {
|
||||
private static int PROFILES_PER_REQUEST = 100;
|
||||
private static final String PROFILE_URL = "https://api.mojang.com/profiles/minecraft";
|
||||
private final JSONParser jsonParser = new JSONParser();
|
||||
private final List<String> names;
|
||||
private final boolean rateLimiting;
|
||||
|
||||
//cache for username -> uuid lookups
|
||||
static HashMap<String, UUID> lookupCache;
|
||||
|
||||
//record of username -> proper casing updates
|
||||
static HashMap<String, String> correctedNames;
|
||||
|
||||
public UUIDFetcher(List<String> names, boolean rateLimiting) {
|
||||
this.names = names;
|
||||
this.rateLimiting = rateLimiting;
|
||||
}
|
||||
|
||||
public UUIDFetcher(List<String> names) {
|
||||
this(names, true);
|
||||
}
|
||||
|
||||
public void call() throws Exception
|
||||
{
|
||||
if(lookupCache == null)
|
||||
{
|
||||
lookupCache = new HashMap<String, UUID>();
|
||||
}
|
||||
|
||||
if(correctedNames == null)
|
||||
{
|
||||
correctedNames = new HashMap<String, String>();
|
||||
}
|
||||
|
||||
GriefPrevention.AddLogEntry("UUID conversion process started. Please be patient - this may take a while.");
|
||||
|
||||
GriefPrevention.AddLogEntry("Mining your local world data to save calls to Mojang...");
|
||||
OfflinePlayer [] players = GriefPrevention.instance.getServer().getOfflinePlayers();
|
||||
for(OfflinePlayer player : players)
|
||||
{
|
||||
if(player.getName() != null && player.getUniqueId() != null)
|
||||
{
|
||||
lookupCache.put(player.getName(), player.getUniqueId());
|
||||
lookupCache.put(player.getName().toLowerCase(), player.getUniqueId());
|
||||
correctedNames.put(player.getName().toLowerCase(), player.getName());
|
||||
}
|
||||
}
|
||||
|
||||
//try to get correct casing from local data
|
||||
GriefPrevention.AddLogEntry("Checking local server data to get correct casing for player names...");
|
||||
for(int i = 0; i < names.size(); i++)
|
||||
{
|
||||
String name = names.get(i);
|
||||
String correctCasingName = correctedNames.get(name);
|
||||
if(correctCasingName != null && !name.equals(correctCasingName))
|
||||
{
|
||||
GriefPrevention.AddLogEntry(name + " --> " + correctCasingName);
|
||||
names.set(i, correctCasingName);
|
||||
}
|
||||
}
|
||||
|
||||
//look for local uuid's first
|
||||
GriefPrevention.AddLogEntry("Checking local server data for UUIDs already seen...");
|
||||
for(int i = 0; i < names.size(); i++)
|
||||
{
|
||||
String name = names.get(i);
|
||||
UUID uuid = lookupCache.get(name);
|
||||
if(uuid != null)
|
||||
{
|
||||
GriefPrevention.AddLogEntry(name + " --> " + uuid.toString());
|
||||
names.remove(i--);
|
||||
}
|
||||
}
|
||||
|
||||
//for online mode, call Mojang to resolve the rest
|
||||
if(GriefPrevention.instance.getServer().getOnlineMode())
|
||||
{
|
||||
GriefPrevention.AddLogEntry("Calling Mojang to get UUIDs for remaining unresolved players (this is the slowest step)...");
|
||||
|
||||
for (int i = 0; i * PROFILES_PER_REQUEST < names.size(); i++)
|
||||
{
|
||||
boolean retry = false;
|
||||
JSONArray array = null;
|
||||
do
|
||||
{
|
||||
HttpURLConnection connection = createConnection();
|
||||
String body = JSONArray.toJSONString(names.subList(i * PROFILES_PER_REQUEST, Math.min((i + 1) * PROFILES_PER_REQUEST, names.size())));
|
||||
writeBody(connection, body);
|
||||
retry = false;
|
||||
array = null;
|
||||
try
|
||||
{
|
||||
array = (JSONArray) jsonParser.parse(new InputStreamReader(connection.getInputStream()));
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
//in case of error 429 too many requests, pause and then retry later
|
||||
if(e.getMessage().contains("429"))
|
||||
{
|
||||
retry = true;
|
||||
|
||||
//if this is the first time we're sending anything, the batch size must be too big
|
||||
//try reducing it
|
||||
if(i == 0 && PROFILES_PER_REQUEST > 1)
|
||||
{
|
||||
GriefPrevention.AddLogEntry("Batch size " + PROFILES_PER_REQUEST + " seems too large. Looking for a workable batch size...");
|
||||
PROFILES_PER_REQUEST = Math.max(PROFILES_PER_REQUEST - 5, 1);
|
||||
}
|
||||
|
||||
//otherwise, keep the batch size which has worked for previous iterations
|
||||
//but wait a little while before trying again.
|
||||
else
|
||||
{
|
||||
GriefPrevention.AddLogEntry("Mojang says we're sending requests too fast. Will retry every 30 seconds until we succeed...");
|
||||
Thread.sleep(30000);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
}while(retry);
|
||||
|
||||
for (Object profile : array) {
|
||||
JSONObject jsonProfile = (JSONObject) profile;
|
||||
String id = (String) jsonProfile.get("id");
|
||||
String name = (String) jsonProfile.get("name");
|
||||
UUID uuid = UUIDFetcher.getUUID(id);
|
||||
GriefPrevention.AddLogEntry(name + " --> " + uuid.toString());
|
||||
lookupCache.put(name, uuid);
|
||||
lookupCache.put(name.toLowerCase(), uuid);
|
||||
}
|
||||
if (rateLimiting) {
|
||||
Thread.sleep(200L);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//for offline mode, generate UUIDs for the rest
|
||||
else
|
||||
{
|
||||
GriefPrevention.AddLogEntry("Generating offline mode UUIDs for remaining unresolved players...");
|
||||
|
||||
for(int i = 0; i < names.size(); i++)
|
||||
{
|
||||
String name = names.get(i);
|
||||
UUID uuid = java.util.UUID.nameUUIDFromBytes(("OfflinePlayer:" + name).getBytes(Charsets.UTF_8));
|
||||
GriefPrevention.AddLogEntry(name + " --> " + uuid.toString());
|
||||
lookupCache.put(name, uuid);
|
||||
lookupCache.put(name.toLowerCase(), uuid);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void writeBody(HttpURLConnection connection, String body) throws Exception {
|
||||
OutputStream stream = connection.getOutputStream();
|
||||
stream.write(body.getBytes());
|
||||
stream.flush();
|
||||
stream.close();
|
||||
}
|
||||
|
||||
private static HttpURLConnection createConnection() throws Exception {
|
||||
URL url = new URL(PROFILE_URL);
|
||||
HttpURLConnection connection = (HttpURLConnection) url.openConnection();
|
||||
connection.setRequestMethod("POST");
|
||||
connection.setRequestProperty("Content-Type", "application/json");
|
||||
connection.setUseCaches(false);
|
||||
connection.setDoInput(true);
|
||||
connection.setDoOutput(true);
|
||||
return connection;
|
||||
}
|
||||
|
||||
private static UUID getUUID(String id) {
|
||||
return UUID.fromString(id.substring(0, 8) + "-" + id.substring(8, 12) + "-" + id.substring(12, 16) + "-" + id.substring(16, 20) + "-" +id.substring(20, 32));
|
||||
}
|
||||
|
||||
public static byte[] toBytes(UUID uuid) {
|
||||
ByteBuffer byteBuffer = ByteBuffer.wrap(new byte[16]);
|
||||
byteBuffer.putLong(uuid.getMostSignificantBits());
|
||||
byteBuffer.putLong(uuid.getLeastSignificantBits());
|
||||
return byteBuffer.array();
|
||||
}
|
||||
|
||||
public static UUID fromBytes(byte[] array) {
|
||||
if (array.length != 16) {
|
||||
throw new IllegalArgumentException("Illegal byte array length: " + array.length);
|
||||
}
|
||||
ByteBuffer byteBuffer = ByteBuffer.wrap(array);
|
||||
long mostSignificant = byteBuffer.getLong();
|
||||
long leastSignificant = byteBuffer.getLong();
|
||||
return new UUID(mostSignificant, leastSignificant);
|
||||
}
|
||||
|
||||
public static UUID getUUIDOf(String name) throws Exception
|
||||
{
|
||||
UUID result = lookupCache.get(name);
|
||||
if(result == null)
|
||||
{
|
||||
//throw up our hands and report the problem in the logs
|
||||
//this player will lose his land claim blocks, but claims will stay in place as admin claims
|
||||
throw new IllegalArgumentException(name);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,333 @@
|
||||
/*
|
||||
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.ArrayList;
|
||||
|
||||
import org.bukkit.Location;
|
||||
import org.bukkit.Material;
|
||||
import org.bukkit.World;
|
||||
import org.bukkit.block.Block;
|
||||
import org.bukkit.block.BlockFace;
|
||||
import org.bukkit.entity.Player;
|
||||
|
||||
//represents a visualization sent to a player
|
||||
//FEATURE: to show players visually where claim boundaries are, we send them fake block change packets
|
||||
//the result is that those players see new blocks, but the world hasn't been changed. other players can't see the new blocks, either.
|
||||
public class Visualization
|
||||
{
|
||||
public ArrayList<VisualizationElement> elements = new ArrayList<VisualizationElement>();
|
||||
|
||||
//sends a visualization to a player
|
||||
public static void Apply(Player player, Visualization visualization)
|
||||
{
|
||||
PlayerData playerData = GriefPrevention.instance.dataStore.getPlayerData(player.getUniqueId());
|
||||
|
||||
//if he has any current visualization, clear it first
|
||||
if(playerData.currentVisualization != null)
|
||||
{
|
||||
Visualization.Revert(player);
|
||||
}
|
||||
|
||||
//if he's online, create a task to send him the visualization
|
||||
if(player.isOnline() && visualization.elements.size() > 0 && visualization.elements.get(0).location.getWorld().equals(player.getWorld()))
|
||||
{
|
||||
GriefPrevention.instance.getServer().getScheduler().scheduleSyncDelayedTask(GriefPrevention.instance, new VisualizationApplicationTask(player, playerData, visualization), 1L);
|
||||
}
|
||||
}
|
||||
|
||||
//reverts a visualization by sending another block change list, this time with the real world block values
|
||||
@SuppressWarnings("deprecation")
|
||||
public static void Revert(Player player)
|
||||
{
|
||||
if(!player.isOnline()) return;
|
||||
|
||||
PlayerData playerData = GriefPrevention.instance.dataStore.getPlayerData(player.getUniqueId());
|
||||
|
||||
Visualization visualization = playerData.currentVisualization;
|
||||
|
||||
if(playerData.currentVisualization != null)
|
||||
{
|
||||
//locality
|
||||
int minx = player.getLocation().getBlockX() - 100;
|
||||
int minz = player.getLocation().getBlockZ() - 100;
|
||||
int maxx = player.getLocation().getBlockX() + 100;
|
||||
int maxz = player.getLocation().getBlockZ() + 100;
|
||||
|
||||
//remove any elements which are too far away
|
||||
visualization.removeElementsOutOfRange(visualization.elements, minx, minz, maxx, maxz);
|
||||
|
||||
//send real block information for any remaining elements
|
||||
for(int i = 0; i < visualization.elements.size(); i++)
|
||||
{
|
||||
VisualizationElement element = visualization.elements.get(i);
|
||||
|
||||
//check player still in world where visualization exists
|
||||
if(i == 0)
|
||||
{
|
||||
if(!player.getWorld().equals(element.location.getWorld())) return;
|
||||
}
|
||||
|
||||
player.sendBlockChange(element.location, element.realMaterial, element.realData);
|
||||
}
|
||||
|
||||
playerData.currentVisualization = null;
|
||||
}
|
||||
}
|
||||
|
||||
//convenience method to build a visualization from a claim
|
||||
//visualizationType determines the style (gold blocks, silver, red, diamond, etc)
|
||||
public static Visualization FromClaim(Claim claim, int height, VisualizationType visualizationType, Location locality)
|
||||
{
|
||||
//visualize only top level claims
|
||||
if(claim.parent != null)
|
||||
{
|
||||
return FromClaim(claim.parent, height, visualizationType, locality);
|
||||
}
|
||||
|
||||
Visualization visualization = new Visualization();
|
||||
|
||||
//add subdivisions first
|
||||
for(int i = 0; i < claim.children.size(); i++)
|
||||
{
|
||||
Claim child = claim.children.get(i);
|
||||
if(!child.inDataStore) continue;
|
||||
visualization.addClaimElements(child, height, VisualizationType.Subdivision, locality);
|
||||
}
|
||||
|
||||
//special visualization for administrative land claims
|
||||
if(claim.isAdminClaim() && visualizationType == VisualizationType.Claim)
|
||||
{
|
||||
visualizationType = VisualizationType.AdminClaim;
|
||||
}
|
||||
|
||||
//add top level last so that it takes precedence (it shows on top when the child claim boundaries overlap with its boundaries)
|
||||
visualization.addClaimElements(claim, height, visualizationType, locality);
|
||||
|
||||
return visualization;
|
||||
}
|
||||
|
||||
//adds a claim's visualization to the current visualization
|
||||
//handy for combining several visualizations together, as when visualization a top level claim with several subdivisions inside
|
||||
//locality is a performance consideration. only create visualization blocks for around 100 blocks of the locality
|
||||
@SuppressWarnings("deprecation")
|
||||
private void addClaimElements(Claim claim, int height, VisualizationType visualizationType, Location locality)
|
||||
{
|
||||
Location smallXsmallZ = claim.getLesserBoundaryCorner();
|
||||
Location bigXbigZ = claim.getGreaterBoundaryCorner();
|
||||
World world = smallXsmallZ.getWorld();
|
||||
boolean waterIsTransparent = locality.getBlock().getType() == Material.STATIONARY_WATER;
|
||||
|
||||
int smallx = smallXsmallZ.getBlockX();
|
||||
int smallz = smallXsmallZ.getBlockZ();
|
||||
int bigx = bigXbigZ.getBlockX();
|
||||
int bigz = bigXbigZ.getBlockZ();
|
||||
|
||||
Material cornerMaterial;
|
||||
Material accentMaterial;
|
||||
|
||||
ArrayList<VisualizationElement> newElements = new ArrayList<VisualizationElement>();
|
||||
|
||||
if(visualizationType == VisualizationType.Claim)
|
||||
{
|
||||
cornerMaterial = Material.GLOWSTONE;
|
||||
accentMaterial = Material.GOLD_BLOCK;
|
||||
}
|
||||
|
||||
else if(visualizationType == VisualizationType.AdminClaim)
|
||||
{
|
||||
cornerMaterial = Material.GLOWSTONE;
|
||||
accentMaterial = Material.PUMPKIN;
|
||||
}
|
||||
|
||||
else if(visualizationType == VisualizationType.Subdivision)
|
||||
{
|
||||
cornerMaterial = Material.IRON_BLOCK;
|
||||
accentMaterial = Material.WOOL;
|
||||
}
|
||||
|
||||
else if(visualizationType == VisualizationType.RestoreNature)
|
||||
{
|
||||
cornerMaterial = Material.DIAMOND_BLOCK;
|
||||
accentMaterial = Material.DIAMOND_BLOCK;
|
||||
}
|
||||
|
||||
else
|
||||
{
|
||||
cornerMaterial = Material.GLOWING_REDSTONE_ORE;
|
||||
accentMaterial = Material.NETHERRACK;
|
||||
}
|
||||
|
||||
//initialize visualization elements without Y values and real data
|
||||
//that will be added later for only the visualization elements within visualization range
|
||||
|
||||
//locality
|
||||
int minx = locality.getBlockX() - 75;
|
||||
int minz = locality.getBlockZ() - 75;
|
||||
int maxx = locality.getBlockX() + 75;
|
||||
int maxz = locality.getBlockZ() + 75;
|
||||
|
||||
final int STEP = 10;
|
||||
|
||||
//top line
|
||||
newElements.add(new VisualizationElement(new Location(world, smallx, 0, bigz), cornerMaterial, (byte)0, Material.AIR, (byte)0));
|
||||
newElements.add(new VisualizationElement(new Location(world, smallx + 1, 0, bigz), accentMaterial, (byte)0, Material.AIR, (byte)0));
|
||||
for(int x = smallx + STEP; x < bigx - STEP / 2; x += STEP)
|
||||
{
|
||||
if(x > minx && x < maxx)
|
||||
newElements.add(new VisualizationElement(new Location(world, x, 0, bigz), accentMaterial, (byte)0, Material.AIR, (byte)0));
|
||||
}
|
||||
newElements.add(new VisualizationElement(new Location(world, bigx - 1, 0, bigz), accentMaterial, (byte)0, Material.AIR, (byte)0));
|
||||
|
||||
//bottom line
|
||||
newElements.add(new VisualizationElement(new Location(world, smallx + 1, 0, smallz), accentMaterial, (byte)0, Material.AIR, (byte)0));
|
||||
for(int x = smallx + STEP; x < bigx - STEP / 2; x += STEP)
|
||||
{
|
||||
if(x > minx && x < maxx)
|
||||
newElements.add(new VisualizationElement(new Location(world, x, 0, smallz), accentMaterial, (byte)0, Material.AIR, (byte)0));
|
||||
}
|
||||
newElements.add(new VisualizationElement(new Location(world, bigx - 1, 0, smallz), accentMaterial, (byte)0, Material.AIR, (byte)0));
|
||||
|
||||
//left line
|
||||
newElements.add(new VisualizationElement(new Location(world, smallx, 0, smallz), cornerMaterial, (byte)0, Material.AIR, (byte)0));
|
||||
newElements.add(new VisualizationElement(new Location(world, smallx, 0, smallz + 1), accentMaterial, (byte)0, Material.AIR, (byte)0));
|
||||
for(int z = smallz + STEP; z < bigz - STEP / 2; z += STEP)
|
||||
{
|
||||
if(z > minz && z < maxz)
|
||||
newElements.add(new VisualizationElement(new Location(world, smallx, 0, z), accentMaterial, (byte)0, Material.AIR, (byte)0));
|
||||
}
|
||||
newElements.add(new VisualizationElement(new Location(world, smallx, 0, bigz - 1), accentMaterial, (byte)0, Material.AIR, (byte)0));
|
||||
|
||||
//right line
|
||||
newElements.add(new VisualizationElement(new Location(world, bigx, 0, smallz), cornerMaterial, (byte)0, Material.AIR, (byte)0));
|
||||
newElements.add(new VisualizationElement(new Location(world, bigx, 0, smallz + 1), accentMaterial, (byte)0, Material.AIR, (byte)0));
|
||||
for(int z = smallz + STEP; z < bigz - STEP / 2; z += STEP)
|
||||
{
|
||||
if(z > minz && z < maxz)
|
||||
newElements.add(new VisualizationElement(new Location(world, bigx, 0, z), accentMaterial, (byte)0, Material.AIR, (byte)0));
|
||||
}
|
||||
newElements.add(new VisualizationElement(new Location(world, bigx, 0, bigz - 1), accentMaterial, (byte)0, Material.AIR, (byte)0));
|
||||
newElements.add(new VisualizationElement(new Location(world, bigx, 0, bigz), cornerMaterial, (byte)0, Material.AIR, (byte)0));
|
||||
|
||||
//remove any out of range elements
|
||||
this.removeElementsOutOfRange(newElements, minx, minz, maxx, maxz);
|
||||
|
||||
//remove any elements outside the claim
|
||||
for(int i = 0; i < newElements.size(); i++)
|
||||
{
|
||||
VisualizationElement element = newElements.get(i);
|
||||
if(!claim.contains(element.location, true, false))
|
||||
{
|
||||
newElements.remove(i--);
|
||||
}
|
||||
}
|
||||
|
||||
//set Y values and real block information for any remaining visualization blocks
|
||||
for(VisualizationElement element : newElements)
|
||||
{
|
||||
Location tempLocation = element.location;
|
||||
element.location = getVisibleLocation(tempLocation.getWorld(), tempLocation.getBlockX(), height, tempLocation.getBlockZ(), waterIsTransparent);
|
||||
height = element.location.getBlockY();
|
||||
element.realMaterial = element.location.getBlock().getType();
|
||||
element.realData = element.location.getBlock().getData();
|
||||
}
|
||||
|
||||
this.elements.addAll(newElements);
|
||||
}
|
||||
|
||||
//removes any elements which are out of visualization range
|
||||
private void removeElementsOutOfRange(ArrayList<VisualizationElement> elements, int minx, int minz, int maxx, int maxz)
|
||||
{
|
||||
for(int i = 0; i < elements.size(); i++)
|
||||
{
|
||||
Location location = elements.get(i).location;
|
||||
if(location.getX() < minx || location.getX() > maxx || location.getZ() < minz || location.getZ() > maxz)
|
||||
{
|
||||
elements.remove(i--);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//finds a block the player can probably see. this is how visualizations "cling" to the ground or ceiling
|
||||
private static Location getVisibleLocation(World world, int x, int y, int z, boolean waterIsTransparent)
|
||||
{
|
||||
Block block = world.getBlockAt(x, y, z);
|
||||
BlockFace direction = (isTransparent(block, waterIsTransparent)) ? BlockFace.DOWN : BlockFace.UP;
|
||||
|
||||
while( block.getY() >= 1 &&
|
||||
block.getY() < world.getMaxHeight() - 1 &&
|
||||
(!isTransparent(block.getRelative(BlockFace.UP), waterIsTransparent) || isTransparent(block, waterIsTransparent)))
|
||||
{
|
||||
block = block.getRelative(direction);
|
||||
}
|
||||
|
||||
return block.getLocation();
|
||||
}
|
||||
|
||||
//helper method for above. allows visualization blocks to sit underneath partly transparent blocks like grass and fence
|
||||
private static boolean isTransparent(Block block, boolean waterIsTransparent)
|
||||
{
|
||||
//Blacklist
|
||||
switch (block.getType())
|
||||
{
|
||||
case SNOW:
|
||||
return false;
|
||||
}
|
||||
|
||||
//Whitelist TODO: some of this might already be included in isTransparent()
|
||||
switch (block.getType())
|
||||
{
|
||||
case AIR:
|
||||
case FENCE:
|
||||
case ACACIA_FENCE:
|
||||
case BIRCH_FENCE:
|
||||
case DARK_OAK_FENCE:
|
||||
case JUNGLE_FENCE:
|
||||
case NETHER_FENCE:
|
||||
case SPRUCE_FENCE:
|
||||
case FENCE_GATE:
|
||||
case ACACIA_FENCE_GATE:
|
||||
case BIRCH_FENCE_GATE:
|
||||
case DARK_OAK_FENCE_GATE:
|
||||
case SPRUCE_FENCE_GATE:
|
||||
case JUNGLE_FENCE_GATE:
|
||||
case SIGN:
|
||||
case SIGN_POST:
|
||||
case WALL_SIGN:
|
||||
return true;
|
||||
}
|
||||
|
||||
if ((waterIsTransparent && block.getType() == Material.STATIONARY_WATER) ||
|
||||
block.getType().isTransparent())
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
public static Visualization fromClaims(Iterable<Claim> claims, int height, VisualizationType type, Location locality)
|
||||
{
|
||||
Visualization visualization = new Visualization();
|
||||
|
||||
for(Claim claim : claims)
|
||||
{
|
||||
visualization.addClaimElements(claim, height, type, locality);
|
||||
}
|
||||
|
||||
return visualization;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
/*
|
||||
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.entity.Player;
|
||||
|
||||
//applies a visualization for a player by sending him block change packets
|
||||
class VisualizationApplicationTask implements Runnable
|
||||
{
|
||||
private Visualization visualization;
|
||||
private Player player;
|
||||
private PlayerData playerData;
|
||||
|
||||
public VisualizationApplicationTask(Player player, PlayerData playerData, Visualization visualization)
|
||||
{
|
||||
this.visualization = visualization;
|
||||
this.playerData = playerData;
|
||||
this.player = player;
|
||||
}
|
||||
|
||||
@SuppressWarnings("deprecation")
|
||||
@Override
|
||||
public void run()
|
||||
{
|
||||
//for each element (=block) of the visualization
|
||||
for(int i = 0; i < visualization.elements.size(); i++)
|
||||
{
|
||||
VisualizationElement element = visualization.elements.get(i);
|
||||
|
||||
//send the player a fake block change event
|
||||
if(!element.location.getChunk().isLoaded()) continue; //cheap distance check
|
||||
player.sendBlockChange(element.location, element.visualizedMaterial, element.visualizedData);
|
||||
}
|
||||
|
||||
//remember the visualization applied to this player for later (so it can be inexpensively reverted)
|
||||
playerData.currentVisualization = visualization;
|
||||
|
||||
//schedule automatic visualization reversion in 60 seconds.
|
||||
GriefPrevention.instance.getServer().getScheduler().scheduleSyncDelayedTask(
|
||||
GriefPrevention.instance,
|
||||
new VisualizationReversionTask(player, playerData, visualization),
|
||||
20L * 60); //60 seconds
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
/*
|
||||
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.Location;
|
||||
import org.bukkit.Material;
|
||||
|
||||
//represents a "fake" block sent to a player as part of a visualization
|
||||
public class VisualizationElement
|
||||
{
|
||||
public Location location;
|
||||
public Material visualizedMaterial;
|
||||
public byte visualizedData;
|
||||
public Material realMaterial;
|
||||
public byte realData;
|
||||
|
||||
public VisualizationElement(Location location, Material visualizedMaterial, byte visualizedData, Material realMaterial, byte realData)
|
||||
{
|
||||
this.location = location;
|
||||
this.visualizedMaterial= visualizedMaterial;
|
||||
this.visualizedData = visualizedData;
|
||||
this.realData = realData;
|
||||
this.realMaterial = realMaterial;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
/*
|
||||
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 me.ryanhamshire.GriefPrevention.events.VisualizationEvent;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.entity.Player;
|
||||
|
||||
import java.util.Collections;
|
||||
|
||||
//applies a visualization for a player by sending him block change packets
|
||||
class VisualizationReversionTask implements Runnable
|
||||
{
|
||||
private Visualization visualization;
|
||||
private Player player;
|
||||
private PlayerData playerData;
|
||||
|
||||
public VisualizationReversionTask(Player player, PlayerData playerData, Visualization visualization)
|
||||
{
|
||||
this.visualization = visualization;
|
||||
this.playerData = playerData;
|
||||
this.player = player;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run()
|
||||
{
|
||||
//don't do anything if the player's current visualization is different from the one scheduled to revert
|
||||
if(playerData.currentVisualization != visualization) return;
|
||||
|
||||
// alert plugins of a visualization
|
||||
Bukkit.getPluginManager().callEvent(new VisualizationEvent(player, Collections.<Claim>emptySet()));
|
||||
|
||||
Visualization.Revert(player);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
/*
|
||||
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;
|
||||
|
||||
//just an enumeration of the visualization types, which determine what materials will be for the fake blocks
|
||||
public enum VisualizationType
|
||||
{
|
||||
Claim,
|
||||
Subdivision,
|
||||
ErrorClaim,
|
||||
RestoreNature,
|
||||
AdminClaim
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
package me.ryanhamshire.GriefPrevention;
|
||||
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.Material;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.inventory.ItemFactory;
|
||||
import org.bukkit.inventory.ItemStack;
|
||||
import org.bukkit.inventory.meta.BookMeta;
|
||||
|
||||
public class WelcomeTask implements Runnable
|
||||
{
|
||||
private Player player;
|
||||
|
||||
public WelcomeTask(Player player)
|
||||
{
|
||||
this.player = player;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run()
|
||||
{
|
||||
//abort if player has logged out since this task was scheduled
|
||||
if(!this.player.isOnline()) return;
|
||||
|
||||
//offer advice and a helpful link
|
||||
GriefPrevention.sendMessage(player, TextMode.Instr, Messages.AvoidGriefClaimLand);
|
||||
GriefPrevention.sendMessage(player, TextMode.Instr, Messages.SurvivalBasicsVideo2, DataStore.SURVIVAL_VIDEO_URL);
|
||||
|
||||
//give the player a reference book for later
|
||||
if(GriefPrevention.instance.config_claims_supplyPlayerManual)
|
||||
{
|
||||
ItemFactory factory = Bukkit.getItemFactory();
|
||||
BookMeta meta = (BookMeta) factory.getItemMeta(Material.WRITTEN_BOOK);
|
||||
|
||||
DataStore datastore = GriefPrevention.instance.dataStore;
|
||||
meta.setAuthor(datastore.getMessage(Messages.BookAuthor));
|
||||
meta.setTitle(datastore.getMessage(Messages.BookTitle));
|
||||
|
||||
StringBuilder page1 = new StringBuilder();
|
||||
String URL = datastore.getMessage(Messages.BookLink, DataStore.SURVIVAL_VIDEO_URL);
|
||||
String intro = datastore.getMessage(Messages.BookIntro);
|
||||
|
||||
page1.append(URL).append("\n\n");
|
||||
page1.append(intro).append("\n\n");
|
||||
String editToolName = GriefPrevention.instance.config_claims_modificationTool.name().replace('_', ' ').toLowerCase();
|
||||
String infoToolName = GriefPrevention.instance.config_claims_investigationTool.name().replace('_', ' ').toLowerCase();
|
||||
String configClaimTools = datastore.getMessage(Messages.BookTools, editToolName, infoToolName);
|
||||
page1.append(configClaimTools);
|
||||
if(GriefPrevention.instance.config_claims_automaticClaimsForNewPlayersRadius < 0)
|
||||
{
|
||||
page1.append(datastore.getMessage(Messages.BookDisabledChestClaims));
|
||||
}
|
||||
|
||||
StringBuilder page2 = new StringBuilder(datastore.getMessage(Messages.BookUsefulCommands)).append("\n\n");
|
||||
page2.append("/Trust /UnTrust /TrustList\n");
|
||||
page2.append("/ClaimsList\n");
|
||||
page2.append("/AbandonClaim\n\n");
|
||||
page2.append("/Claim /ExtendClaim\n");
|
||||
|
||||
page2.append("/IgnorePlayer\n\n");
|
||||
|
||||
page2.append("/SubdivideClaims\n");
|
||||
page2.append("/AccessTrust\n");
|
||||
page2.append("/ContainerTrust\n");
|
||||
page2.append("/PermissionTrust");
|
||||
|
||||
meta.setPages(page1.toString(), page2.toString());
|
||||
|
||||
ItemStack item = new ItemStack(Material.WRITTEN_BOOK);
|
||||
item.setItemMeta(meta);
|
||||
player.getInventory().addItem(item);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
package me.ryanhamshire.GriefPrevention;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
class WordFinder
|
||||
{
|
||||
private Pattern pattern;
|
||||
|
||||
WordFinder(List<String> wordsToFind)
|
||||
{
|
||||
if(wordsToFind.size() == 0) return;
|
||||
|
||||
StringBuilder patternBuilder = new StringBuilder();
|
||||
for(String word : wordsToFind)
|
||||
{
|
||||
if(!word.isEmpty() && !word.trim().isEmpty())
|
||||
{
|
||||
patternBuilder.append("|(([^\\w]|^)" + Pattern.quote(word) + "([^\\w]|$))");
|
||||
}
|
||||
}
|
||||
|
||||
String patternString = patternBuilder.toString();
|
||||
if(patternString.length() > 1)
|
||||
{
|
||||
//trim extraneous leading pipe (|)
|
||||
patternString = patternString.substring(1);
|
||||
}
|
||||
|
||||
this.pattern = Pattern.compile(patternString, Pattern.CASE_INSENSITIVE | Pattern.UNICODE_CASE);
|
||||
}
|
||||
|
||||
boolean hasMatch(String input)
|
||||
{
|
||||
if(this.pattern == null) return false;
|
||||
|
||||
Matcher matcher = this.pattern.matcher(input);
|
||||
return matcher.find();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
package me.ryanhamshire.GriefPrevention;
|
||||
|
||||
import org.bukkit.Location;
|
||||
import org.bukkit.World;
|
||||
import org.bukkit.entity.Player;
|
||||
|
||||
import com.sk89q.worldedit.BlockVector;
|
||||
import com.sk89q.worldguard.LocalPlayer;
|
||||
import com.sk89q.worldguard.bukkit.WorldGuardPlugin;
|
||||
import com.sk89q.worldguard.bukkit.permission.RegionPermissionModel;
|
||||
import com.sk89q.worldguard.protection.ApplicableRegionSet;
|
||||
import com.sk89q.worldguard.protection.flags.DefaultFlag;
|
||||
import com.sk89q.worldguard.protection.managers.RegionManager;
|
||||
import com.sk89q.worldguard.protection.regions.ProtectedCuboidRegion;
|
||||
import com.sk89q.worldguard.protection.regions.ProtectedRegion;
|
||||
|
||||
class WorldGuardWrapper
|
||||
{
|
||||
private WorldGuardPlugin worldGuard = null;
|
||||
|
||||
public WorldGuardWrapper() throws ClassNotFoundException
|
||||
{
|
||||
this.worldGuard = (WorldGuardPlugin)GriefPrevention.instance.getServer().getPluginManager().getPlugin("WorldGuard");
|
||||
}
|
||||
|
||||
public boolean canBuild(Location lesserCorner, Location greaterCorner, Player creatingPlayer)
|
||||
{
|
||||
World world = lesserCorner.getWorld();
|
||||
|
||||
if (worldGuard == null)
|
||||
{
|
||||
GriefPrevention.AddLogEntry("WorldGuard is out of date and not enabled. Please update or remove WorldGuard.", CustomLogEntryTypes.Debug, false);
|
||||
return true;
|
||||
}
|
||||
|
||||
if(new RegionPermissionModel(this.worldGuard, creatingPlayer).mayIgnoreRegionProtection(world)) return true;
|
||||
|
||||
RegionManager manager = this.worldGuard.getRegionManager(world);
|
||||
|
||||
if(manager != null)
|
||||
{
|
||||
ProtectedCuboidRegion tempRegion = new ProtectedCuboidRegion(
|
||||
"GP_TEMP",
|
||||
new BlockVector(lesserCorner.getX(), 0, lesserCorner.getZ()),
|
||||
new BlockVector(greaterCorner.getX(), world.getMaxHeight(), greaterCorner.getZ()));
|
||||
ApplicableRegionSet overlaps = manager.getApplicableRegions(tempRegion);
|
||||
LocalPlayer localPlayer = worldGuard.wrapPlayer(creatingPlayer);
|
||||
for (ProtectedRegion r : overlaps.getRegions()) {
|
||||
if (!manager.getApplicableRegions(r).testState(localPlayer, DefaultFlag.BUILD)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,102 @@
|
||||
package me.ryanhamshire.GriefPrevention.events;
|
||||
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.event.Event;
|
||||
import org.bukkit.event.HandlerList;
|
||||
|
||||
/**
|
||||
* Called when GP is about to deliver claim blocks to a player (~every 10 minutes)
|
||||
*
|
||||
* @author RoboMWM
|
||||
* 11/15/2016.
|
||||
*/
|
||||
public class AccrueClaimBlocksEvent extends Event
|
||||
{
|
||||
// Custom Event Requirements
|
||||
private static final HandlerList handlers = new HandlerList();
|
||||
public static HandlerList getHandlerList() {
|
||||
return handlers;
|
||||
}
|
||||
@Override
|
||||
public HandlerList getHandlers() {
|
||||
return handlers;
|
||||
}
|
||||
|
||||
private Player player;
|
||||
private int blocksToAccrue;
|
||||
private boolean isIdle = false;
|
||||
private boolean cancelled = false;
|
||||
|
||||
/**
|
||||
* @param player Player receiving accruals
|
||||
* @param blocksToAccrue Blocks to accrue
|
||||
*
|
||||
* @deprecated Use {@link #AccrueClaimBlocksEvent(Player, int, boolean)} instead
|
||||
*/
|
||||
public AccrueClaimBlocksEvent(Player player, int blocksToAccrue)
|
||||
{
|
||||
this.player = player;
|
||||
this.blocksToAccrue = blocksToAccrue / 6;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param player Player receiving accruals
|
||||
* @param blocksToAccrue Blocks to accrue
|
||||
* @param isIdle Whether player is detected as idle
|
||||
*/
|
||||
public AccrueClaimBlocksEvent(Player player, int blocksToAccrue, boolean isIdle)
|
||||
{
|
||||
this.player = player;
|
||||
this.blocksToAccrue = blocksToAccrue / 6;
|
||||
this.isIdle = isIdle;
|
||||
}
|
||||
|
||||
public Player getPlayer()
|
||||
{
|
||||
return this.player;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return amount of claim blocks GP will deliver to the player for this 10 minute interval
|
||||
*/
|
||||
public int getBlocksToAccrue()
|
||||
{
|
||||
return this.blocksToAccrue;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return whether the player was detected as idle (used for idle accrual percentage)
|
||||
*/
|
||||
public boolean isIdle() {
|
||||
return this.isIdle;
|
||||
}
|
||||
|
||||
public boolean isCancelled()
|
||||
{
|
||||
return this.cancelled;
|
||||
}
|
||||
|
||||
/**
|
||||
* Modify the amount of claim blocks to deliver to the player for this 10 minute interval
|
||||
* @param blocksToAccrue blocks to deliver
|
||||
*/
|
||||
public void setBlocksToAccrue(int blocksToAccrue)
|
||||
{
|
||||
this.blocksToAccrue = blocksToAccrue;
|
||||
}
|
||||
|
||||
/**
|
||||
* Similar to setBlocksToAccrue(int), but automatically converting from a per-hour rate value to a 10-minute rate value
|
||||
* @param blocksToAccruePerHour the per-hour rate of blocks to deliver
|
||||
*/
|
||||
|
||||
public void setBlocksToAccruePerHour(int blocksToAccruePerHour)
|
||||
{
|
||||
this.blocksToAccrue = blocksToAccruePerHour / 6;
|
||||
}
|
||||
|
||||
public void setCancelled(boolean cancel)
|
||||
{
|
||||
this.cancelled = cancel;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
package me.ryanhamshire.GriefPrevention.events;
|
||||
|
||||
import me.ryanhamshire.GriefPrevention.Claim;
|
||||
|
||||
import org.bukkit.event.Event;
|
||||
import org.bukkit.event.HandlerList;
|
||||
|
||||
/**
|
||||
* This event gets called whenever a claim is going to be deleted. This event is
|
||||
* not called when a claim is resized.
|
||||
*
|
||||
* @author Tux2
|
||||
*
|
||||
*/
|
||||
public class ClaimDeletedEvent extends Event{
|
||||
|
||||
// Custom Event Requirements
|
||||
private static final HandlerList handlers = new HandlerList();
|
||||
|
||||
public static HandlerList getHandlerList() {
|
||||
return handlers;
|
||||
}
|
||||
|
||||
private Claim claim;
|
||||
|
||||
public ClaimDeletedEvent(Claim claim) {
|
||||
this.claim = claim;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the claim to be deleted.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public Claim getClaim() {
|
||||
return claim;
|
||||
}
|
||||
|
||||
@Override
|
||||
public HandlerList getHandlers() {
|
||||
return handlers;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
package me.ryanhamshire.GriefPrevention.events;
|
||||
|
||||
import me.ryanhamshire.GriefPrevention.Claim;
|
||||
|
||||
import org.bukkit.event.Cancellable;
|
||||
import org.bukkit.event.Event;
|
||||
import org.bukkit.event.HandlerList;
|
||||
|
||||
//if cancelled, the claim will not be deleted
|
||||
public class ClaimExpirationEvent extends Event implements Cancellable
|
||||
{
|
||||
private static final HandlerList handlers = new HandlerList();
|
||||
private boolean cancelled = false;
|
||||
|
||||
public static HandlerList getHandlerList()
|
||||
{
|
||||
return handlers;
|
||||
}
|
||||
|
||||
Claim claim;
|
||||
|
||||
public ClaimExpirationEvent(Claim claim)
|
||||
{
|
||||
this.claim = claim;
|
||||
}
|
||||
|
||||
public Claim getClaim()
|
||||
{
|
||||
return this.claim;
|
||||
}
|
||||
|
||||
@Override
|
||||
public HandlerList getHandlers()
|
||||
{
|
||||
return handlers;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isCancelled()
|
||||
{
|
||||
return this.cancelled;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setCancelled(boolean cancelled)
|
||||
{
|
||||
this.cancelled = cancelled;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
package me.ryanhamshire.GriefPrevention.events;
|
||||
|
||||
import me.ryanhamshire.GriefPrevention.Messages;
|
||||
import org.bukkit.event.Event;
|
||||
import org.bukkit.event.HandlerList;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
|
||||
/**
|
||||
* Called when GP is retrieving the denial message to send to the player when canceling an action
|
||||
*
|
||||
* @author RoboMWM
|
||||
* Created 1/4/2017.
|
||||
*/
|
||||
public class DeniedMessageEvent extends Event
|
||||
{
|
||||
// Custom Event Requirements
|
||||
private static final HandlerList handlers = new HandlerList();
|
||||
public static HandlerList getHandlerList() {
|
||||
return handlers;
|
||||
}
|
||||
@Override
|
||||
public HandlerList getHandlers() {
|
||||
return handlers;
|
||||
}
|
||||
|
||||
private String message;
|
||||
private Messages messageID;
|
||||
|
||||
public DeniedMessageEvent(Messages messageID, String message)
|
||||
{
|
||||
this.message = message;
|
||||
this.messageID = messageID;
|
||||
}
|
||||
|
||||
public Messages getMessageID()
|
||||
{
|
||||
return this.messageID;
|
||||
}
|
||||
|
||||
public String getMessage()
|
||||
{
|
||||
return this.message;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the message to print to the player.
|
||||
* @param message Cannot be null. Set to an empty string if you wish for no message to be printed.
|
||||
*/
|
||||
public void setMessage(@Nonnull String message)
|
||||
{
|
||||
this.message = message;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,89 @@
|
||||
package me.ryanhamshire.GriefPrevention.events;
|
||||
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.event.Event;
|
||||
import org.bukkit.event.HandlerList;
|
||||
|
||||
/**
|
||||
* Called when GP is about to kick or ban a player
|
||||
*
|
||||
* @author BillyGalbreath
|
||||
* 03/10/2017.
|
||||
*/
|
||||
public class PlayerKickBanEvent extends Event
|
||||
{
|
||||
private static final HandlerList handlers = new HandlerList();
|
||||
|
||||
public static HandlerList getHandlerList()
|
||||
{
|
||||
return handlers;
|
||||
}
|
||||
|
||||
@Override
|
||||
public HandlerList getHandlers()
|
||||
{
|
||||
return handlers;
|
||||
}
|
||||
|
||||
private Player player;
|
||||
private String reason;
|
||||
private String source;
|
||||
private boolean ban;
|
||||
private boolean cancelled = false;
|
||||
|
||||
/**
|
||||
* @param player Player getting kicked and/or banned
|
||||
* @param reason Reason message for kick/ban
|
||||
* @param source What caused the kick/ban
|
||||
* @param ban True if player is getting banned
|
||||
*/
|
||||
public PlayerKickBanEvent(Player player, String reason, String source, boolean ban)
|
||||
{
|
||||
this.player = player;
|
||||
this.reason = reason;
|
||||
this.source = source;
|
||||
this.ban = ban;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return player getting kicked/banned
|
||||
*/
|
||||
public Player getPlayer()
|
||||
{
|
||||
return this.player;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return reason player is getting kicked/banned
|
||||
*/
|
||||
public String getReason()
|
||||
{
|
||||
return this.reason;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return source that is kicking/banning the player
|
||||
*/
|
||||
public String getSource()
|
||||
{
|
||||
return this.source;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return is player getting banned
|
||||
*/
|
||||
public boolean getBan()
|
||||
{
|
||||
return this.ban;
|
||||
}
|
||||
|
||||
public boolean isCancelled()
|
||||
{
|
||||
return this.cancelled;
|
||||
}
|
||||
|
||||
public void setCancelled(boolean cancel)
|
||||
{
|
||||
this.cancelled = cancel;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
package me.ryanhamshire.GriefPrevention.events;
|
||||
|
||||
import org.bukkit.event.Cancellable;
|
||||
import org.bukkit.event.Event;
|
||||
import org.bukkit.event.HandlerList;
|
||||
import org.bukkit.event.block.BlockBreakEvent;
|
||||
|
||||
//if cancelled, GriefPrevention will allow a block to be broken which it would not have otherwise
|
||||
public class PreventBlockBreakEvent extends Event implements Cancellable
|
||||
{
|
||||
private static final HandlerList handlers = new HandlerList();
|
||||
private boolean cancelled = false;
|
||||
private BlockBreakEvent innerEvent;
|
||||
|
||||
public static HandlerList getHandlerList()
|
||||
{
|
||||
return handlers;
|
||||
}
|
||||
|
||||
public PreventBlockBreakEvent(BlockBreakEvent innerEvent)
|
||||
{
|
||||
this.innerEvent = innerEvent;
|
||||
}
|
||||
|
||||
public BlockBreakEvent getInnerEvent()
|
||||
{
|
||||
return this.innerEvent;
|
||||
}
|
||||
|
||||
@Override
|
||||
public HandlerList getHandlers()
|
||||
{
|
||||
return handlers;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isCancelled()
|
||||
{
|
||||
return this.cancelled;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setCancelled(boolean cancelled)
|
||||
{
|
||||
this.cancelled = cancelled;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
package me.ryanhamshire.GriefPrevention.events;
|
||||
|
||||
import me.ryanhamshire.GriefPrevention.Claim;
|
||||
|
||||
import org.bukkit.event.Cancellable;
|
||||
import org.bukkit.event.Event;
|
||||
import org.bukkit.event.HandlerList;
|
||||
|
||||
//if cancelled, GriefPrevention will not cancel the PvP event it's processing.
|
||||
public class PreventPvPEvent extends Event implements Cancellable
|
||||
{
|
||||
private static final HandlerList handlers = new HandlerList();
|
||||
private boolean cancelled = false;
|
||||
|
||||
public static HandlerList getHandlerList()
|
||||
{
|
||||
return handlers;
|
||||
}
|
||||
|
||||
Claim claim;
|
||||
|
||||
public PreventPvPEvent(Claim claim)
|
||||
{
|
||||
this.claim = claim;
|
||||
}
|
||||
|
||||
public Claim getClaim()
|
||||
{
|
||||
return this.claim;
|
||||
}
|
||||
|
||||
@Override
|
||||
public HandlerList getHandlers()
|
||||
{
|
||||
return handlers;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isCancelled()
|
||||
{
|
||||
return this.cancelled;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setCancelled(boolean cancelled)
|
||||
{
|
||||
this.cancelled = cancelled;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
package me.ryanhamshire.GriefPrevention.events;
|
||||
|
||||
import me.ryanhamshire.GriefPrevention.Claim;
|
||||
|
||||
import org.bukkit.event.Cancellable;
|
||||
import org.bukkit.event.Event;
|
||||
import org.bukkit.event.HandlerList;
|
||||
|
||||
//if cancelled, GriefPrevention will not protect items dropped by a player on death
|
||||
public class ProtectDeathDropsEvent extends Event implements Cancellable
|
||||
{
|
||||
private static final HandlerList handlers = new HandlerList();
|
||||
private boolean cancelled = false;
|
||||
|
||||
public static HandlerList getHandlerList()
|
||||
{
|
||||
return handlers;
|
||||
}
|
||||
|
||||
Claim claim;
|
||||
|
||||
public ProtectDeathDropsEvent(Claim claim)
|
||||
{
|
||||
this.claim = claim;
|
||||
}
|
||||
|
||||
public Claim getClaim()
|
||||
{
|
||||
return this.claim;
|
||||
}
|
||||
|
||||
@Override
|
||||
public HandlerList getHandlers()
|
||||
{
|
||||
return handlers;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isCancelled()
|
||||
{
|
||||
return this.cancelled;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setCancelled(boolean cancelled)
|
||||
{
|
||||
this.cancelled = cancelled;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
package me.ryanhamshire.GriefPrevention.events;
|
||||
|
||||
import me.ryanhamshire.GriefPrevention.Claim;
|
||||
|
||||
import org.bukkit.Location;
|
||||
import org.bukkit.event.Cancellable;
|
||||
import org.bukkit.event.Event;
|
||||
import org.bukkit.event.HandlerList;
|
||||
|
||||
//if destination field is set, then GriefPrevention will send the player to that location instead of searching for one
|
||||
public class SaveTrappedPlayerEvent extends Event implements Cancellable
|
||||
{
|
||||
private static final HandlerList handlers = new HandlerList();
|
||||
private boolean cancelled = false;
|
||||
private Location destination = null;
|
||||
|
||||
public static HandlerList getHandlerList()
|
||||
{
|
||||
return handlers;
|
||||
}
|
||||
|
||||
Claim claim;
|
||||
|
||||
public SaveTrappedPlayerEvent(Claim claim)
|
||||
{
|
||||
this.claim = claim;
|
||||
}
|
||||
|
||||
public Location getDestination()
|
||||
{
|
||||
return destination;
|
||||
}
|
||||
|
||||
public void setDestination(Location destination)
|
||||
{
|
||||
this.destination = destination;
|
||||
}
|
||||
|
||||
public Claim getClaim()
|
||||
{
|
||||
return this.claim;
|
||||
}
|
||||
|
||||
@Override
|
||||
public HandlerList getHandlers()
|
||||
{
|
||||
return handlers;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isCancelled()
|
||||
{
|
||||
return this.cancelled;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setCancelled(boolean cancelled)
|
||||
{
|
||||
this.cancelled = cancelled;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
package me.ryanhamshire.GriefPrevention.events;
|
||||
|
||||
import me.ryanhamshire.GriefPrevention.Claim;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.event.HandlerList;
|
||||
import org.bukkit.event.player.PlayerEvent;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
|
||||
/**
|
||||
* Called when GriefPrevention is sending claim visuals to a player
|
||||
*/
|
||||
public class VisualizationEvent extends PlayerEvent {
|
||||
private static final HandlerList handlers = new HandlerList();
|
||||
private final Collection<Claim> claims;
|
||||
private final boolean showSubdivides;
|
||||
|
||||
/**
|
||||
* New visualization being sent to player
|
||||
*
|
||||
* @param player Player receiving visuals
|
||||
* @param claim The claim being visualized (with subdivides), or null if visuals being removed
|
||||
*/
|
||||
public VisualizationEvent(Player player, Claim claim) {
|
||||
super(player);
|
||||
this.claims = Collections.singleton(claim);
|
||||
this.showSubdivides = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* New visualization being sent to player
|
||||
*
|
||||
* @param player Player receiving visuals
|
||||
* @param claims Claims being visualized (without subdivides)
|
||||
*/
|
||||
public VisualizationEvent(Player player, Collection<Claim> claims) {
|
||||
super(player);
|
||||
this.claims = claims;
|
||||
this.showSubdivides = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the claims being visualized, or null if visualization being removed
|
||||
*
|
||||
* @return Claims being visualized
|
||||
*/
|
||||
public Collection<Claim> getClaims() {
|
||||
return claims;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if subdivide claims are being shown
|
||||
*
|
||||
* @return True if subdivide claims are being shown
|
||||
*/
|
||||
public boolean showSubdivides() {
|
||||
return showSubdivides;
|
||||
}
|
||||
|
||||
@Override
|
||||
public HandlerList getHandlers() {
|
||||
return handlers;
|
||||
}
|
||||
|
||||
public static HandlerList getHandlerList() {
|
||||
return handlers;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
/**
|
||||
*
|
||||
*/
|
||||
/**
|
||||
* @author Ryan
|
||||
*
|
||||
*/
|
||||
package me.ryanhamshire.GriefPrevention.events;
|
||||
@@ -0,0 +1,41 @@
|
||||
This document describes the public API, which you can use to create extensions to GriefPrevention which add new features. Before I get into the specifics, let me give you a few examples of often-requested features which, to my knowledge, have not yet been implemented by anyone. If you want to make a big impact with a small project, these are the go-to areas! If you publish one of these extensions on BukkitDev, please contact me and I'll add a link from my project to yours.
|
||||
|
||||
Claim Buy/Sell
|
||||
|
||||
I keep saying no to this because I'm developing an anti grief plugin, not a real estate plugin. But it's a common ask. Lots of people would use an extension that allowed them to use server money to buy and sell claims, or to lease subdivisions.
|
||||
|
||||
More Locks
|
||||
|
||||
Many have asked for wooden doors, trap doors, and fence gates to require /AccessTrust. I've insisted that because players generally expect these to be openable (based on the Vanilla experience), players should just "earn" their privacy by finding some iron and building an iron door. Nonetheless, some folks definitely want this.
|
||||
|
||||
Claim Flags
|
||||
|
||||
Sometimes, folks want to add special flags to their claims like "no monsters spawn here". They can do this today by adding other plugins like WorldGuard, which are compatible with GriefPrevention, but it would be nice if they could just use one plugin (and an extension). I think their flag ideas come mostly from Residence and WorldGuard, so you can look there for ideas.
|
||||
|
||||
Claim Entry/Exit Messages
|
||||
|
||||
I keep telling people NO, I won't do this because it's not anti-grief-related and it's expensive to constantly track player movement. But folks want it, and they keep asking for it. You could build an extension which adds some slash commands for naming claims, and displays enter/exit messages as players walk around.
|
||||
|
||||
Now the specifics! Please note, these are the supported operations. I've done my best to "hide" fields and methods which you shouldn't play with, but if you happen to notice something not discussed here, it's best not to fiddle with it. If in doubt, at the very least look at my source code and comments before using something you're unfamiliar with in an extension.
|
||||
|
||||
Getting the Claim at a Location
|
||||
|
||||
Managing Permissions in a Claim
|
||||
|
||||
Creating a New Claim
|
||||
|
||||
Resizing or Moving a Claim
|
||||
|
||||
Extending a Claim Downward
|
||||
|
||||
Changing a Claim's Owner
|
||||
|
||||
Updating Other Claim Fields
|
||||
|
||||
Uniquely Identifying a Claim
|
||||
|
||||
Starting a Siege
|
||||
|
||||
Ending a Siege
|
||||
|
||||
Getting/Updating Player Data
|
||||
@@ -0,0 +1,371 @@
|
||||
name: GriefPrevention
|
||||
main: me.ryanhamshire.GriefPrevention.GriefPrevention
|
||||
softdepend: [Vault, Multiverse-Core, My Worlds, MystCraft, Transporter, TheUnderground, WorldGuard, WorldEdit, RoyalCommands, MultiWorld, Denizen]
|
||||
dev-url: http://dev.bukkit.org/server-mods/grief-prevention
|
||||
loadbefore: [TheUnderground]
|
||||
version: ${project.version}
|
||||
commands:
|
||||
abandonclaim:
|
||||
description: Deletes a claim.
|
||||
usage: /AbandonClaim
|
||||
aliases: [unclaim, declaim, removeclaim, disclaim]
|
||||
permission: griefprevention.claims
|
||||
abandontoplevelclaim:
|
||||
description: Deletes a claim and all its subdivisions.
|
||||
usage: /AbandonTopLevelClaim
|
||||
permission: griefprevention.claims
|
||||
abandonallclaims:
|
||||
description: Deletes ALL your claims.
|
||||
usage: /AbandonAllClaims
|
||||
permission: griefprevention.claims
|
||||
trust:
|
||||
description: Grants a player full access to your claim(s).
|
||||
usage: /Trust <player> Grants a player permission to build. See also /UnTrust, /ContainerTrust, /AccessTrust, and /PermissionTrust.
|
||||
aliases: tr
|
||||
permission: griefprevention.claims
|
||||
untrust:
|
||||
description: Revokes a player's access to your claim(s).
|
||||
usage: /UnTrust <player>
|
||||
aliases: ut
|
||||
permission: griefprevention.claims
|
||||
containertrust:
|
||||
description: Grants a player access to your claim's containers, crops, animals, bed, buttons, and levers.
|
||||
usage: /ContainerTrust <player>. Grants a player access to your inventory, crops, animals, bed, and buttons/levers.
|
||||
aliases: ct
|
||||
permission: griefprevention.claims
|
||||
accesstrust:
|
||||
description: Grants a player entry to your claim(s) and use of your bed.
|
||||
usage: /AccessTrust <player>. Grants a player access to your bed, buttons, and levers.
|
||||
aliases: at
|
||||
permission: griefprevention.claims
|
||||
permissiontrust:
|
||||
description: Grants a player permission to grant his level of permission to others.
|
||||
usage: /PermissionTrust <player>. Permits a player to share his permission level with others.
|
||||
aliases: pt
|
||||
permission: griefprevention.claims
|
||||
subdivideclaims:
|
||||
description: Switches the shovel tool to subdivision mode, used to subdivide your claims.
|
||||
usage: /SubdivideClaims
|
||||
aliases: [sc, subdivideclaim]
|
||||
permission: griefprevention.claims
|
||||
restrictsubclaim:
|
||||
description: Restricts a subclaim, so that it inherits no permissions from the parent claim
|
||||
usage: /restrictsubclaim
|
||||
aliases: rsc
|
||||
permission: griefprevention.claims
|
||||
adjustbonusclaimblocks:
|
||||
description: Adds or subtracts bonus claim blocks for a player.
|
||||
usage: /AdjustBonusClaimBlocks <player> <amount>
|
||||
permission: griefprevention.adjustclaimblocks
|
||||
aliases: acb
|
||||
adjustbonusclaimblocksall:
|
||||
description: Adds or subtracts bonus claim blocks for all online players.
|
||||
usage: /AdjustBonusClaimBlocksAll <player> <amount>
|
||||
permission: griefprevention.adjustclaimblocks
|
||||
aliases: acball
|
||||
setaccruedclaimblocks:
|
||||
description: Updates a player's accrued claim block total.
|
||||
usage: /SetAccruedClaimBlocks <player> <amount>
|
||||
permission: griefprevention.adjustclaimblocks
|
||||
aliases: scb
|
||||
deleteclaim:
|
||||
description: Deletes the claim you're standing in, even if it's not your claim.
|
||||
usage: /DeleteClaim
|
||||
permission: griefprevention.deleteclaims
|
||||
deleteallclaims:
|
||||
description: Deletes all of another player's claims.
|
||||
usage: /DeleteAllClaims <player>
|
||||
permission: griefprevention.deleteclaims
|
||||
deleteclaimsinworld:
|
||||
description: Deletes all the claims in a world. Only usable at the server console.
|
||||
usage: /DeleteClaimsInWorld <world>
|
||||
aliases: [deleteallclaimsinworld, clearclaimsinworld, clearallclaimsinworld]
|
||||
permission: griefprevention.deleteclaimsinworld
|
||||
deleteuserclaimsinworld:
|
||||
description: Deletes all the non-admin claims in a world. Only usable at the server console.
|
||||
usage: /DeleteUserClaimsInWorld <world>
|
||||
aliases: [deletealluserclaimsinworld, clearuserclaimsinworld, clearalluserclaimsinworld]
|
||||
permission: griefprevention.deleteclaimsinworld
|
||||
adminclaims:
|
||||
description: Switches the shovel tool to administrative claims mode.
|
||||
usage: /AdminClaims
|
||||
permission: griefprevention.adminclaims
|
||||
aliases: ac
|
||||
restorenature:
|
||||
description: Switches the shovel tool to restoration mode.
|
||||
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
|
||||
aliases: bc
|
||||
permission: griefprevention.claims
|
||||
extendclaim:
|
||||
description: Resizes the land claim you're standing in by pushing or pulling its boundary in the direction you're facing.
|
||||
usage: /ExtendClaim <numberOfBlocks>
|
||||
aliases: [expandclaim, resizeclaim]
|
||||
permission: griefprevention.claims
|
||||
claim:
|
||||
description: Creates a land claim centered at your current location.
|
||||
usage: /Claim [optional radius]
|
||||
aliases: [createclaim, makeclaim, newclaim]
|
||||
permission: griefprevention.claims
|
||||
buyclaimblocks:
|
||||
description: Purchases additional claim blocks with server money. Doesn't work on servers without a Vault-compatible economy plugin.
|
||||
usage: /BuyClaimBlocks <numberOfBlocks>
|
||||
aliases: buyclaim
|
||||
permission: griefprevention.buysellclaimblocks
|
||||
sellclaimblocks:
|
||||
description: Sells your claim blocks for server money. Doesn't work on servers without a Vault-compatible economy plugin.
|
||||
usage: /SellClaimBlocks <numberOfBlocks>
|
||||
aliases: sellclaim
|
||||
permission: griefprevention.buysellclaimblocks
|
||||
trapped:
|
||||
description: Ejects you to nearby unclaimed land. Has a substantial cooldown period.
|
||||
usage: /Trapped
|
||||
permission: griefprevention.trapped
|
||||
trustlist:
|
||||
description: Lists permissions for the claim you're standing in.
|
||||
usage: /TrustList
|
||||
permission: griefprevention.claims
|
||||
siege:
|
||||
description: Initiates a siege versus another player.
|
||||
usage: /Siege <playerName>
|
||||
permission: griefprevention.siege
|
||||
ignoreclaims:
|
||||
description: Toggles ignore claims mode.
|
||||
usage: /IgnoreClaims
|
||||
permission: griefprevention.ignoreclaims
|
||||
aliases: ic
|
||||
deletealladminclaims:
|
||||
description: Deletes all administrative claims.
|
||||
usage: /DeleteAllAdminClaims
|
||||
permission: griefprevention.adminclaims
|
||||
adminclaimslist:
|
||||
description: Lists all administrative claims.
|
||||
usage: /AdminClaimsList
|
||||
permission: griefprevention.adminclaims
|
||||
transferclaim:
|
||||
description: Converts an administrative claim to a private claim.
|
||||
usage: /TransferClaim <player>
|
||||
aliases: giveclaim
|
||||
permission: griefprevention.transferclaim
|
||||
unlockdrops:
|
||||
description: Allows other players to pick up the items you dropped when you died.
|
||||
usage: /UnlockDrops
|
||||
permission: griefprevention.unlockdrops
|
||||
claimslist:
|
||||
description: Lists information about a player's claim blocks and claims.
|
||||
usage: /ClaimsList or /ClaimsList <player>
|
||||
aliases: [claimlist, listclaims]
|
||||
permission: griefprevention.claims
|
||||
claimexplosions:
|
||||
description: Toggles whether explosives may be used in a specific land claim.
|
||||
usage: /ClaimExplosions
|
||||
permission: griefprevention.claims
|
||||
aliases: claimexplosion
|
||||
softmute:
|
||||
description: Toggles whether a player's messages will only reach other soft-muted players.
|
||||
usage: /SoftMute <player>
|
||||
permission: griefprevention.softmute
|
||||
gpreload:
|
||||
description: Reloads Grief Prevention's configuration settings. Does NOT totally reload the entire plugin.
|
||||
usage: /gpreload
|
||||
permission: griefprevention.reload
|
||||
givepet:
|
||||
description: Allows a player to give away a pet he or she tamed.
|
||||
usage: /GivePet <player>
|
||||
permission: griefprevention.givepet
|
||||
gpblockinfo:
|
||||
description: Allows an administrator to get technical information about blocks in the world and items in hand.
|
||||
usage: /GPBlockInfo
|
||||
permission: griefprevention.gpblockinfo
|
||||
ignoreplayer:
|
||||
description: Ignores another player's chat messages.
|
||||
usage: /IgnorePlayer <player name>
|
||||
aliases: [ignore]
|
||||
permission: griefprevention.ignore
|
||||
unignoreplayer:
|
||||
description: Unignores another player's chat messages.
|
||||
usage: /UnIgnorePlayer <player name>
|
||||
aliases: [unignore]
|
||||
permission: griefprevention.ignore
|
||||
ignoredplayerlist:
|
||||
description: Lists the players you're ignoring in chat.
|
||||
usage: /IgnoredPlayerList
|
||||
aliases: [ignores, ignored, ignorelist, ignoredlist, listignores, listignored, ignoring]
|
||||
permission: griefprevention.ignore
|
||||
separate:
|
||||
description: Forces two players to ignore each other in chat.
|
||||
usage: /Separate <player1> <player2>
|
||||
permission: griefprevention.separate
|
||||
unseparate:
|
||||
description: Reverses /separate.
|
||||
usage: /UnSeparate <player1> <player2>
|
||||
permission: griefprevention.separate
|
||||
claimbook:
|
||||
description: Gives a player a manual about claiming land.
|
||||
usage: /ClaimBook <player>
|
||||
permission: griefprevention.claimbook
|
||||
permissions:
|
||||
griefprevention.createclaims:
|
||||
description: Grants permission to create claims.
|
||||
default: true
|
||||
griefprevention.admin.*:
|
||||
description: Grants all administrative functionality.
|
||||
children:
|
||||
griefprevention.restorenature: true
|
||||
griefprevention.restorenatureaggressive: true
|
||||
griefprevention.ignoreclaims: true
|
||||
griefprevention.adminclaims: true
|
||||
griefprevention.adjustclaimblocks: true
|
||||
griefprevention.deleteclaims: true
|
||||
griefprevention.spam: true
|
||||
griefprevention.lava: true
|
||||
griefprevention.eavesdrop: true
|
||||
griefprevention.deathblow: true
|
||||
griefprevention.softmute: true
|
||||
griefprevention.reload: true
|
||||
griefprevention.visualizenearbyclaims: true
|
||||
griefprevention.overrideclaimcountlimit: true
|
||||
griefprevention.transferclaim: true
|
||||
griefprevention.claimslistother: true
|
||||
griefprevention.siegeimmune: true
|
||||
griefprevention.separate: true
|
||||
griefprevention.eavesdropsigns: true
|
||||
griefprevention.claimbook: true
|
||||
griefprevention.notignorable: true
|
||||
griefprevention.seeinactivity: true
|
||||
griefprevention.eavesdropimmune: true
|
||||
griefprevention.deleteclaimsinworld: true
|
||||
griefprevention.siegeteleport: true
|
||||
griefprevention.unlockothersdrops: true
|
||||
griefprevention.seeclaimsize: true
|
||||
griefprevention.siegeimmune:
|
||||
description: Makes a player immune to /Siege.
|
||||
default: op
|
||||
griefprevention.givepet:
|
||||
description: Grants permission to use /GivePet.
|
||||
default: true
|
||||
griefprevention.siege:
|
||||
description: Grants permission to use /Siege.
|
||||
default: true
|
||||
griefprevention.unlockdrops:
|
||||
description: Grants permission to use /UnlockDrops.
|
||||
default: true
|
||||
griefprevention.unlockothersdrops:
|
||||
description: Grants permission to use /UnlockDrops for other players.
|
||||
default: op
|
||||
griefprevention.trapped:
|
||||
description: Grants permission to use /Trapped.
|
||||
default: true
|
||||
griefprevention.claimslistother:
|
||||
description: Grants permission to use /ClaimsList to get another player's information.
|
||||
default: op
|
||||
griefprevention.restorenature:
|
||||
description: Grants permission to use /RestoreNature.
|
||||
default: op
|
||||
griefprevention.transferclaim:
|
||||
description: Grants permission to use /TransferClaim.
|
||||
default: op
|
||||
griefprevention.ignoreclaims:
|
||||
description: Grants permission to use /IgnoreClaims.
|
||||
default: op
|
||||
griefprevention.adminclaims:
|
||||
description: Grants permission to create administrative claims.
|
||||
default: op
|
||||
griefprevention.deleteclaims:
|
||||
description: Grants permission to delete other players' claims.
|
||||
default: op
|
||||
griefprevention.deleteclaimsinworld:
|
||||
description: Not used. DeleteClaimsInWorld must be executed at the server console.
|
||||
default: op
|
||||
griefprevention.adjustclaimblocks:
|
||||
description: Grants permission to add or remove bonus blocks from a player's account.
|
||||
default: op
|
||||
griefprevention.spam:
|
||||
description: Grants permission to log in, send messages, and send commands rapidly.
|
||||
default: op
|
||||
griefprevention.lava:
|
||||
description: Grants permission to place lava near the surface and outside of claims.
|
||||
default: op
|
||||
griefprevention.eavesdrop:
|
||||
description: Allows a player to see whispered chat messages (/tell) and softmuted messages.
|
||||
default: op
|
||||
griefprevention.eavesdropsigns:
|
||||
description: Allows a player to see sign placements as chat messages.
|
||||
default: op
|
||||
griefprevention.restorenatureaggressive:
|
||||
description: Grants access to /RestoreNatureAggressive and /RestoreNatureFill.
|
||||
default: op
|
||||
griefprevention.deathblow:
|
||||
description: Grants access to /DeathBlow.
|
||||
default: op
|
||||
griefprevention.reload:
|
||||
description: Grants access to /gpreload.
|
||||
default: op
|
||||
griefprevention.softmute:
|
||||
description: Grants access to /SoftMute.
|
||||
default: op
|
||||
griefprevention.claims:
|
||||
description: Grants access to claim-related slash commands.
|
||||
default: true
|
||||
griefprevention.buysellclaimblocks:
|
||||
description: Grants access to claim block buy/sell commands.
|
||||
default: true
|
||||
griefprevention.visualizenearbyclaims:
|
||||
description: Allows a player to see all nearby claims at once.
|
||||
default: op
|
||||
griefprevention.seeclaimsize:
|
||||
description: Allows a player to see claim size for other players claims when right clicking with investigation tool
|
||||
default: op
|
||||
griefprevention.gpblockinfo:
|
||||
description: Grants access to /GPBlockInfo.
|
||||
default: op
|
||||
griefprevention.overrideclaimcountlimit:
|
||||
description: Allows players to create more claims than the limit specified by the config.
|
||||
default: op
|
||||
griefprevention.separate:
|
||||
description: Grants access to /Separate and /UnSeparate.
|
||||
default: op
|
||||
griefprevention.ignore:
|
||||
description: Grants access to /Ignore, /Unignore, and /IgnoreList
|
||||
default: true
|
||||
griefprevention.claimbook:
|
||||
description: Grants access to /ClaimBook.
|
||||
default: op
|
||||
griefprevention.notignorable:
|
||||
description: Players with this permission can't be ignored.
|
||||
default: op
|
||||
griefprevention.seeinactivity:
|
||||
description: Players with this permission can see how long a claim owner has been offline.
|
||||
default: op
|
||||
griefprevention.eavesdropimmune:
|
||||
description: Players with this permission can't have their private messages eavesdropped.
|
||||
default: op
|
||||
griefprevention.fasteraccrual:
|
||||
description: Players with this permission accrue claim blocks at the faster rate specified in the config file.
|
||||
default: false
|
||||
griefprevention.fastestaccrual:
|
||||
description: Players with this permission accrue claim blocks at the fastest rate specified in the config file.
|
||||
default: false
|
||||
griefprevention.moreaccrued:
|
||||
description: Players with this permission can accrue more claim blocks (limit specified in the config file).
|
||||
default: false
|
||||
griefprevention.mostaccrued:
|
||||
description: Players with this permission can accrue more claim blocks (limit specified in the config file).
|
||||
default: false
|
||||
griefprevention.siegeteleport:
|
||||
description: Players with this permission can teleport into and out of besieged areas.
|
||||
default: op
|
||||
Reference in New Issue
Block a user