package net.citizensnpcs.commands; import java.io.File; import java.io.FileOutputStream; import java.net.URL; import java.nio.channels.Channels; import java.nio.channels.ReadableByteChannel; import java.nio.file.Files; import java.time.Duration; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.Iterator; import java.util.List; import java.util.UUID; import java.util.stream.Collectors; import org.bukkit.Art; import org.bukkit.Bukkit; import org.bukkit.ChatColor; import org.bukkit.DyeColor; import org.bukkit.GameMode; import org.bukkit.Location; import org.bukkit.Material; import org.bukkit.OfflinePlayer; import org.bukkit.Sound; import org.bukkit.World; import org.bukkit.block.Block; import org.bukkit.block.BlockFace; import org.bukkit.command.CommandSender; import org.bukkit.command.ConsoleCommandSender; import org.bukkit.entity.Ageable; import org.bukkit.entity.ArmorStand; import org.bukkit.entity.Damageable; import org.bukkit.entity.Entity; import org.bukkit.entity.EntityType; import org.bukkit.entity.Horse; import org.bukkit.entity.Ocelot; import org.bukkit.entity.Player; import org.bukkit.entity.Rabbit; import org.bukkit.entity.Villager.Profession; import org.bukkit.entity.Zombie; import org.bukkit.event.player.PlayerTeleportEvent.TeleportCause; import org.bukkit.inventory.InventoryHolder; import org.bukkit.inventory.ItemStack; import org.bukkit.inventory.meta.SkullMeta; import org.json.simple.JSONObject; import org.json.simple.parser.JSONParser; import com.google.common.base.Joiner; import com.google.common.base.Splitter; import com.google.common.base.Strings; import com.google.common.collect.Iterables; import com.google.common.collect.Lists; import com.google.common.io.BaseEncoding; 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.ai.tree.StatusMapper; import net.citizensnpcs.api.command.Arg; import net.citizensnpcs.api.command.Command; import net.citizensnpcs.api.command.CommandContext; import net.citizensnpcs.api.command.CommandMessages; import net.citizensnpcs.api.command.Flag; import net.citizensnpcs.api.command.Requirements; import net.citizensnpcs.api.command.exception.CommandException; import net.citizensnpcs.api.command.exception.CommandUsageException; import net.citizensnpcs.api.command.exception.NoPermissionsException; import net.citizensnpcs.api.command.exception.RequirementMissingException; import net.citizensnpcs.api.command.exception.ServerCommandException; import net.citizensnpcs.api.event.CommandSenderCloneNPCEvent; import net.citizensnpcs.api.event.CommandSenderCreateNPCEvent; import net.citizensnpcs.api.event.DespawnReason; import net.citizensnpcs.api.event.NPCTeleportEvent; import net.citizensnpcs.api.event.PlayerCloneNPCEvent; import net.citizensnpcs.api.event.PlayerCreateNPCEvent; import net.citizensnpcs.api.event.SpawnReason; import net.citizensnpcs.api.gui.InventoryMenu; import net.citizensnpcs.api.npc.BlockBreaker; import net.citizensnpcs.api.npc.BlockBreaker.BlockBreakerConfiguration; import net.citizensnpcs.api.npc.MemoryNPCDataStore; import net.citizensnpcs.api.npc.NPC; import net.citizensnpcs.api.npc.NPC.NPCUpdate; import net.citizensnpcs.api.npc.NPCRegistry; import net.citizensnpcs.api.npc.templates.Template; import net.citizensnpcs.api.npc.templates.TemplateRegistry; import net.citizensnpcs.api.trait.Trait; import net.citizensnpcs.api.trait.trait.Equipment; import net.citizensnpcs.api.trait.trait.Equipment.EquipmentSlot; import net.citizensnpcs.api.trait.trait.Inventory; import net.citizensnpcs.api.trait.trait.MobType; import net.citizensnpcs.api.trait.trait.Owner; import net.citizensnpcs.api.trait.trait.PlayerFilter; import net.citizensnpcs.api.trait.trait.Spawned; import net.citizensnpcs.api.util.EntityDim; import net.citizensnpcs.api.util.Messaging; import net.citizensnpcs.api.util.Paginator; import net.citizensnpcs.api.util.Placeholders; import net.citizensnpcs.api.util.SpigotUtil; import net.citizensnpcs.commands.gui.NPCConfigurator; import net.citizensnpcs.commands.history.CommandHistory; import net.citizensnpcs.commands.history.CreateNPCHistoryItem; import net.citizensnpcs.commands.history.RemoveNPCHistoryItem; import net.citizensnpcs.editor.Editor; import net.citizensnpcs.npc.EntityControllers; import net.citizensnpcs.npc.NPCSelector; import net.citizensnpcs.trait.Age; import net.citizensnpcs.trait.Anchors; import net.citizensnpcs.trait.ArmorStandTrait; import net.citizensnpcs.trait.BoundingBoxTrait; import net.citizensnpcs.trait.ClickRedirectTrait; import net.citizensnpcs.trait.CommandTrait; import net.citizensnpcs.trait.CommandTrait.CommandTraitError; import net.citizensnpcs.trait.CommandTrait.ExecutionMode; import net.citizensnpcs.trait.CommandTrait.ItemRequirementGUI; import net.citizensnpcs.trait.CommandTrait.NPCCommandBuilder; import net.citizensnpcs.trait.Controllable; import net.citizensnpcs.trait.CurrentLocation; import net.citizensnpcs.trait.DropsTrait; import net.citizensnpcs.trait.EnderCrystalTrait; import net.citizensnpcs.trait.EndermanTrait; import net.citizensnpcs.trait.FollowTrait; import net.citizensnpcs.trait.GameModeTrait; import net.citizensnpcs.trait.Gravity; import net.citizensnpcs.trait.HologramTrait; import net.citizensnpcs.trait.HomeTrait; import net.citizensnpcs.trait.HorseModifiers; import net.citizensnpcs.trait.LookClose; import net.citizensnpcs.trait.MirrorTrait; import net.citizensnpcs.trait.MountTrait; import net.citizensnpcs.trait.OcelotModifiers; import net.citizensnpcs.trait.PacketNPC; import net.citizensnpcs.trait.PaintingTrait; import net.citizensnpcs.trait.PausePathfindingTrait; import net.citizensnpcs.trait.Poses; import net.citizensnpcs.trait.Powered; import net.citizensnpcs.trait.RabbitType; import net.citizensnpcs.trait.RotationTrait; import net.citizensnpcs.trait.ScoreboardTrait; import net.citizensnpcs.trait.SheepTrait; import net.citizensnpcs.trait.ShopTrait; import net.citizensnpcs.trait.ShopTrait.NPCShop; import net.citizensnpcs.trait.SitTrait; import net.citizensnpcs.trait.SkinLayers; import net.citizensnpcs.trait.SkinLayers.Layer; import net.citizensnpcs.trait.SkinTrait; import net.citizensnpcs.trait.SlimeSize; import net.citizensnpcs.trait.VillagerProfession; import net.citizensnpcs.trait.WitherTrait; import net.citizensnpcs.trait.WolfModifiers; import net.citizensnpcs.trait.shop.StoredShops; import net.citizensnpcs.trait.waypoint.Waypoints; import net.citizensnpcs.util.Anchor; import net.citizensnpcs.util.Messages; import net.citizensnpcs.util.MojangSkinGenerator; import net.citizensnpcs.util.NMS; import net.citizensnpcs.util.PlayerAnimation; import net.citizensnpcs.util.StringHelper; import net.citizensnpcs.util.Util; @Requirements(selected = true, ownership = true) public class NPCCommands { private final CommandHistory history; private final NPCSelector selector; private final StoredShops shops; private final TemplateRegistry templateRegistry; private final NPCRegistry temporaryRegistry; public NPCCommands(Citizens plugin) { selector = plugin.getNPCSelector(); shops = plugin.getShops(); templateRegistry = plugin.getTemplateRegistry(); temporaryRegistry = CitizensAPI.createCitizensBackedNPCRegistry(new MemoryNPCDataStore()); history = new CommandHistory(selector); } @Command( aliases = { "npc" }, usage = "activationrange [range]", desc = "", modifiers = { "activationrange" }, min = 1, max = 2, permission = "citizens.npc.activationrange") public void activationrange(CommandContext args, CommandSender sender, NPC npc, @Arg(1) Integer range) { if (range == null) { npc.data().remove(NPC.Metadata.ACTIVATION_RANGE); } else { npc.data().setPersistent(NPC.Metadata.ACTIVATION_RANGE, range); } Messaging.sendTr(sender, Messages.ACTIVATION_RANGE_SET, range); } @Command( aliases = { "npc" }, usage = "age [age] (-l(ock))", desc = "", flags = "l", modifiers = { "age" }, min = 1, max = 2, permission = "citizens.npc.age") public void age(CommandContext args, CommandSender sender, NPC npc) throws CommandException { if (!npc.isSpawned() || !(npc.getEntity() instanceof Ageable) && !(npc.getEntity() instanceof Zombie) && !npc.getEntity().getType().name().equals("TADPOLE")) throw new CommandException(Messages.MOBTYPE_CANNOT_BE_AGED, npc.getName()); Age trait = npc.getOrAddTrait(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 > 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 = "aggressive [true|false]", desc = "", modifiers = { "aggressive" }, min = 1, max = 2, permission = "citizens.npc.aggressive") public void aggressive(CommandContext args, CommandSender sender, NPC npc, @Arg(1) Boolean aggressive) { boolean aggro = aggressive != null ? aggressive : !npc.data().get(NPC.Metadata.AGGRESSIVE, false); npc.data().set(NPC.Metadata.AGGRESSIVE, aggro); NMS.setAggressive(npc.getEntity(), aggro); } @Command( aliases = { "npc" }, usage = "ai (true|false)", desc = "", modifiers = { "ai" }, min = 1, max = 2, permission = "citizens.npc.ai") public void ai(CommandContext args, CommandSender sender, NPC npc, @Arg(1) Boolean explicit) throws CommandException { boolean useAI = explicit == null ? !npc.useMinecraftAI() : explicit; npc.setUseMinecraftAI(useAI); Messaging.sendTr(sender, useAI ? Messages.USING_MINECRAFT_AI : Messages.NOT_USING_MINECRAFT_AI); } @Command( aliases = { "npc" }, usage = "anchor (--save [name]|--assume [name]|--remove [name]) (-a) (-c)", desc = "", flags = "ac", modifiers = { "anchor" }, min = 1, max = 3, permission = "citizens.npc.anchor") public void anchor(CommandContext args, CommandSender sender, NPC npc, @Flag("save") String save, @Flag("assume") String assume, @Flag("remove") String remove) throws CommandException { Anchors trait = npc.getOrAddTrait(Anchors.class); if (save != null) { if (save.isEmpty()) throw new CommandException(Messages.INVALID_ANCHOR_NAME); if (args.getSenderLocation() == null) throw new ServerCommandException(); if (args.hasFlag('c')) { if (trait.addAnchor(save, args.getSenderTargetBlockLocation())) { Messaging.sendTr(sender, Messages.ANCHOR_ADDED); } else throw new CommandException(Messages.ANCHOR_ALREADY_EXISTS, save); } else if (trait.addAnchor(save, args.getSenderLocation())) { Messaging.sendTr(sender, Messages.ANCHOR_ADDED); } else throw new CommandException(Messages.ANCHOR_ALREADY_EXISTS, save); } else if (assume != null) { if (assume.isEmpty()) throw new CommandException(Messages.INVALID_ANCHOR_NAME); Anchor anchor = trait.getAnchor(assume); if (anchor == null) throw new CommandException(Messages.ANCHOR_MISSING, assume); npc.teleport(anchor.getLocation(), TeleportCause.COMMAND); } else if (remove != null) { if (remove.isEmpty()) throw new CommandException(Messages.INVALID_ANCHOR_NAME); if (trait.removeAnchor(trait.getAnchor(remove))) { Messaging.sendTr(sender, Messages.ANCHOR_REMOVED); } else throw new CommandException(Messages.ANCHOR_MISSING, remove); } else if (!args.hasFlag('a')) { Paginator paginator = new Paginator().header("Anchors").console(sender instanceof ConsoleCommandSender); paginator.addLine("Key: [[ID]] Name World Location (X,Y,Z)"); for (int i = 0; i < trait.getAnchors().size(); i++) { if (trait.getAnchors().get(i).isLoaded()) { 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); } else { String[] parts = trait.getAnchors().get(i).getUnloadedValue(); String line = i + " " + trait.getAnchors().get(i).getName() + " " + parts[0] + " " + parts[1] + ", " + parts[2] + ", " + parts[3] + " (unloaded)"; paginator.addLine(line); } } int page = args.getInteger(1, 1); if (!paginator.sendPage(sender, page)) throw new CommandException(Messages.COMMAND_PAGE_MISSING, page); } // Assume Player's position if (!args.hasFlag('a')) return; if (sender instanceof ConsoleCommandSender) throw new ServerCommandException(); npc.teleport(args.getSenderLocation(), TeleportCause.COMMAND); } @Command( aliases = { "npc" }, usage = "armorstand --visible [visible] --small [small] --marker [marker] --gravity [gravity] --arms [arms] --baseplate [baseplate] --(head|body|leftarm|leftleg|rightarm|rightleg)pose [angle x,y,z]", desc = "", modifiers = { "armorstand" }, min = 1, max = 1, valueFlags = { "bodypose", "leftarmpose", "rightarmpose", "leftlegpose", "rightlegpose" }, permission = "citizens.npc.armorstand") @Requirements(selected = true, ownership = true, types = EntityType.ARMOR_STAND) public void armorstand(CommandContext args, CommandSender sender, NPC npc, @Flag("visible") Boolean visible, @Flag("small") Boolean small, @Flag("gravity") Boolean gravity, @Flag("arms") Boolean arms, @Flag("marker") Boolean marker, @Flag("baseplate") Boolean baseplate) throws CommandException { ArmorStandTrait trait = npc.getOrAddTrait(ArmorStandTrait.class); if (visible != null) { trait.setVisible(visible); } if (small != null) { trait.setSmall(small); } if (gravity != null) { trait.setGravity(gravity); } if (marker != null) { trait.setMarker(marker); } if (arms != null) { trait.setHasArms(arms); } if (baseplate != null) { trait.setHasBaseplate(baseplate); } ArmorStand ent = (ArmorStand) npc.getEntity(); if (args.hasValueFlag("headpose")) { ent.setHeadPose(args.parseEulerAngle(args.getFlag("headpose"))); } if (args.hasValueFlag("bodypose")) { ent.setBodyPose(args.parseEulerAngle(args.getFlag("bodypose"))); } if (args.hasValueFlag("leftarmpose")) { ent.setLeftArmPose(args.parseEulerAngle(args.getFlag("leftarmpose"))); } if (args.hasValueFlag("leftlegpose")) { ent.setLeftLegPose(args.parseEulerAngle(args.getFlag("leftlegpose"))); } if (args.hasValueFlag("rightarmpose")) { ent.setRightArmPose(args.parseEulerAngle(args.getFlag("rightarmpose"))); } if (args.hasValueFlag("rightlegpose")) { ent.setRightLegPose(args.parseEulerAngle(args.getFlag("rightlegpose"))); } } @Command( aliases = { "npc" }, usage = "breakblock --location [x,y,z] --radius [radius]", desc = "", modifiers = { "breakblock" }, min = 1, max = 1, valueFlags = "location", permission = "citizens.npc.breakblock") @Requirements(selected = true, ownership = true, livingEntity = true) public void breakblock(CommandContext args, CommandSender sender, NPC npc, @Flag("radius") Double radius) throws CommandException { BlockBreakerConfiguration cfg = new BlockBreakerConfiguration(); if (radius != null) { cfg.radius(radius); } else if (Setting.DEFAULT_BLOCK_BREAKER_RADIUS.asDouble() > 0) { cfg.radius(Setting.DEFAULT_BLOCK_BREAKER_RADIUS.asDouble()); } if (npc.getEntity() instanceof InventoryHolder) { cfg.blockBreaker((block, itemstack) -> { org.bukkit.inventory.Inventory inventory = ((InventoryHolder) npc.getEntity()).getInventory(); Location location = npc.getEntity().getLocation(); for (ItemStack drop : block.getDrops(itemstack)) { for (ItemStack unadded : inventory.addItem(drop).values()) { location.getWorld().dropItemNaturally(npc.getEntity().getLocation(), unadded); } } }); } BlockBreaker breaker = npc.getBlockBreaker(args.getSenderTargetBlockLocation().getBlock(), cfg); npc.getDefaultGoalController().addBehavior(StatusMapper.singleUse(breaker), 1); } @Command( aliases = { "npc" }, usage = "chunkload (-t(emporary))", desc = "", modifiers = { "chunkload", "cload" }, min = 1, max = 1, flags = "t", permission = "citizens.npc.chunkload") @Requirements(selected = true, ownership = true) public void chunkload(CommandContext args, CommandSender sender, NPC npc) { boolean enabled = !npc.data().get(NPC.Metadata.KEEP_CHUNK_LOADED, Setting.KEEP_CHUNKS_LOADED.asBoolean()); if (args.hasFlag('t')) { npc.data().set(NPC.Metadata.KEEP_CHUNK_LOADED, enabled); } else { npc.data().setPersistent(NPC.Metadata.KEEP_CHUNK_LOADED, enabled); } Messaging.sendTr(sender, enabled ? Messages.CHUNKLOAD_SET : Messages.CHUNKLOAD_UNSET, npc.getName()); } @Command( aliases = { "npc" }, usage = "collidable", desc = "", modifiers = { "collidable", "pushable" }, min = 1, max = 1, permission = "citizens.npc.collidable") @Requirements(ownership = true, selected = true) public void collidable(CommandContext args, CommandSender sender, NPC npc) throws CommandException { npc.data().setPersistent(NPC.Metadata.COLLIDABLE, !npc.data().get(NPC.Metadata.COLLIDABLE, !npc.isProtected())); Messaging.sendTr(sender, npc.data(). get(NPC.Metadata.COLLIDABLE) ? Messages.COLLIDABLE_SET : Messages.COLLIDABLE_UNSET, npc.getName()); } @Command( aliases = { "npc" }, usage = "command|cmd (add [command] | remove [id|all] | permissions [permissions] | sequential | cycle | random | forgetplayer (uuid) | clearerror [type] (name|uuid) | errormsg [type] [msg] | persistsequence [true|false] | cost [cost] (id) | expcost [cost] (id) | itemcost (id)) (-s(hift)) (-l[eft]/-r[ight]) (-p[layer] -o[p]), --cooldown --gcooldown [seconds] --delay [ticks] --permissions [perms] --n [max # of uses]", desc = "", modifiers = { "command", "cmd" }, min = 1, flags = "lrpos", permission = "citizens.npc.command") public void command(CommandContext args, CommandSender sender, NPC npc, @Flag(value = { "permissions", "permission" }) String permissions, @Flag(value = "cost", defValue = "-1") Double cost, @Flag(value = "expcost", defValue = "-1") Integer experienceCost, @Flag(value = "cooldown", defValue = "0") Duration cooldown, @Flag(value = "gcooldown", defValue = "0") Duration gcooldown, @Flag(value = "n", defValue = "-1") int n, @Flag(value = "delay", defValue = "0") Duration delay, @Arg( value = 1, completions = { "add", "remove", "permissions", "persistsequence", "sequential", "cycle", "random", "forgetplayer", "hideerrors", "errormsg", "clearerror", "expcost", "itemcost", "cost" }) String action) throws CommandException { CommandTrait commands = npc.getOrAddTrait(CommandTrait.class); if (args.argsLength() == 1) { commands.describe(sender); } else if (action.equalsIgnoreCase("add")) { if (args.argsLength() == 2) throw new CommandUsageException(); if (args.hasFlag('o') && !sender.hasPermission("citizens.admin")) throw new NoPermissionsException(); String command = args.getJoinedStrings(2); CommandTrait.Hand hand = args.hasFlag('l') && args.hasFlag('r') ? CommandTrait.Hand.BOTH : args.hasFlag('l') ? CommandTrait.Hand.LEFT : CommandTrait.Hand.RIGHT; if (args.hasFlag('s') && hand != CommandTrait.Hand.BOTH) { hand = hand == CommandTrait.Hand.LEFT ? CommandTrait.Hand.SHIFT_LEFT : CommandTrait.Hand.SHIFT_RIGHT; } List perms = Lists.newArrayList(); if (permissions != null) { perms.addAll(Arrays.asList(permissions.split(","))); } if (command.toLowerCase().startsWith("npc select")) throw new CommandException("npc select not currently supported within commands. Use --id instead"); try { int id = commands.addCommand(new NPCCommandBuilder(command, hand).addPerms(perms) .player(args.hasFlag('p') || args.hasFlag('o')).op(args.hasFlag('o')).cooldown(cooldown) .cost(cost).experienceCost(experienceCost).globalCooldown(gcooldown).n(n).delay(delay)); Messaging.sendTr(sender, Messages.COMMAND_ADDED, command, id); } catch (NumberFormatException ex) { throw new CommandException(CommandMessages.INVALID_NUMBER); } } else if (action.equalsIgnoreCase("forgetplayer")) { if (args.argsLength() < 3) { commands.clearPlayerHistory(null); Messaging.sendTr(sender, Messages.NPC_COMMAND_ALL_PLAYERS_FORGOTTEN, npc.getName()); return; } String raw = args.getString(2); OfflinePlayer who = Bukkit.getPlayerExact(raw); if (who == null) { who = Bukkit.getOfflinePlayer(UUID.fromString(raw)); } if (who == null || !who.hasPlayedBefore()) throw new CommandException(Messages.NPC_COMMAND_INVALID_PLAYER, raw); commands.clearPlayerHistory(who.getUniqueId()); Messaging.sendTr(sender, Messages.NPC_COMMAND_PLAYER_FORGOTTEN, who.getUniqueId()); } else if (action.equalsIgnoreCase("clearerror")) { if (args.argsLength() < 3) throw new CommandException(Messages.NPC_COMMAND_INVALID_ERROR_MESSAGE, Util.listValuesPretty(CommandTraitError.values())); CommandTraitError which = Util.matchEnum(CommandTraitError.values(), args.getString(2)); if (which == null) throw new CommandException(Messages.NPC_COMMAND_INVALID_ERROR_MESSAGE, Util.listValuesPretty(CommandTraitError.values())); if (args.argsLength() < 4) { commands.clearHistory(which, null); Messaging.sendTr(sender, Messages.NPC_COMMAND_ALL_ERRORS_CLEARED, npc.getName(), Util.prettyEnum(which)); return; } String raw = args.getString(3); OfflinePlayer who = Bukkit.getPlayerExact(raw); if (who == null) { who = Bukkit.getOfflinePlayer(UUID.fromString(raw)); } if (who == null || !who.hasPlayedBefore()) throw new CommandException(Messages.NPC_COMMAND_INVALID_PLAYER, raw); commands.clearHistory(which, who.getUniqueId()); Messaging.sendTr(sender, Messages.NPC_COMMAND_ERRORS_CLEARED, Util.prettyEnum(which), who.getUniqueId()); } else if (action.equalsIgnoreCase("sequential")) { commands.setExecutionMode(commands.getExecutionMode() == ExecutionMode.SEQUENTIAL ? ExecutionMode.LINEAR : ExecutionMode.SEQUENTIAL); Messaging.sendTr(sender, commands.getExecutionMode() == ExecutionMode.SEQUENTIAL ? Messages.COMMANDS_SEQUENTIAL_SET : Messages.COMMANDS_SEQUENTIAL_UNSET); } else if (action.equalsIgnoreCase("cycle")) { commands.setExecutionMode( commands.getExecutionMode() == ExecutionMode.CYCLE ? ExecutionMode.LINEAR : ExecutionMode.CYCLE); Messaging.sendTr(sender, commands.getExecutionMode() == ExecutionMode.CYCLE ? Messages.COMMANDS_CYCLE_SET : Messages.COMMANDS_CYCLE_UNSET); } else if (action.equalsIgnoreCase("persistsequence")) { if (args.argsLength() == 2) { commands.setPersistSequence(!commands.persistSequence()); } else { commands.setPersistSequence(Boolean.parseBoolean(args.getString(3))); } Messaging.sendTr(sender, commands.persistSequence() ? Messages.COMMANDS_PERSIST_SEQUENCE_SET : Messages.COMMANDS_PERSIST_SEQUENCE_UNSET); } else if (action.equalsIgnoreCase("remove")) { if (args.argsLength() == 2) throw new CommandUsageException(); if (args.getString(2).equalsIgnoreCase("all")) { commands.clear(); Messaging.sendTr(sender, Messages.COMMANDS_CLEARED, npc.getName()); } else { int id = args.getInteger(2, -1); if (!commands.hasCommandId(id)) throw new CommandException(Messages.COMMAND_UNKNOWN_COMMAND_ID, id); commands.removeCommandById(id); Messaging.sendTr(sender, Messages.COMMAND_REMOVED, id); } } else if (action.equalsIgnoreCase("permissions") || action.equalsIgnoreCase("perms")) { if (!sender.hasPermission("citizens.admin")) throw new NoPermissionsException(); List temporaryPermissions = Arrays.asList(args.getSlice(2)); commands.setTemporaryPermissions(temporaryPermissions); Messaging.sendTr(sender, Messages.COMMAND_TEMPORARY_PERMISSIONS_SET, Joiner.on(' ').join(temporaryPermissions)); } else if (action.equalsIgnoreCase("cost")) { if (args.argsLength() == 2) throw new CommandException(Messages.COMMAND_MISSING_COST); commands.setCost(args.getDouble(2)); Messaging.sendTr(sender, Messages.COMMAND_COST_SET, args.getDouble(2)); } else if (action.equalsIgnoreCase("expcost")) { if (args.argsLength() == 2) throw new CommandException(Messages.COMMAND_MISSING_COST); commands.setExperienceCost(args.getInteger(2)); Messaging.sendTr(sender, Messages.COMMAND_EXPERIENCE_COST_SET, args.getInteger(2)); } else if (action.equalsIgnoreCase("hideerrors")) { commands.setHideErrorMessages(!commands.isHideErrorMessages()); Messaging.sendTr(sender, commands.isHideErrorMessages() ? Messages.COMMAND_HIDE_ERROR_MESSAGES_SET : Messages.COMMAND_HIDE_ERROR_MESSAGES_UNSET); } else if (action.equalsIgnoreCase("random")) { commands.setExecutionMode( commands.getExecutionMode() == ExecutionMode.RANDOM ? ExecutionMode.LINEAR : ExecutionMode.RANDOM); Messaging.sendTr(sender, commands.getExecutionMode() == ExecutionMode.RANDOM ? Messages.COMMANDS_RANDOM_SET : Messages.COMMANDS_RANDOM_UNSET); } else if (action.equalsIgnoreCase("itemcost")) { if (!(sender instanceof Player)) throw new CommandException(CommandMessages.MUST_BE_INGAME); if (args.argsLength() == 2) { InventoryMenu.createSelfRegistered(new ItemRequirementGUI(commands)).present((Player) sender); } else { InventoryMenu.createSelfRegistered(new ItemRequirementGUI(commands, args.getInteger(2))) .present((Player) sender); } } else if (action.equalsIgnoreCase("errormsg")) { CommandTraitError which = Util.matchEnum(CommandTraitError.values(), args.getString(2)); if (which == null) throw new CommandException(Messages.NPC_COMMAND_INVALID_ERROR_MESSAGE, Util.listValuesPretty(CommandTraitError.values())); commands.setCustomErrorMessage(which, args.getString(3)); } else throw new CommandUsageException(); } @Command( aliases = { "npc" }, usage = "configgui", desc = "", modifiers = { "configgui" }, min = 1, max = 1, permission = "citizens.npc.configgui") public void configgui(CommandContext args, Player sender, NPC npc) { InventoryMenu.createSelfRegistered(new NPCConfigurator(npc)).present(sender); } @Command( aliases = { "npc" }, usage = "controllable|control (-m(ount),-y,-n,-o(wner required))", desc = "", modifiers = { "controllable", "control" }, min = 1, max = 1, flags = "myno") public void controllable(CommandContext args, CommandSender sender, NPC npc) throws CommandException { if ((npc.isSpawned() && !sender.hasPermission( "citizens.npc.controllable." + npc.getEntity().getType().name().toLowerCase().replace("_", ""))) || !sender.hasPermission("citizens.npc.controllable")) throw new NoPermissionsException(); if (!npc.hasTrait(Controllable.class)) { npc.getOrAddTrait(Controllable.class).setEnabled(false); } Controllable trait = npc.getOrAddTrait(Controllable.class); boolean enabled = trait.toggle(); if (args.hasFlag('y')) { enabled = trait.setEnabled(true); } else if (args.hasFlag('n')) { enabled = trait.setEnabled(false); } trait.setOwnerRequired(args.hasFlag('o')); String key = enabled ? Messages.CONTROLLABLE_SET : Messages.CONTROLLABLE_REMOVED; Messaging.sendTr(sender, key, npc.getName()); if (enabled && args.hasFlag('m') && sender instanceof Player) { trait.mount((Player) sender); } } @Command( aliases = { "npc" }, usage = "copy (--name newname)", desc = "", modifiers = { "copy" }, min = 1, max = 1, permission = "citizens.npc.copy") public void copy(CommandContext args, CommandSender sender, NPC npc, @Flag("name") String name) throws CommandException { if (name == null) { name = npc.getRawName(); } NPC copy = npc.clone(); if (!copy.getRawName().equals(name)) { copy.setName(name); } if (copy.getOrAddTrait(Spawned.class).shouldSpawn() && args.getSenderLocation() != null) { Location location = args.getSenderLocation(); location.getChunk().load(); copy.teleport(location, TeleportCause.COMMAND); copy.getOrAddTrait(CurrentLocation.class).setLocation(location); } CommandSenderCreateNPCEvent event = sender instanceof Player ? new PlayerCloneNPCEvent((Player) sender, npc, copy) : new CommandSenderCloneNPCEvent(sender, npc, copy); Bukkit.getPluginManager().callEvent(event); if (event.isCancelled()) { event.getNPC().destroy(); String reason = "Couldn't create NPC."; if (!event.getCancelReason().isEmpty()) { reason += " Reason: " + event.getCancelReason(); } throw new CommandException(reason); } Messaging.sendTr(sender, Messages.NPC_COPIED, npc.getName()); selector.select(sender, copy); history.add(sender, new CreateNPCHistoryItem(copy)); } @Command( aliases = { "npc" }, usage = "create [name] ((-b(aby),u(nspawned),s(ilent),t(emporary),c(enter),p(acket)) --at [x,y,z,world] --type [type] --item (item) --trait ['trait1, trait2...'] --model [model name] --nameplate [true|false|hover] --temporaryticks [ticks] --registry [registry name]", desc = "", flags = "bustpc", modifiers = { "create" }, min = 2, permission = "citizens.npc.create") @Requirements public void create(CommandContext args, CommandSender sender, NPC npc, @Flag("at") Location at, @Flag(value = "type", defValue = "PLAYER") EntityType type, @Flag("trait") String traits, @Flag(value = "nameplate", completions = { "true", "false", "hover" }) String nameplate, @Flag("temporaryticks") Integer temporaryTicks, @Flag("item") String item, @Flag("template") String templateName, @Flag("registry") String registryName) throws CommandException { String name = args.getJoinedStrings(1).trim(); if (args.hasValueFlag("type")) { if (type == null) throw new CommandException(Messaging.tr(Messages.NPC_CREATE_INVALID_MOBTYPE, args.getFlag("type"))); else if (!EntityControllers.controllerExistsForType(type)) throw new CommandException(Messaging.tr(Messages.NPC_CREATE_MISSING_MOBTYPE, args.getFlag("type"))); } int nameLength = SpigotUtil.getMaxNameLength(type); if (Placeholders.replace(Messaging.parseComponents(name), sender, npc).length() > nameLength) { Messaging.sendErrorTr(sender, Messages.NPC_NAME_TOO_LONG, nameLength); name = name.substring(0, nameLength); } if (name.length() == 0) throw new CommandException(); if (!sender.hasPermission("citizens.npc.create.*") && !sender.hasPermission("citizens.npc.createall") && !sender.hasPermission("citizens.npc.create." + type.name().toLowerCase().replace("_", ""))) throw new NoPermissionsException(); if ((at != null || registryName != null || traits != null || templateName != null) && !sender.hasPermission("citizens.npc.admin")) throw new NoPermissionsException(); NPCRegistry registry = CitizensAPI.getNPCRegistry(); if (registryName != null) { registry = CitizensAPI.getNamedNPCRegistry(registryName); if (registry == null) { registry = CitizensAPI.createNamedNPCRegistry(registryName, new MemoryNPCDataStore()); Messaging.send(sender, "An in-memory registry has been created named [[" + registryName + "]]."); } } if (args.hasFlag('t') || temporaryTicks != null) { registry = temporaryRegistry; } if (item != null) { ItemStack stack = Util.parseItemStack(null, item); npc = registry.createNPCUsingItem(type, name, stack); } else { npc = registry.createNPC(type, name); } String msg = "Created [[" + npc.getName() + "]] (ID [[" + npc.getId() + "]])"; if (args.hasFlag('b')) { msg += " as a baby"; npc.getOrAddTrait(Age.class).setAge(-24000); } if (args.hasFlag('s')) { npc.data().set(NPC.Metadata.SILENT, true); } if (nameplate != null) { npc.data().set(NPC.Metadata.NAMEPLATE_VISIBLE, nameplate.equalsIgnoreCase("hover") ? nameplate.toLowerCase() : Boolean.parseBoolean(nameplate)); } if (!Setting.SERVER_OWNS_NPCS.asBoolean()) { npc.getOrAddTrait(Owner.class).setOwner(sender); } if (temporaryTicks != null) { NPC temp = npc; Bukkit.getScheduler().scheduleSyncDelayedTask(CitizensAPI.getPlugin(), () -> { if (temporaryRegistry.getByUniqueId(temp.getUniqueId()) == temp) { temp.destroy(); } }, temporaryTicks); } npc.getOrAddTrait(MobType.class).setType(type); if (args.hasFlag('p')) { npc.addTrait(PacketNPC.class); } Location spawnLoc = args.getSenderLocation(); CommandSenderCreateNPCEvent event = sender instanceof Player ? new PlayerCreateNPCEvent((Player) sender, npc) : new CommandSenderCreateNPCEvent(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); } if (at != null) { spawnLoc = at; spawnLoc.getChunk().load(); } if (spawnLoc == null) { npc.destroy(); throw new CommandException(Messages.INVALID_SPAWN_LOCATION); } if (args.hasFlag('c')) { spawnLoc = Util.getCenterLocation(spawnLoc.getBlock()); } if (!args.hasFlag('u')) { npc.spawn(spawnLoc, SpawnReason.CREATE); } if (traits != null) { Iterable parts = Splitter.on(',').trimResults().split(traits); 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 (templateName != null) { Iterable parts = Splitter.on(',').trimResults().split(templateName); StringBuilder builder = new StringBuilder(); for (String part : parts) { if (part.contains(":")) { Template template = templateRegistry.getTemplateByNamespacedKey(part); if (template == null) continue; template.apply(npc); builder.append(StringHelper.wrap(part) + ", "); continue; } Collection