diff --git a/main/src/main/java/net/citizensnpcs/Settings.java b/main/src/main/java/net/citizensnpcs/Settings.java index c8b6ac509..56b145226 100644 --- a/main/src/main/java/net/citizensnpcs/Settings.java +++ b/main/src/main/java/net/citizensnpcs/Settings.java @@ -129,6 +129,7 @@ public void loadFromKey(DataKey root) { NEW_PATHFINDER_CHECK_BOUNDING_BOXES("npc.pathfinding.new-finder.check-bounding-boxes", false), NEW_PATHFINDER_OPENS_DOORS("npc.pathfinding.new-finder.open-doors", false), NPC_ATTACK_DISTANCE("npc.pathfinding.attack-range", 1.75 * 1.75), + NPC_COMMAND_GLOBAL_COMMAND_DELAY("npc.commands.global-delay-seconds", 0), NPC_COMMAND_MAXIMUM_TIMES_USED_MESSAGE("npc.commands.error-messages.maximum-times-used", "You have reached the maximum number of uses ({0})."), NPC_COMMAND_MISSING_ITEM_MESSAGE("npc.commands.error-messages.missing-item", "Missing {1} {0}"), diff --git a/main/src/main/java/net/citizensnpcs/commands/NPCCommands.java b/main/src/main/java/net/citizensnpcs/commands/NPCCommands.java index de453a0f4..5188e55bd 100644 --- a/main/src/main/java/net/citizensnpcs/commands/NPCCommands.java +++ b/main/src/main/java/net/citizensnpcs/commands/NPCCommands.java @@ -98,6 +98,7 @@ import net.citizensnpcs.trait.ArmorStandTrait; import net.citizensnpcs.trait.ClickRedirectTrait; import net.citizensnpcs.trait.CommandTrait; +import net.citizensnpcs.trait.CommandTrait.CommandTraitMessages; import net.citizensnpcs.trait.CommandTrait.ExecutionMode; import net.citizensnpcs.trait.CommandTrait.ItemRequirementGUI; import net.citizensnpcs.trait.CommandTrait.NPCCommandBuilder; @@ -375,7 +376,7 @@ public void collidable(CommandContext args, CommandSender sender, NPC npc) throw @Command( aliases = { "npc" }, - usage = "command|cmd (add [command] | remove [id] | permissions [permissions] | sequential | random | (exp|item)cost [cost]) (-l[eft]/-r[ight]) (-p[layer] -o[p]), --cooldown --gcooldown [seconds] --delay [ticks] --permissions [perms] --n [max # of uses]", + usage = "command|cmd (add [command] | remove [id] | permissions [permissions] | sequential | random | errormsg [type] [msg] | (exp|item)cost [cost]) (-l[eft]/-r[ight]) (-p[layer] -o[p]), --cooldown --gcooldown [seconds] --delay [ticks] --permissions [perms] --n [max # of uses]", desc = "Controls commands which will be run when clicking on an NPC", help = Messages.NPC_COMMAND_HELP, modifiers = { "command", "cmd" }, @@ -444,6 +445,12 @@ public void command(CommandContext args, CommandSender sender, NPC npc) throws C if (!(sender instanceof Player)) throw new CommandException(CommandMessages.MUST_BE_INGAME); InventoryMenu.createSelfRegistered(new ItemRequirementGUI(commands)).present(((Player) sender)); + } else if (args.getString(1).equalsIgnoreCase("errormsg")) { + CommandTraitMessages which = Util.matchEnum(CommandTraitMessages.values(), args.getString(2)); + if (which == null) + throw new CommandException(Messages.NPC_COMMAND_INVALID_ERROR_MESSAGE, + Util.listValuesPretty(CommandTraitMessages.values())); + commands.setCustomErrorMessage(which, args.getString(3)); } else { throw new CommandUsageException(); } diff --git a/main/src/main/java/net/citizensnpcs/trait/CommandTrait.java b/main/src/main/java/net/citizensnpcs/trait/CommandTrait.java index b5955bcd6..3fb5f7824 100644 --- a/main/src/main/java/net/citizensnpcs/trait/CommandTrait.java +++ b/main/src/main/java/net/citizensnpcs/trait/CommandTrait.java @@ -61,10 +61,10 @@ public class CommandTrait extends Trait { @Persist(keyType = Integer.class) @DelegatePersistence(NPCCommandPersister.class) private final Map commands = Maps.newHashMap(); - @Persist(keyType = UUID.class, reify = true) - private final Map cooldowns = Maps.newHashMap(); @Persist private double cost = -1; + @Persist + private final Map customErrorMessages = Maps.newEnumMap(CommandTraitMessages.class); private final Map> executionErrors = Maps.newHashMap(); @Persist private ExecutionMode executionMode = ExecutionMode.LINEAR; @@ -76,6 +76,8 @@ public class CommandTrait extends Trait { private boolean hideErrorMessages; @Persist private final List itemRequirements = Lists.newArrayList(); + @Persist(keyType = UUID.class, reify = true, value = "cooldowns") + private final Map playerTracking = Maps.newHashMap(); @Persist private final List temporaryPermissions = Lists.newArrayList(); @@ -231,7 +233,7 @@ public int compare(NPCCommand o1, NPCCommand o2) { } for (NPCCommand command : commandList) { if (executionMode == ExecutionMode.SEQUENTIAL) { - PlayerNPCCommand info = cooldowns.get(player.getUniqueId()); + PlayerNPCCommand info = playerTracking.get(player.getUniqueId()); if (info != null && info.lastUsedHand != hand) { info.lastUsedHand = hand; info.lastUsedId = -1; @@ -255,10 +257,10 @@ private void runCommand(final Player player, NPCCommand command) { Runnable runnable = new Runnable() { @Override public void run() { - PlayerNPCCommand info = cooldowns.get(player.getUniqueId()); + PlayerNPCCommand info = playerTracking.get(player.getUniqueId()); if (info == null && (executionMode == ExecutionMode.SEQUENTIAL || PlayerNPCCommand.requiresTracking(command))) { - cooldowns.put(player.getUniqueId(), info = new PlayerNPCCommand()); + playerTracking.put(player.getUniqueId(), info = new PlayerNPCCommand()); } if (info != null && !info.canUse(CommandTrait.this, player, command)) { return; @@ -329,7 +331,7 @@ public void removeCommandById(int id) { @Override public void save(DataKey key) { Collection commands = this.commands.values(); - for (PlayerNPCCommand playerCommand : cooldowns.values()) { + for (PlayerNPCCommand playerCommand : playerTracking.values()) { playerCommand.prune(globalCooldowns, commands); } } @@ -345,7 +347,7 @@ private void sendErrorMessage(Player player, CommandTraitMessages msg, Function< return; sent.add(msg); } - String messageRaw = msg.setting.asString(); + String messageRaw = customErrorMessages.getOrDefault(msg, msg.setting.asString()); if (transform != null) { messageRaw = transform.apply(messageRaw); } @@ -358,6 +360,10 @@ public void setCost(double cost) { this.cost = cost; } + public void setCustomErrorMessage(CommandTraitMessages which, String message) { + customErrorMessages.put(which, message); + } + public void setExecutionMode(ExecutionMode mode) { this.executionMode = mode; } @@ -375,7 +381,7 @@ public void setTemporaryPermissions(List permissions) { temporaryPermissions.addAll(permissions); } - private enum CommandTraitMessages { + public enum CommandTraitMessages { MAXIMUM_TIMES_USED(Setting.NPC_COMMAND_MAXIMUM_TIMES_USED_MESSAGE), MISSING_EXPERIENCE(Setting.NPC_COMMAND_NOT_ENOUGH_EXPERIENCE_MESSAGE), MISSING_ITEM(Setting.NPC_COMMAND_MISSING_ITEM_MESSAGE), @@ -644,10 +650,11 @@ public boolean canUse(CommandTrait trait, Player player, NPCCommand command) { return false; } } + long globalDelay = Setting.NPC_COMMAND_GLOBAL_COMMAND_DELAY.asLong(); long currentTimeSec = System.currentTimeMillis() / 1000; String commandKey = command.getEncodedKey(); if (lastUsed.containsKey(commandKey)) { - long deadline = ((Number) lastUsed.get(commandKey)).longValue() + command.cooldown; + long deadline = ((Number) lastUsed.get(commandKey)).longValue() + command.cooldown + globalDelay; if (currentTimeSec < deadline) { long seconds = deadline - currentTimeSec; trait.sendErrorMessage(player, CommandTraitMessages.ON_COOLDOWN, @@ -671,7 +678,7 @@ public boolean canUse(CommandTrait trait, Player player, NPCCommand command) { trait.sendErrorMessage(player, CommandTraitMessages.MAXIMUM_TIMES_USED, null, command.n); return false; } - if (command.cooldown > 0) { + if (command.cooldown > 0 || globalDelay > 0) { lastUsed.put(commandKey, currentTimeSec); } if (command.globalCooldown > 0) { @@ -691,7 +698,8 @@ public void prune(Map globalCooldowns, Collection comm String commandKey = command.getEncodedKey(); commandKeys.add(commandKey); Number number = lastUsed.get(commandKey); - if (number != null && number.longValue() + command.cooldown <= currentTimeSec) { + if (number != null && number.longValue() + command.cooldown + + Setting.NPC_COMMAND_GLOBAL_COMMAND_DELAY.asLong() <= currentTimeSec) { lastUsed.remove(commandKey); } if (globalCooldowns != null) { @@ -718,7 +726,8 @@ public void prune(Map globalCooldowns, Collection comm public static boolean requiresTracking(NPCCommand command) { return command.globalCooldown > 0 || command.cooldown > 0 || command.n > 0 - || (command.perms != null && command.perms.size() > 0); + || (command.perms != null && command.perms.size() > 0) + || Setting.NPC_COMMAND_GLOBAL_COMMAND_DELAY.asLong() > 0; } } diff --git a/main/src/main/java/net/citizensnpcs/trait/text/Text.java b/main/src/main/java/net/citizensnpcs/trait/text/Text.java index 38c416feb..8f7e0eb04 100644 --- a/main/src/main/java/net/citizensnpcs/trait/text/Text.java +++ b/main/src/main/java/net/citizensnpcs/trait/text/Text.java @@ -31,6 +31,7 @@ import net.citizensnpcs.api.util.DataKey; import net.citizensnpcs.api.util.Messaging; import net.citizensnpcs.api.util.Paginator; +import net.citizensnpcs.api.util.Placeholders; import net.citizensnpcs.editor.Editor; import net.citizensnpcs.trait.HologramTrait; import net.citizensnpcs.util.Messages; @@ -265,10 +266,10 @@ private boolean sendText(Player player) { HologramTrait trait = npc.getOrAddTrait(HologramTrait.class); if (speechIndex == -1) { speechIndex = trait.getLines().size(); - trait.addLine(text.get(index)); + trait.addLine(Placeholders.replace(text.get(index), player)); bubbleTicks = Setting.DEFAULT_TEXT_SPEECH_BUBBLE_TICKS.asInt(); } else if (speechIndex < trait.getLines().size()) { - trait.setLine(speechIndex, text.get(index)); + trait.setLine(speechIndex, Placeholders.replace(text.get(index), player)); } } else { npc.getDefaultSpeechController().speak(new SpeechContext(text.get(index), player)); diff --git a/main/src/main/java/net/citizensnpcs/util/Messages.java b/main/src/main/java/net/citizensnpcs/util/Messages.java index 7b7086057..63010623c 100644 --- a/main/src/main/java/net/citizensnpcs/util/Messages.java +++ b/main/src/main/java/net/citizensnpcs/util/Messages.java @@ -240,6 +240,7 @@ public class Messages { public static final String NPC_ALREADY_SELECTED = "citizens.commands.npc.select.already-selected"; public static final String NPC_ALREADY_SPAWNED = "citizens.commands.npc.spawn.already-spawned"; public static final String NPC_COMMAND_HELP = "citizens.commands.npc.command.help"; + public static final String NPC_COMMAND_INVALID_ERROR_MESSAGE = "citizens.commands.npc.command.invalid-error-message"; public static final String NPC_COPIED = "citizens.commands.npc.copy.copied"; public static final String NPC_CREATE_INVALID_MOBTYPE = "citizens.commands.npc.create.invalid-mobtype"; public static final String NPC_CREATE_MISSING_MOBTYPE = "citizens.commands.npc.create.mobtype-missing"; diff --git a/main/src/main/resources/messages_en.properties b/main/src/main/resources/messages_en.properties index 005155cd1..89797e2d3 100644 --- a/main/src/main/resources/messages_en.properties +++ b/main/src/main/resources/messages_en.properties @@ -54,6 +54,7 @@ citizens.commands.npc.collidable.set=[[{0}]] will now collide with entities. citizens.commands.npc.collidable.unset=[[{0}]] will no longer collide with entities. citizens.commands.npc.command.none-added=No commands have been added. citizens.commands.npc.command.cost-set=Set cost per click to [[{0}]]. +citizens.commands.npc.command.invalid-error-message=Invalid error message. Valid messages are [[{0}]]. citizens.commands.npc.command.hide-error-messages-set=Now hiding error messages. citizens.commands.npc.command.hide-error-messages-unset=No longer hiding error messages. citizens.commands.npc.command.experience-cost-set=Set experience cost per click to [[{0}]].