2018-06-06 16:25:17 +01:00

788 lines
24 KiB
Java

/*
* ItemCase 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/gpl.html>.
*/
package com.gmail.bleedobsidian.itemcase;
import com.gmail.bleedobsidian.itemcase.managers.ItemcaseManager;
import java.util.ArrayList;
import java.util.Collection;
import java.util.UUID;
import org.bukkit.Bukkit;
import org.bukkit.Chunk;
import org.bukkit.Location;
import org.bukkit.Material;
import org.bukkit.OfflinePlayer;
import org.bukkit.World;
import org.bukkit.entity.Entity;
import org.bukkit.entity.Item;
import org.bukkit.event.EventHandler;
import org.bukkit.event.EventPriority;
import org.bukkit.event.Listener;
import org.bukkit.event.block.Action;
import org.bukkit.event.block.BlockBreakEvent;
import org.bukkit.event.block.BlockPlaceEvent;
import org.bukkit.event.entity.EntityPickupItemEvent;
import org.bukkit.event.entity.ItemDespawnEvent;
import org.bukkit.event.player.PlayerInteractEvent;
import org.bukkit.inventory.EquipmentSlot;
import org.bukkit.inventory.Inventory;
import org.bukkit.inventory.ItemStack;
import org.bukkit.inventory.meta.ItemMeta;
import org.bukkit.metadata.FixedMetadataValue;
import org.bukkit.scheduler.BukkitRunnable;
import org.bukkit.util.Vector;
/**
* Handler of an Itemcase.
*
* @author Jesse Prescott (BleedObsidian)
*/
public final class Itemcase {
/**
* The name of the inventory for Itemcase storage.
*/
public static final String INVENTORY_NAME = "ItemCase Storage";
/**
* Types of Itemcase.
*/
public static enum Type {
SHOWCASE,
SHOP_BUY,
SHOP_SELL,
SHOP_MULTI
};
/**
* Storage types.
*/
public static enum StorageType {
FINITE,
INFINITE
}
/**
* The ItemStack that this itemcase is showing.
*/
private final ItemStack itemStack;
/**
* The Location of this itemcase.
*/
private final Location location;
/**
* The chunk that this itemcase is in.
*/
private final Chunk chunk;
/**
* The owner of this itemcase.
*/
private final OfflinePlayer owner;
/**
* This itemcase's task.
*/
private ItemcaseTask task;
/**
* The active item that is currently on display.
*/
private Item displayItem;
/**
* This itemcase's Type.
*/
private Type type = Type.SHOWCASE;
/**
* The storage type of this itemcase.
*/
private StorageType storageType = StorageType.FINITE;
/**
* The storage of this itemcase.
*/
private Inventory storage;
/**
* The buy price of this itemcase.
*/
private double buyPrice = 0;
/**
* The sell price of this itemcase.
*/
private double sellPrice = 0;
/**
* Constructor.
*
* @param type The type of Itemcase.
* @param itemStack The ItemStack to be displayed.
* @param location The location of the itemcase.
* @param owner The owner of this itemcase.
*/
public Itemcase(Type type, ItemStack itemStack, Location location,
OfflinePlayer owner) {
// Set type.
this.type = type;
// Set item stack and ensure stack size is 1.
this.itemStack = itemStack.clone();
this.itemStack.setAmount(1);
// Set location.
this.location = location;
// Set the chunk. Accessing the chunk with location.getChunk() when we
// need it causes the chunk to load, meaning the chunk appears as always
// loaded. Storing it now means we can actually see when it becomes
// unloaded.
this.chunk = location.getChunk();
// Set owner.
this.owner = owner;
}
/**
* Spawn the display item.
*/
public void spawnItem() {
// Get the world that this itemcase is in.
World world = this.location.getWorld();
// If task task was previously cancelled or never made.
if(this.task == null || this.task.isCancelled()) {
// Schedule itemcase task to execute every 200 server
// ticks (10 secs).
this.task = new ItemcaseTask(this);
this.task.runTaskTimer(ItemCaseCore.instance, 0, 200);
}
// Check if the chunk is currently loaded.
if(!world.isChunkLoaded(this.chunk)) {
// Not loaded, so don't try to spawn display item.
return;
}
// If there was a display item previously.
if(this.displayItem != null) {
// Remove this item.
this.displayItem.remove();
}
// Get the location to spawn the display item at.
Location displayItemSpawnLocation = this.getDisplayItemSpawnLocation();
// Get itemstack.
ItemStack itemStack = this.itemStack.clone();
// Set display name to random UUID to prevent graphical item stacking.
ItemMeta metadata = itemStack.getItemMeta();
metadata.setDisplayName("com.gmail.bleedobsidian.itemcase:" +
UUID.randomUUID().toString());
itemStack.setItemMeta(metadata);
// Spawn the item.
this.displayItem = this.location.getWorld().dropItem(
displayItemSpawnLocation, itemStack);
// Add custom metadata so we know that this item is a display item and
// shouldn't be picked up by players and which itemcase it belongs to.
this.displayItem.setMetadata("ItemCase", new FixedMetadataValue(
ItemCaseCore.instance, this.location.toVector()));
// Prevent the item from having a random veloctiy when spawning so that
// it falls directly down in to the middle of the block.
this.displayItem.setVelocity(new Vector(0, 0, 0));
}
/**
* Despawn the display item for this itemcase.
*/
public void despawnItem() {
// Cancel running task.
this.task.cancel();
// Remove current display item from world.
this.displayItem.remove();
}
/**
* Checks if a given item entity is an itemcase display item of any kind.
*
* @param item Item.
* @return Boolean.
*/
private static boolean isItemcaseDisplayItem(Item item) {
// If item does not have a display name, it can't be a display item.
if(!item.getItemStack().getItemMeta().hasDisplayName()) {
return false;
}
// Attempt to split display name by ':'
String[] displayNameParts =
item.getItemStack().getItemMeta().getDisplayName().split(":");
// If display name does not have exactly two parts, it can't be a
// display item.
if(displayNameParts.length != 2) {
return false;
}
// Check if item belongs to us, if it does return true.
return displayNameParts[0].equals("com.gmail.bleedobsidian.itemcase");
}
/**
* Take stock from storage.
*
* @param amount Amount to take.
*/
public void takeStock(int amount) {
// If storage is infinite.
if(this.storageType == StorageType.INFINITE) {
// Exit.
return;
}
// Get items.
ItemStack items = this.itemStack.clone();
items.setAmount(amount);
// Remove from storage.
this.storage.removeItem(items);
// Save.
ItemCaseCore.instance.getItemcaseManager().saveItemcases(this);
}
/**
* Add stock to storage.
*
* @param amount Amount to add.
*/
public void addStock(int amount) {
// If storage is infinite.
if(this.storageType == StorageType.INFINITE) {
// Exit.
return;
}
// Get items.
ItemStack items = this.itemStack.clone();
items.setAmount(amount);
// Add to storage.
this.storage.addItem(items);
// Save.
ItemCaseCore.instance.getItemcaseManager().saveItemcases(this);
}
/**
* @param amount The amount of items needed.
* @return If the itemcase has enough in stock.
*/
public boolean hasEnough(int amount) {
// If storage is infinite.
if(this.storageType == StorageType.INFINITE) {
// Return true.
return true;
}
// Return if storage contains enough.
return this.storage.containsAtLeast(this.itemStack, amount);
}
/**
* @return The amount of stock this itemcase has.
*/
public int getStockLevel() {
// Return count.
return InventoryUtils.count(this.storage, this.itemStack);
}
/**
* @return The ItemStack that this itemcase is showing.
*/
public ItemStack getItemStack() {
// The ItemStack of this Itemcase.
return this.itemStack;
}
/**
* @return The Location of this itemcase.
*/
public Location getLocation() {
// Location of this itemcase.
return this.location;
}
/**
* @return The owner of this itemcase.
*/
public OfflinePlayer getOwner() {
// Owner of this itemcase.
return this.owner;
}
/**
* @return Display item.
*/
public Item getDisplayItem() {
// Display item.
return this.displayItem;
}
/**
* @param type Type.
*/
public void setType(Type type) {
// Set type.
this.type = type;
}
/**
* @return Type.
*/
public Type getType() {
// Return type.
return this.type;
}
/**
* @param storageType StorageType.
*/
public void setStorageType(StorageType storageType) {
// If toggling from finite to infinite.
if(this.storageType == StorageType.FINITE &&
storageType == StorageType.INFINITE) {
// Set storage.
this.storage =
Bukkit.createInventory(null, 54, Itemcase.INVENTORY_NAME);
}
// Set storage type.
this.storageType= storageType;
}
/**
* @return Storage Type.
*/
public StorageType getStorageType() {
// Return storage type.
return this.storageType;
}
/**
* @param inventory Inventory.
*/
public void setStorage(Inventory inventory) {
// Set inventory.
this.storage = inventory;
}
/**
* @return Inventory.
*/
public Inventory getStorage() {
// Return inventory.
return this.storage;
}
/**
* @param buyPrice Buy price.
*/
public void setBuyPrice(double buyPrice) {
// Set price.
this.buyPrice = buyPrice;
}
/**
* @return Buy price.
*/
public double getBuyPrice() {
// Return price.
return this.buyPrice;
}
/**
* @param buyPrice Sell price.
*/
public void setSellPrice(double sellPrice) {
// Set price.
this.sellPrice = sellPrice;
}
/**
* @return Sell price.
*/
public double getSellPrice() {
// Return price.
return this.sellPrice;
}
/**
* @return The default location to spawn the display item.
*/
public Location getDisplayItemSpawnLocation() {
// Get block type of this itemcase.
Material type = this.location.getBlock().getType();
// The relative Y coordinate.
double relY = 0;
// If block is a slab.
if(type == Material.STEP ||
type == Material.STONE_SLAB2 ||
type == Material.WOOD_STEP ||
type == Material.PURPUR_SLAB) {
// Set relY.
relY = 0.6;
} else {
// Set relY.
relY = 1.6;
}
// Create a location that is in the centre of the block and slightly
// above.
Location displayItemLocation = new Location(
this.location.getWorld(),
this.location.getBlockX() + 0.5,
this.location.getBlockY() + relY,
this.location.getBlockZ() + 0.5);
// Return the default location to spawn the display item.
return displayItemLocation;
}
/**
* A listener used for the functionality of an itemcase. This listener is
* used to prevent display items from being picked up or despawned.
*/
public static final class ItemcaseListener implements Listener {
@EventHandler(priority = EventPriority.HIGHEST)
public void onEntityPickupItem(EntityPickupItemEvent event) {
// If this item entity is a display item.
if(Itemcase.isItemcaseDisplayItem(event.getItem())) {
// Prevent this item from being picked up.
event.setCancelled(true);
}
}
@EventHandler(priority = EventPriority.HIGHEST)
public void onItemDespawn(ItemDespawnEvent event) {
// If this item entity is a display item.
if(Itemcase.isItemcaseDisplayItem(event.getEntity())) {
// Prevent this item from despawning.
event.setCancelled(true);
}
}
@EventHandler(priority = EventPriority.NORMAL)
public void onBlockBreakEvent(BlockBreakEvent event) {
// Get ItemcaseManager.
ItemcaseManager itemcaseManager =
ItemCaseCore.instance.getItemcaseManager();
// For every Itemcase.
for(Itemcase itemcase : itemcaseManager.getItemcases()) {
// Check if this block was an itemcase.
if(itemcase.location.equals(event.getBlock().getLocation())) {
// Cancel the event.
event.setCancelled(true);
}
}
}
@EventHandler(priority = EventPriority.NORMAL)
public void onBlockPlaceEvent(BlockPlaceEvent event) {
// Get ItemcaseManager.
ItemcaseManager itemcaseManager =
ItemCaseCore.instance.getItemcaseManager();
// For every Itemcase.
for(Itemcase itemcase : itemcaseManager.getItemcases()) {
// Get block type.
Material type = itemcase.getLocation().getBlock().getType();
// Check if the block placed was on an itemcase.
if(itemcase.location.equals(event.getBlock().getLocation())) {
// Cancel the event.
event.setCancelled(true);
}
// If not a slab.
if(type != Material.STEP &&
type != Material.WOOD_STEP &&
type != Material.STONE_SLAB2 &&
type != Material.PURPUR_SLAB) {
// Check if the block placed was 1 above an itemcase.
if(itemcase.location.clone().add(0, 1, 0)
.equals(event.getBlock().getLocation())) {
// Cancel the event.
event.setCancelled(true);
}
}
}
}
@EventHandler(priority = EventPriority.NORMAL)
public void onPlayerInteractEvent(PlayerInteractEvent event) {
// Check if block was involved.
if(!event.hasBlock()) {
// Exit.
return;
}
// Check if action was correct.
if(event.getAction() != Action.RIGHT_CLICK_BLOCK) {
// Exit.
return;
}
// If off-hand, skip.
if(event.getHand() == EquipmentSlot.OFF_HAND) {
// Exit.
return;
}
// If player is sneaking.
if(event.getPlayer().isSneaking()) {
// Exit.
return;
}
// If block is not an ItemCase.
if(!ItemCaseCore.instance.getItemcaseManager().isItemcase(
event.getClickedBlock().getLocation())) {
// Exit.
return;
}
// Get itemcase.
Itemcase itemcase = ItemCaseCore.instance.getItemcaseManager()
.getItemcase(event.getClickedBlock().getLocation());
// If itemcase is not a shop.
if(itemcase.getType() == Type.SHOWCASE) {
// Exit.
return;
}
// Create new order.
ItemCaseCore.instance.getOrderManager().createOrder(itemcase,
event.getPlayer());
// Cancel event.
event.setCancelled(true);
}
}
/**
* A runnable task that is executed every 10 seconds to check if an
* itemcase's display item is for some reason dead or has been moved. This
* is particularly useful when servers use anti-lag plugins that forcibly
* kill entities or a player has somehow caused an item to move.
*/
private final class ItemcaseTask extends BukkitRunnable {
/**
* The itemcase that this task is for.
*/
private final Itemcase itemcase;
/**
* Constructor.
*
* @param itemcase The itemcase this task is for.
*/
public ItemcaseTask(Itemcase itemcase) {
// Set itemcase.
this.itemcase = itemcase;
}
@Override
public void run() {
// If chunk is not currently loaded.
if(!this.itemcase.chunk.isLoaded()) {
// Dont bother running this task.
return;
}
// Get the default display item location.
Location location = this.itemcase.getLocation();
// List of valid materials.
ArrayList<Material> materials =
ItemCaseCore.instance.getConfigFile().getMaterials();
// Check if this block still exists.
if(!materials.contains(location.getBlock().getType())) {
// Set to default.
location.getBlock().setType(materials.get(0));
}
// Get all entites near itemcase. (Uses quite a large area just in
// case item is not where it should be).
Collection<Entity> entities = location.getWorld().getNearbyEntities(
location, 5f, 5f, 5f);
// A counter to count how many itemcase items are nearby.
int numberOfItemcaseItems = 0;
// Loop through every entity.
for(Entity entity : entities) {
// If the entity is not an item, skip.
if(!(entity instanceof Item)) {
continue;
}
// Check the entity is an ItemCase display item.
if(entity.hasMetadata("ItemCase")) {
// Get the vector location that belongs to this itemcase
// item.
Vector entityVector = (Vector) ((FixedMetadataValue)
entity.getMetadata("ItemCase").get(0)).value();
// Check that the entity is an item for this itemcase only.
if(entityVector.getBlockX() !=
this.itemcase.location.getBlockX() ||
entityVector.getBlockY() !=
this.itemcase.location.getBlockY() ||
entityVector.getBlockZ() !=
this.itemcase.location.getBlockZ()) {
// Skip if not.
continue;
}
// Increment counter.
numberOfItemcaseItems++;
// If the entity is not the current item we know about, we
// must have a duplicate caused by nms.
if(this.itemcase.getDisplayItem().getUniqueId().compareTo(
entity.getUniqueId()) != 0) {
// Remove this item.
entity.remove();
} else {
// If the item on the floor has the same UUID, update
// the reference just in case it has changed.
this.itemcase.displayItem = (Item) entity;
}
// If the item has no metadata but is some form of display item.
} else if(Itemcase.isItemcaseDisplayItem((Item) entity)) {
// Remove this item as it is probably left over from a
// restart hence no metadata.
entity.remove();
}
}
// If there were no itemcase items nearby.
if(numberOfItemcaseItems == 0) {
// Spawn a new item as for some reason the item has despawned
// (usually because of anti-lag plugins.)
this.itemcase.spawnItem();
}
// Get the current location of the display item.
double x = this.itemcase.getDisplayItem().getLocation().getX();
double y = this.itemcase.getDisplayItem().getLocation().getBlockY();
double z = this.itemcase.getDisplayItem().getLocation().getZ();
// Get the correct spawn location of the display item.
Location displayItemSpawnLocation =
this.itemcase.getDisplayItemSpawnLocation();
// Check if the display item has for some reason moved.
if(x != displayItemSpawnLocation.getX() ||
y != displayItemSpawnLocation.getBlockY() ||
z != displayItemSpawnLocation.getZ()) {
// Move the display item back to where it should be.
this.itemcase.getDisplayItem().teleport(
displayItemSpawnLocation);
}
}
}
}