/*
* Copyright 2011 Tyler Blair. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without modification, are
* permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this list of
* conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice, this list
* of conditions and the following disclaimer in the documentation and/or other materials
* provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR ''AS IS'' AND ANY EXPRESS OR IMPLIED
* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
* FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
* ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* The views and conclusions contained in the software and documentation are those of the
* authors and contributors and should not be interpreted as representing official policies,
* either expressed or implied, of anybody else.
*/
package com.griefcraft.util;
import com.griefcraft.cache.ProtectionCache;
import com.griefcraft.lwc.LWC;
import com.griefcraft.model.Protection;
import com.griefcraft.util.matchers.DoorMatcher;
import com.griefcraft.util.matchers.DoubleChestMatcher;
import com.griefcraft.util.matchers.GravityMatcher;
import com.griefcraft.util.matchers.WallMatcher;
import org.bukkit.Material;
import org.bukkit.block.Block;
import org.bukkit.block.BlockState;
import java.util.Collections;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
/**
* Searches for blocks that can potentially be a protection
*/
public class ProtectionFinder {
/**
* The LWC object to work with
*/
private LWC lwc;
/**
* The base block to match off of
*/
private BlockState baseBlock = null;
/**
* The matched protection if found
*/
private Protection matchedProtection = null;
/**
* If we already checked the database
*/
private boolean searched = false;
/**
* All of the matched blocks
*/
private final List<BlockState> blocks = new LinkedList<BlockState>();
/**
* All of the blocks that are protectables
*/
private final List<BlockState> protectables = new LinkedList<BlockState>();
public ProtectionFinder(LWC lwc) {
this.lwc = lwc;
}
/**
* Try and match blocks using the given base block
*
* @param baseBlock
* @return TRUE if a set of blocks was found
*/
public boolean matchBlocks(Block baseBlock) {
return matchBlocks(baseBlock.getState());
}
/**
* Try and match blocks using the given base block
*
* @param baseBlock
* @return TRUE if a set of blocks was found
*/
public boolean matchBlocks(BlockState baseBlock) {
// Did we already find a protection?
if (matchedProtection != null) {
return true;
}
// First, reset
reset();
this.baseBlock = baseBlock;
// Add the base block
addBlock(baseBlock);
// Check the base-block
Result result;
if ((result = tryLoadProtection(baseBlock, false)) != Result.E_NOT_FOUND) {
return result == Result.E_FOUND;
}
// Now attempt to use the matchers
for (Matcher matcher : getProtectionMatchers()) {
boolean matches = matcher.matches(this);
if (matches) {
// we matched a protection somewhere!
return true;
}
}
// No matches
searched = true;
return false;
}
/**
* Do a full sweeping match of all the blocks for a given protection
*/
public void fullMatchBlocks() {
// Reset the blocks
blocks.clear();
// Add the base block
blocks.add(baseBlock);
// Go through each matcher and execute it
for (Matcher matcher : getProtectionMatchers()) {
if (matcher.matches(this)) {
break;
}
}
}
/**
* Get the possible protection matchers that can match the protection
*
* @return
*/
public Matcher[] getProtectionMatchers() {
Material material = baseBlock.getType();
if (material == Material.HOPPER) {
return new Matcher[0];
}
// Double chests
if (DoubleChestMatcher.PROTECTABLES_CHESTS.contains(material)) {
return new Matcher[] {
new DoubleChestMatcher()
};
}
// Gravity
else if (GravityMatcher.PROTECTABLES_POSTS.contains(material)) {
return new Matcher[] {
new GravityMatcher()
};
}
// Doors
else if (DoorMatcher.PROTECTABLES_DOORS.contains(material)) {
return new Matcher[] {
new DoorMatcher()
};
}
// Anything else
else {
return new Matcher[] {
new DoorMatcher(),
new GravityMatcher(),
new WallMatcher()
};
}
}
/**
* Add a block to the matched blocks
*
* @param block
*/
public void addBlock(Block block) {
addBlock(block.getState());
}
/**
* Add a block to the matched blocks
*
* @param block
*/
public void addBlock(BlockState block) {
if (!blocks.contains(block)) {
blocks.add(block);
}
}
/**
* Load the protection for the calculated protectables.
* Returns NULL if no protection was found.
*
* @return
*/
public Protection loadProtection() {
return loadProtection(false);
}
/**
* Load the protection for the calculated protectables.
* Returns NULL if no protection was found.
*
* @param noAutoCache if a match is found, don't cache it to be the protection we use
* @return
*/
public Protection loadProtection(boolean noAutoCache) {
// Do we have a result already cached?
if (searched) {
return matchedProtection;
}
// Calculate the protectables
calculateProtectables();
searched = true;
for (BlockState block : protectables) {
if (tryLoadProtection(block, noAutoCache) == Result.E_FOUND) {
return matchedProtection;
}
}
return null;
}
/**
* Try and load a protection for a given block. If succeded, cache it locally
*
* @param block
* @param noAutoCache if a match is found, don't cache it to be the protection we use
* @return
*/
protected Result tryLoadProtection(BlockState block, boolean noAutoCache) {
if (matchedProtection != null) {
return Result.E_FOUND;
}
LWC lwc = LWC.getInstance();
ProtectionCache cache = lwc.getProtectionCache();
// Check the cache
if ((matchedProtection = cache.getProtection(block)) != null) {
searched = true;
if (matchedProtection.getProtectionFinder() == null) {
fullMatchBlocks();
matchedProtection.setProtectionFinder(this);
cache.addProtection(matchedProtection);
}
return Result.E_FOUND;
}
// Manual intervention is required
if (block.getType() == Material.REDSTONE_WIRE || block.getType() == Material.REDSTONE_TORCH_OFF ||
block.getType() == Material.REDSTONE_TORCH_ON) {
return Result.E_ABORT;
}
// don't bother trying to load it if it is not protectable
if (!lwc.isProtectable(block)) {
return Result.E_NOT_FOUND;
}
// Null-check
if (block.getWorld() == null) {
lwc.log("World is null for the block " + block);
return Result.E_NOT_FOUND;
}
Protection protection = lwc.getPhysicalDatabase().loadProtection(block.getWorld().getName(), block.getX(), block.getY(), block.getZ());
if (protection != null) {
if (protection.getProtectionFinder() == null) {
protection.setProtectionFinder(this);
fullMatchBlocks();
cache.addProtection(matchedProtection);
}
// ensure it's the right block
if (protection.getBlockId() > 0) {
if (protection.isBlockInWorld()) {
if (noAutoCache) {
return Result.E_FOUND;
}
this.matchedProtection = protection;
searched = true;
} else {
// Corrupted protection
lwc.log("Removing corrupted protection: " + protection);
protection.remove();
}
}
}
return this.matchedProtection != null ? Result.E_FOUND : Result.E_NOT_FOUND;
}
/**
* Get the finder's base block
*
* @return
*/
public BlockState getBaseBlock() {
return baseBlock;
}
/**
* Get an immutable set of the matched blocks
*
* @return
*/
public List<BlockState> getBlocks() {
return Collections.unmodifiableList(blocks);
}
/**
* Remove a block from the finder
*
* @param block
*/
public void removeBlock(BlockState block) {
Iterator<BlockState> iter = blocks.iterator();
while (iter.hasNext()) {
if (lwc.blockEquals(block, iter.next())) {
iter.remove();
}
}
}
/**
* Reset the matcher state
*/
private void reset() {
blocks.clear();
protectables.clear();
baseBlock = null;
searched = false;
}
/**
* From the matched blocks calculate and store the blocks that are protectable
*/
private void calculateProtectables() {
// reset the matched protectables
protectables.clear();
searched = false;
// if there's only 1 block it was already checked (the base block)
int size = blocks.size();
if (size == 1) {
return;
}
// go through the blocks
for (int index = 1; index < size; index++) {
BlockState state = blocks.get(index);
if (lwc.isProtectable(state)) {
protectables.add(state);
}
}
}
/**
* Matches protections
*/
public interface Matcher {
/**
* Check if the block matches any VALID protections.
*
* @return
*/
public boolean matches(ProtectionFinder finder);
}
private enum Result { E_FOUND, E_ABORT, E_NOT_FOUND }
}