This commit is contained in:
Ryan Hamshire 2012-03-29 17:27:12 -07:00
commit e68fd63194
27 changed files with 6781 additions and 0 deletions

0
README Normal file
View File

123
plugin.yml Normal file
View File

@ -0,0 +1,123 @@
name: GriefPrevention
main: me.ryanhamshire.GriefPrevention.GriefPrevention
softdepend: [Vault]
version: 3.2
commands:
abandonclaim:
description: Deletes a claim.
usage: /abandonclaim
abandonallclaims:
description: Deletes ALL your claims.
usage: /AbandonAllClaims
trust:
description: Grants a player full access to your claim(s).
usage: /Trust <player> See also /UnTrust, /ContainerTrust, /AccessTrust, and /PermissionTrust.
aliases: t
untrust:
description: Revokes a player's access to your claim(s).
usage: /UnTrust <player>
aliases: ut
containertrust:
description: Grants a player access to your containers.
usage: /ContainerTrust <player>
aliases: ct
accesstrust:
description: Grants a player entry to your claim(s) and use of your bed.
usage: /AccessTrust <player>
aliases: at
permissiontrust:
description: Grants a player permission to grant his level of permission to others.
usage: /PermissionTrust <player>
aliases: pt
subdivideclaims:
description: Switches the shovel tool to subdivision mode, used to subdivide your claims.
usage: /SubdivideClaims
aliases: sc
adjustbonusclaimblocks:
description: Adds or subtracts bonus claim blocks for a player.
usage: /AdjustBonusClaimBlocks <player> <amount>
permission: griefprevention.adjustclaimblocks
aliases: acb
deleteclaim:
description: Deletes the claim you're standing in, even if it's not your claim.
usage: /DeleteClaim
permission: griefprevention.deleteclaims
aliases: dc
deleteallclaims:
description: Deletes all of another player's claims.
usage: /DeleteAllClaims <player>
permission: griefprevention.deleteclaims
adminclaims:
description: Switches the shovel tool to administrative claims mode.
usage: /AdminClaims
permission: griefprevention.adminclaims
aliases: ac
restorenature:
description: Switches the shovel tool to restoration mode.
usage: /RestoreNature
permission: griefprevention.restorenature
aliases: rn
basicclaims:
description: Switches the shovel tool back to basic claims mode.
usage: /BasicClaims
aliases: bc
buyclaimblocks:
description: Purchases additional claim blocks with server money. Doesn't work on servers without a Vault-compatible economy plugin.
usage: /BuyClaimBlocks <numberOfBlocks>
aliases: buyclaim
sellclaimblocks:
description: Sells your claim blocks for server money. Doesn't work on servers without a Vault-compatible economy plugin.
usage: /SellClaimBlocks <numberOfBlocks>
aliases: sellclaim
trapped:
description: Ejects you to nearby unclaimed land. Usable once per 8 hours.
usage: /Trapped
trustlist:
description: Lists permissions for the claim you're standing in.
usage: /TrustList
siege:
description: Initiates a siege versus another player.
usage: /Siege <playerName>
ignoreclaims:
description: Toggles ignore claims mode.
usage: /IgnoreClaims
permission: griefprevention.ignoreclaims
aliases: ic
deletealladminclaims:
description: Deletes all administrative claims.
usage: /DeleteAllAdminClaims
permission: adminclaims
permissions:
griefprevention.createclaims:
description: Grants permission to create claims.
default: op
griefprevention.admin.*:
description: Grants all administrative functionality.
children:
griefprevention.restorenature: true
griefprevention.ignoreclaims: true
griefprevention.adminclaims: true
griefprevention.adjustclaimblocks: true
griefprevention.deleteclaims: true
griefprevention.spam: true
griefprevention.restorenature:
description: Grants permission to use /RestoreNature.
default: op
griefprevention.ignoreclaims:
description: Grants permission to use /IgnoreClaims.
default: op
griefprevention.adminclaims:
description: Grants permission to create administrative claims.
default: op
griefprevention.deleteclaims:
description: Grants permission to delete other players' claims.
default: op
griefprevention.adjustclaimblocks:
description: Grants permission to add or remove bonus blocks from a player's account.
default: op
griefprevention.spam:
description: Grants permission to log in, send messages, and send commands rapidly.
default: op
griefprevention.lava:
description: Grants permission to place lava near the surface and outside of claims.
default: op

View File

@ -0,0 +1,405 @@
/*
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.List;
import org.bukkit.GameMode;
import org.bukkit.Location;
import org.bukkit.Material;
import org.bukkit.OfflinePlayer;
import org.bukkit.block.Block;
import org.bukkit.block.Chest;
import org.bukkit.entity.Player;
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.BlockDamageEvent;
import org.bukkit.event.block.BlockFromToEvent;
import org.bukkit.event.block.BlockIgniteEvent;
import org.bukkit.event.block.BlockIgniteEvent.IgniteCause;
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.inventory.Inventory;
import org.bukkit.inventory.ItemStack;
import org.bukkit.inventory.PlayerInventory;
//event handlers related to blocks
public class BlockEventHandler implements Listener
{
//convenience reference to singleton datastore
private DataStore dataStore;
//boring typical constructor
public BlockEventHandler(DataStore dataStore)
{
this.dataStore = dataStore;
}
//when a block is damaged...
@EventHandler(ignoreCancelled = true)
public void onBlockDamaged(BlockDamageEvent event)
{
Block block = event.getBlock();
Player player = event.getPlayer();
//only care about player-damaged blocks
if(player == null) return;
//FEATURE: players may add items to a chest they don't have permission for by hitting it
//if it's a chest
if(block.getType() == Material.CHEST)
{
//only care about non-creative mode players, since those would outright break the box in one hit
if(player.getGameMode() == GameMode.CREATIVE) return;
//only care if the player has an itemstack in hand
PlayerInventory playerInventory = player.getInventory();
ItemStack stackInHand = playerInventory.getItemInHand();
if(stackInHand == null || stackInHand.getType() == Material.AIR) return;
//only care if the chest is in a claim, and the player does not have access to the chest
Claim claim = this.dataStore.getClaimAt(block.getLocation(), false, null);
if(claim == null || claim.allowContainers(player) == null) return;
//if the player is under siege, he can't give away items
PlayerData playerData = this.dataStore.getPlayerData(event.getPlayer().getName());
if(playerData.siegeData != null)
{
GriefPrevention.sendMessage(player, TextMode.Err, "You can't give away items while involved in a siege.");
event.setCancelled(true);
return;
}
//NOTE: to eliminate accidental give-aways, first hit on a chest displays a confirmation message
//subsequent hits donate item to the chest
//if first time damaging this chest, show confirmation message
if(playerData.lastChestDamageLocation == null || !block.getLocation().equals(playerData.lastChestDamageLocation))
{
//remember this location
playerData.lastChestDamageLocation = block.getLocation();
//give the player instructions
GriefPrevention.sendMessage(player, TextMode.Instr, "To give away the item(s) in your hand, left-click the chest again.");
}
//otherwise, try to donate the item stack in hand
else
{
//look for empty slot in chest
Chest chest = (Chest)block.getState();
Inventory chestInventory = chest.getInventory();
int availableSlot = chestInventory.firstEmpty();
//if there isn't one
if(availableSlot < 0)
{
//tell the player and stop here
GriefPrevention.sendMessage(player, TextMode.Err, "This chest is full.");
return;
}
//otherwise, transfer item stack from player to chest
//NOTE: Inventory.addItem() is smart enough to add items to existing stacks, making filling a chest with garbage as a grief very difficult
chestInventory.addItem(stackInHand);
playerInventory.setItemInHand(new ItemStack(Material.AIR));
//and confirm for the player
GriefPrevention.sendMessage(player, TextMode.Success, "Item(s) transferred to chest!");
}
}
}
//when a player breaks a block...
@EventHandler(ignoreCancelled = true, priority = EventPriority.HIGHEST)
public void onBlockBreak(BlockBreakEvent breakEvent)
{
Player player = breakEvent.getPlayer();
Block block = breakEvent.getBlock();
PlayerData playerData = this.dataStore.getPlayerData(player.getName());
Claim claim = this.dataStore.getClaimAt(block.getLocation(), true, playerData.lastClaim);
//if there's a claim here
if(claim != null)
{
//cache the claim for later reference
playerData.lastClaim = claim;
//check permissions
String noBuildReason = claim.allowBreak(player, block.getType());
//if permission to break and breaking UNDER the claim
if(block.getY() < claim.lesserBoundaryCorner.getBlockY())
{
if(noBuildReason == null)
{
//extend the claim downward beyond the breakage point
this.dataStore.extendClaim(claim, claim.getLesserBoundaryCorner().getBlockY() - GriefPrevention.instance.config_claims_claimsExtendIntoGroundDistance);
}
}
//otherwise if not allowed to break blocks here, tell the player why
else if(noBuildReason != null)
{
breakEvent.setCancelled(true);
GriefPrevention.sendMessage(player, TextMode.Err, noBuildReason);
return;
}
}
//FEATURE: automatically clean up hanging treetops
//if it's a log
if(block.getType() == Material.LOG && GriefPrevention.instance.config_trees_removeFloatingTreetops)
{
//run the specialized code for treetop removal (see below)
GriefPrevention.instance.handleLogBroken(block);
}
}
//when a player places a block...
@EventHandler(ignoreCancelled = true, priority = EventPriority.HIGHEST)
public void onBlockPlace(BlockPlaceEvent placeEvent)
{
Player player = placeEvent.getPlayer();
Block block = placeEvent.getBlock();
//FEATURE: limit fire placement, to prevent PvP-by-fire and general fiery messes
//if placed block is fire and pvp is off, block it apply limitations based on the block it's placed on
if(block.getType() == Material.FIRE && !block.getWorld().getPVP())
{
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) && block.getY() <= location.getBlockY() - 1 && location.distanceSquared(block.getLocation()) < 9)
{
player.sendMessage(block.getY() + " " + otherPlayer.getLocation().getBlockY());
GriefPrevention.sendMessage(player, TextMode.Err, "You can't start a fire this close to " + otherPlayer.getName() + ".");
placeEvent.setCancelled(true);
return;
}
}
}
//if the block is being placed within an existing claim
PlayerData playerData = this.dataStore.getPlayerData(player.getName());
Claim claim = this.dataStore.getClaimAt(block.getLocation(), true, playerData.lastClaim);
if(claim != null)
{
playerData.lastClaim = claim;
String noBuildReason = claim.allowBuild(player);
//if the player has permission for the claim and he's placing UNDER the claim
if(block.getY() < claim.lesserBoundaryCorner.getBlockY())
{
if(noBuildReason == null)
{
//extend the claim downward
this.dataStore.extendClaim(claim, claim.getLesserBoundaryCorner().getBlockY() - GriefPrevention.instance.config_claims_claimsExtendIntoGroundDistance);
}
}
//otherwise if he doesn't have permission, tell him why
else if(noBuildReason != null)
{
placeEvent.setCancelled(true);
GriefPrevention.sendMessage(player, TextMode.Err, noBuildReason);
}
}
//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(block.getType() == Material.CHEST && GriefPrevention.instance.config_claims_automaticClaimsForNewPlayersRadius > -1 && GriefPrevention.instance.claimsEnabledForWorld(block.getWorld()))
{
//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, "This chest can't be protected because it's too deep underground. Consider moving it.");
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.claims.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.getName(), null);
GriefPrevention.sendMessage(player, TextMode.Success, "This chest is protected.");
}
//otherwise, create a claim in the area around the chest
else
{
//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
while(radius >= 0 && !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.getName(),
null).succeeded)
{
radius--;
}
//notify and explain to player
GriefPrevention.sendMessage(player, TextMode.Success, "This chest and nearby blocks are protected from breakage and theft. The gold and glowstone blocks mark the protected area.");
//show the player the protected area
Claim newClaim = this.dataStore.getClaimAt(block.getLocation(), false, null);
Visualization visualization = Visualization.FromClaim(newClaim, block.getY(), VisualizationType.Claim);
Visualization.Apply(player, visualization);
}
//instructions for using /trust
GriefPrevention.sendMessage(player, TextMode.Instr, "Use the /trust command to grant other players access.");
//unless special permission is required to create a claim with the shovel, educate the player about the shovel
if(!GriefPrevention.instance.config_claims_creationRequiresPermission)
{
GriefPrevention.sendMessage(player, TextMode.Instr, "To claim more land, use a golden shovel.");
}
}
//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, "This chest is NOT protected. Consider expanding an existing claim or creating a new one.");
}
}
}
//blocks "pushing" other players' blocks around (pistons)
@EventHandler(ignoreCancelled = true, priority = EventPriority.HIGHEST)
public void onBlockPistonExtend (BlockPistonExtendEvent event)
{
//who owns the piston, if anyone?
String pistonClaimOwnerName = "_";
Claim claim = this.dataStore.getClaimAt(event.getBlock().getLocation(), false, null);
if(claim != null) pistonClaimOwnerName = claim.getOwnerName();
//which blocks are being pushed?
List<Block> blocks = event.getBlocks();
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, null);
if(claim != null && !claim.getOwnerName().equals(pistonClaimOwnerName))
{
event.setCancelled(true);
return;
}
}
}
//blocks theft by pulling blocks out of a claim (again pistons)
@EventHandler(ignoreCancelled = true, priority = EventPriority.HIGHEST)
public void onBlockPistonRetract (BlockPistonRetractEvent event)
{
//we only care about sticky pistons
if(!event.isSticky()) return;
//who owns the moving block, if anyone?
String movingBlockOwnerName = "_";
Claim movingBlockClaim = this.dataStore.getClaimAt(event.getRetractLocation(), false, null);
if(movingBlockClaim != null) movingBlockOwnerName = movingBlockClaim.getOwnerName();
//who owns the piston, if anyone?
String pistonOwnerName = "_";
Location pistonLocation = event.getBlock().getLocation();
Claim pistonClaim = this.dataStore.getClaimAt(pistonLocation, false, null);
if(pistonClaim != null) pistonOwnerName = pistonClaim.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);
}
}
//blocks are ignited ONLY by flint and steel (not by being near lava, open flames, etc)
@EventHandler(priority = EventPriority.HIGHEST)
public void onBlockIgnite (BlockIgniteEvent igniteEvent)
{
if(igniteEvent.getCause() != IgniteCause.FLINT_AND_STEEL) igniteEvent.setCancelled(true);
}
//fire doesn't spread, but other blocks still do (mushrooms and vines, for example)
@EventHandler(priority = EventPriority.HIGHEST)
public void onBlockSpread (BlockSpreadEvent spreadEvent)
{
if(spreadEvent.getSource().getType() == Material.FIRE) spreadEvent.setCancelled(true);
}
//blocks are not destroyed by fire
@EventHandler(priority = EventPriority.HIGHEST)
public void onBlockBurn (BlockBurnEvent burnEvent)
{
burnEvent.setCancelled(true);
}
//ensures fluids don't flow into claims, unless out of another claim where the owner is trusted to build in the receiving claim
@EventHandler(ignoreCancelled = true, priority = EventPriority.HIGHEST)
public void onBlockFromTo (BlockFromToEvent spreadEvent)
{
//where to?
Block toBlock = spreadEvent.getToBlock();
Claim toClaim = this.dataStore.getClaimAt(toBlock.getLocation(), false, null);
//if spreading into a claim
if(toClaim != null)
{
//from where?
Block fromBlock = spreadEvent.getBlock();
Claim fromClaim = this.dataStore.getClaimAt(fromBlock.getLocation(), false, null);
//who owns the spreading block, if anyone?
OfflinePlayer fromOwner = null;
if(fromClaim != null)
{
//if it's within the same claim, allow it
if(fromClaim == toClaim) return;
fromOwner = GriefPrevention.instance.getServer().getOfflinePlayer(fromClaim.ownerName);
}
//cancel unless the owner of the spreading block is allowed to build in the receiving claim
if(fromOwner == null || fromOwner.getPlayer() == null || toClaim.allowBuild(fromOwner.getPlayer()) != null)
{
spreadEvent.setCancelled(true);
}
}
}
}

View File

@ -0,0 +1,37 @@
/*
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;
//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 int typeId;
public byte data;
public BlockSnapshot(Location location, int typeId, byte data)
{
this.location = location;
this.typeId = typeId;
this.data = data;
}
}

View File

@ -0,0 +1,554 @@
/*
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.Calendar;
import java.util.Date;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import org.bukkit.*;
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
//IF MODIFIED, THE CLAIM DATA FILE'S NAME WILL CHANGE. ANY MODIFICATIONS MUST BE HANDLED VERY CAREFULLY
Location lesserBoundaryCorner;
Location greaterBoundaryCorner;
//modification date. this comes from the file timestamp during load, and is updated with runtime changes
public Date modifiedDate;
//ownername. for admin claims, this is the empty string
//use getOwnerName() to get a friendly name (will be "an administrator" for admin claims)
public String ownerName;
//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> playerNameToClaimPermissionMap = 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;
//parent claim
//only used for claim subdivisions. top level claims have null here
public Claim parent = null;
//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()
{
return this.ownerName.isEmpty();
}
//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;
}
//main constructor. note that only creating a claim instance does nothing - a claim must be added to the data store to be effective
Claim(Location lesserBoundaryCorner, Location greaterBoundaryCorner, String ownerName, String [] builderNames, String [] containerNames, String [] accessorNames, String [] managerNames)
{
//modification date
this.modifiedDate = Calendar.getInstance().getTime();
//store corners
this.lesserBoundaryCorner = lesserBoundaryCorner;
this.greaterBoundaryCorner = greaterBoundaryCorner;
//if trying to create a claim under the max depth, auto-correct y values
if(this.lesserBoundaryCorner.getBlockY() < GriefPrevention.instance.config_claims_maxDepth)
{
this.lesserBoundaryCorner.setY(GriefPrevention.instance.config_claims_maxDepth);
}
if(this.greaterBoundaryCorner.getBlockY() < GriefPrevention.instance.config_claims_maxDepth)
{
this.greaterBoundaryCorner.setY(GriefPrevention.instance.config_claims_maxDepth);
}
//owner
this.ownerName = ownerName;
//other permissions
for(int i = 0; i < builderNames.length; i++)
{
String name = builderNames[i];
if(name != null && !name.isEmpty())
{
this.playerNameToClaimPermissionMap.put(name, ClaimPermission.Build);
}
}
for(int i = 0; i < containerNames.length; i++)
{
String name = containerNames[i];
if(name != null && !name.isEmpty())
{
this.playerNameToClaimPermissionMap.put(name, ClaimPermission.Inventory);
}
}
for(int i = 0; i < accessorNames.length; i++)
{
String name = accessorNames[i];
if(name != null && !name.isEmpty())
{
this.playerNameToClaimPermissionMap.put(name, ClaimPermission.Access);
}
}
for(int i = 0; i < managerNames.length; i++)
{
String name = managerNames[i];
if(name != null && !name.isEmpty())
{
this.managers.add(name);
}
}
}
//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;
}
//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),
"", new String[] {}, new String[] {}, new String[] {}, new String[] {});
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)
{
//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(this.ownerName.equals(player.getName()))
{
if(this.siegeData != null)
{
return "Claims can't be modified while under siege.";
}
//otherwise, owners can do whatever
return null;
}
//permission inheritance for subdivisions
if(this.parent != null)
return this.parent.allowBuild(player);
//error message if all else fails
return "Only " + this.getOwnerName() + " can modify this claim.";
}
//build permission check
public String allowBuild(Player player)
{
//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 "This claim is under siege by " + this.siegeData.attacker.getName() + ". No one can build here.";
}
//no building while in pvp combat
PlayerData playerData = GriefPrevention.instance.dataStore.getPlayerData(player.getName());
if(playerData.inPvpCombat())
{
return "You can't build in claims during PvP combat.";
}
//owners can make changes, or admins with ignore claims mode enabled
if(this.ownerName.equals(player.getName()) || GriefPrevention.instance.dataStore.getPlayerData(player.getName()).ignoreClaims) return null;
//anyone with explicit build permission can make changes
ClaimPermission permissionLevel = this.playerNameToClaimPermissionMap.get(player.getName().toLowerCase());
if(ClaimPermission.Build == permissionLevel) return null;
//also everyone is a member of the "public", so check for public permission
permissionLevel = this.playerNameToClaimPermissionMap.get("public");
if(ClaimPermission.Build == permissionLevel) return null;
//subdivision permission inheritance
if(this.parent != null)
return this.parent.allowBuild(player);
//failure message for all other cases
return "You don't have " + this.getOwnerName() + "'s permission to build here.";
}
//break permission check
public String allowBreak(Player player, Material material)
{
//if under siege, some blocks will be breakable
if(this.siegeData != null)
{
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.getId() == material.getId())
{
breakable = true;
break;
}
}
//custom error messages for siege mode
if(!breakable)
{
return "That material is too tough to break.";
}
else if(this.ownerName.equals(player.getName()))
{
return "You can't make changes while under siege.";
}
else
{
return null;
}
}
//if not under siege, build rules apply
return this.allowBuild(player);
}
//access permission check
public String allowAccess(Player player)
{
//everyone always has access to admin claims
if(this.isAdminClaim()) return null;
//following a siege where the defender lost, the claim will allow everyone access for a time
if(this.doorsOpen) return null;
//claim owner and admins in ignoreclaims mode have access
if(this.ownerName.equals(player.getName()) || GriefPrevention.instance.dataStore.getPlayerData(player.getName()).ignoreClaims) return null;
//look for explicit individual access, inventory, or build permission
ClaimPermission permissionLevel = this.playerNameToClaimPermissionMap.get(player.getName().toLowerCase());
if(ClaimPermission.Build == permissionLevel || ClaimPermission.Inventory == permissionLevel || ClaimPermission.Access == permissionLevel) return null;
//also check for public permission
permissionLevel = this.playerNameToClaimPermissionMap.get("public");
if(ClaimPermission.Build == permissionLevel || ClaimPermission.Inventory == permissionLevel || ClaimPermission.Access == permissionLevel) return null;
//permission inheritance for subdivisions
if(this.parent != null)
return this.parent.allowAccess(player);
//catch-all error message for all other cases
return "You don't have " + this.getOwnerName() + "'s permission to use that.";
}
//inventory permission check
public String allowContainers(Player player)
{
//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 "This claim is under siege by " + siegeData.attacker.getName() + ". No one can access containers here right now.";
}
//containers are always accessible in admin claims
if(this.isAdminClaim()) return null;
//owner and administrators in ignoreclaims mode have access
if(this.ownerName.equals(player.getName()) || GriefPrevention.instance.dataStore.getPlayerData(player.getName()).ignoreClaims) return null;
//check for explicit individual container or build permission
ClaimPermission permissionLevel = this.playerNameToClaimPermissionMap.get(player.getName().toLowerCase());
if(ClaimPermission.Build == permissionLevel || ClaimPermission.Inventory == permissionLevel) return null;
//check for public container or build permission
permissionLevel = this.playerNameToClaimPermissionMap.get("public");
if(ClaimPermission.Build == permissionLevel || ClaimPermission.Inventory == permissionLevel) return null;
//permission inheritance for subdivisions
if(this.parent != null)
return this.parent.allowContainers(player);
//error message for all other cases
return "You don't have " + this.getOwnerName() + "'s permission to use that.";
}
//grant permission check, relatively simple
public String allowGrantPermission(Player player)
{
//anyone who can modify the claim, or who's explicitly in the managers (/PermissionTrust) list can do this
if(this.allowEdit(player) == null || this.managers.contains(player.getName())) return null;
//permission inheritance for subdivisions
if(this.parent != null)
return this.parent.allowGrantPermission(player);
//generic error message
return "You don't have " + this.getOwnerName() + "'s permission to grant permission here.";
}
//grants a permission for a player or the public
public void setPermission(String playerName, ClaimPermission permissionLevel)
{
this.playerNameToClaimPermissionMap.put(playerName.toLowerCase(), permissionLevel);
}
//revokes a permission for a player or the public
public void dropPermission(String playerName)
{
this.playerNameToClaimPermissionMap.remove(playerName.toLowerCase());
}
//clears all permissions (except owner of course)
public void clearPermissions()
{
this.playerNameToClaimPermissionMap.clear();
}
//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.playerNameToClaimPermissionMap.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.ownerName.length() == 0)
return "an administrator";
return this.ownerName;
}
//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;
int x = location.getBlockX();
int y = location.getBlockY();
int z = location.getBlockZ();
//main check
boolean inClaim = (ignoreHeight || y >= this.lesserBoundaryCorner.getBlockY()) &&
x >= this.lesserBoundaryCorner.getBlockX() &&
x <= this.greaterBoundaryCorner.getBlockX() &&
z >= this.lesserBoundaryCorner.getBlockZ() &&
z <= this.greaterBoundaryCorner.getBlockZ();
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.
//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;
}
}

View File

@ -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
}

View File

@ -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;
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,64 @@
/*
GriefPrevention Server Plugin for Minecraft
Copyright (C) 2011 Ryan Hamshire
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package me.ryanhamshire.GriefPrevention;
import org.bukkit.Location;
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
{
@Override
public void run()
{
Player [] players = GriefPrevention.instance.getServer().getOnlinePlayers();
//for each online player
for(int i = 0; i < players.length; i++)
{
Player player = players[i];
DataStore dataStore = GriefPrevention.instance.dataStore;
PlayerData playerData = dataStore.getPlayerData(player.getName());
Location lastLocation = playerData.lastAfkCheckLocation;
try //distance squared will throw an exception if the player has changed worlds
{
//if he's not in a vehicle and has moved at least three blocks since the last check
if(!player.isInsideVehicle() && (lastLocation == null || lastLocation.distanceSquared(player.getLocation()) >= 9))
{
playerData.accruedClaimBlocks += GriefPrevention.instance.config_claims_blocksAccruedPerHour / 12;
//respect limits
if(playerData.accruedClaimBlocks > GriefPrevention.instance.config_claims_maxAccruedBlocks)
{
playerData.accruedClaimBlocks = GriefPrevention.instance.config_claims_maxAccruedBlocks;
}
dataStore.savePlayerData(player.getName(), playerData);
}
}
catch(Exception e) { }
//remember current location for next time
playerData.lastAfkCheckLocation = player.getLocation();
}
}
}

View File

@ -0,0 +1,306 @@
/*
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.List;
import org.bukkit.Material;
import org.bukkit.block.Block;
import org.bukkit.entity.Animals;
import org.bukkit.entity.Arrow;
import org.bukkit.entity.Creeper;
import org.bukkit.entity.Enderman;
import org.bukkit.entity.Entity;
import org.bukkit.entity.LivingEntity;
import org.bukkit.entity.Player;
import org.bukkit.entity.ThrownPotion;
import org.bukkit.entity.Vehicle;
import org.bukkit.event.EventHandler;
import org.bukkit.event.Listener;
import org.bukkit.event.entity.EntityChangeBlockEvent;
import org.bukkit.event.entity.EntityDamageByEntityEvent;
import org.bukkit.event.entity.EntityDamageEvent;
import org.bukkit.event.entity.EntityDeathEvent;
import org.bukkit.event.entity.EntityExplodeEvent;
import org.bukkit.event.painting.PaintingBreakByEntityEvent;
import org.bukkit.event.painting.PaintingBreakEvent;
import org.bukkit.event.painting.PaintingPlaceEvent;
//handles events related to entities
class EntityEventHandler implements Listener
{
//convenience reference for the singleton datastore
private DataStore dataStore;
public EntityEventHandler(DataStore dataStore)
{
this.dataStore = dataStore;
}
//when an entity explodes...
@EventHandler(ignoreCancelled = true)
public void onEntityExplode(EntityExplodeEvent explodeEvent)
{
List<Block> blocks = explodeEvent.blockList();
Entity entity = explodeEvent.getEntity();
//FEATURE: creepers don't destroy blocks when they explode near or above sea level
if(GriefPrevention.instance.config_creepersDontDestroySurface && entity instanceof Creeper)
{
if(entity.getLocation().getBlockY() > entity.getLocation().getWorld().getSeaLevel() - 7)
{
blocks.clear(); //explosion still happens, can damage creatures/players, but no blocks will be destroyed
return;
}
}
//FEATURE: creating an explosion near a claim doesn't damage any of the claimed blocks
Claim claim = null;
for(int i = 0; i < blocks.size(); i++) //for each destroyed block
{
Block block = blocks.get(i);
if(block.getType() == Material.AIR) continue; //if it's air, we don't care
claim = this.dataStore.getClaimAt(block.getLocation(), false, claim);
//if the block is claimed, remove it from the list of destroyed blocks
if(claim != null)
{
blocks.remove(i--);
}
//if the block is not claimed and is a log, trigger the anti-tree-top code
else if(block.getType() == Material.LOG)
{
GriefPrevention.instance.handleLogBroken(block);
}
}
}
//when an entity dies...
@EventHandler
public void onEntityDeath(EntityDeathEvent event)
{
//FEATURE: when a player is involved in a siege (attacker or defender role)
//his death will end the siege
LivingEntity entity = event.getEntity();
if(!(entity instanceof Player)) return; //only tracking players
Player player = (Player)entity;
PlayerData playerData = this.dataStore.getPlayerData(player.getName());
//if involved in a siege
if(playerData.siegeData != null)
{
//end it, with the dieing player being the loser
this.dataStore.endSiege(playerData.siegeData, null, player.getName());
}
}
//when an entity picks up an item
@EventHandler
public void onEntityPickup(EntityChangeBlockEvent event)
{
//FEATURE: endermen don't steal claimed blocks
//if its an enderman
if(event.getEntity() instanceof Enderman)
{
//and the block is claimed
if(this.dataStore.getClaimAt(event.getBlock().getLocation(), false, null) != null)
{
//he doesn't get to steal it
event.setCancelled(true);
}
}
}
//when a painting is broken
@EventHandler(ignoreCancelled = true)
public void onPaintingBreak(PaintingBreakEvent event)
{
//FEATURE: claimed paintings are protected from breakage
//only allow players to break paintings, not anything else (like water and explosions)
if(!(event instanceof PaintingBreakByEntityEvent))
{
event.setCancelled(true);
return;
}
PaintingBreakByEntityEvent entityEvent = (PaintingBreakByEntityEvent)event;
//which claim is the painting in?
Claim claim = this.dataStore.getClaimAt(event.getPainting().getLocation(), false, null);
if(claim == null) return;
//who is removing it?
Entity remover = entityEvent.getRemover();
//again, making sure the breaker is a player
if(!(remover instanceof Player))
{
event.setCancelled(true);
return;
}
//if the player doesn't have build permission, don't allow the breakage
Player playerRemover = (Player)entityEvent.getRemover();
String noBuildReason = claim.allowBuild(playerRemover);
if(noBuildReason != null)
{
event.setCancelled(true);
GriefPrevention.sendMessage(playerRemover, TextMode.Err, noBuildReason);
}
}
//when a painting is placed...
@EventHandler(ignoreCancelled = true)
public void onPaintingPlace(PaintingPlaceEvent event)
{
//FEATURE: similar to above, placing a painting requires build permission in the claim
//which claim is the painting in?
Claim claim = this.dataStore.getClaimAt(event.getBlock().getLocation(), false, null);
if(claim == null) return;
//if the player doesn't have permission, don't allow the placement
String noBuildReason = claim.allowBuild(event.getPlayer());
if(noBuildReason != null)
{
event.setCancelled(true);
GriefPrevention.sendMessage(event.getPlayer(), TextMode.Err, noBuildReason);
}
}
//when an entity is damaged
@EventHandler(ignoreCancelled = true)
public void onEntityDamage (EntityDamageEvent event)
{
//only actually interested in entities damaging entities (ignoring environmental damage)
if(!(event instanceof EntityDamageByEntityEvent)) return;
EntityDamageByEntityEvent subEvent = (EntityDamageByEntityEvent) event;
//determine which player is attacking, if any
Player attacker = null;
Entity damageSource = subEvent.getDamager();
if(damageSource instanceof Player)
{
attacker = (Player)damageSource;
}
else if(damageSource instanceof Arrow)
{
Arrow arrow = (Arrow)damageSource;
if(arrow.getShooter() instanceof Player)
{
attacker = (Player)arrow.getShooter();
}
}
else if(damageSource instanceof ThrownPotion)
{
ThrownPotion potion = (ThrownPotion)damageSource;
if(potion.getShooter() instanceof Player)
{
attacker = (Player)potion.getShooter();
}
}
//if the attacker is a player and defender is a player (pvp combat)
if(attacker != null && event.getEntity() instanceof Player)
{
//if pvp is disabled, cancel the event
if(!event.getEntity().getWorld().getPVP())
{
event.setCancelled(true);
return;
}
//FEATURE: prevent players who very recently participated in pvp combat from hiding inventory to protect it from looting
//FEATURE: prevent players who are in pvp combat from logging out to avoid being defeated
Player defender = (Player)(event.getEntity());
PlayerData defenderData = this.dataStore.getPlayerData(((Player)event.getEntity()).getName());
PlayerData attackerData = this.dataStore.getPlayerData(attacker.getName());
long now = Calendar.getInstance().getTimeInMillis();
defenderData.lastPvpTimestamp = now;
defenderData.lastPvpPlayer = attacker.getName();
attackerData.lastPvpTimestamp = now;
attackerData.lastPvpPlayer = defender.getName();
//FEATURE: prevent pvp in the first minute after spawn, and prevent pvp when one or both players have no inventory
//otherwise if protecting spawning players
if(GriefPrevention.instance.config_pvp_protectFreshSpawns)
{
if(defenderData.pvpImmune)
{
event.setCancelled(true);
return;
}
if(attackerData.pvpImmune)
{
event.setCancelled(true);
return;
}
}
}
//FEATURE: protect claimed animals, boats, minecarts
//NOTE: animals can be lead with wheat, vehicles can be pushed around.
//so unless precautions are taken by the owner, a resourceful thief might find ways to steal anyway
//if theft protection is enabled
if(GriefPrevention.instance.config_claims_preventTheft && event instanceof EntityDamageByEntityEvent)
{
//if the entity is an animal or a vehicle
if (subEvent.getEntity() instanceof Animals || subEvent.getEntity() instanceof Vehicle)
{
Claim claim = this.dataStore.getClaimAt(event.getEntity().getLocation(), false, null);
//if it's claimed
if(claim != null)
{
//if damaged by anything other than a player, cancel the event
if(attacker == null)
{
event.setCancelled(true);
}
//otherwise the player damaging the entity must have permission
else
{
String noContainersReason = claim.allowContainers(attacker);
if(noContainersReason != null)
{
event.setCancelled(true);
GriefPrevention.sendMessage(attacker, TextMode.Err, "That belongs to " + claim.getOwnerName() + ".");
}
}
}
}
}
}
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,130 @@
/*
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.util.Calendar;
import java.util.Date;
import java.util.Vector;
import org.bukkit.Location;
//holds all of GriefPrevention's player-tied data
public class PlayerData
{
//the player's claims
public Vector<Claim> claims = new Vector<Claim>();
//how many claim blocks the player has earned via play time
public int accruedClaimBlocks = GriefPrevention.instance.config_claims_initialBlocks;
//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
public int bonusClaimBlocks = 0;
//what "mode" the shovel is in determines what it will do when it's used
public ShovelMode shovelMode = ShovelMode.Basic;
//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;
//the timestamp for the last time the player used /trapped
public Date lastTrappedUsage;
//whether or not the player has a pending /trapped rescue
public boolean pendingTrapped = false;
//last place the player damaged a chest
public Location lastChestDamageLocation = null;
//spam
public Date lastLogin; //when the player last logged into the server
public String lastMessage = ""; //the player's last chat message, or slash command complete with parameters
public Date lastMessageTimestamp = new Date(); //last time the player sent a chat message or used a monitored slash command
public int spamCount = 0; //number of consecutive "spams"
//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 = "";
PlayerData()
{
//default last login date value to a year ago to ensure a brand new player can log in
//see login cooldown feature, PlayerEventHandler.onPlayerLogin()
//if the player successfully logs in, this value will be overwritten with the current date and time
Calendar lastYear = Calendar.getInstance();
lastYear.add(Calendar.YEAR, -1);
this.lastLogin = lastYear.getTime();
this.lastTrappedUsage = lastYear.getTime();
}
//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 > 15000) //15 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.accruedClaimBlocks + this.bonusClaimBlocks;
for(int i = 0; i < this.claims.size(); i++)
{
Claim claim = this.claims.get(i);
remainingBlocks -= claim.getArea();
}
return remainingBlocks;
}
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,69 @@
/*
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.util.Calendar;
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;
//player data
private Player player;
public PlayerRescueTask(Player player, Location location)
{
this.player = player;
this.location = location;
}
@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.getName());
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, "You moved! Rescue cancelled.");
return;
}
//otherwise find a place to teleport him
Location destination = GriefPrevention.instance.ejectPlayer(this.player);
//log entry, in case admins want to investigate the "trap"
GriefPrevention.AddLogEntry("Rescued trapped player " + player.getName() + " from " + this.location.toString() + " to " + destination.toString() + ".");
//timestamp this successful save so that he can't use /trapped again for a while
playerData.lastTrappedUsage = Calendar.getInstance().getTime();
}
}

View File

@ -0,0 +1,41 @@
This document describes the public API, which you can use to create extensions to GriefPrevention which add new features. Before I get into the specifics, let me give you a few examples of often-requested features which, to my knowledge, have not yet been implemented by anyone. If you want to make a big impact with a small project, these are the go-to areas! If you publish one of these extensions on BukkitDev, please contact me and I'll add a link from my project to yours.
Claim Buy/Sell
I keep saying no to this because I'm developing an anti grief plugin, not a real estate plugin. But it's a common ask. Lots of people would use an extension that allowed them to use server money to buy and sell claims, or to lease subdivisions.
More Locks
Many have asked for wooden doors, trap doors, and fence gates to require /AccessTrust. I've insisted that because players generally expect these to be openable (based on the Vanilla experience), players should just "earn" their privacy by finding some iron and building an iron door. Nonetheless, some folks definitely want this.
Claim Flags
Sometimes, folks want to add special flags to their claims like "no monsters spawn here". They can do this today by adding other plugins like WorldGuard, which are compatible with GriefPrevention, but it would be nice if they could just use one plugin (and an extension). I think their flag ideas come mostly from Residence and WorldGuard, so you can look there for ideas.
Claim Entry/Exit Messages
I keep telling people NO, I won't do this because it's not anti-grief-related and it's expensive to constantly track player movement. But folks want it, and they keep asking for it. You could build an extension which adds some slash commands for naming claims, and displays enter/exit messages as players walk around.
Now the specifics! Please note, these are the supported operations. I've done my best to "hide" fields and methods which you shouldn't play with, but if you happen to notice something not discussed here, it's best not to fiddle with it. If in doubt, at the very least look at my source code and comments before using something you're unfamiliar with in an extension.
Getting the Claim at a Location
Managing Permissions in a Claim
Creating a New Claim
Resizing or Moving a Claim
Extending a Claim Downward
Changing a Claim's Owner
Updating Other Claim Fields
Uniquely Identifying a Claim
Starting a Siege
Ending a Siege
Getting/Updating Player Data

View File

@ -0,0 +1,97 @@
/*
GriefPrevention Server Plugin for Minecraft
Copyright (C) 2011 Ryan Hamshire
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package me.ryanhamshire.GriefPrevention;
import org.bukkit.Chunk;
import org.bukkit.Location;
import org.bukkit.block.Block;
import org.bukkit.entity.Entity;
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;
}
@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; 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.getTypeId() || blockUpdate.data != currentBlock.getData())
{
Claim claim = GriefPrevention.instance.dataStore.getClaimAt(blockUpdate.location, false, cachedClaim);
if(claim != null)
{
cachedClaim = claim;
break;
}
currentBlock.setTypeId(blockUpdate.typeId);
currentBlock.setData(blockUpdate.data);
}
}
}
}
//clean up any entities in the chunk
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.remove();
}
//show visualization to player
Claim claim = new Claim(lesserCorner, greaterCorner, "", new String[] {}, new String[] {}, new String[] {}, new String[] {});
Visualization visualization = Visualization.FromClaim(claim, player.getLocation().getBlockY(), VisualizationType.RestoreNature);
Visualization.Apply(player, visualization);
}
}

View File

@ -0,0 +1,470 @@
/*
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.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 int seaLevel;
//two lists of materials
private ArrayList<Integer> notAllowedToHang; //natural blocks which don't naturally hang in their air
private ArrayList<Integer> playerBlocks; //a "complete" list of player-placed blocks. MUST BE MAINTAINED as patches introduce more
public RestoreNatureProcessingTask(BlockSnapshot[][][] snapshots, int miny, Environment environment, Biome biome, Location lesserBoundaryCorner, Location greaterBoundaryCorner, int seaLevel, Player player)
{
this.snapshots = snapshots;
this.miny = miny;
this.environment = environment;
this.lesserBoundaryCorner = lesserBoundaryCorner;
this.greaterBoundaryCorner = greaterBoundaryCorner;
this.biome = biome;
this.seaLevel = seaLevel;
this.player = player;
this.notAllowedToHang = new ArrayList<Integer>();
this.notAllowedToHang.add(Material.DIRT.getId());
this.notAllowedToHang.add(Material.GRASS.getId());
this.notAllowedToHang.add(Material.SNOW.getId());
this.notAllowedToHang.add(Material.LOG.getId());
//NOTE on this list. why not make a list of natural blocks?
//answer: better to leave a few player blocks than to remove too many natural blocks. remember we're "restoring nature"
//a few extra player blocks can be manually removed, but it will be impossible to guess exactly which natural materials to use in replacements
this.playerBlocks = new ArrayList<Integer>();
this.playerBlocks.add(Material.BED_BLOCK.getId());
this.playerBlocks.add(Material.WOOD.getId());
this.playerBlocks.add(Material.BOOKSHELF.getId());
this.playerBlocks.add(Material.BREWING_STAND.getId());
this.playerBlocks.add(Material.BRICK.getId());
this.playerBlocks.add(Material.COBBLESTONE.getId());
this.playerBlocks.add(Material.OBSIDIAN.getId());
this.playerBlocks.add(Material.GLASS.getId());
this.playerBlocks.add(Material.LAPIS_BLOCK.getId());
this.playerBlocks.add(Material.DISPENSER.getId());
this.playerBlocks.add(Material.NOTE_BLOCK.getId());
this.playerBlocks.add(Material.POWERED_RAIL.getId());
this.playerBlocks.add(Material.DETECTOR_RAIL.getId());
this.playerBlocks.add(Material.PISTON_STICKY_BASE.getId());
this.playerBlocks.add(Material.PISTON_BASE.getId());
this.playerBlocks.add(Material.PISTON_EXTENSION.getId());
this.playerBlocks.add(Material.WOOL.getId());
this.playerBlocks.add(Material.PISTON_MOVING_PIECE.getId());
this.playerBlocks.add(Material.GOLD_BLOCK.getId());
this.playerBlocks.add(Material.IRON_BLOCK.getId());
this.playerBlocks.add(Material.DOUBLE_STEP.getId());
this.playerBlocks.add(Material.STEP.getId());
this.playerBlocks.add(Material.CROPS.getId());
this.playerBlocks.add(Material.TNT.getId());
this.playerBlocks.add(Material.MOSSY_COBBLESTONE.getId());
this.playerBlocks.add(Material.TORCH.getId());
this.playerBlocks.add(Material.FIRE.getId());
this.playerBlocks.add(Material.WOOD_STAIRS.getId());
this.playerBlocks.add(Material.CHEST.getId());
this.playerBlocks.add(Material.REDSTONE_WIRE.getId());
this.playerBlocks.add(Material.DIAMOND_BLOCK.getId());
this.playerBlocks.add(Material.WORKBENCH.getId());
this.playerBlocks.add(Material.SOIL.getId());
this.playerBlocks.add(Material.FURNACE.getId());
this.playerBlocks.add(Material.BURNING_FURNACE.getId());
this.playerBlocks.add(Material.WOODEN_DOOR.getId());
this.playerBlocks.add(Material.SIGN_POST.getId());
this.playerBlocks.add(Material.LADDER.getId());
this.playerBlocks.add(Material.RAILS.getId());
this.playerBlocks.add(Material.COBBLESTONE_STAIRS.getId());
this.playerBlocks.add(Material.WALL_SIGN.getId());
this.playerBlocks.add(Material.STONE_PLATE.getId());
this.playerBlocks.add(Material.LEVER.getId());
this.playerBlocks.add(Material.IRON_DOOR_BLOCK.getId());
this.playerBlocks.add(Material.WOOD_PLATE.getId());
this.playerBlocks.add(Material.REDSTONE_TORCH_ON.getId());
this.playerBlocks.add(Material.REDSTONE_TORCH_OFF.getId());
this.playerBlocks.add(Material.STONE_BUTTON.getId());
this.playerBlocks.add(Material.SNOW_BLOCK.getId());
this.playerBlocks.add(Material.JUKEBOX.getId());
this.playerBlocks.add(Material.FENCE.getId());
this.playerBlocks.add(Material.PORTAL.getId());
this.playerBlocks.add(Material.JACK_O_LANTERN.getId());
this.playerBlocks.add(Material.CAKE_BLOCK.getId());
this.playerBlocks.add(Material.DIODE_BLOCK_ON.getId());
this.playerBlocks.add(Material.DIODE_BLOCK_OFF.getId());
this.playerBlocks.add(Material.TRAP_DOOR.getId());
this.playerBlocks.add(Material.SMOOTH_BRICK.getId());
this.playerBlocks.add(Material.HUGE_MUSHROOM_1.getId());
this.playerBlocks.add(Material.HUGE_MUSHROOM_2.getId());
this.playerBlocks.add(Material.IRON_FENCE.getId());
this.playerBlocks.add(Material.THIN_GLASS.getId());
this.playerBlocks.add(Material.MELON_STEM.getId());
this.playerBlocks.add(Material.FENCE_GATE.getId());
this.playerBlocks.add(Material.BRICK_STAIRS.getId());
this.playerBlocks.add(Material.SMOOTH_STAIRS.getId());
this.playerBlocks.add(Material.ENCHANTMENT_TABLE.getId());
this.playerBlocks.add(Material.BREWING_STAND.getId());
this.playerBlocks.add(Material.CAULDRON.getId());
this.playerBlocks.add(Material.DIODE_BLOCK_ON.getId());
this.playerBlocks.add(Material.DIODE_BLOCK_ON.getId());
//these are unnatural in the standard world, but not in the nether
if(this.environment != Environment.NETHER)
{
this.playerBlocks.add(Material.NETHERRACK.getId());
this.playerBlocks.add(Material.SOUL_SAND.getId());
this.playerBlocks.add(Material.GLOWSTONE.getId());
this.playerBlocks.add(Material.NETHER_BRICK.getId());
this.playerBlocks.add(Material.NETHER_FENCE.getId());
this.playerBlocks.add(Material.NETHER_BRICK_STAIRS.getId());
}
//these are unnatural in sandy biomes, but not elsewhere
if(this.biome == Biome.DESERT || this.biome == Biome.DESERT_HILLS || this.biome == Biome.BEACH)
{
this.playerBlocks.add(Material.LEAVES.getId());
this.playerBlocks.add(Material.LOG.getId());
}
}
@Override
public void run()
{
//order is important!
//remove any blocks which are definitely player placed
this.removePlayerBlocks();
//remove natural blocks which are unnaturally hanging in the air
this.removeHanging();
//remove natural blocks which are unnaturally stacked high
this.removeWallsAndTowers();
//cover surface stone and gravel with sand or grass, as the biome requires
this.coverSurfaceStone();
//fill unnatural thin trenches and single-block potholes
this.fillHolesAndTrenches();
//fill water depressions and fix unnatural surface ripples
this.fixWater();
//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);
}
private void removePlayerBlocks()
{
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];
if(this.playerBlocks.contains(block.typeId))
{
block.typeId = Material.AIR.getId();
}
}
}
}
}
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.getId() || underBlock.typeId == Material.WATER.getId())
{
if(this.notAllowedToHang.contains(block.typeId))
{
block.typeId = Material.AIR.getId();
}
}
}
}
}
}
private void removeWallsAndTowers()
{
int [] excludedBlocksArray = new int []
{
Material.CACTUS.getId(),
Material.LONG_GRASS.getId(),
Material.RED_MUSHROOM.getId(),
Material.BROWN_MUSHROOM.getId(),
Material.DEAD_BUSH.getId(),
Material.SAPLING.getId(),
Material.YELLOW_FLOWER.getId(),
Material.RED_ROSE.getId(),
Material.SUGAR_CANE_BLOCK.getId(),
Material.VINE.getId(),
Material.PUMPKIN.getId(),
Material.WATER_LILY.getId(),
Material.LEAVES.getId()
};
ArrayList<Integer> excludedBlocks = new ArrayList<Integer>();
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);
if(excludedBlocks.contains(this.snapshots[x][thisy][z].typeId)) continue;
int righty = this.highestY(x + 1, z);
int lefty = this.highestY(x - 1, z);
while(lefty < thisy && righty < thisy)
{
this.snapshots[x][thisy--][z].typeId = Material.AIR.getId();
changed = true;
}
int upy = this.highestY(x, z + 1);
int downy = this.highestY(x, z - 1);
while(upy < thisy && downy < thisy)
{
this.snapshots[x][thisy--][z].typeId = Material.AIR.getId();
changed = true;
}
}
}
}while(changed);
}
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);
BlockSnapshot block = snapshots[x][y][z];
if(block.typeId == Material.STONE.getId() || block.typeId == Material.GRAVEL.getId() || block.typeId == Material.DIRT.getId())
{
if(this.biome == Biome.DESERT || this.biome == Biome.DESERT_HILLS || this.biome == Biome.BEACH)
{
this.snapshots[x][y][z].typeId = Material.SAND.getId();
}
else
{
this.snapshots[x][y][z].typeId = Material.GRASS.getId();
}
}
}
}
}
private void fillHolesAndTrenches()
{
ArrayList<Integer> fillableBlocks = new ArrayList<Integer>();
fillableBlocks.add(Material.AIR.getId());
fillableBlocks.add(Material.STATIONARY_WATER.getId());
fillableBlocks.add(Material.STATIONARY_LAVA.getId());
ArrayList<Integer> notSuitableForFillBlocks = new ArrayList<Integer>();
notSuitableForFillBlocks.add(Material.LONG_GRASS.getId());
notSuitableForFillBlocks.add(Material.CACTUS.getId());
notSuitableForFillBlocks.add(Material.STATIONARY_WATER.getId());
notSuitableForFillBlocks.add(Material.STATIONARY_LAVA.getId());
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);
}
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.getId() || block.typeId == Material.STATIONARY_LAVA.getId())
{
if(underBlock.typeId == Material.AIR.getId() || (underBlock.data != 0))
{
block.typeId = Material.AIR.getId();
}
}
}
}
}
//fill water depressions
do
{
changed = false;
for(int y = this.seaLevel - 10; 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.getId() || (block.typeId == Material.STATIONARY_WATER.getId() && 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.getId() || underBlock.data != 0) continue;
//count adjacent source water blocks
byte adjacentSourceWaterCount = 0;
if(leftBlock.typeId == Material.STATIONARY_WATER.getId() && leftBlock.data == 0)
{
adjacentSourceWaterCount++;
}
if(rightBlock.typeId == Material.STATIONARY_WATER.getId() && rightBlock.data == 0)
{
adjacentSourceWaterCount++;
}
if(upBlock.typeId == Material.STATIONARY_WATER.getId() && upBlock.data == 0)
{
adjacentSourceWaterCount++;
}
if(downBlock.typeId == Material.STATIONARY_WATER.getId() && downBlock.data == 0)
{
adjacentSourceWaterCount++;
}
//at least two adjacent blocks must be source water
if(adjacentSourceWaterCount >= 2)
{
block.typeId = Material.STATIONARY_WATER.getId();
block.data = 0;
changed = true;
}
}
}
}
}
}while(changed);
}
private int highestY(int x, int z)
{
int y;
for(y = snapshots[0].length - 1; y >= 0; y--)
{
BlockSnapshot block = this.snapshots[x][y][z];
if(block.typeId != Material.AIR.getId() &&
!(block.typeId == Material.STATIONARY_WATER.getId() && block.data != 0) &&
!(block.typeId == Material.STATIONARY_LAVA.getId() && block.data != 0))
{
return y;
}
}
return y;
}
}

View File

@ -0,0 +1,56 @@
/*
GriefPrevention Server Plugin for Minecraft
Copyright (C) 2011 Ryan Hamshire
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package me.ryanhamshire.GriefPrevention;
import org.bukkit.entity.Player;
//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
Player [] onlinePlayers = GriefPrevention.instance.getServer().getOnlinePlayers();
for(int j = 0; j < onlinePlayers.length; j++)
{
Player player = onlinePlayers[j];
if(claim.contains(player.getLocation(), false, false) && claim.allowAccess(player) != null)
{
GriefPrevention.sendMessage(player, TextMode.Err, "Looting time is up! Ejected from the claim.");
GriefPrevention.instance.ejectPlayer(player);
}
}
}
}
}

View File

@ -0,0 +1,28 @@
/*
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;
//enumeration for golden shovel modes
enum ShovelMode
{
Basic,
Admin,
Subdivide,
RestoreNature
}

View File

@ -0,0 +1,110 @@
/*
GriefPrevention Server Plugin for Minecraft
Copyright (C) 2011 Ryan Hamshire
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package me.ryanhamshire.GriefPrevention;
import org.bukkit.entity.Player;
//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());
}
//or defender wins if the attacker leaves
else if(!attackerRemains && defenderRemains)
{
dataStore.endSiege(this.siegeData, defender.getName(), attacker.getName());
}
//if they both left, but are still close together, the battle continues (check again later)
else if(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());
}
}
//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 * 60);
}
}

View File

@ -0,0 +1,40 @@
/*
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.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);
}
}

View File

@ -0,0 +1,31 @@
/*
GriefPrevention Server Plugin for Minecraft
Copyright (C) 2011 Ryan Hamshire
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package me.ryanhamshire.GriefPrevention;
import org.bukkit.ChatColor;
//just a few constants for chat color codes
class TextMode
{
final static ChatColor Info = ChatColor.BLUE;
final static ChatColor Instr = ChatColor.YELLOW;
final static ChatColor Warn = ChatColor.GOLD;
final static ChatColor Err = ChatColor.RED;
final static ChatColor Success = ChatColor.GREEN;
}

View File

@ -0,0 +1,100 @@
/*
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.util.ArrayList;
import org.bukkit.Chunk;
import org.bukkit.Location;
import org.bukkit.Material;
import org.bukkit.block.Block;
import org.bukkit.block.BlockFace;
//FEATURE: treetops left unnaturally hanging will be automatically cleaned up
//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 TreeCleanupTask implements Runnable
{
private Block originalChoppedBlock; //first block chopped in the tree
private Block originalRootBlock; //where the root of the tree used to be
private ArrayList<Block> originalTreeBlocks; //a list of other log blocks determined to be part of this tree
public TreeCleanupTask(Block originalChoppedBlock, Block originalRootBlock, ArrayList<Block> originalTreeBlocks)
{
this.originalChoppedBlock = originalChoppedBlock;
this.originalRootBlock = originalRootBlock;
this.originalTreeBlocks = originalTreeBlocks;
}
@Override
public void run()
{
//if this chunk is no longer loaded, load it and come back in a few seconds
Chunk chunk = this.originalChoppedBlock.getWorld().getChunkAt(this.originalChoppedBlock);
if(!chunk.isLoaded())
{
chunk.load();
GriefPrevention.instance.getServer().getScheduler().scheduleSyncDelayedTask(GriefPrevention.instance, this, 100L);
return;
}
//if the block originally chopped has been replaced with anything but air, something has been built (or has grown here)
//in that case, don't do any cleanup
if(this.originalChoppedBlock.getWorld().getBlockAt(this.originalChoppedBlock.getLocation()).getType() != Material.AIR) return;
//scan the original tree block locations to see if any of them have been replaced
for(int i = 0; i < this.originalTreeBlocks.size(); i++)
{
Location location = this.originalTreeBlocks.get(i).getLocation();
Block currentBlock = location.getBlock();
//if the block has been replaced, stop here, we won't do any cleanup
if(currentBlock.getType() != Material.LOG && currentBlock.getType() != Material.AIR)
{
return;
}
}
//otherwise scan again, this time removing any remaining log blocks
boolean logsRemaining = false;
for(int i = 0; i < this.originalTreeBlocks.size(); i++)
{
Location location = this.originalTreeBlocks.get(i).getLocation();
Block currentBlock = location.getBlock();
if(currentBlock.getType() == Material.LOG)
{
logsRemaining = true;
currentBlock.setType(Material.AIR);
}
}
//if any were actually removed and we're set to automatically replant griefed trees, place a sapling where the root block was previously
if(logsRemaining && GriefPrevention.instance.config_trees_regrowGriefedTrees)
{
Block currentBlock = this.originalRootBlock.getLocation().getBlock();
//make sure there's grass or dirt underneath
if(currentBlock.getType() == Material.AIR && (currentBlock.getRelative(BlockFace.DOWN).getType() == Material.DIRT || currentBlock.getRelative(BlockFace.DOWN).getType() == Material.GRASS))
{
currentBlock.setType(Material.SAPLING);
currentBlock.setData(this.originalRootBlock.getData()); //makes the sapling type match the original tree type
}
}
}
}

View File

@ -0,0 +1,214 @@
/*
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.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.getName());
//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 in about half a second
if(player.isOnline())
{
GriefPrevention.instance.getServer().getScheduler().scheduleSyncDelayedTask(GriefPrevention.instance, new VisualizationApplicationTask(player, playerData, visualization), 10L);
}
}
//reverts a visualization by sending another block change list, this time with the real world block values
public static void Revert(Player player)
{
PlayerData playerData = GriefPrevention.instance.dataStore.getPlayerData(player.getName());
Visualization visualization = playerData.currentVisualization;
if(playerData.currentVisualization != null)
{
if(player.isOnline())
{
for(int i = 0; i < visualization.elements.size(); i++)
{
VisualizationElement element = visualization.elements.get(i);
Block block = element.location.getBlock();
player.sendBlockChange(element.location, block.getType(), block.getData());
}
}
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)
{
//visualize only top level claims
if(claim.parent != null)
{
return FromClaim(claim.parent, height, visualizationType);
}
Visualization visualization = new Visualization();
//add subdivisions first
for(int i = 0; i < claim.children.size(); i++)
{
visualization.addClaimElements(claim.children.get(i), height, VisualizationType.Subdivision);
}
//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);
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
private void addClaimElements(Claim claim, int height, VisualizationType visualizationType)
{
Location smallXsmallZ = claim.getLesserBoundaryCorner();
Location bigXbigZ = claim.getGreaterBoundaryCorner();
World world = smallXsmallZ.getWorld();
int smallx = smallXsmallZ.getBlockX();
int smallz = smallXsmallZ.getBlockZ();
int bigx = bigXbigZ.getBlockX();
int bigz = bigXbigZ.getBlockZ();
Material cornerMaterial;
Material accentMaterial;
if(visualizationType == VisualizationType.Claim)
{
cornerMaterial = Material.GLOWSTONE;
accentMaterial = Material.GOLD_BLOCK;
}
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.LAVA;
accentMaterial = Material.NETHERRACK;
}
//bottom left corner
this.elements.add(new VisualizationElement(getVisibleLocation(world, smallx, height, smallz), cornerMaterial, (byte)0));
this.elements.add(new VisualizationElement(getVisibleLocation(world, smallx + 1, height, smallz), accentMaterial, (byte)0));
this.elements.add(new VisualizationElement(getVisibleLocation(world, smallx, height, smallz + 1), accentMaterial, (byte)0));
//bottom right corner
this.elements.add(new VisualizationElement(getVisibleLocation(world, bigx, height, smallz), cornerMaterial, (byte)0));
this.elements.add(new VisualizationElement(getVisibleLocation(world, bigx - 1, height, smallz), accentMaterial, (byte)0));
this.elements.add(new VisualizationElement(getVisibleLocation(world, bigx, height, smallz + 1), accentMaterial, (byte)0));
//top right corner
this.elements.add(new VisualizationElement(getVisibleLocation(world, bigx, height, bigz), cornerMaterial, (byte)0));
this.elements.add(new VisualizationElement(getVisibleLocation(world, bigx - 1, height, bigz), accentMaterial, (byte)0));
this.elements.add(new VisualizationElement(getVisibleLocation(world, bigx, height, bigz - 1), accentMaterial, (byte)0));
//top left corner
this.elements.add(new VisualizationElement(getVisibleLocation(world, smallx, height, bigz), cornerMaterial, (byte)0));
this.elements.add(new VisualizationElement(getVisibleLocation(world, smallx + 1, height, bigz), accentMaterial, (byte)0));
this.elements.add(new VisualizationElement(getVisibleLocation(world, smallx, height, bigz - 1), accentMaterial, (byte)0));
//top line
for(int x = smallx + 10; x < bigx - 10; x += 10)
{
this.elements.add(new VisualizationElement(getVisibleLocation(world, x, height, bigz), accentMaterial, (byte)0));
}
//bottom line
for(int x = smallx + 10; x < bigx - 10; x += 10)
{
this.elements.add(new VisualizationElement(getVisibleLocation(world, x, height, smallz), accentMaterial, (byte)0));
}
//left line
for(int z = smallz + 10; z < bigz - 10; z += 10)
{
this.elements.add(new VisualizationElement(getVisibleLocation(world, smallx, height, z), accentMaterial, (byte)0));
}
//right line
for(int z = smallz + 10; z < bigz - 10; z += 10)
{
this.elements.add(new VisualizationElement(getVisibleLocation(world, bigx, height, z), accentMaterial, (byte)0));
}
}
//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)
{
Block block = world.getBlockAt(x, y, z);
BlockFace direction = (isTransparent(block)) ? BlockFace.DOWN : BlockFace.UP;
while( block.getY() >= 1 &&
block.getY() < world.getMaxHeight() - 1 &&
(!isTransparent(block.getRelative(BlockFace.UP)) || isTransparent(block)))
{
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)
{
return ( block.getType() == Material.AIR ||
block.getType() == Material.LONG_GRASS ||
block.getType() == Material.FENCE ||
block.getType() == Material.LEAVES ||
block.getType() == Material.RED_ROSE ||
block.getType() == Material.CHEST ||
block.getType() == Material.YELLOW_FLOWER );
}
}

View File

@ -0,0 +1,52 @@
/*
GriefPrevention Server Plugin for Minecraft
Copyright (C) 2011 Ryan Hamshire
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package me.ryanhamshire.GriefPrevention;
import org.bukkit.entity.Player;
//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;
}
@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
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;
}
}

View File

@ -0,0 +1,36 @@
/*
GriefPrevention Server Plugin for Minecraft
Copyright (C) 2011 Ryan Hamshire
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package me.ryanhamshire.GriefPrevention;
import org.bukkit.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 VisualizationElement(Location location, Material visualizedMaterial, byte visualizedData)
{
this.location = location;
this.visualizedMaterial= visualizedMaterial;
this.visualizedData = visualizedData;
}
}

View File

@ -0,0 +1,28 @@
/*
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;
//just an enumeration of the visualization types, which determine what materials will be for the fake blocks
public enum VisualizationType
{
Claim,
Subdivision,
ErrorClaim,
RestoreNature
}