diff --git a/src/main/java/net/citizensnpcs/api/ai/Navigator.java b/src/main/java/net/citizensnpcs/api/ai/Navigator.java index b2547ff8..2f441fad 100644 --- a/src/main/java/net/citizensnpcs/api/ai/Navigator.java +++ b/src/main/java/net/citizensnpcs/api/ai/Navigator.java @@ -23,6 +23,16 @@ public interface Navigator { */ void cancelNavigation(CancelReason reason); + /** + * @see #canNavigateTo(Location, NavigatorParameters) + */ + boolean canNavigateTo(Location dest); + + /** + * Returns whether the NPC can navigate to the given destination with the navigator parameters. + */ + boolean canNavigateTo(Location dest, NavigatorParameters params); + /** * Returns the {@link NavigatorParameters} local to this navigator. These parameters are copied to local target * parameters when a new target is started. diff --git a/src/main/java/net/citizensnpcs/api/astar/pathfinder/MinecraftBlockExaminer.java b/src/main/java/net/citizensnpcs/api/astar/pathfinder/MinecraftBlockExaminer.java index 2cb484c6..edff913f 100644 --- a/src/main/java/net/citizensnpcs/api/astar/pathfinder/MinecraftBlockExaminer.java +++ b/src/main/java/net/citizensnpcs/api/astar/pathfinder/MinecraftBlockExaminer.java @@ -110,17 +110,17 @@ public void run() { if (next.getY() > prev.getY()) { npc.getEntity().setVelocity(npc.getEntity().getVelocity().setY(0.3)); if (sneakingForScaffolding) { - npc.data().set(NPC.SNEAKING_METADATA, sneakingForScaffolding = false); + npc.data().set(NPC.Metadata.SNEAKING, sneakingForScaffolding = false); } } else if ((isScaffolding(in) || isScaffolding(next.getType()))) { if (loc.distance(next.getLocation().add(0.5, 1, 0.5)) < 0.4) { - npc.data().set(NPC.SNEAKING_METADATA, sneakingForScaffolding = true); + npc.data().set(NPC.Metadata.SNEAKING, sneakingForScaffolding = true); } } else if (next.getY() < prev.getY()) { npc.getEntity().setVelocity(npc.getEntity().getVelocity().setY(-0.2)); } } else if (sneakingForScaffolding) { - npc.data().set(NPC.SNEAKING_METADATA, sneakingForScaffolding = false); + npc.data().set(NPC.Metadata.SNEAKING, sneakingForScaffolding = false); } } }); @@ -128,7 +128,7 @@ public void run() { @Override public void onCompletion(CancelReason cancelReason) { npc.data().set("running-ladder", false); - npc.data().set(NPC.SNEAKING_METADATA, false); + npc.data().set(NPC.Metadata.SNEAKING, false); } }); added = true; diff --git a/src/main/java/net/citizensnpcs/api/gui/InventoryMenu.java b/src/main/java/net/citizensnpcs/api/gui/InventoryMenu.java index 2bda6523..a47f303d 100644 --- a/src/main/java/net/citizensnpcs/api/gui/InventoryMenu.java +++ b/src/main/java/net/citizensnpcs/api/gui/InventoryMenu.java @@ -217,9 +217,9 @@ private void handleClick(InventoryClickEvent event) { return; } - if (!isOurInventory(clicked)) { + if (!isOurInventory(clicked)) return; - } + switch (event.getAction()) { case COLLECT_TO_CURSOR: event.setCancelled(true); @@ -230,8 +230,10 @@ private void handleClick(InventoryClickEvent event) { default: break; } + if (event.getSlot() < 0) return; + InventoryMenuSlot slot = page.ctx.getSlot(event.getSlot()); CitizensInventoryClickEvent ev = new CitizensInventoryClickEvent(event, pickupAmount); PageContext pg = page; diff --git a/src/main/java/net/citizensnpcs/api/npc/AbstractNPC.java b/src/main/java/net/citizensnpcs/api/npc/AbstractNPC.java index e823e6d3..3ed114f4 100644 --- a/src/main/java/net/citizensnpcs/api/npc/AbstractNPC.java +++ b/src/main/java/net/citizensnpcs/api/npc/AbstractNPC.java @@ -56,15 +56,15 @@ public abstract class AbstractNPC implements NPC { private final int id; private Supplier itemProvider = () -> { Material id = Material.STONE; - int data = data().get(NPC.ITEM_DATA_METADATA, data().get("falling-block-data", 0)); - if (data().has(NPC.ITEM_ID_METADATA)) { - id = Material.getMaterial(data(). get(NPC.ITEM_ID_METADATA), false); + int data = data().get(NPC.Metadata.ITEM_DATA, data().get("falling-block-data", 0)); + if (data().has(NPC.Metadata.ITEM_ID)) { + id = Material.getMaterial(data(). get(NPC.Metadata.ITEM_ID), false); } if (id == Material.AIR) { id = Material.STONE; Messaging.severe(getId(), "invalid Material: converted to stone"); } - return new org.bukkit.inventory.ItemStack(id, data().get(NPC.ITEM_AMOUNT_METADATA, 1), (short) data); + return new org.bukkit.inventory.ItemStack(id, data().get(NPC.Metadata.ITEM_AMOUNT, 1), (short) data); }; private final MetadataStore metadata = new SimpleMetadataStore() { @Override @@ -323,12 +323,12 @@ public boolean hasTrait(Class trait) { @Override public boolean isFlyable() { - return data().get(NPC.FLYABLE_METADATA, false); + return data().get(NPC.Metadata.FLYABLE, false); } @Override public boolean isProtected() { - return data().get(NPC.DEFAULT_PROTECTED_METADATA, true); + return data().get(NPC.Metadata.DEFAULT_PROTECTED, true); } @Override @@ -409,21 +409,21 @@ public void removeTrait(Class traitClass) { public boolean requiresNameHologram() { return getEntityType() != EntityType.ARMOR_STAND && ((name.length() > 16 && getEntityType() == EntityType.PLAYER) - || data().get(NPC.ALWAYS_USE_NAME_HOLOGRAM_METADATA, false) + || data().get(NPC.Metadata.ALWAYS_USE_NAME_HOLOGRAM, false) || (coloredNameStringCache != null && coloredNameStringCache.contains("§x")) || !Placeholders.replace(name, null, this).equals(name)); } @Override public void save(DataKey root) { - if (!metadata.get(NPC.SHOULD_SAVE_METADATA, true)) + if (!metadata.get(NPC.Metadata.SHOULD_SAVE, true)) return; metadata.saveTo(root.getRelative("metadata")); root.setString("name", name); root.setString("uuid", uuid.toString()); - if (data().has(NPC.ITEM_ID_METADATA)) { + if (data().has(NPC.Metadata.ITEM_ID)) { ItemStack stack = itemProvider.get(); ItemStorage.saveItem(root.getRelative("itemprovider"), stack); } else { @@ -458,12 +458,12 @@ public void save(DataKey root) { @Override public void setAlwaysUseNameHologram(boolean use) { - data().setPersistent(NPC.ALWAYS_USE_NAME_HOLOGRAM_METADATA, use); + data().setPersistent(NPC.Metadata.ALWAYS_USE_NAME_HOLOGRAM, use); } @Override public void setFlyable(boolean flyable) { - data().setPersistent(NPC.FLYABLE_METADATA, flyable); + data().setPersistent(NPC.Metadata.FLYABLE, flyable); } @Override @@ -503,12 +503,12 @@ public void setName(String name) { @Override public void setProtected(boolean isProtected) { - data().setPersistent(NPC.DEFAULT_PROTECTED_METADATA, isProtected); + data().setPersistent(NPC.Metadata.DEFAULT_PROTECTED, isProtected); } @Override public void setUseMinecraftAI(boolean use) { - data().setPersistent(NPC.USE_MINECRAFT_AI_METADATA, use); + data().setPersistent(NPC.Metadata.USE_MINECRAFT_AI, use); } private void teleport(final Entity entity, Location location, int delay) { @@ -581,6 +581,6 @@ public void updateCustomName() { @Override public boolean useMinecraftAI() { - return data().get(NPC.USE_MINECRAFT_AI_METADATA, false); + return data().get(NPC.Metadata.USE_MINECRAFT_AI, false); } } diff --git a/src/main/java/net/citizensnpcs/api/npc/NPC.java b/src/main/java/net/citizensnpcs/api/npc/NPC.java index c62a0af2..95607d96 100644 --- a/src/main/java/net/citizensnpcs/api/npc/NPC.java +++ b/src/main/java/net/citizensnpcs/api/npc/NPC.java @@ -257,6 +257,8 @@ public interface NPC extends Agent, Cloneable { */ public boolean isSpawned(); + public boolean isUpdating(NPCUpdate update); + /** * Loads the {@link NPC} from the given {@link DataKey}. This reloads all traits, respawns the NPC and sets it up * for execution. Should not be called often. @@ -285,6 +287,8 @@ public interface NPC extends Agent, Cloneable { */ public void save(DataKey key); + public void scheduleUpdate(NPCUpdate update); + /** * Sets whether to always use a name hologram instead of the in-built Minecraft name. * @@ -572,125 +576,25 @@ public enum Metadata { public String getKey() { return key; } + + public static Metadata byKey(String name) { + for (Metadata v : NPC.Metadata.values()) { + if (v.key.equals(name)) + return v; + } + return null; + } + + public static Metadata byName(String name) { + try { + return valueOf(name); + } catch (IllegalArgumentException iae) { + return null; + } + } } - public static final String ALWAYS_USE_NAME_HOLOGRAM_METADATA = "always-use-name-hologram"; - /** - * The Minecraft ambient sound played. String - Minecraft sound name - */ - public static final String AMBIENT_SOUND_METADATA = "ambient-sound"; - /** - * Whether the NPC is collidable with Players or not. Boolean. - */ - public static final String COLLIDABLE_METADATA = "collidable"; - /** - * Whether the NPC can damage other Entities. Boolean. - */ - public static final String DAMAGE_OTHERS_METADATA = "damage-others"; - /** - * The Minecraft sound played when the NPC dies. String - Minecraft sound name. - */ - public static final String DEATH_SOUND_METADATA = "death-sound"; - /** - * Whether the NPC is 'protected' i.e. invulnerable to damage. Boolean. - */ - public static final String DEFAULT_PROTECTED_METADATA = "protected"; - public static final String DISABLE_DEFAULT_STUCK_ACTION_METADATA = "disable-default-stuck-action"; - /** - * Whether the NPC drops its inventory after death. Boolean. - */ - public static final String DROPS_ITEMS_METADATA = "drops-items"; - /** - * Whether the NPC is 'flyable' i.e. will fly when pathfinding. Boolean. - */ - public static final String FLYABLE_METADATA = "flyable"; - /** - * Whether the NPC is currently glowing. Boolean. - */ - public static final String GLOWING_METADATA = "glowing"; - /** - * The Minecraft sound to play when hurt. String - Minecraft sound name. - */ - public static final String HURT_SOUND_METADATA = "hurt-sound"; - /** - * The Item amount. Integer. - */ - public static final String ITEM_AMOUNT_METADATA = "item-type-amount"; - /** - * The Item data. Byte. - */ - public static final String ITEM_DATA_METADATA = "item-type-data"; - /** - * The Item ID. String. - */ - public static final String ITEM_ID_METADATA = "item-type-id"; - /** - * Whether to keep chunk loaded. Boolean. - */ - public static final String KEEP_CHUNK_LOADED_METADATA = "keep-chunk-loaded"; - /** - * Whether the NPC is leashable. Boolean. - */ - public static final String LEASH_PROTECTED_METADATA = "protected-leash"; - /** - * The Minecart item data. Byte. - */ - public static final String MINECART_ITEM_DATA_METADATA = "minecart-item-data"; - /** - * The Minecart item name. - */ - public static final String MINECART_ITEM_METADATA = "minecart-item-name"; - /** - * The Minecart item offset as defined by Minecraft. {@link Minecart#setDisplayBlockOffset(int)} - */ - public static final String MINECART_OFFSET_METADATA = "minecart-item-offset"; - /** - * Whether the NPC's nameplate should be visible. Boolean. - */ - public static final String NAMEPLATE_VISIBLE_METADATA = "nameplate-visible"; - /** - * Whether to open doors while pathfinding. Boolean. - */ - public static final String PATHFINDER_OPEN_DOORS_METADATA = "pathfinder-open-doors"; - /** - * Whether to remove players from the player list. Boolean defaults to true. - */ - public static final String REMOVE_FROM_PLAYERLIST_METADATA = "removefromplayerlist"; - /** - * The Integer delay to respawn in ticks after death. Only works if non-zero. - */ - public static final String RESPAWN_DELAY_METADATA = "respawn-delay"; - /** - * The fake NPC scoreboard team name because Minecraft requires a team name. Usually will be a random UUID in String - * form. - */ - public static final String SCOREBOARD_FAKE_TEAM_NAME_METADATA = "fake-scoreboard-team-name"; - /** - * Whether to save / persist across server restarts. Boolean. - */ - public static final String SHOULD_SAVE_METADATA = "should-save"; - /** - * Whether to suppress sounds. Boolean. - */ - public static final String SILENT_METADATA = "silent-sounds"; - /** - * Whether to sneak. Boolean. - */ - public static final String SNEAKING_METADATA = "citizens-sneaking"; - /** - * Whether to allow swimming. Boolean. - */ - public static final String SWIMMING_METADATA = "swim"; - /** - * Whether to prevent NPC being targeted by hostile mobs. Boolean. - */ - public static final String TARGETABLE_METADATA = "protected-target"; - /** - * Whether to use Minecraft AI. Boolean. - */ - public static final String USE_MINECRAFT_AI_METADATA = "minecraft-ai"; - /** - * Whether to block Minecraft villager trades. Boolean defaults to true. - */ - public static final String VILLAGER_BLOCK_TRADES = "villager-trades"; + public enum NPCUpdate { + PACKET; + } } \ No newline at end of file diff --git a/src/main/java/net/citizensnpcs/api/npc/SimpleMetadataStore.java b/src/main/java/net/citizensnpcs/api/npc/SimpleMetadataStore.java index 195af3d0..4b417609 100644 --- a/src/main/java/net/citizensnpcs/api/npc/SimpleMetadataStore.java +++ b/src/main/java/net/citizensnpcs/api/npc/SimpleMetadataStore.java @@ -1,16 +1,17 @@ package net.citizensnpcs.api.npc; -import java.util.Iterator; import java.util.Map; import java.util.Map.Entry; import com.google.common.base.Preconditions; import com.google.common.collect.Maps; +import net.citizensnpcs.api.npc.NPC.Metadata; import net.citizensnpcs.api.util.DataKey; public class SimpleMetadataStore implements MetadataStore { private final Map metadata = Maps.newHashMap(); + private final Map npcMetadata = Maps.newEnumMap(NPC.Metadata.class); private void checkPrimitive(Object data) { Preconditions.checkNotNull(data, "data cannot be null"); @@ -27,6 +28,19 @@ public MetadataStore clone() { return copy; } + @Override + public T get(NPC.Metadata key) { + Preconditions.checkNotNull(key, "key cannot be null"); + MetadataObject normal = this.npcMetadata.get(key); + return normal == null ? null : (T) normal.value; + } + + @Override + public T get(NPC.Metadata key, T def) { + T t = this.get(key); + return t == null ? def : t; + } + @Override @SuppressWarnings("unchecked") public T get(String key) { @@ -44,6 +58,12 @@ public T get(String key, T def) { return t; } + @Override + public boolean has(NPC.Metadata key) { + Preconditions.checkNotNull(key, "key cannot be null"); + return this.npcMetadata.containsKey(key); + } + @Override public boolean has(String key) { Preconditions.checkNotNull(key, "key cannot be null"); @@ -52,15 +72,22 @@ public boolean has(String key) { @Override public void loadFrom(DataKey key) { - Iterator> itr = metadata.entrySet().iterator(); - while (itr.hasNext()) { - if (itr.next().getValue().persistent) { - itr.remove(); + metadata.entrySet().removeIf(e -> e.getValue().persistent); + npcMetadata.entrySet().removeIf(e -> e.getValue().persistent); + for (DataKey sub : key.getSubKeys()) { + NPC.Metadata meta = Metadata.byKey(sub.name()); + if (meta != null) { + setPersistent(meta, sub.getRaw("")); + } else { + setPersistent(sub.name(), sub.getRaw("")); } } - for (DataKey subKey : key.getSubKeys()) { - setPersistent(subKey.name(), subKey.getRaw("")); - } + + } + + @Override + public void remove(NPC.Metadata key) { + npcMetadata.remove(key); } @Override @@ -76,6 +103,23 @@ public void saveTo(DataKey key) { key.setRaw(entry.getKey(), entry.getValue().value); } } + + for (Entry entry : npcMetadata.entrySet()) { + if (entry.getValue().persistent) { + key.setRaw(entry.getKey().getKey(), entry.getValue().value); + } + } + } + + @Override + public void set(NPC.Metadata key, Object data) { + Preconditions.checkNotNull(key, "key cannot be null"); + if (data == null) { + this.remove(key); + } else { + this.npcMetadata.put(key, new MetadataObject(data, false)); + } + } @Override @@ -86,18 +130,36 @@ public void set(String key, Object data) { } else { metadata.put(key, new MetadataObject(data, false)); } + } @Override + public void setPersistent(NPC.Metadata key, Object data) { + Preconditions.checkNotNull(key, "key cannot be null"); + if (data == null) { + this.remove(key); + } else { + this.checkPrimitive(data); + this.npcMetadata.put(key, new MetadataObject(data, true)); + } + } + + @Override + public void setPersistent(String key, Object data) { Preconditions.checkNotNull(key, "key cannot be null"); - checkPrimitive(data); - metadata.put(key, new MetadataObject(data, true)); + if (data == null) { + this.remove(key); + } else { + this.checkPrimitive(data); + this.metadata.put(key, new MetadataObject(data, true)); + } } @Override + public int size() { - return metadata.size(); + return this.metadata.size() + this.npcMetadata.size(); } private static class MetadataObject {