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;
|
||||
Reference in New Issue
Block a user