diff --git a/src/main/java/net/citizensnpcs/Citizens.java b/src/main/java/net/citizensnpcs/Citizens.java index b0a8afed8..965dbdee2 100644 --- a/src/main/java/net/citizensnpcs/Citizens.java +++ b/src/main/java/net/citizensnpcs/Citizens.java @@ -38,6 +38,7 @@ import net.citizensnpcs.npc.CitizensCharacterManager; import net.citizensnpcs.npc.CitizensNPC; import net.citizensnpcs.npc.CitizensNPCManager; import net.citizensnpcs.npc.CitizensTraitManager; +import net.citizensnpcs.npc.NPCSelector; import net.citizensnpcs.util.Messaging; import net.citizensnpcs.util.Metrics; import net.citizensnpcs.util.StringHelper; @@ -61,6 +62,7 @@ public class Citizens extends JavaPlugin implements CitizensPlugin { private ClassLoader contextClassLoader; private CitizensNPCManager npcManager; private Storage saves; + private NPCSelector selector; private TraitManager traitManager; @Override @@ -77,6 +79,10 @@ public class Citizens extends JavaPlugin implements CitizensPlugin { return npcManager; } + public NPCSelector getNPCSelector() { + return selector; + } + @Override public File getScriptFolder() { return new File(getDataFolder(), "scripts"); @@ -89,10 +95,6 @@ public class Citizens extends JavaPlugin implements CitizensPlugin { @Override public boolean onCommand(CommandSender sender, Command cmd, String cmdName, String[] args) { - Player player = null; - if (sender instanceof Player) - player = (Player) sender; - try { // must put command into split. String[] split = new String[args.length + 1]; @@ -105,19 +107,17 @@ public class Citizens extends JavaPlugin implements CitizensPlugin { return suggestClosestModifier(sender, split[0], modifier); } - NPC npc = null; - if (player != null && player.getMetadata("selected").size() > 0) - npc = npcManager.getNPC(player.getMetadata("selected").get(0).asInt()); + NPC npc = selector.getSelected(sender); // TODO: change the args supplied to a context style system for // flexibility (ie. adding more context in the future without // changing everything) try { - commands.execute(split, player, player == null ? sender : player, npc); + commands.execute(split, sender, sender, npc); } catch (ServerCommandException ex) { Messaging.send(sender, "You must be in-game to execute that command."); } catch (CommandUsageException ex) { - Messaging.sendError(player, ex.getMessage()); - Messaging.sendError(player, ex.getUsage()); + Messaging.sendError(sender, ex.getMessage()); + Messaging.sendError(sender, ex.getUsage()); } catch (WrappedCommandException ex) { throw ex.getCause(); } catch (UnhandledCommandException ex) { @@ -126,12 +126,12 @@ public class Citizens extends JavaPlugin implements CitizensPlugin { Messaging.sendError(sender, ex.getMessage()); } } catch (NumberFormatException ex) { - Messaging.sendError(player, "That is not a valid number."); + Messaging.sendError(sender, "That is not a valid number."); } catch (Throwable ex) { ex.printStackTrace(); - if (player != null) { - Messaging.sendError(player, "Please report this error: [See console]"); - Messaging.sendError(player, ex.getClass().getName() + ": " + ex.getMessage()); + if (sender instanceof Player) { + Messaging.sendError(sender, "Please report this error: [See console]"); + Messaging.sendError(sender, ex.getClass().getName() + ": " + ex.getMessage()); } } return true; @@ -170,8 +170,9 @@ public class Citizens extends JavaPlugin implements CitizensPlugin { setupStorage(); - npcManager = new CitizensNPCManager(this, saves); + npcManager = new CitizensNPCManager(saves); traitManager = new CitizensTraitManager(this); + selector = new NPCSelector(this); CitizensAPI.setImplementation(this); getServer().getPluginManager().registerEvents(new EventListen(npcManager), this); @@ -195,6 +196,12 @@ public class Citizens extends JavaPlugin implements CitizensPlugin { } } + @Override + public void onImplementationChanged() { + Messaging.severe("Citizens implementation changed, disabling plugin."); + Bukkit.getPluginManager().disablePlugin(this); + } + private void registerCommands() { commands.setInjector(new Injector(this)); @@ -292,8 +299,9 @@ public class Citizens extends JavaPlugin implements CitizensPlugin { @Override public void run() { try { - Messaging.log("Starting Metrics"); Metrics metrics = new Metrics(Citizens.this); + if (metrics.isOptOut()) + return; metrics.addCustomData(new Metrics.Plotter("Total NPCs") { @Override public int getValue() { @@ -303,8 +311,9 @@ public class Citizens extends JavaPlugin implements CitizensPlugin { Metrics.Graph graph = metrics.createGraph("Character Type Usage"); characterManager.addPlotters(graph); metrics.start(); + Messaging.log("Metrics started."); } catch (IOException ex) { - Messaging.log("Unable to load metrics"); + Messaging.log("Unable to load metrics.", ex.getMessage()); } } }.start(); @@ -333,10 +342,4 @@ public class Citizens extends JavaPlugin implements CitizensPlugin { } private static final String COMPATIBLE_MC_VERSION = "1.2.5"; - - @Override - public void onImplementationChanged() { - Messaging.severe("Citizens implementation changed, disabling plugin."); - Bukkit.getPluginManager().disablePlugin(this); - } } \ No newline at end of file diff --git a/src/main/java/net/citizensnpcs/EventListen.java b/src/main/java/net/citizensnpcs/EventListen.java index f76c39b62..f698df7eb 100644 --- a/src/main/java/net/citizensnpcs/EventListen.java +++ b/src/main/java/net/citizensnpcs/EventListen.java @@ -4,18 +4,16 @@ import net.citizensnpcs.Settings.Setting; import net.citizensnpcs.api.event.NPCLeftClickEvent; import net.citizensnpcs.api.event.NPCRightClickEvent; import net.citizensnpcs.api.npc.NPC; -import net.citizensnpcs.api.trait.trait.Owner; import net.citizensnpcs.editor.Editor; import net.citizensnpcs.npc.CitizensNPCManager; import net.citizensnpcs.npc.entity.EntityHumanNPC; import net.citizensnpcs.trait.CurrentLocation; import net.citizensnpcs.trait.text.Text; -import net.citizensnpcs.util.Messaging; +import net.citizensnpcs.util.Util; import org.bukkit.Bukkit; import org.bukkit.Chunk; import org.bukkit.Location; -import org.bukkit.Material; import org.bukkit.craftbukkit.CraftServer; import org.bukkit.craftbukkit.entity.CraftPlayer; import org.bukkit.entity.Player; @@ -34,7 +32,6 @@ import org.bukkit.event.world.ChunkUnloadEvent; import org.bukkit.event.world.WorldLoadEvent; import org.bukkit.event.world.WorldUnloadEvent; -import com.google.common.base.Splitter; import com.google.common.collect.ArrayListMultimap; import com.google.common.collect.ListMultimap; import com.google.gson.internal.Pair; @@ -47,18 +44,6 @@ public class EventListen implements Listener { this.npcManager = npcManager; } - private boolean isSettingFulfilled(Player player, Setting setting) { - String parts = setting.asString(); - if (parts.contains("*")) - return true; - for (String part : Splitter.on(',').split(parts)) { - if (Material.matchMaterial(part) == player.getItemInHand().getType()) { - return true; - } - } - return false; - } - /* * Chunk events */ @@ -140,19 +125,9 @@ public class EventListen implements Listener { Bukkit.getPluginManager().callEvent(rightClickEvent); if (rightClickEvent.isCancelled()) return; - - if (!player.hasMetadata("selected") || player.getMetadata("selected").size() == 0 - || player.getMetadata("selected").get(0).asInt() != npc.getId()) { - if (isSettingFulfilled(player, Setting.SELECTION_ITEM) && (npc.getTrait(Owner.class).isOwner(player))) { - npcManager.selectNPC(player, npc); - Messaging.sendWithNPC(player, Setting.SELECTION_MESSAGE.asString(), npc); - if (!Setting.QUICK_SELECT.asBoolean()) - return; - } - } // If the NPC isn't a close talker // TODO: move this into text.class - if (isSettingFulfilled(player, Setting.TALK_ITEM) && !npc.getTrait(Text.class).shouldTalkClose()) + if (Util.isSettingFulfilled(player, Setting.TALK_ITEM) && !npc.getTrait(Text.class).shouldTalkClose()) npc.getTrait(Text.class).sendText(player); if (npc.getCharacter() != null) diff --git a/src/main/java/net/citizensnpcs/command/CommandManager.java b/src/main/java/net/citizensnpcs/command/CommandManager.java index 5124af057..8ee6bb6af 100644 --- a/src/main/java/net/citizensnpcs/command/CommandManager.java +++ b/src/main/java/net/citizensnpcs/command/CommandManager.java @@ -26,6 +26,7 @@ import net.citizensnpcs.command.exception.ServerCommandException; import net.citizensnpcs.command.exception.UnhandledCommandException; import net.citizensnpcs.command.exception.WrappedCommandException; +import org.bukkit.command.CommandSender; import org.bukkit.command.ConsoleCommandSender; import org.bukkit.entity.EntityType; import org.bukkit.entity.Player; @@ -58,25 +59,26 @@ public class CommandManager { * Attempt to execute a command. This version takes a separate command name * (for the root command) and then a list of following arguments. */ - public void execute(String cmd, String[] args, Player player, Object... methodArgs) throws CommandException { + public void execute(String cmd, String[] args, CommandSender sender, Object... methodArgs) throws CommandException { String[] newArgs = new String[args.length + 1]; System.arraycopy(args, 0, newArgs, 1, args.length); newArgs[0] = cmd; Object[] newMethodArgs = new Object[methodArgs.length + 1]; System.arraycopy(methodArgs, 0, newMethodArgs, 1, methodArgs.length); - executeMethod(null, newArgs, player, newMethodArgs); + executeMethod(null, newArgs, sender, newMethodArgs); } // Attempt to execute a command. - public void execute(String[] args, Player player, Object... methodArgs) throws CommandException { + public void execute(String[] args, CommandSender sender, Object... methodArgs) throws CommandException { Object[] newMethodArgs = new Object[methodArgs.length + 1]; System.arraycopy(methodArgs, 0, newMethodArgs, 1, methodArgs.length); - executeMethod(null, args, player, newMethodArgs); + executeMethod(null, args, sender, newMethodArgs); } // Attempt to execute a command. - public void executeMethod(Method parent, String[] args, Player player, Object[] methodArgs) throws CommandException { + public void executeMethod(Method parent, String[] args, CommandSender sender, Object[] methodArgs) + throws CommandException { String cmdName = args[0]; String modifier = args.length > 1 ? args[1] : ""; @@ -91,34 +93,31 @@ public class CommandManager { if (method == null && parent == null) throw new UnhandledCommandException(); - if (methodArgs[1] instanceof Player && !hasPermission(method, player)) + if (methodArgs[1] instanceof Player && !hasPermission(method, sender)) throw new NoPermissionsException(); - if (methodArgs[1] instanceof Player) { - Requirements cmdRequirements = requirements.get(method); - if (cmdRequirements != null) { - NPC npc = (NPC) methodArgs[2]; + Requirements cmdRequirements = requirements.get(method); + if (cmdRequirements != null) { + NPC npc = (NPC) methodArgs[2]; - // Requirements - if (cmdRequirements.selected() && npc == null) - throw new RequirementMissingException("You must have an NPC selected to execute that command."); + // Requirements + if (cmdRequirements.selected() && npc == null) + throw new RequirementMissingException("You must have an NPC selected to execute that command."); - if (cmdRequirements.ownership() && npc != null - && !npc.getTrait(Owner.class).getOwner().equals(player.getName()) - && !player.hasPermission("citizens.admin")) - throw new RequirementMissingException("You must be the owner of this NPC to execute that command."); + if (cmdRequirements.ownership() && npc != null && !sender.hasPermission("citizens.admin") + && !npc.getTrait(Owner.class).isOwnedBy(sender)) + throw new RequirementMissingException("You must be the owner of this NPC to execute that command."); - if (npc != null) { - Set types = Sets.newEnumSet(Arrays.asList(cmdRequirements.types()), EntityType.class); - if (types.contains(EntityType.UNKNOWN)) - types = EnumSet.allOf(EntityType.class); - types.removeAll(Sets.newHashSet(cmdRequirements.excludedTypes())); + if (npc != null) { + Set types = Sets.newEnumSet(Arrays.asList(cmdRequirements.types()), EntityType.class); + if (types.contains(EntityType.UNKNOWN)) + types = EnumSet.allOf(EntityType.class); + types.removeAll(Sets.newHashSet(cmdRequirements.excludedTypes())); - EntityType type = EntityType.valueOf(npc.getTrait(MobType.class).getType()); - if (!types.contains(type)) { - throw new RequirementMissingException("The NPC cannot be the mob type '" - + type.name().toLowerCase().replace('_', '-') + "' to use that command."); - } + EntityType type = EntityType.valueOf(npc.getTrait(MobType.class).getType()); + if (!types.contains(type)) { + throw new RequirementMissingException("The NPC cannot be the mob type '" + + type.name().toLowerCase().replace('_', '-') + "' to use that command."); } } } @@ -201,17 +200,17 @@ public class CommandManager { } // Returns whether a player has access to a command. - private boolean hasPermission(Method method, Player player) { + private boolean hasPermission(Method method, CommandSender sender) { Command cmd = method.getAnnotation(Command.class); - if (cmd.permission().isEmpty() || hasPermission(player, cmd.permission()) || hasPermission(player, "admin")) + if (cmd.permission().isEmpty() || hasPermission(sender, cmd.permission()) || hasPermission(sender, "admin")) return true; return false; } // Returns whether a player has permission. - private boolean hasPermission(Player player, String perm) { - return player.hasPermission("citizens." + perm); + private boolean hasPermission(CommandSender sender, String perm) { + return sender.hasPermission("citizens." + perm); } /* diff --git a/src/main/java/net/citizensnpcs/command/command/NPCCommands.java b/src/main/java/net/citizensnpcs/command/command/NPCCommands.java index 863bf617f..2af2ea9b7 100644 --- a/src/main/java/net/citizensnpcs/command/command/NPCCommands.java +++ b/src/main/java/net/citizensnpcs/command/command/NPCCommands.java @@ -19,6 +19,7 @@ import net.citizensnpcs.command.ServerCommand; import net.citizensnpcs.command.exception.CommandException; import net.citizensnpcs.command.exception.NoPermissionsException; import net.citizensnpcs.npc.CitizensNPCManager; +import net.citizensnpcs.npc.NPCSelector; import net.citizensnpcs.trait.Age; import net.citizensnpcs.trait.Behaviour; import net.citizensnpcs.trait.Controllable; @@ -46,9 +47,11 @@ import com.google.common.base.Splitter; public class NPCCommands { private final CharacterManager characterManager = CitizensAPI.getCharacterManager(); private final CitizensNPCManager npcManager; + private final NPCSelector selector; public NPCCommands(Citizens plugin) { npcManager = plugin.getNPCManager(); + selector = plugin.getNPCSelector(); } @Command( @@ -204,7 +207,7 @@ public class NPCCommands { // Set age after entity spawns npc.getTrait(Age.class).setAge(age); - npcManager.selectNPC(player, npc); + selector.select(player, npc); Messaging.send(player, msg); } @@ -241,14 +244,14 @@ public class NPCCommands { npcs.add(add); } else if (args.getValueFlags().size() == 0 && sender instanceof Player) { for (NPC add : npcManager) { - if (!npcs.contains(add) && add.getTrait(Owner.class).isOwner((Player) sender)) + if (!npcs.contains(add) && add.getTrait(Owner.class).isOwnedBy(sender)) npcs.add(add); } } else { if (args.hasValueFlag("owner")) { String name = args.getFlag("owner"); for (NPC add : npcManager) { - if (!npcs.contains(add) && add.getTrait(Owner.class).getOwner().equals(name)) + if (!npcs.contains(add) && add.getTrait(Owner.class).isOwnedBy(name)) npcs.add(add); } } @@ -322,17 +325,18 @@ public class NPCCommands { min = 1, max = 2, permission = "npc.owner") - public void owner(CommandContext args, Player player, NPC npc) throws CommandException { + @ServerCommand + public void owner(CommandContext args, CommandSender sender, NPC npc) throws CommandException { if (args.argsLength() == 1) { - Messaging.send(player, StringHelper.wrap(npc.getName() + "'s Owner: ") + Messaging.send(sender, StringHelper.wrap(npc.getName() + "'s Owner: ") + npc.getTrait(Owner.class).getOwner()); return; } String name = args.getString(1); - if (npc.getTrait(Owner.class).getOwner().equals(name)) + if (npc.getTrait(Owner.class).isOwnedBy(name)) throw new CommandException("'" + name + "' is already the owner of " + npc.getName() + "."); - npc.getTrait(Owner.class).setOwner(name.equalsIgnoreCase("server") ? "server" : name); - Messaging.send(player, (name.equalsIgnoreCase("server") ? "The server" : StringHelper.wrap(name)) + npc.getTrait(Owner.class).setOwner(name); + Messaging.send(sender, (name.equalsIgnoreCase("server") ? "The server" : StringHelper.wrap(name)) + " is now the owner of " + StringHelper.wrap(npc.getName()) + "."); } @@ -344,11 +348,12 @@ public class NPCCommands { min = 1, max = 1, permission = "npc.power") + @ServerCommand @Requirements(selected = true, ownership = true, types = { EntityType.CREEPER }) - public void power(CommandContext args, Player player, NPC npc) { + public void power(CommandContext args, CommandSender sender, NPC npc) { String msg = StringHelper.wrap(npc.getName()) + " will " + (npc.getTrait(Powered.class).toggle() ? "now" : "no longer"); - Messaging.send(player, msg += " be powered."); + Messaging.send(sender, msg += " be powered."); } @Command( @@ -359,13 +364,14 @@ public class NPCCommands { min = 2, max = 2, permission = "npc.profession") + @ServerCommand @Requirements(selected = true, ownership = true, types = { EntityType.VILLAGER }) - public void profession(CommandContext args, Player player, NPC npc) throws CommandException { + public void profession(CommandContext args, CommandSender sender, NPC npc) throws CommandException { String profession = args.getString(1); try { npc.getTrait(VillagerProfession.class).setProfession(Profession.valueOf(profession.toUpperCase())); Messaging.send( - player, + sender, StringHelper.wrap(npc.getName()) + " is now the profession " + StringHelper.wrap(profession.toUpperCase()) + "."); } catch (IllegalArgumentException ex) { @@ -397,7 +403,7 @@ public class NPCCommands { Player player = (Player) sender; if (npc == null) throw new CommandException("You must have an NPC selected to execute that command."); - if (!npc.getTrait(Owner.class).isOwner(player)) + if (!npc.getTrait(Owner.class).isOwnedBy(player)) throw new CommandException("You must be the owner of this NPC to execute that command."); if (!player.hasPermission("citizens.npc.remove") && !player.hasPermission("citizens.admin")) throw new NoPermissionsException(); @@ -413,17 +419,17 @@ public class NPCCommands { min = 2, max = 2, permission = "npc.rename") - public void rename(CommandContext args, Player player, NPC npc) { + @ServerCommand + public void rename(CommandContext args, CommandSender sender, NPC npc) { String oldName = npc.getName(); String newName = args.getString(1); if (newName.length() > 16) { - Messaging.sendError(player, "NPC names cannot be longer than 16 characters. The name has been shortened."); + Messaging.sendError(sender, "NPC names cannot be longer than 16 characters. The name has been shortened."); newName = newName.substring(0, 15); } npc.setName(newName); - Messaging.send(player, - ChatColor.GREEN + "You renamed " + StringHelper.wrap(oldName) + " to " + StringHelper.wrap(newName) - + "."); + String msg = String.format("You renamed %s to %s.", StringHelper.wrap(oldName), StringHelper.wrap(newName)); + Messaging.send(sender, ChatColor.GREEN + msg); } @Command( @@ -435,14 +441,15 @@ public class NPCCommands { max = 2, permission = "npc.select") @Requirements(ownership = true) - public void select(CommandContext args, Player player, NPC npc) throws CommandException { + @ServerCommand + public void select(CommandContext args, CommandSender sender, NPC npc) throws CommandException { NPC toSelect = npcManager.getNPC(args.getInteger(1)); if (toSelect == null || !toSelect.getTrait(Spawned.class).shouldSpawn()) throw new CommandException("No NPC with the ID '" + args.getInteger(1) + "' is spawned."); if (npc != null && toSelect.getId() == npc.getId()) throw new CommandException("You already have that NPC selected."); - npcManager.selectNPC(player, toSelect); - Messaging.sendWithNPC(player, Setting.SELECTION_MESSAGE.asString(), toSelect); + selector.select(sender, toSelect); + Messaging.sendWithNPC(sender, Setting.SELECTION_MESSAGE.asString(), toSelect); } @Command( @@ -459,11 +466,11 @@ public class NPCCommands { if (respawn == null) throw new CommandException("No NPC with the ID '" + args.getInteger(1) + "' exists."); - if (!respawn.getTrait(Owner.class).isOwner(player)) + if (!respawn.getTrait(Owner.class).isOwnedBy(player)) throw new CommandException("You must be the owner of this NPC to execute that command."); if (respawn.spawn(player.getLocation())) { - npcManager.selectNPC(player, respawn); + selector.select(player, respawn); Messaging.send(player, ChatColor.GREEN + "You respawned " + StringHelper.wrap(respawn.getName()) + " at your location."); } else @@ -479,12 +486,13 @@ public class NPCCommands { min = 1, max = 1, permission = "npc.controllable") - public void controllable(CommandContext args, Player player, NPC npc) { + @ServerCommand + public void controllable(CommandContext args, CommandSender sender, NPC npc) { boolean enabled = npc.getTrait(Controllable.class).toggle(); if (enabled) { - Messaging.send(player, StringHelper.wrap(npc.getName()) + " can no longer be controlled."); + Messaging.send(sender, StringHelper.wrap(npc.getName()) + " can now be controlled."); } else { - Messaging.send(player, StringHelper.wrap(npc.getName()) + " can now be controlled."); + Messaging.send(sender, StringHelper.wrap(npc.getName()) + " can no longer be controlled."); } } @@ -505,14 +513,8 @@ public class NPCCommands { Messaging.send(player, ChatColor.GREEN + "You teleported to " + StringHelper.wrap(npc.getName()) + "."); } - @Command( - aliases = { "npc" }, - usage = "tphere", - desc = "Teleport a NPC to your location", - modifiers = { "tphere" }, - min = 1, - max = 1, - permission = "npc.tphere") + @Command(aliases = { "npc" }, usage = "tphere", desc = "Teleport a NPC to your location", modifiers = { "tphere", + "move" }, min = 1, max = 1, permission = "npc.tphere") public void tphere(CommandContext args, Player player, NPC npc) { // Spawn the NPC if it isn't spawned to prevent NPEs if (!npc.isSpawned()) diff --git a/src/main/java/net/citizensnpcs/npc/CitizensNPC.java b/src/main/java/net/citizensnpcs/npc/CitizensNPC.java index 522b81226..4ee2f0c65 100644 --- a/src/main/java/net/citizensnpcs/npc/CitizensNPC.java +++ b/src/main/java/net/citizensnpcs/npc/CitizensNPC.java @@ -16,6 +16,7 @@ import net.citizensnpcs.util.Messaging; import net.citizensnpcs.util.StringHelper; import net.minecraft.server.EntityLiving; +import org.apache.commons.lang.Validate; import org.bukkit.Bukkit; import org.bukkit.Location; import org.bukkit.entity.LivingEntity; @@ -50,7 +51,7 @@ public abstract class CitizensNPC extends AbstractNPC { @Override public boolean despawn() { if (!isSpawned()) { - Messaging.debug("The NPC with the ID '" + getId() + "' is already despawned."); + Messaging.debug(String.format("The NPC with the ID '%d' is already despawned.", getId())); return false; } @@ -103,7 +104,7 @@ public abstract class CitizensNPC extends AbstractNPC { try { character.load(root.getRelative("characters." + character.getName())); } catch (NPCLoadException e) { - Messaging.severe("Unable to load character " + character.getName() + "."); + Messaging.severe(String.format("Unable to load character '%s'.", character.getName())); e.printStackTrace(); } setCharacter(character); @@ -113,23 +114,27 @@ public abstract class CitizensNPC extends AbstractNPC { for (DataKey traitKey : root.getRelative("traits").getSubKeys()) { Trait trait = traitManager.getTrait(traitKey.name(), this); if (trait == null) { - Messaging.severe("Skipping trait with name '" + traitKey.name() + "' while loading '" + getId() - + "': trait does not exist. Was it registered properly or has the name changed?"); + Messaging.severe(String.format( + "Found missing trait '%s' while loading NPC ID: '%d' - skipped. Has the name changed?", + traitKey.name(), getId())); continue; } addTrait(trait); try { getTrait(trait.getClass()).load(traitKey); } catch (Exception ex) { - Messaging.log("[Citizens] The trait '" + traitKey.name() - + "' failed to load properly for the NPC with the ID '" + getId() + "'. " + ex.getMessage()); + Messaging.log(String.format("The trait '%s' failed to load properly for NPC ID: '%d'.", + traitKey.name(), getId()), ex.getMessage()); ex.printStackTrace(); } } // Spawn the NPC - if (getTrait(Spawned.class).shouldSpawn()) - spawn(getTrait(CurrentLocation.class).getLocation()); + if (getTrait(Spawned.class).shouldSpawn()) { + Location spawnLoc = getTrait(CurrentLocation.class).getLocation(); + if (spawnLoc != null) + spawn(spawnLoc); + } } @Override @@ -162,11 +167,11 @@ public abstract class CitizensNPC extends AbstractNPC { @Override public boolean spawn(Location loc) { + Validate.notNull(loc, "location cannot be null"); if (isSpawned()) { - Messaging.debug("The NPC with the ID '" + getId() + "' is already spawned."); + Messaging.debug("NPC (ID: " + getId() + ") is already spawned."); return false; } - NPCSpawnEvent spawnEvent = new NPCSpawnEvent(this, loc); Bukkit.getPluginManager().callEvent(spawnEvent); if (spawnEvent.isCancelled()) @@ -177,9 +182,8 @@ public abstract class CitizensNPC extends AbstractNPC { mcEntity.world.addEntity(mcEntity); mcEntity.world.players.remove(mcEntity); - // Set the location - getTrait(CurrentLocation.class).spawn(loc); // Set the spawned state + getTrait(CurrentLocation.class).setLocation(loc); getTrait(Spawned.class).setSpawned(true); // Modify NPC using traits after the entity has been created diff --git a/src/main/java/net/citizensnpcs/npc/CitizensNPCManager.java b/src/main/java/net/citizensnpcs/npc/CitizensNPCManager.java index 7c345a2ae..f6a89de63 100644 --- a/src/main/java/net/citizensnpcs/npc/CitizensNPCManager.java +++ b/src/main/java/net/citizensnpcs/npc/CitizensNPCManager.java @@ -7,13 +7,10 @@ import java.util.Iterator; import java.util.List; import java.util.Map; -import net.citizensnpcs.Citizens; -import net.citizensnpcs.api.event.NPCSelectEvent; import net.citizensnpcs.api.npc.NPC; import net.citizensnpcs.api.npc.NPCManager; import net.citizensnpcs.api.npc.character.Character; import net.citizensnpcs.api.util.Storage; -import net.citizensnpcs.editor.Editor; import net.citizensnpcs.npc.ai.NPCHandle; import net.citizensnpcs.npc.entity.CitizensBlazeNPC; import net.citizensnpcs.npc.entity.CitizensCaveSpiderNPC; @@ -43,24 +40,18 @@ import net.citizensnpcs.npc.entity.CitizensWolfNPC; import net.citizensnpcs.npc.entity.CitizensZombieNPC; import net.citizensnpcs.util.ByIdArray; -import org.bukkit.Bukkit; import org.bukkit.craftbukkit.entity.CraftEntity; import org.bukkit.entity.Entity; import org.bukkit.entity.EntityType; import org.bukkit.entity.LivingEntity; -import org.bukkit.entity.Player; -import org.bukkit.metadata.FixedMetadataValue; -import org.bukkit.metadata.MetadataValue; public class CitizensNPCManager implements NPCManager { private final ByIdArray npcs = new ByIdArray(); - private final Citizens plugin; private final Storage saves; private final Map> types = new EnumMap>( EntityType.class); - public CitizensNPCManager(Citizens plugin, Storage saves) { - this.plugin = plugin; + public CitizensNPCManager(Storage saves) { this.saves = saves; types.put(EntityType.BLAZE, CitizensBlazeNPC.class); @@ -132,6 +123,8 @@ public class CitizensNPCManager implements NPCManager { @Override public NPC getNPC(int id) { + if (id < 0) + throw new IllegalArgumentException("invalid id"); return npcs.get(id); } @@ -164,27 +157,14 @@ public class CitizensNPCManager implements NPCManager { Iterator itr = iterator(); while (itr.hasNext()) { NPC npc = itr.next(); + itr.remove(); npc.despawn(); removeData(npc); - itr.remove(); } } private void removeData(NPC npc) { saves.getKey("npc").removeKey(String.valueOf(npc.getId())); - removeMetadata(npc); - } - - private void removeMetadata(NPC npc) { - // Remove metadata from selectors - if (npc.hasMetadata("selectors")) { - for (MetadataValue value : npc.getMetadata("selectors")) { - Player search = Bukkit.getPlayerExact(value.asString()); - if (search != null) - search.removeMetadata("selected", plugin); - } - npc.removeMetadata("selectors", plugin); - } } public void safeRemove() { @@ -193,26 +173,10 @@ public class CitizensNPCManager implements NPCManager { while (itr.hasNext()) { NPC npc = itr.next(); itr.remove(); - removeMetadata(npc); npc.despawn(); } } - public void selectNPC(Player player, NPC npc) { - // Remove existing selection if any - if (player.hasMetadata("selected")) - player.removeMetadata("selected", plugin); - - player.setMetadata("selected", new FixedMetadataValue(plugin, npc.getId())); - npc.setMetadata("selectors", new FixedMetadataValue(plugin, player.getName())); - - // Remove editor if the player has one - Editor.leave(player); - - // Call selection event - player.getServer().getPluginManager().callEvent(new NPCSelectEvent(npc, player)); - } - private CitizensNPC getByType(EntityType type, int id, String name) { Class npcClass = types.get(type); if (npcClass == null) diff --git a/src/main/java/net/citizensnpcs/npc/CitizensTraitManager.java b/src/main/java/net/citizensnpcs/npc/CitizensTraitManager.java index 963695515..a4588f56c 100644 --- a/src/main/java/net/citizensnpcs/npc/CitizensTraitManager.java +++ b/src/main/java/net/citizensnpcs/npc/CitizensTraitManager.java @@ -5,7 +5,6 @@ import java.util.HashMap; import java.util.Map; import java.util.Map.Entry; -import net.citizensnpcs.Citizens; import net.citizensnpcs.api.npc.NPC; import net.citizensnpcs.api.trait.Trait; import net.citizensnpcs.api.trait.TraitFactory; @@ -34,9 +33,9 @@ public class CitizensTraitManager implements TraitManager { private final Map, Constructor> CACHED_CTORS = new HashMap, Constructor>(); private final Map>> registered = new HashMap>>(); - public CitizensTraitManager(Citizens plugin) { - // Register Citizens traits - // TODO: make it automatic without hax (annotations) + // TODO: handle Plugin-setting/names better and avoid cruft. also find a + // way to avoid naming conflicts + public CitizensTraitManager(Plugin plugin) { registerTrait(new TraitFactory(Age.class).withName("age").withPlugin(plugin)); registerTrait(new TraitFactory(CurrentLocation.class).withName("location").withPlugin(plugin)); registerTrait(new TraitFactory(Equipment.class).withName("equipment").withPlugin(plugin)); @@ -62,8 +61,8 @@ public class CitizensTraitManager implements TraitManager { if (!CACHED_CTORS.containsKey(trait)) { try { - // TODO: perhaps replace this fixed constructor with a context - // class of sorts, which can have extra environment variables. + // TODO: replace this fixed constructor with a context class + // which can have extra environment variables. constructor = trait.getConstructor(NPC.class); if (constructor == null) constructor = trait.getConstructor(CitizensNPC.class); @@ -110,18 +109,18 @@ public class CitizensTraitManager implements TraitManager { @SuppressWarnings("unchecked") @Override public T getTrait(String name) { - for (Plugin plugin : registered.keySet()) { - if (!registered.get(plugin).containsKey(name)) - return null; - return (T) create(registered.get(plugin).get(name), null); + for (Map> entry : registered.values()) { + if (!entry.containsKey(name)) + continue; + return (T) create(entry.get(name), null); } return null; } @SuppressWarnings("unchecked") public T getTrait(String name, NPC npc) { - for (Plugin plugin : registered.keySet()) { - Class clazz = registered.get(plugin).get(name); + for (Map> entry : registered.values()) { + Class clazz = entry.get(name); if (clazz == null) continue; return (T) getTrait(clazz, npc); diff --git a/src/main/java/net/citizensnpcs/npc/NPCSelector.java b/src/main/java/net/citizensnpcs/npc/NPCSelector.java new file mode 100644 index 000000000..1d353513a --- /dev/null +++ b/src/main/java/net/citizensnpcs/npc/NPCSelector.java @@ -0,0 +1,99 @@ +package net.citizensnpcs.npc; + +import java.util.List; + +import net.citizensnpcs.Settings.Setting; +import net.citizensnpcs.api.CitizensAPI; +import net.citizensnpcs.api.event.NPCRemoveEvent; +import net.citizensnpcs.api.event.NPCRightClickEvent; +import net.citizensnpcs.api.event.NPCSelectEvent; +import net.citizensnpcs.api.npc.NPC; +import net.citizensnpcs.api.trait.trait.Owner; +import net.citizensnpcs.editor.Editor; +import net.citizensnpcs.util.Messaging; +import net.citizensnpcs.util.Util; + +import org.bukkit.Bukkit; +import org.bukkit.command.CommandSender; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.Listener; +import org.bukkit.metadata.FixedMetadataValue; +import org.bukkit.metadata.MetadataValue; +import org.bukkit.plugin.Plugin; + +public class NPCSelector implements Listener { + private final Plugin plugin; + private int consoleSelectedNPC = -1; + + public NPCSelector(Plugin plugin) { + this.plugin = plugin; + Bukkit.getPluginManager().registerEvents(this, plugin); + } + + @EventHandler + public void onNPCRightClick(NPCRightClickEvent event) { + Player player = event.getClicker(); + NPC npc = event.getNPC(); + npc.getMetadata(null); + List selected = player.getMetadata("selected"); + if (selected == null || selected.size() == 0 || selected.get(0).asInt() != npc.getId()) { + if (Util.isSettingFulfilled(player, Setting.SELECTION_ITEM) + && (npc.getTrait(Owner.class).isOwnedBy(player))) { + player.removeMetadata("selected", plugin); + select(player, npc); + Messaging.sendWithNPC(player, Setting.SELECTION_MESSAGE.asString(), npc); + if (!Setting.QUICK_SELECT.asBoolean()) + return; + } + } + } + + @EventHandler + public void onNPCRemove(NPCRemoveEvent event) { + NPC npc = event.getNPC(); + for (MetadataValue value : npc.getMetadata("selectors")) { + if (value.asString().equals("console")) { + consoleSelectedNPC = -1; + } else { + Player search = Bukkit.getPlayerExact(value.asString()); + if (search != null) + search.removeMetadata("selected", plugin); + } + } + npc.removeMetadata("selectors", plugin); + } + + public void select(CommandSender sender, NPC npc) { + // Remove existing selection if any + if (sender instanceof Player) { + Player player = (Player) sender; + if (player.hasMetadata("selected")) + player.removeMetadata("selected", plugin); + + player.setMetadata("selected", new FixedMetadataValue(plugin, npc.getId())); + npc.setMetadata("selectors", new FixedMetadataValue(plugin, player.getName())); + + // Remove editor if the player has one + Editor.leave(player); + } else { + this.consoleSelectedNPC = npc.getId(); + npc.setMetadata("selectors", new FixedMetadataValue(plugin, "console")); + } + + Bukkit.getPluginManager().callEvent(new NPCSelectEvent(npc, sender)); + } + + public NPC getSelected(CommandSender sender) { + if (sender instanceof Player) { + List metadata = ((Player) sender).getMetadata("selected"); + if (metadata.size() == 0) + return null; + return CitizensAPI.getNPCManager().getNPC(metadata.get(0).asInt()); + } else { + if (consoleSelectedNPC == -1) + return null; + return CitizensAPI.getNPCManager().getNPC(consoleSelectedNPC); + } + } +} diff --git a/src/main/java/net/citizensnpcs/npc/ai/MCTargetStrategy.java b/src/main/java/net/citizensnpcs/npc/ai/MCTargetStrategy.java index f80b1daee..9f6e0860e 100644 --- a/src/main/java/net/citizensnpcs/npc/ai/MCTargetStrategy.java +++ b/src/main/java/net/citizensnpcs/npc/ai/MCTargetStrategy.java @@ -1,19 +1,14 @@ package net.citizensnpcs.npc.ai; import net.citizensnpcs.npc.CitizensNPC; +import net.citizensnpcs.util.Util; import net.minecraft.server.EntityLiving; import net.minecraft.server.EntityMonster; import net.minecraft.server.EntityPlayer; -import net.minecraft.server.Packet; import net.minecraft.server.Packet18ArmAnimation; -import org.bukkit.Bukkit; -import org.bukkit.Location; -import org.bukkit.World; import org.bukkit.craftbukkit.entity.CraftLivingEntity; -import org.bukkit.craftbukkit.entity.CraftPlayer; import org.bukkit.entity.LivingEntity; -import org.bukkit.entity.Player; public class MCTargetStrategy implements PathStrategy { private final boolean aggro; @@ -48,26 +43,13 @@ public class MCTargetStrategy implements PathStrategy { } else if (handle instanceof EntityPlayer) { EntityPlayer humanHandle = (EntityPlayer) handle; humanHandle.attack(target); - sendPacketNearby(handle.getBukkitEntity().getLocation(), new Packet18ArmAnimation(humanHandle, 1), 64); + Util.sendPacketNearby(handle.getBukkitEntity().getLocation(), new Packet18ArmAnimation(humanHandle, 1), + 64); } } return false; } - private void sendPacketNearby(Location location, Packet packet, double radius) { - radius *= radius; - final World world = location.getWorld(); - for (Player ply : Bukkit.getServer().getOnlinePlayers()) { - if (ply == null || world != ply.getWorld()) { - continue; - } - if (location.distanceSquared(ply.getLocation()) > radius) { - continue; - } - ((CraftPlayer) ply).getHandle().netServerHandler.sendPacket(packet); - } - } - private static final double ATTACK_DISTANCE = 1.75 * 1.75; } \ No newline at end of file diff --git a/src/main/java/net/citizensnpcs/npc/entity/CitizensHumanNPC.java b/src/main/java/net/citizensnpcs/npc/entity/CitizensHumanNPC.java index 34e5fea69..2ecf3adaa 100644 --- a/src/main/java/net/citizensnpcs/npc/entity/CitizensHumanNPC.java +++ b/src/main/java/net/citizensnpcs/npc/entity/CitizensHumanNPC.java @@ -1,5 +1,6 @@ package net.citizensnpcs.npc.entity; +import net.citizensnpcs.api.CitizensAPI; import net.citizensnpcs.api.trait.trait.Equipment; import net.citizensnpcs.editor.Equipable; import net.citizensnpcs.npc.CitizensNPC; @@ -10,6 +11,7 @@ import net.minecraft.server.EntityLiving; import net.minecraft.server.ItemInWorldManager; import net.minecraft.server.WorldServer; +import org.bukkit.Bukkit; import org.bukkit.Location; import org.bukkit.Material; import org.bukkit.craftbukkit.CraftWorld; @@ -23,12 +25,19 @@ public class CitizensHumanNPC extends CitizensNPC implements Equipable { } @Override - protected EntityLiving createHandle(Location loc) { + protected EntityLiving createHandle(final Location loc) { WorldServer ws = ((CraftWorld) loc.getWorld()).getHandle(); - EntityHumanNPC handle = new EntityHumanNPC(ws.getServer().getServer(), ws, + final EntityHumanNPC handle = new EntityHumanNPC(ws.getServer().getServer(), ws, StringHelper.parseColors(getFullName()), new ItemInWorldManager(ws), this); - handle.setPositionRotation(loc.getX(), loc.getY(), loc.getZ(), loc.getYaw(), loc.getPitch()); - handle.X = loc.getYaw() % 360; + Bukkit.getScheduler().scheduleSyncDelayedTask(CitizensAPI.getPlugin(), new Runnable() { + @Override + public void run() { + handle.X = loc.getYaw() % 360; + handle.getBukkitEntity().teleport(loc); + // set the position in another tick - if done immediately, + // minecraft will not update the player's position. + } + }); return handle; } @@ -124,7 +133,7 @@ public class CitizensHumanNPC extends CitizensNPC implements Equipable { public void update() { super.update(); if (isSpawned() && getBukkitEntity().getLocation().getChunk().isLoaded()) { - mcEntity.move(0, -0.1, 0); + mcEntity.move(0, -0.2, 0); // gravity! also works around an entity.onGround not updating issue // (onGround is normally updated by the client) } diff --git a/src/main/java/net/citizensnpcs/npc/entity/EntityHumanNPC.java b/src/main/java/net/citizensnpcs/npc/entity/EntityHumanNPC.java index f7a4269b8..9d45fc7af 100644 --- a/src/main/java/net/citizensnpcs/npc/entity/EntityHumanNPC.java +++ b/src/main/java/net/citizensnpcs/npc/entity/EntityHumanNPC.java @@ -44,6 +44,9 @@ public class EntityHumanNPC extends EntityPlayer implements NPCHandle { @Override public void F_() { super.F_(); + if (motX != 0 || motZ != 0 || motY != 0) { + a(0, 0); + } if (noDamageTicks > 0) --noDamageTicks; npc.update(); @@ -54,7 +57,7 @@ public class EntityHumanNPC extends EntityPlayer implements NPCHandle { getControllerLook().a(); getControllerJump().b(); if (aZ) { - if (aT() || aU()) { + if (aV() || aU()) { motY += 0.04; } else if (onGround && q == 0) { motY = 0.5; diff --git a/src/main/java/net/citizensnpcs/trait/Controllable.java b/src/main/java/net/citizensnpcs/trait/Controllable.java index c16b8d7cd..ebbd92b30 100644 --- a/src/main/java/net/citizensnpcs/trait/Controllable.java +++ b/src/main/java/net/citizensnpcs/trait/Controllable.java @@ -24,9 +24,11 @@ public class Controllable extends Trait implements Runnable, Listener, Toggleabl } private void jump() { - if (!npc.getHandle().onGround) + boolean allowed = npc.getHandle().onGround; + if (!allowed) return; npc.getHandle().motY = JUMP_VELOCITY; + // TODO: make jumping work in liquid or make liquids float the npc } @Override @@ -36,22 +38,28 @@ public class Controllable extends Trait implements Runnable, Listener, Toggleabl @EventHandler public void onPlayerInteract(PlayerInteractEvent event) { + if (!npc.isSpawned()) + return; EntityPlayer handle = ((CraftPlayer) event.getPlayer()).getHandle(); Action performed = event.getAction(); if (performed == Action.PHYSICAL || !handle.equals(npc.getHandle().passenger)) return; if (performed == Action.LEFT_CLICK_AIR || performed == Action.LEFT_CLICK_BLOCK) { jump(); - } else if (-170F >= event.getPlayer().getLocation().getPitch()) { - event.getPlayer().leaveVehicle(); } } @EventHandler public void onRightClick(NPCRightClickEvent event) { - if (!event.getNPC().equals(npc) || npc.getHandle().passenger != null) + if (!npc.isSpawned() || !event.getNPC().equals(npc)) return; EntityPlayer handle = ((CraftPlayer) event.getClicker()).getHandle(); + if (npc.getHandle().passenger != null) { + if (npc.getHandle().passenger == handle) { + event.getClicker().leaveVehicle(); + } + return; + } handle.setPassengerOf(npc.getHandle()); } @@ -59,8 +67,9 @@ public class Controllable extends Trait implements Runnable, Listener, Toggleabl public void run() { if (!npc.isSpawned() || npc.getHandle().passenger == null) return; - npc.getHandle().motX += npc.getHandle().passenger.motX; - npc.getHandle().motZ += npc.getHandle().passenger.motZ; + boolean onGround = npc.getHandle().onGround; + npc.getHandle().motX += npc.getHandle().passenger.motX * (onGround ? GROUND_SPEED : AIR_SPEED); + npc.getHandle().motZ += npc.getHandle().passenger.motZ * (onGround ? GROUND_SPEED : AIR_SPEED); } @Override @@ -68,6 +77,8 @@ public class Controllable extends Trait implements Runnable, Listener, Toggleabl key.setBoolean("enabled", enabled); } + private static final double GROUND_SPEED = 4; + private static final double AIR_SPEED = 1.5; private static final double JUMP_VELOCITY = 0.6; @Override diff --git a/src/main/java/net/citizensnpcs/trait/CurrentLocation.java b/src/main/java/net/citizensnpcs/trait/CurrentLocation.java index 4e1d0c77e..c4a4eb91a 100644 --- a/src/main/java/net/citizensnpcs/trait/CurrentLocation.java +++ b/src/main/java/net/citizensnpcs/trait/CurrentLocation.java @@ -20,6 +20,10 @@ public class CurrentLocation extends Trait implements Runnable { return loc; } + public void setLocation(Location loc) { + this.loc = loc; + } + @Override public void load(DataKey key) throws NPCLoadException { if (Bukkit.getWorld(key.getString("world")) == null) @@ -31,7 +35,7 @@ public class CurrentLocation extends Trait implements Runnable { @Override public void run() { - if (npc.getBukkitEntity() == null) + if (!npc.isSpawned()) return; loc = npc.getBukkitEntity().getLocation(); @@ -39,6 +43,11 @@ public class CurrentLocation extends Trait implements Runnable { @Override public void save(DataKey key) { + if (loc == null) { + key.removeKey(getName()); + return; + } + key.setString("world", loc.getWorld().getName()); key.setDouble("x", loc.getX()); key.setDouble("y", loc.getY()); @@ -47,10 +56,6 @@ public class CurrentLocation extends Trait implements Runnable { key.setDouble("pitch", loc.getPitch()); } - public void spawn(Location loc) { - this.loc = loc; - } - @Override public String toString() { return "CurrentLocation{" + loc + "}"; diff --git a/src/main/java/net/citizensnpcs/util/Util.java b/src/main/java/net/citizensnpcs/util/Util.java new file mode 100644 index 000000000..31a3c707b --- /dev/null +++ b/src/main/java/net/citizensnpcs/util/Util.java @@ -0,0 +1,57 @@ +package net.citizensnpcs.util; + +import net.citizensnpcs.Settings.Setting; +import net.minecraft.server.Packet; + +import org.apache.commons.lang.Validate; +import org.bukkit.Bukkit; +import org.bukkit.Location; +import org.bukkit.Material; +import org.bukkit.World; +import org.bukkit.craftbukkit.entity.CraftPlayer; +import org.bukkit.entity.Player; + +import com.google.common.base.Splitter; + +public class Util { + // Static class for small (emphasis small) utility methods + private Util() { + } + + public static boolean isSettingFulfilled(Player player, Setting setting) { + String parts = setting.asString(); + if (parts.contains("*")) + return true; + for (String part : Splitter.on(',').split(parts)) { + if (Material.matchMaterial(part) == player.getItemInHand().getType()) { + return true; + } + } + return false; + } + + public static void sendToOnline(Packet... packets) { + Validate.notNull(packets, "packets cannot be null"); + for (Player player : Bukkit.getOnlinePlayers()) { + if (player == null || !player.isOnline()) + continue; + for (Packet packet : packets) { + ((CraftPlayer) player).getHandle().netServerHandler.sendPacket(packet); + } + } + } + + public static void sendPacketNearby(Location location, Packet packet, double radius) { + radius *= radius; + final World world = location.getWorld(); + for (Player ply : Bukkit.getServer().getOnlinePlayers()) { + if (ply == null || world != ply.getWorld()) { + continue; + } + if (location.distanceSquared(ply.getLocation()) > radius) { + continue; + } + ((CraftPlayer) ply).getHandle().netServerHandler.sendPacket(packet); + } + } +}