Implement non-nms dependant Itemcase.
An implementation of the original legacy Itemcase that uses no nms code.
This commit is contained in:
parent
b0e9dbdaca
commit
5179e6b332
@ -0,0 +1,419 @@
|
||||
/*
|
||||
* 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.Collection;
|
||||
import java.util.UUID;
|
||||
import org.bukkit.Chunk;
|
||||
import org.bukkit.Location;
|
||||
import org.bukkit.Material;
|
||||
import org.bukkit.World;
|
||||
import org.bukkit.entity.Entity;
|
||||
import org.bukkit.entity.Item;
|
||||
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.BlockPlaceEvent;
|
||||
import org.bukkit.event.entity.EntityPickupItemEvent;
|
||||
import org.bukkit.event.entity.ItemDespawnEvent;
|
||||
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 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 Player owner;
|
||||
|
||||
/**
|
||||
* This itemcase's task.
|
||||
*/
|
||||
private final ItemcaseTask task;
|
||||
|
||||
/**
|
||||
* The active item that is currently on display.
|
||||
*/
|
||||
private Item displayItem;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param itemStack The ItemStack to be displayed.
|
||||
* @param location The location of the itemcase.
|
||||
* @param owner The owner of this itemcase.
|
||||
*/
|
||||
public Itemcase(ItemStack itemStack, Location location, Player owner) {
|
||||
|
||||
// Set item stack and ensure stack size is 1.
|
||||
this.itemStack = itemStack.clone();
|
||||
this.itemStack.setAmount(1);
|
||||
|
||||
// Set display name to random UUID to prevent graphical item stacking.
|
||||
ItemMeta metadata = this.itemStack.getItemMeta();
|
||||
metadata.setDisplayName("com.gmail.bleedobsidian.itemcase:" +
|
||||
UUID.randomUUID().toString());
|
||||
this.itemStack.setItemMeta(metadata);
|
||||
|
||||
// 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;
|
||||
|
||||
// Schedule itemcase task to execute every 200 server ticks (10 secs).
|
||||
this.task = new ItemcaseTask(this);
|
||||
this.task.runTaskTimer(ItemCaseCore.instance, 0, 200);
|
||||
|
||||
// Spawn display item for the first time.
|
||||
this.spawnItem();
|
||||
}
|
||||
|
||||
/**
|
||||
* Spawn the display item.
|
||||
*/
|
||||
public void spawnItem() {
|
||||
|
||||
// Get the world that this itemcase is in.
|
||||
World world = this.location.getWorld();
|
||||
|
||||
// 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();
|
||||
|
||||
// Spawn the item.
|
||||
this.displayItem = this.location.getWorld().dropItem(
|
||||
displayItemSpawnLocation, this.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() {
|
||||
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");
|
||||
}
|
||||
|
||||
/**
|
||||
* @return The Location of this itemcase.
|
||||
*/
|
||||
public Location getLocation() {
|
||||
|
||||
// Location of this itemcase.
|
||||
return this.location;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Display item.
|
||||
*/
|
||||
public Item getDisplayItem() {
|
||||
|
||||
// Display item.
|
||||
return this.displayItem;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return The default location to spawn the display item.
|
||||
*/
|
||||
public Location getDisplayItemSpawnLocation() {
|
||||
|
||||
// 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() + 1.5,
|
||||
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) {
|
||||
|
||||
// If the material is not some sort of step.
|
||||
if(event.getBlock().getType() != Material.STEP) {
|
||||
|
||||
// Ignore event.
|
||||
return;
|
||||
}
|
||||
|
||||
// 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()) {
|
||||
|
||||
// Check if the block placed was on an itemcase.
|
||||
if(itemcase.location.equals(event.getBlock().getLocation())) {
|
||||
|
||||
// Cancel the 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.
|
||||
*/
|
||||
public static 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() {
|
||||
|
||||
// Get the default display item location.
|
||||
Location location = this.itemcase.getLocation();
|
||||
|
||||
// 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() - 1 ||
|
||||
z != displayItemSpawnLocation.getZ()) {
|
||||
|
||||
// Move the display item back to where it should be.
|
||||
this.itemcase.getDisplayItem().teleport(
|
||||
displayItemSpawnLocation);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user