package net.citizensnpcs.command.command; import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; import java.util.List; import net.citizensnpcs.Citizens; import net.citizensnpcs.Settings.Setting; import net.citizensnpcs.api.CitizensAPI; import net.citizensnpcs.api.ai.speech.SpeechContext; import net.citizensnpcs.api.event.PlayerCreateNPCEvent; import net.citizensnpcs.api.npc.NPC; import net.citizensnpcs.api.npc.NPCRegistry; import net.citizensnpcs.api.trait.Trait; import net.citizensnpcs.api.trait.trait.MobType; import net.citizensnpcs.api.trait.trait.Owner; import net.citizensnpcs.api.trait.trait.Spawned; import net.citizensnpcs.api.trait.trait.Speech; import net.citizensnpcs.api.util.DataKey; import net.citizensnpcs.api.util.MemoryDataKey; import net.citizensnpcs.command.Command; import net.citizensnpcs.command.CommandContext; import net.citizensnpcs.command.Requirements; import net.citizensnpcs.command.exception.CommandException; import net.citizensnpcs.command.exception.NoPermissionsException; import net.citizensnpcs.command.exception.ServerCommandException; import net.citizensnpcs.npc.CitizensNPC; import net.citizensnpcs.npc.EntityControllers; import net.citizensnpcs.npc.NPCSelector; import net.citizensnpcs.npc.Template; import net.citizensnpcs.npc.ai.speech.TalkableEntity; import net.citizensnpcs.trait.Age; import net.citizensnpcs.trait.Anchors; import net.citizensnpcs.trait.Behaviour; import net.citizensnpcs.trait.Controllable; import net.citizensnpcs.trait.CurrentLocation; import net.citizensnpcs.trait.Gravity; import net.citizensnpcs.trait.LookClose; import net.citizensnpcs.trait.NPCSkeletonType; import net.citizensnpcs.trait.Poses; import net.citizensnpcs.trait.Powered; import net.citizensnpcs.trait.SlimeSize; import net.citizensnpcs.trait.VillagerProfession; import net.citizensnpcs.trait.ZombieModifier; import net.citizensnpcs.util.Anchor; import net.citizensnpcs.util.Messages; import net.citizensnpcs.util.Messaging; import net.citizensnpcs.util.NMS; import net.citizensnpcs.util.Paginator; import net.citizensnpcs.util.StringHelper; import net.citizensnpcs.util.Util; import org.bukkit.Bukkit; import org.bukkit.GameMode; import org.bukkit.Location; import org.bukkit.World; import org.bukkit.command.BlockCommandSender; import org.bukkit.command.CommandSender; import org.bukkit.command.ConsoleCommandSender; import org.bukkit.entity.Ageable; import org.bukkit.entity.Entity; import org.bukkit.entity.EntityType; import org.bukkit.entity.LivingEntity; import org.bukkit.entity.Player; import org.bukkit.entity.Skeleton.SkeletonType; import org.bukkit.entity.Villager.Profession; import org.bukkit.event.player.PlayerTeleportEvent.TeleportCause; import com.google.common.base.Splitter; import com.google.common.collect.Iterables; import com.google.common.collect.Lists; @Requirements(selected = true, ownership = true) public class NPCCommands { private final NPCRegistry npcRegistry; private final NPCSelector selector; public NPCCommands(Citizens plugin) { npcRegistry = CitizensAPI.getNPCRegistry(); selector = plugin.getNPCSelector(); } @Command( aliases = { "npc" }, usage = "age [age] (-l)", desc = "Set the age of a NPC", help = Messages.COMMAND_AGE_HELP, flags = "l", modifiers = { "age" }, min = 1, max = 2, permission = "npc.age") @Requirements(selected = true, ownership = true, types = { EntityType.CHICKEN, EntityType.COW, EntityType.OCELOT, EntityType.PIG, EntityType.SHEEP, EntityType.VILLAGER, EntityType.WOLF }) public void age(CommandContext args, CommandSender sender, NPC npc) throws CommandException { Age trait = npc.getTrait(Age.class); boolean toggleLock = args.hasFlag('l'); if (toggleLock) { Messaging.sendTr(sender, trait.toggle() ? Messages.AGE_LOCKED : Messages.AGE_UNLOCKED); } if (args.argsLength() <= 1) { if (!toggleLock) trait.describe(sender); return; } int age = 0; try { age = args.getInteger(1); if (age < -24000 || age > 0) throw new CommandException(Messages.INVALID_AGE); Messaging.sendTr(sender, Messages.AGE_SET_NORMAL, npc.getName(), age); } catch (NumberFormatException ex) { if (args.getString(1).equalsIgnoreCase("baby")) { age = -24000; Messaging.sendTr(sender, Messages.AGE_SET_BABY, npc.getName()); } else if (args.getString(1).equalsIgnoreCase("adult")) { age = 0; Messaging.sendTr(sender, Messages.AGE_SET_ADULT, npc.getName()); } else throw new CommandException(Messages.INVALID_AGE); } trait.setAge(age); } @Command( aliases = { "npc" }, usage = "anchor (--save [name]|--assume [name]|--remove [name]) (-a)", desc = "Changes/Saves/Lists NPC's location anchor(s)", flags = "a", modifiers = { "anchor" }, min = 1, max = 2, permission = "npc.anchor") @Requirements(selected = true, ownership = true, types = EntityType.PLAYER) public void anchor(CommandContext args, CommandSender sender, NPC npc) throws CommandException { Anchors trait = npc.getTrait(Anchors.class); if (args.hasValueFlag("save")) { if (args.getFlag("save").isEmpty()) throw new CommandException(Messages.INVALID_ANCHOR_NAME); if (args.getSenderLocation() == null) throw new ServerCommandException(); if (trait.addAnchor(args.getFlag("save"), args.getSenderLocation())) { Messaging.sendTr(sender, Messages.ANCHOR_ADDED); } else throw new CommandException(Messages.ANCHOR_ALREADY_EXISTS, args.getFlag("save")); } else if (args.hasValueFlag("assume")) { if (args.getFlag("assume").isEmpty()) throw new CommandException(Messages.INVALID_ANCHOR_NAME); Anchor anchor = trait.getAnchor(args.getFlag("assume")); if (anchor == null) throw new CommandException(Messages.ANCHOR_MISSING, args.getFlag("assume")); npc.getBukkitEntity().teleport(anchor.getLocation()); } else if (args.hasValueFlag("remove")) { if (args.getFlag("remove").isEmpty()) throw new CommandException(Messages.INVALID_ANCHOR_NAME); if (trait.removeAnchor(trait.getAnchor(args.getFlag("remove")))) Messaging.sendTr(sender, Messages.ANCHOR_REMOVED); else throw new CommandException(Messages.ANCHOR_MISSING, args.getFlag("remove")); } else if (!args.hasFlag('a')) { Paginator paginator = new Paginator().header("Anchors"); paginator.addLine("Key: ID Name World Location (X,Y,Z)"); for (int i = 0; i < trait.getAnchors().size(); i++) { String line = "" + i + " " + trait.getAnchors().get(i).getName() + " " + trait.getAnchors().get(i).getLocation().getWorld().getName() + " " + trait.getAnchors().get(i).getLocation().getBlockX() + ", " + trait.getAnchors().get(i).getLocation().getBlockY() + ", " + trait.getAnchors().get(i).getLocation().getBlockZ(); paginator.addLine(line); } int page = args.getInteger(1, 1); if (!paginator.sendPage(sender, page)) throw new CommandException(Messages.COMMAND_PAGE_MISSING); } // Assume Player's position if (!args.hasFlag('a')) return; if (sender instanceof ConsoleCommandSender) throw new ServerCommandException(); npc.getBukkitEntity().teleport(args.getSenderLocation()); } @Command( aliases = { "npc" }, usage = "behaviour [scripts] (-r)", desc = "Sets the behaviour of a NPC", help = Messages.BEHAVIOUR_HELP, modifiers = { "behaviour", "behavior", "ai" }, flags = "r", min = 2, permission = "npc.behaviour") public void behaviour(CommandContext args, CommandSender sender, NPC npc) throws CommandException { Iterable files = Splitter.on(',').split(args.getJoinedStrings(1, ',')); if (args.hasFlag('r')) { npc.getTrait(Behaviour.class).removeScripts(files); Messaging.sendTr(sender, Messages.BEHAVIOURS_REMOVED); } else { npc.getTrait(Behaviour.class).addScripts(files); Messaging.sendTr(sender, Messages.BEHAVIOURS_ADDED); } } @Command( aliases = { "npc" }, usage = "controllable|control -f", desc = "Toggles whether the NPC can be ridden and controlled", modifiers = { "controllable", "control" }, min = 1, max = 1, flags = "f") public void controllable(CommandContext args, CommandSender sender, NPC npc) throws CommandException { if ((npc.isSpawned() && !sender.hasPermission("citizens.npc.controllable." + npc.getBukkitEntity().getType().toString().toLowerCase())) || !sender.hasPermission("citizens.npc.controllable")) throw new NoPermissionsException(); Controllable trait = npc.getTrait(Controllable.class); boolean enabled = trait.toggle(); String key = enabled ? Messages.CONTROLLABLE_SET : Messages.CONTROLLABLE_REMOVED; Messaging.sendTr(sender, key, npc.getName()); } @Command( aliases = { "npc" }, usage = "copy (--name newname)", desc = "Copies an NPC", modifiers = { "copy" }, min = 1, max = 1, permission = "npc.copy") public void copy(CommandContext args, CommandSender sender, NPC npc) { EntityType type = npc.getTrait(MobType.class).getType(); String name = args.getFlag("name", npc.getFullName()); CitizensNPC copy = (CitizensNPC) npcRegistry.createNPC(type, name); CitizensNPC from = (CitizensNPC) npc; DataKey key = new MemoryDataKey(); from.save(key); copy.load(key); if (copy.isSpawned() && args.getSenderLocation() != null) { Location location = args.getSenderLocation(); copy.getBukkitEntity().teleport(location); copy.getTrait(CurrentLocation.class).setLocation(location); } for (Trait trait : copy.getTraits()) trait.onCopy(); Messaging.sendTr(sender, Messages.NPC_COPIED, npc.getName()); selector.select(sender, copy); } @Command( aliases = { "npc" }, usage = "create [name] ((-b,u) --at (x:y:z:world) --type (type) --trait ('trait1, trait2...') --b (behaviours))", desc = "Create a new NPC", flags = "bu", modifiers = { "create" }, min = 2, permission = "npc.create") @Requirements public void create(CommandContext args, CommandSender sender, NPC npc) throws CommandException { String name = StringHelper.parseColors(args.getJoinedStrings(1)); if (name.length() > 16) { Messaging.sendErrorTr(sender, Messages.NPC_NAME_TOO_LONG); name = name.substring(0, 15); } if (name.length() <= 0) throw new CommandException(); EntityType type = EntityType.PLAYER; if (args.hasValueFlag("type")) { String inputType = args.getFlag("type"); type = Util.matchEntityType(inputType); if (type == null) { Messaging.sendErrorTr(sender, Messages.NPC_CREATE_INVALID_MOBTYPE, inputType); type = EntityType.PLAYER; } else if (!LivingEntity.class.isAssignableFrom(type.getEntityClass())) { Messaging.sendErrorTr(sender, Messages.NOT_LIVING_MOBTYPE, type); type = EntityType.PLAYER; } } npc = npcRegistry.createNPC(type, name); String msg = "You created [[" + npc.getName() + "]]"; int age = 0; if (args.hasFlag('b')) { if (!Ageable.class.isAssignableFrom(type.getEntityClass())) Messaging.sendErrorTr(sender, Messages.MOBTYPE_CANNOT_BE_AGED, type.name().toLowerCase() .replace("_", "-")); else { age = -24000; msg += " as a baby"; } } if (args.hasValueFlag("b") || args.hasValueFlag("behaviours") || args.hasValueFlag("behaviors")) { String behaviours = args.getFlag("b", args.getFlag("behaviours", args.getFlag("behaviors"))); npc.getTrait(Behaviour.class).addScripts(Splitter.on(',').split(behaviours)); msg += " with the specified behaviours"; } msg += "."; // Initialize necessary traits if (!Setting.SERVER_OWNS_NPCS.asBoolean()) npc.getTrait(Owner.class).setOwner(sender.getName()); npc.getTrait(MobType.class).setType(type); Location spawnLoc = null; if (sender instanceof Player) { spawnLoc = args.getSenderLocation(); PlayerCreateNPCEvent event = new PlayerCreateNPCEvent((Player) sender, npc); Bukkit.getPluginManager().callEvent(event); if (event.isCancelled()) { npc.destroy(); String reason = "Couldn't create NPC."; if (!event.getCancelReason().isEmpty()) reason += " Reason: " + event.getCancelReason(); throw new CommandException(reason); } } else if (sender instanceof BlockCommandSender) { spawnLoc = args.getSenderLocation(); } if (args.hasValueFlag("at")) { String[] parts = Iterables.toArray(Splitter.on(':').split(args.getFlag("at")), String.class); if (parts.length > 0) { String worldName = args.getSenderLocation() != null ? args.getSenderLocation().getWorld() .getName() : ""; int x = 0, y = 0, z = 0; float yaw = 0F, pitch = 0F; switch (parts.length) { case 6: pitch = Float.parseFloat(parts[5]); case 5: yaw = Float.parseFloat(parts[4]); case 4: worldName = parts[3]; case 3: x = Integer.parseInt(parts[0]); y = Integer.parseInt(parts[1]); z = Integer.parseInt(parts[2]); break; default: throw new CommandException(Messages.INVALID_SPAWN_LOCATION); } World world = Bukkit.getWorld(worldName); if (world == null) throw new CommandException(Messages.INVALID_SPAWN_LOCATION); spawnLoc = new Location(world, x, y, z, yaw, pitch); } else { Player search = Bukkit.getPlayerExact(args.getFlag("at")); if (search == null) throw new CommandException(Messages.PLAYER_NOT_FOUND_FOR_SPAWN); spawnLoc = search.getLocation(); } } if (spawnLoc == null) { npc.destroy(); throw new CommandException(Messages.INVALID_SPAWN_LOCATION); } if (!args.hasFlag('u')) npc.spawn(spawnLoc); if (args.hasValueFlag("trait")) { Iterable parts = Splitter.on(',').trimResults().split(args.getFlag("trait")); StringBuilder builder = new StringBuilder(); for (String tr : parts) { Trait trait = CitizensAPI.getTraitFactory().getTrait(tr); if (trait == null) continue; npc.addTrait(trait); builder.append(StringHelper.wrap(tr) + ", "); } if (builder.length() > 0) builder.delete(builder.length() - 2, builder.length()); msg += " with traits " + builder.toString(); } if (args.hasValueFlag("template")) { Iterable parts = Splitter.on(',').trimResults().split(args.getFlag("template")); StringBuilder builder = new StringBuilder(); for (String part : parts) { Template template = Template.byName(part); if (template == null) continue; template.apply(npc); builder.append(StringHelper.wrap(part) + ", "); } if (builder.length() > 0) builder.delete(builder.length() - 2, builder.length()); msg += " with templates " + builder.toString(); } // Set age after entity spawns if (npc.getBukkitEntity() instanceof Ageable) npc.getTrait(Age.class).setAge(age); selector.select(sender, npc); Messaging.send(sender, msg); } @Command( aliases = { "npc" }, usage = "despawn (id)", desc = "Despawn a NPC", modifiers = { "despawn" }, min = 1, max = 2, permission = "npc.despawn") @Requirements public void despawn(CommandContext args, CommandSender sender, NPC npc) throws CommandException { if (npc == null || args.argsLength() == 2) { if (args.argsLength() < 2) throw new CommandException(Messages.COMMAND_MUST_HAVE_SELECTED); int id = args.getInteger(1); npc = CitizensAPI.getNPCRegistry().getById(id); if (npc == null) throw new CommandException(Messages.NO_NPC_WITH_ID_FOUND, id); } npc.getTrait(Spawned.class).setSpawned(false); npc.despawn(); Messaging.sendTr(sender, Messages.NPC_DESPAWNED, npc.getName()); } @Command( aliases = { "npc" }, usage = "gamemode [gamemode]", desc = "Changes the gamemode", modifiers = { "gravity" }, min = 1, max = 2, permission = "npc.gravity") @Requirements(selected = true, ownership = true, types = { EntityType.PLAYER }) public void gamemode(CommandContext args, CommandSender sender, NPC npc) { Player player = (Player) npc.getBukkitEntity(); if (args.argsLength() == 1) { Messaging.sendTr(sender, Messages.GAMEMODE_DESCRIBE, npc.getName(), player.getGameMode().name() .toLowerCase()); return; } GameMode mode = null; try { int value = args.getInteger(1); mode = GameMode.getByValue(value); } catch (NumberFormatException ex) { try { mode = GameMode.valueOf(args.getString(1)); } catch (IllegalArgumentException e) { } } if (mode == null) { Messaging.sendErrorTr(sender, Messages.GAMEMODE_INVALID, args.getString(1)); return; } player.setGameMode(mode); Messaging.sendTr(sender, Messages.GAMEMODE_SET, mode.name().toLowerCase()); } @Command( aliases = { "npc" }, usage = "gravity", desc = "Toggles gravity", modifiers = { "gravity" }, min = 1, max = 1, permission = "npc.gravity") public void gravity(CommandContext args, CommandSender sender, NPC npc) { boolean enabled = npc.getTrait(Gravity.class).toggle(); String key = enabled ? Messages.GRAVITY_ENABLED : Messages.GRAVITY_DISABLED; Messaging.sendTr(sender, key, npc.getName()); } @Command( aliases = { "npc" }, usage = "list (page) ((-a) --owner (owner) --type (type) --char (char))", desc = "List NPCs", flags = "a", modifiers = { "list" }, min = 1, max = 2, permission = "npc.list") @Requirements public void list(CommandContext args, CommandSender sender, NPC npc) throws CommandException { List npcs = new ArrayList(); if (args.hasFlag('a')) { for (NPC add : npcRegistry) npcs.add(add); } else if (args.getValueFlags().size() == 0 && sender instanceof Player) { for (NPC add : npcRegistry) { 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 : npcRegistry) { if (!npcs.contains(add) && add.getTrait(Owner.class).isOwnedBy(name)) npcs.add(add); } } if (args.hasValueFlag("type")) { EntityType type = Util.matchEntityType(args.getFlag("type")); if (type == null) throw new CommandException(Messages.COMMAND_INVALID_MOBTYPE, type); for (NPC add : npcRegistry) { if (!npcs.contains(add) && add.getTrait(MobType.class).getType() == type) npcs.add(add); } } } Paginator paginator = new Paginator().header("NPCs"); paginator.addLine("Key: ID Name"); for (int i = 0; i < npcs.size(); i += 2) { String line = "" + npcs.get(i).getId() + " " + npcs.get(i).getName(); if (npcs.size() >= i + 2) line += " " + "" + npcs.get(i + 1).getId() + " " + npcs.get(i + 1).getName(); paginator.addLine(line); } int page = args.getInteger(1, 1); if (!paginator.sendPage(sender, page)) throw new CommandException(Messages.COMMAND_PAGE_MISSING); } @Command( aliases = { "npc" }, usage = "lookclose", desc = "Toggle whether a NPC will look when a player is near", modifiers = { "lookclose", "look", "rotate" }, min = 1, max = 1, permission = "npc.lookclose") public void lookClose(CommandContext args, CommandSender sender, NPC npc) { Messaging.sendTr(sender, npc.getTrait(LookClose.class).toggle() ? Messages.LOOKCLOSE_SET : Messages.LOOKCLOSE_STOPPED, npc.getName()); } @Command( aliases = { "npc" }, usage = "mount", desc = "Mounts a controllable NPC", modifiers = { "mount" }, min = 1, max = 1, permission = "npc.controllable") public void mount(CommandContext args, Player player, NPC npc) { boolean enabled = npc.hasTrait(Controllable.class) && npc.getTrait(Controllable.class).isEnabled(); if (!enabled) { Messaging.sendTr(player, Messages.NPC_NOT_CONTROLLABLE, npc.getName()); return; } boolean success = npc.getTrait(Controllable.class).mount(player); if (!success) Messaging.sendTr(player, Messages.FAILED_TO_MOUNT_NPC, npc.getName()); } @Command( aliases = { "npc" }, usage = "moveto x:y:z:world | x y z world", desc = "Teleports a NPC to a given location", modifiers = "moveto", min = 1, permission = "npc.moveto") public void moveto(CommandContext args, CommandSender sender, NPC npc) throws CommandException { // Spawn the NPC if it isn't spawned to prevent NPEs if (!npc.isSpawned()) npc.spawn(npc.getTrait(CurrentLocation.class).getLocation()); Location current = npc.getBukkitEntity().getLocation(); Location to; if (args.argsLength() > 1) { String[] parts = Iterables.toArray(Splitter.on(':').split(args.getJoinedStrings(1, ':')), String.class); if (parts.length != 4 && parts.length != 3) throw new CommandException(Messages.MOVETO_FORMAT); double x = Double.parseDouble(parts[0]); double y = Double.parseDouble(parts[1]); double z = Double.parseDouble(parts[2]); World world = parts.length == 4 ? Bukkit.getWorld(parts[3]) : current.getWorld(); if (world == null) throw new CommandException(Messages.WORLD_NOT_FOUND); to = new Location(world, x, y, z, current.getYaw(), current.getPitch()); } else { to = current.clone(); if (args.hasValueFlag("x")) to.setX(args.getFlagDouble("x")); if (args.hasValueFlag("y")) to.setY(args.getFlagDouble("y")); if (args.hasValueFlag("z")) to.setZ(args.getFlagDouble("z")); if (args.hasValueFlag("yaw")) to.setYaw((float) args.getFlagDouble("yaw")); if (args.hasValueFlag("pitch")) to.setPitch((float) args.getFlagDouble("pitch")); if (args.hasValueFlag("world")) { World world = Bukkit.getWorld(args.getFlag("world")); if (world == null) throw new CommandException(Messages.WORLD_NOT_FOUND); to.setWorld(world); } } npc.getBukkitEntity().teleport(to, TeleportCause.COMMAND); Messaging.sendTr(sender, Messages.MOVETO_TELEPORTED, npc.getName(), to); } @Command(aliases = { "npc" }, desc = "Show basic NPC information", max = 0, permission = "npc.info") public void npc(CommandContext args, CommandSender sender, NPC npc) { Messaging.send(sender, StringHelper.wrapHeader(npc.getName())); Messaging.send(sender, " ID: " + npc.getId()); Messaging.send(sender, " Type: " + npc.getTrait(MobType.class).getType()); if (npc.isSpawned()) { Location loc = npc.getBukkitEntity().getLocation(); String format = " Spawned at %d, %d, %d in world %s"; Messaging.send(sender, String.format(format, loc.getBlockX(), loc.getBlockY(), loc.getBlockZ(), loc.getWorld().getName())); } Messaging.send(sender, " Traits"); for (Trait trait : npc.getTraits()) { if (CitizensAPI.getTraitFactory().isInternalTrait(trait)) continue; String message = " - " + trait.getName(); Messaging.send(sender, message); } } @Command( aliases = { "npc" }, usage = "owner [name]", desc = "Set the owner of an NPC", modifiers = { "owner" }, min = 1, max = 2, permission = "npc.owner") public void owner(CommandContext args, CommandSender sender, NPC npc) throws CommandException { Owner ownerTrait = npc.getTrait(Owner.class); if (args.argsLength() == 1) { Messaging.sendTr(sender, Messages.NPC_OWNER, npc.getName(), ownerTrait.getOwner()); return; } String name = args.getString(1); if (ownerTrait.isOwnedBy(name)) throw new CommandException(Messages.ALREADY_OWNER, name, npc.getName()); ownerTrait.setOwner(name); boolean serverOwner = name.equalsIgnoreCase(Owner.SERVER); Messaging.sendTr(sender, serverOwner ? Messages.OWNER_SET_SERVER : Messages.OWNER_SET, npc.getName(), name); } @Command( aliases = { "npc" }, usage = "pathrange [range]", desc = "Sets an NPC's pathfinding range", modifiers = { "pathrange", "pathfindingrange", "prange" }, min = 2, max = 2, permission = "npc.pathfindingrange") public void pathfindingRange(CommandContext args, CommandSender sender, NPC npc) { double range = Math.max(1, args.getDouble(1)); npc.getNavigator().getDefaultParameters().range((float) range); Messaging.sendTr(sender, Messages.PATHFINDING_RANGE_SET, range); } @Command( aliases = { "npc" }, usage = "playerlist (-a,r)", desc = "Sets whether the NPC is put in the playerlist", modifiers = { "playerlist" }, min = 1, max = 1, flags = "ar", permission = "npc.playerlist") @Requirements(types = EntityType.PLAYER) public void playerlist(CommandContext args, CommandSender sender, NPC npc) { boolean remove = !npc.data().get("removefromplayerlist", Setting.REMOVE_PLAYERS_FROM_PLAYER_LIST.asBoolean()); if (args.hasFlag('a')) remove = false; else if (args.hasFlag('r')) remove = true; npc.data().setPersistent("removefromplayerlist", remove); if (npc.isSpawned()) NMS.addOrRemoveFromPlayerList(npc.getBukkitEntity(), remove); Messaging.sendTr(sender, remove ? Messages.REMOVED_FROM_PLAYERLIST : Messages.ADDED_TO_PLAYERLIST, npc.getName()); } @Command( aliases = { "npc" }, usage = "pose (--save [name]|--assume [name]|--remove [name]) (-a)", desc = "Changes/Saves/Lists NPC's head pose(s)", flags = "a", modifiers = { "pose" }, min = 1, max = 2, permission = "npc.pose") @Requirements(selected = true, ownership = true, types = EntityType.PLAYER) public void pose(CommandContext args, CommandSender sender, NPC npc) throws CommandException { Poses trait = npc.getTrait(Poses.class); if (args.hasValueFlag("save")) { if (args.getFlag("save").isEmpty()) throw new CommandException(Messages.INVALID_POSE_NAME); if (args.getSenderLocation() == null) throw new ServerCommandException(); if (trait.addPose(args.getFlag("save"), args.getSenderLocation())) { Messaging.sendTr(sender, Messages.POSE_ADDED); } else throw new CommandException(Messages.POSE_ALREADY_EXISTS, args.getFlag("assume")); } else if (args.hasValueFlag("assume")) { String pose = args.getFlag("assume"); if (pose.isEmpty()) throw new CommandException(Messages.INVALID_POSE_NAME); if (!trait.hasPose(pose)) throw new CommandException(Messages.POSE_MISSING, pose); trait.assumePose(pose); } else if (args.hasValueFlag("remove")) { if (args.getFlag("remove").isEmpty()) throw new CommandException(Messages.INVALID_POSE_NAME); if (trait.removePose(args.getFlag("remove"))) { Messaging.sendTr(sender, Messages.POSE_REMOVED); } else throw new CommandException(Messages.POSE_MISSING, args.getFlag("remove")); } else if (!args.hasFlag('a')) { trait.describe(sender, args.getInteger(1, 1)); } // Assume Player's pose if (!args.hasFlag('a')) return; if (args.getSenderLocation() == null) throw new ServerCommandException(); Location location = args.getSenderLocation(); trait.assumePose(location); } @Command( aliases = { "npc" }, usage = "power", desc = "Toggle a creeper NPC as powered", modifiers = { "power" }, min = 1, max = 1, permission = "npc.power") @Requirements(selected = true, ownership = true, types = { EntityType.CREEPER }) public void power(CommandContext args, CommandSender sender, NPC npc) { Messaging.sendTr(sender, npc.getTrait(Powered.class).toggle() ? Messages.POWERED_SET : Messages.POWERED_STOPPED); } @Command( aliases = { "npc" }, usage = "profession|prof [profession]", desc = "Set a NPC's profession", modifiers = { "profession", "prof" }, min = 2, max = 2, permission = "npc.profession") @Requirements(selected = true, ownership = true, types = { EntityType.VILLAGER }) public void profession(CommandContext args, CommandSender sender, NPC npc) throws CommandException { String profession = args.getString(1); Profession parsed; try { parsed = Profession.valueOf(profession.toUpperCase()); } catch (IllegalArgumentException ex) { throw new CommandException(Messages.INVALID_PROFESSION); } npc.getTrait(VillagerProfession.class).setProfession(parsed); Messaging.sendTr(sender, Messages.PROFESSION_SET, npc.getName(), profession); } @Command(aliases = { "npc" }, usage = "remove|rem (all)", desc = "Remove a NPC", modifiers = { "remove", "rem" }, min = 1, max = 2) @Requirements public void remove(CommandContext args, CommandSender sender, NPC npc) throws CommandException { if (args.argsLength() == 2) { if (!args.getString(1).equalsIgnoreCase("all")) throw new CommandException(Messages.REMOVE_INCORRECT_SYNTAX); if (!sender.hasPermission("citizens.admin.remove.all") && !sender.hasPermission("citizens.admin")) throw new NoPermissionsException(); npcRegistry.deregisterAll(); Messaging.sendTr(sender, Messages.REMOVED_ALL_NPCS); return; } if (!(sender instanceof Player)) throw new CommandException(Messages.COMMAND_MUST_BE_INGAME); Player player = (Player) sender; if (npc == null) throw new CommandException(Messages.COMMAND_MUST_HAVE_SELECTED); if (!npc.getTrait(Owner.class).isOwnedBy(player)) throw new CommandException(Messages.COMMAND_MUST_BE_OWNER); if (!player.hasPermission("citizens.npc.remove") && !player.hasPermission("citizens.admin")) throw new NoPermissionsException(); npc.destroy(); Messaging.sendTr(player, Messages.NPC_REMOVED, npc.getName()); } @Command( aliases = { "npc" }, usage = "rename [name]", desc = "Rename a NPC", modifiers = { "rename" }, min = 2, permission = "npc.rename") public void rename(CommandContext args, CommandSender sender, NPC npc) { String oldName = npc.getName(); String newName = args.getJoinedStrings(1); if (newName.length() > 16) { Messaging.sendErrorTr(sender, Messages.NPC_NAME_TOO_LONG); newName = newName.substring(0, 15); } Location prev = npc.isSpawned() ? npc.getBukkitEntity().getLocation() : null; npc.despawn(); npc.setName(newName); if (prev != null) npc.spawn(prev); Messaging.sendTr(sender, Messages.NPC_RENAMED, oldName, newName); } @Command( aliases = { "npc" }, usage = "select|sel [id|name] (--r range)", desc = "Select a NPC with the given ID or name", modifiers = { "select", "sel" }, min = 1, max = 2, permission = "npc.select") @Requirements public void select(CommandContext args, CommandSender sender, NPC npc) throws CommandException { NPC toSelect = null; if (args.argsLength() <= 1) { if (!(sender instanceof Player)) throw new ServerCommandException(); double range = Math.abs(args.getFlagDouble("r", 10)); Entity player = (Player) sender; final Location location = args.getSenderLocation(); List search = player.getNearbyEntities(range, range, range); Collections.sort(search, new Comparator() { @Override public int compare(Entity o1, Entity o2) { double d = o1.getLocation().distanceSquared(location) - o2.getLocation().distanceSquared(location); return d > 0 ? 1 : d < 0 ? -1 : 0; } }); for (Entity possibleNPC : search) { NPC test = npcRegistry.getNPC(possibleNPC); if (test == null) continue; toSelect = test; break; } } else { try { int id = args.getInteger(1); toSelect = npcRegistry.getById(id); } catch (NumberFormatException ex) { String name = args.getString(1); List possible = Lists.newArrayList(); for (NPC test : npcRegistry) { if (test.getName().equalsIgnoreCase(name)) possible.add(test); } if (possible.size() == 1) toSelect = possible.get(0); else if (possible.size() > 1) { SelectionPrompt.start(selector, (Player) sender, possible); return; } } } if (toSelect == null || !toSelect.getTrait(Spawned.class).shouldSpawn()) throw new CommandException(Messages.NPC_NOT_FOUND); if (npc != null && toSelect.getId() == npc.getId()) throw new CommandException(Messages.NPC_ALREADY_SELECTED); selector.select(sender, toSelect); Messaging.sendWithNPC(sender, Setting.SELECTION_MESSAGE.asString(), toSelect); } @Command( aliases = { "npc" }, usage = "skeletontype [type]", desc = "Sets the NPC's skeleton type", modifiers = { "skeletontype", "sktype" }, min = 2, max = 2, permission = "npc.skeletontype") @Requirements(selected = true, ownership = true, types = EntityType.SKELETON) public void skeletonType(CommandContext args, CommandSender sender, NPC npc) throws CommandException { SkeletonType type = (type = SkeletonType.getType(args.getInteger(1))) == null ? SkeletonType .valueOf(args.getString(1)) : type; if (type == null) throw new CommandException(Messages.INVALID_SKELETON_TYPE); npc.getTrait(NPCSkeletonType.class).setType(type); Messaging.sendTr(sender, Messages.SKELETON_TYPE_SET, npc.getName(), type); } @Command( aliases = { "npc" }, usage = "size [size]", desc = "Sets the NPC's size", modifiers = { "size" }, min = 1, max = 2, permission = "npc.size") @Requirements(selected = true, ownership = true, types = { EntityType.MAGMA_CUBE, EntityType.SLIME }) public void slimeSize(CommandContext args, CommandSender sender, NPC npc) { SlimeSize trait = npc.getTrait(SlimeSize.class); if (args.argsLength() <= 1) { trait.describe(sender); return; } int size = Math.max(1, args.getInteger(1)); trait.setSize(size); Messaging.sendTr(sender, Messages.SIZE_SET, npc.getName(), size); } @Command( aliases = { "npc" }, usage = "spawn [id]", desc = "Spawn an existing NPC", modifiers = { "spawn" }, min = 2, max = 2, permission = "npc.spawn") @Requirements(ownership = true) public void spawn(CommandContext args, CommandSender sender, NPC npc) throws CommandException { NPC respawn = npcRegistry.getById(args.getInteger(1)); if (respawn == null) throw new CommandException(Messages.NO_NPC_WITH_ID_FOUND, args.getInteger(1)); if (respawn.isSpawned()) throw new CommandException(Messages.NPC_ALREADY_SPAWNED, respawn.getName()); Location location = respawn.getTrait(CurrentLocation.class).getLocation(); if (location == null) { if (args.getSenderLocation() == null) throw new CommandException(Messages.NO_STORED_SPAWN_LOCATION); location = args.getSenderLocation(); } if (respawn.spawn(location)) { selector.select(sender, respawn); Messaging.sendTr(sender, Messages.NPC_SPAWNED, respawn.getName()); } } @Command( aliases = { "npc" }, usage = "speak message to speak --target npcid|player_name --type vocal_type", desc = "Uses the NPCs SpeechController to talk", modifiers = { "speak" }, min = 2, permission = "npc.speak") public void speak(CommandContext args, CommandSender sender, NPC npc) throws CommandException { String type = npc.getTrait(Speech.class).getDefaultVocalChord(); String message = StringHelper.parseColors(args.getJoinedStrings(1)); if (message.length() <= 0) { Messaging.send(sender, "Default Vocal Chord for " + npc.getName() + ": " + npc.getTrait(Speech.class).getDefaultVocalChord()); return; } SpeechContext context = new SpeechContext(message); if (args.hasValueFlag("target")) { if (args.getFlag("target").matches("\\d+")) { NPC target = CitizensAPI.getNPCRegistry().getById(Integer.valueOf(args.getFlag("target"))); if ( target != null) context.addRecipient(target.getBukkitEntity()); } else { Player player = Bukkit.getPlayer(args.getFlag("target")); if (player != null) context.addRecipient(player); } } if (args.hasValueFlag("type")) { if (CitizensAPI.getSpeechFactory().isRegistered(args.getFlag("type"))) type = args.getFlag("type"); } npc.getDefaultSpeechController().speak(context, type); } @Command( aliases = { "npc" }, usage = "speed [speed]", desc = "Sets the movement speed of an NPC as a percentage", modifiers = { "speed" }, min = 2, max = 2, permission = "npc.speed") public void speed(CommandContext args, CommandSender sender, NPC npc) throws CommandException { float newSpeed = (float) Math.abs(args.getDouble(1)); if (newSpeed >= Setting.MAX_SPEED.asDouble()) throw new CommandException(Messages.SPEED_MODIFIER_ABOVE_LIMIT); npc.getNavigator().getDefaultParameters().speedModifier(newSpeed); Messaging.sendTr(sender, Messages.SPEED_MODIFIER_SET, newSpeed); } @Command( aliases = { "npc" }, usage = "tp", desc = "Teleport to a NPC", modifiers = { "tp", "teleport" }, min = 1, max = 1, permission = "npc.tp") public void tp(CommandContext args, Player player, NPC npc) { Location to = npc.getTrait(CurrentLocation.class).getLocation(); player.teleport(to, TeleportCause.COMMAND); Messaging.sendTr(player, Messages.TELEPORTED_TO_NPC, npc.getName()); } @Command(aliases = { "npc" }, usage = "tphere", desc = "Teleport a NPC to your location", modifiers = { "tphere", "tph", "move" }, min = 1, max = 1, permission = "npc.tphere") public void tphere(CommandContext args, CommandSender sender, NPC npc) throws CommandException { if (args.getSenderLocation() == null) throw new ServerCommandException(); // Spawn the NPC if it isn't spawned to prevent NPEs if (!npc.isSpawned()) { npc.spawn(args.getSenderLocation()); } else npc.getBukkitEntity().teleport(args.getSenderLocation(), TeleportCause.COMMAND); Messaging.sendTr(sender, Messages.NPC_TELEPORTED, npc.getName()); } @Command( aliases = { "npc" }, usage = "type [type]", desc = "Sets an NPC's entity type", modifiers = { "type" }, min = 2, max = 2, permission = "npc.type") public void type(CommandContext args, CommandSender sender, NPC npc) throws CommandException { EntityType type = Util.matchEntityType(args.getString(1)); if (type == null) throw new CommandException(Messages.INVALID_ENTITY_TYPE, args.getString(1)); ((CitizensNPC) npc).setEntityController(EntityControllers.createForType(type)); Messaging.sendTr(sender, Messages.ENTITY_TYPE_SET, npc.getName(), args.getString(1)); } @Command( aliases = { "npc" }, usage = "vulnerable (-t)", desc = "Toggles an NPC's vulnerability", modifiers = { "vulnerable" }, min = 1, max = 1, flags = "t", permission = "npc.vulnerable") public void vulnerable(CommandContext args, CommandSender sender, NPC npc) { boolean vulnerable = !npc.data().get(NPC.DEFAULT_PROTECTED_METADATA, true); if (args.hasFlag('t')) { npc.data().set(NPC.DEFAULT_PROTECTED_METADATA, vulnerable); } else { npc.data().setPersistent(NPC.DEFAULT_PROTECTED_METADATA, vulnerable); } String key = vulnerable ? Messages.VULNERABLE_STOPPED : Messages.VULNERABLE_SET; Messaging.sendTr(sender, key, npc.getName()); } @Command( aliases = { "npc" }, usage = "zombiemod (-b(aby), -v(illager))", desc = "Sets a zombie NPC to be a baby or villager", modifiers = { "zombie", "zombiemod" }, flags = "bv", min = 1, max = 1, permission = "npc.zombiemodifier") @Requirements(selected = true, ownership = true, types = EntityType.ZOMBIE) public void zombieModifier(CommandContext args, CommandSender sender, NPC npc) throws CommandException { ZombieModifier trait = npc.getTrait(ZombieModifier.class); if (args.hasFlag('b')) { boolean isBaby = trait.toggleBaby(); Messaging.sendTr(sender, isBaby ? Messages.ZOMBIE_BABY_SET : Messages.ZOMBIE_BABY_UNSET, npc.getName()); } if (args.hasFlag('v')) { boolean isVillager = trait.toggleVillager(); Messaging.sendTr(sender, isVillager ? Messages.ZOMBIE_VILLAGER_SET : Messages.ZOMBIE_VILLAGER_UNSET, npc.getName()); } } }