/* * This file is part of NucleusFramework for Bukkit, licensed under the MIT License (MIT). * * Copyright (c) JCThePants (www.jcwhatever.com) * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ package com.jcwhatever.nucleus.internal.managed.items.floating; import com.jcwhatever.nucleus.Nucleus; import com.jcwhatever.nucleus.events.floatingitems.FloatingItemDespawnEvent; import com.jcwhatever.nucleus.events.floatingitems.FloatingItemSpawnEvent; import com.jcwhatever.nucleus.managed.entity.ITrackedEntity; import com.jcwhatever.nucleus.managed.items.floating.IFloatingItem; import com.jcwhatever.nucleus.storage.IDataNode; import com.jcwhatever.nucleus.utils.PreCon; import com.jcwhatever.nucleus.utils.coords.LocationUtils; import com.jcwhatever.nucleus.utils.entity.EntityUtils; import com.jcwhatever.nucleus.utils.inventory.InventoryUtils; import com.jcwhatever.nucleus.utils.observer.update.IUpdateSubscriber; import com.jcwhatever.nucleus.utils.observer.update.NamedUpdateAgents; import org.bukkit.Bukkit; import org.bukkit.Location; import org.bukkit.entity.Entity; import org.bukkit.entity.Item; import org.bukkit.entity.Player; import org.bukkit.inventory.ItemStack; import org.bukkit.inventory.meta.ItemMeta; import org.bukkit.plugin.Plugin; import org.bukkit.util.Vector; import javax.annotation.Nullable; import java.util.UUID; /** * Represents a controlled item stack entity. * * <p>Note that a data node to store the entity info to disk is * optional, but recommended even if the the use is only transient. * Without the data node, expect left over items that can be picked * up after server restarts or crashes.</p> */ class FloatingItem implements IFloatingItem { private static final Location CENTERED_LOCATION = new Location(null, 0, 0, 0); private static BukkitListener _listener; private final Plugin _plugin; private final String _name; private final String _searchName; private final ItemStack _item; private final IDataNode _dataNode; private UUID _entityId; private ITrackedEntity _trackedEntity; private boolean _canPickup; private boolean _simulatePickup; private boolean _isCentered = true; private int _respawnTimeSeconds = 20; private boolean _isSpawned; private boolean _isDisposed; private Location _currentLocation; private NamedUpdateAgents _agents = new NamedUpdateAgents(); /** * Constructor. * * @param name The unique name of the item. * @param item The item. * @param initialLocation Optional initial location of the item. * @param dataNode Data node to store item settings in. */ public FloatingItem(Plugin plugin, String name, ItemStack item, @Nullable Location initialLocation, IDataNode dataNode) { PreCon.notNull(plugin); PreCon.notNullOrEmpty(name); PreCon.notNull(item); PreCon.notNull(dataNode); _plugin = plugin; _name = name; _searchName = name.toLowerCase(); _item = item; _currentLocation = initialLocation; _dataNode = dataNode; if (_listener == null) { _listener = new BukkitListener(); Bukkit.getPluginManager().registerEvents(_listener, Nucleus.getPlugin()); } loadSettings(); } @Override public Plugin getPlugin() { return _plugin; } @Override public UUID getUniqueId() { return _entityId; } @Override public String getName() { return _name; } @Override public String getSearchName() { return _searchName; } @Override public ItemStack getItem() { return _item.clone(); } @Override @Nullable public Entity getEntity() { if (_trackedEntity == null) { return null; } return _trackedEntity.getEntity(); } /** * Get the floating items data node, if any. */ @Nullable public IDataNode getDataNode() { return _dataNode; } @Override public boolean isSpawned() { return _isSpawned; } @Override @Nullable public Location getLocation() { return getLocation(new Location(null, 0, 0, 0)); } @Nullable @Override public Location getLocation(Location output) { return LocationUtils.copy(_currentLocation, output); } @Override public boolean canPickup() { return _canPickup; } @Override public void setCanPickup(boolean canPickup) { _canPickup = canPickup; _dataNode.set("can-pickup", canPickup); _dataNode.save(); } @Override public boolean isPickupSimulated() { return _simulatePickup; } @Override public void setPickupSimulated(boolean isPickupSimulated) { _simulatePickup = isPickupSimulated; _dataNode.set("simulate-pickup", isPickupSimulated); _dataNode.save(); } @Override public boolean isCentered() { return _isCentered; } @Override public void setCentered(boolean isCentered) { _isCentered = isCentered; _dataNode.set("is-centered", isCentered); _dataNode.save(); } @Override public int getRespawnTimeSeconds() { return _respawnTimeSeconds; } @Override public void setRespawnTimeSeconds(int seconds) { _respawnTimeSeconds = seconds; _dataNode.set("respawn-time-seconds", seconds); _dataNode.save(); } @Override public boolean spawn() { return _currentLocation != null && spawn(_currentLocation); } @Override public boolean spawn(Location location) { PreCon.notNull(location); if (_isDisposed) throw new RuntimeException("Cannot spawn a disposed item."); location = location.clone(); // clone: prevent external changes from affecting the location FloatingItemSpawnEvent event = new FloatingItemSpawnEvent(this); Nucleus.getEventManager().callBukkit(this, event); if (event.isCancelled()) return false; if (!despawn()) return false; _isSpawned = true; _currentLocation = location; // get corrected location final Location spawnLocation = _isCentered ? LocationUtils.getCenteredLocation(location, CENTERED_LOCATION).add(0, 0.5, 0) // add y 0.5 to prevent falling through surface block : LocationUtils.add(location, 0, 0.5, 0); if (!location.getChunk().isLoaded()) { _listener.registerPendingSpawn(this); return true; } // spawn item entity Entity entity = location.getWorld().dropItem(spawnLocation, _item.clone()); _trackedEntity = EntityUtils.trackEntity(entity); _entityId = entity.getUniqueId(); entity.setVelocity(new Vector(0, 0, 0)); // register entity _listener.register(this); // prevent stack merging Item item = (Item)entity; ItemMeta meta = item.getItemStack().getItemMeta(); meta.setDisplayName(_entityId.toString()); item.getItemStack().setItemMeta(meta); _dataNode.set("location", location); _dataNode.set("is-spawned", true); _dataNode.set("entity-id", _entityId); _dataNode.save(); _agents.update("onSpawn", entity); return true; } @Override public boolean despawn() { if (_trackedEntity == null) return true; Entity entity = _trackedEntity.getEntity(); FloatingItemDespawnEvent event = new FloatingItemDespawnEvent(this); if (Nucleus.getPlugin().isEnabled()) Nucleus.getEventManager().callBukkit(this, event); if (event.isCancelled()) return false; _listener.unregister(this); _isSpawned = false; _listener.unregisterPendingSpawn(this); EntityUtils.removeEntity(entity); _trackedEntity = null; _entityId = null; _dataNode.set("is-spawned", false); if (_plugin.isEnabled()) { _dataNode.set("entity-id", null); } _dataNode.save(); _agents.update("onDespawn", entity); return true; } @Override public boolean give(Player player) { PreCon.notNull(player); if (!InventoryUtils.hasRoom(player.getInventory(), _item)) return false; player.getInventory().addItem(_item.clone()); return true; } @Override public boolean isDisposed() { return _isDisposed; } @Override public void dispose() { despawn(); _listener.unregister(this); _isDisposed = true; } @Override public FloatingItem onSpawn(IUpdateSubscriber<Entity> subscriber) { PreCon.notNull(subscriber); _agents.getAgent("onSpawn").addSubscriber(subscriber); return this; } @Override public FloatingItem onDespawn(IUpdateSubscriber<Entity> subscriber) { PreCon.notNull(subscriber); _agents.getAgent("onDespawn").addSubscriber(subscriber); return this; } @Override public FloatingItem onPickup(IUpdateSubscriber<Player> subscriber) { PreCon.notNull(subscriber); _agents.getAgent("onPickup").addSubscriber(subscriber); return this; } @Override public IFloatingItem onTryPickup(IUpdateSubscriber<Player> subscriber) { PreCon.notNull(subscriber); _agents.getAgent("onTryPickup").addSubscriber(subscriber); return this; } void onPickup(Player p) { _agents.update("onPickup", p); } void onTryPickup(Player p) { _agents.update("onTryPickup", p); } private void loadSettings() { _canPickup = _dataNode.getBoolean("can-pickup", _canPickup); _isCentered = _dataNode.getBoolean("is-centered", _isCentered); _respawnTimeSeconds = _dataNode.getInteger("respawn-time-seconds", _respawnTimeSeconds); _isSpawned = _dataNode.getBoolean("is-spawned", _isSpawned); _currentLocation = _dataNode.getLocation("location", _currentLocation); _entityId = _dataNode.getUUID("entity-id", _entityId); if (_currentLocation != null && _entityId != null) { // Find the entity from the chunk of the stored location Entity entity = EntityUtils.getEntityByUUID(_currentLocation.getChunk(), _entityId); if (entity != null) { _trackedEntity = EntityUtils.trackEntity(entity); } if (entity != null && !_isSpawned) { despawn(); } else if (entity == null && _isSpawned) { spawn(_currentLocation); } else if (entity != null) { _listener.register(this); } } else { _isSpawned = false; } } }