individual costs for NPC commands (#3033)

* Add individual cost option for `/npc cmd cost`

* Add individual experience cost option for `/npc cmd expcost`

* Begin individual item cost impl

Everything is finished except for saving/loading the items. I just need to figure out how to properly save the ItemStacks either by using the DataKey or by somehow figuring out how to use the Persist API

* Add item saving and loading

* Add cost and exp cost to describe message

* Remove debug/todo things

* Make `-1` default to allow for cost-free commands

* Update `describe` function to have proper space alignment

* Change names of cost values

* Update parameter name
This commit is contained in:
Brando! 2023-06-24 22:41:19 -07:00 committed by GitHub
parent 053438bfbf
commit a2d7284fbc
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 183 additions and 34 deletions

View File

@ -444,7 +444,7 @@ public class NPCCommands {
@Command(
aliases = { "npc" },
usage = "command|cmd (add [command] | remove [id] | permissions [permissions] | sequential | random | clearerror [type] (name|uuid) | errormsg [type] [msg] | persistsequence [true|false] | (exp|item)cost [cost]) (-s(hift)) (-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 | 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 = "Controls commands which will be run when clicking on an NPC",
help = Messages.NPC_COMMAND_HELP,
modifiers = { "command", "cmd" },
@ -459,7 +459,7 @@ public class NPCCommands {
@Arg(
value = 1,
completions = { "add", "remove", "permissions", "persistsequence", "sequential", "random",
"hideerrors", "errormsg", "clearerror", "expcost", "itemcost" }) String action)
"hideerrors", "errormsg", "clearerror", "expcost", "itemcost", "cost" }) String action)
throws CommandException {
CommandTrait commands = npc.getOrAddTrait(CommandTrait.class);
if (args.argsLength() == 1) {
@ -532,11 +532,29 @@ public class NPCCommands {
Messaging.sendTr(sender, Messages.COMMAND_TEMPORARY_PERMISSIONS_SET,
Joiner.on(' ').join(temporaryPermissions));
} else if (action.equalsIgnoreCase("cost")) {
commands.setCost(args.getDouble(2));
Messaging.sendTr(sender, Messages.COMMAND_COST_SET, args.getDouble(2));
if (args.argsLength() == 2) {
throw new CommandException(Messages.COMMAND_MISSING_COST);
}
if (args.argsLength() == 4) {
commands.setCost(args.getDouble(2), args.getInteger(3));
Messaging.sendTr(sender, Messages.COMMAND_INDIVIDUAL_COST_SET, args.getDouble(2) == -1 ? "-1 (default)" : args.getDouble(2), args.getInteger(3));
}
else {
commands.setCost(args.getDouble(2));
Messaging.sendTr(sender, Messages.COMMAND_COST_SET, args.getDouble(2));
}
} else if (action.equalsIgnoreCase("expcost")) {
commands.setExperienceCost(args.getInteger(2));
Messaging.sendTr(sender, Messages.COMMAND_EXPERIENCE_COST_SET, args.getInteger(2));
if (args.argsLength() == 2) {
throw new CommandException(Messages.COMMAND_MISSING_COST);
}
if (args.argsLength() == 4) {
commands.setExperienceCost(args.getInteger(2), args.getInteger(3));
Messaging.sendTr(sender, Messages.COMMAND_INDIVIDUAL_EXPERIENCE_COST_SET, args.getInteger(2) == -1 ? "-1 (default)" : args.getInteger(2), args.getInteger(3));
}
else {
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
@ -549,7 +567,12 @@ public class NPCCommands {
} else if (action.equalsIgnoreCase("itemcost")) {
if (!(sender instanceof Player))
throw new CommandException(CommandMessages.MUST_BE_INGAME);
InventoryMenu.createSelfRegistered(new ItemRequirementGUI(commands)).present(((Player) sender));
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)

View File

@ -45,6 +45,7 @@ import net.citizensnpcs.api.persistence.Persister;
import net.citizensnpcs.api.trait.Trait;
import net.citizensnpcs.api.trait.TraitName;
import net.citizensnpcs.api.util.DataKey;
import net.citizensnpcs.api.util.ItemStorage;
import net.citizensnpcs.api.util.Messaging;
import net.citizensnpcs.api.util.Translator;
import net.citizensnpcs.trait.shop.ExperienceAction;
@ -93,7 +94,7 @@ public class CommandTrait extends Trait {
return id;
}
private Transaction chargeCommandCosts(Player player, Hand hand) {
private Transaction chargeCommandCosts(Player player, Hand hand, int id) {
NPCShopAction action = null;
if (player.hasPermission("citizens.npc.command.ignoreerrors.*"))
return Transaction.success();
@ -117,6 +118,26 @@ public class CommandTrait extends Trait {
stack.getAmount());
}
}
if (hasCost(id) && !player.hasPermission("citizens.npc.command.ignoreerrors.cost")) {
action = new MoneyAction(commands.get(id).cost);
if (!action.take(player, 1).isPossible()) {
sendErrorMessage(player, CommandTraitError.MISSING_MONEY, null, commands.get(id).cost);
}
}
if (hasExperienceCost(id) && !player.hasPermission("citizens.npc.command.ignoreerrors.expcost")) {
action = new ExperienceAction(commands.get(id).experienceCost);
if (!action.take(player, 1).isPossible()) {
sendErrorMessage(player, CommandTraitError.MISSING_EXPERIENCE, null, commands.get(id).experienceCost);
}
}
if (hasItemCost(id) && !player.hasPermission("citizens.npc.command.ignoreerrors.itemcost")) {
action = new ItemAction(commands.get(id).itemCost);
if (!action.take(player, 1).isPossible()) {
ItemStack stack = commands.get(id).itemCost.get(0);
sendErrorMessage(player, CommandTraitError.MISSING_ITEM, null, Util.prettyEnum(stack.getType()),
stack.getAmount());
}
}
return action == null ? Transaction.success() : action.take(player, 1);
}
@ -173,37 +194,46 @@ public class CommandTrait extends Trait {
right.add(command);
}
}
String output = "";
List<String> outputList = Lists.newArrayList();
if (cost > 0) {
output += "Cost: " + StringHelper.wrap(cost);
outputList.add("Cost: " + StringHelper.wrap(cost));
}
if (experienceCost > 0) {
output += " XP cost: " + StringHelper.wrap(experienceCost);
outputList.add("XP cost: " + StringHelper.wrap(experienceCost));
}
if (left.size() > 0) {
output += Messaging.tr(Messages.COMMAND_LEFT_HAND_HEADER);
outputList.add(Messaging.tr(Messages.COMMAND_LEFT_HAND_HEADER));
for (NPCCommand command : left) {
output += describe(command);
outputList.add(describe(command));
}
}
if (right.size() > 0) {
output += Messaging.tr(Messages.COMMAND_RIGHT_HAND_HEADER);
outputList.add(Messaging.tr(Messages.COMMAND_RIGHT_HAND_HEADER));
for (NPCCommand command : right) {
output += describe(command);
outputList.add(describe(command));
}
}
if (output.isEmpty()) {
output = Messaging.tr(Messages.COMMAND_NO_COMMANDS_ADDED);
if (outputList.isEmpty()) {
outputList.add(Messaging.tr(Messages.COMMAND_NO_COMMANDS_ADDED));
} else {
output = executionMode + " " + output;
outputList.add(0, executionMode.toString());
}
Messaging.send(sender, output);
StringBuilder output = new StringBuilder();
for (String item : outputList) {
output.append(item);
output.append(" ");
}
Messaging.send(sender, output.toString().trim());
}
private String describe(NPCCommand command) {
String output = Messaging.tr(Messages.COMMAND_DESCRIBE_TEMPLATE, command.command, StringHelper.wrap(
command.cooldown != 0 ? command.cooldown : Setting.NPC_COMMAND_GLOBAL_COMMAND_COOLDOWN.asSeconds()),
String output = Messaging.tr(Messages.COMMAND_DESCRIBE_TEMPLATE,
command.command,
StringHelper.wrap(command.cooldown != 0 ? command.cooldown : Setting.NPC_COMMAND_GLOBAL_COMMAND_COOLDOWN.asSeconds()),
StringHelper.wrap(hasCost(command.id) ? command.cost : "default"),
StringHelper.wrap(hasExperienceCost(command.id) ? command.experienceCost : "default"),
command.id);
if (command.globalCooldown > 0) {
output += "[global " + StringHelper.wrap(command.globalCooldown) + "s]";
@ -285,7 +315,7 @@ public class CommandTrait extends Trait {
}
Transaction charge = null;
if (charged == null) {
charge = chargeCommandCosts(player, hand);
charge = chargeCommandCosts(player, hand, command.id);
if (!charge.isPossible()) {
charged = false;
return;
@ -330,6 +360,10 @@ public class CommandTrait extends Trait {
return cost;
}
public double getCost(int id) {
return commands.get(id).cost;
}
public ExecutionMode getExecutionMode() {
return executionMode;
}
@ -338,6 +372,14 @@ public class CommandTrait extends Trait {
return experienceCost;
}
public int getExperienceCost(int id) {
return commands.get(id).experienceCost;
}
public List<ItemStack> getItemCost(int id) {
return commands.get(id).itemCost;
}
private int getNewId() {
int i = 0;
while (commands.containsKey(i)) {
@ -350,6 +392,18 @@ public class CommandTrait extends Trait {
return commands.containsKey(id);
}
public boolean hasCost(int id) {
return commands.get(id).cost != -1;
}
public boolean hasExperienceCost(int id) {
return commands.get(id).experienceCost != -1;
}
public boolean hasItemCost(int id) {
return !commands.get(id).itemCost.isEmpty();
}
public boolean isHideErrorMessages() {
return hideErrorMessages;
}
@ -375,8 +429,7 @@ public class CommandTrait extends Trait {
}
}
private void sendErrorMessage(Player player, CommandTraitError msg, Function<String, String> transform,
Object... objects) {
private void sendErrorMessage(Player player, CommandTraitError msg, Function<String, String> transform, Object... objects) {
if (hideErrorMessages) {
return;
}
@ -399,6 +452,10 @@ public class CommandTrait extends Trait {
this.cost = cost;
}
public void setCost(double cost, int id) {
commands.get(id).cost = cost;
}
public void setCustomErrorMessage(CommandTraitError which, String message) {
customErrorMessages.put(which, message);
}
@ -411,6 +468,10 @@ public class CommandTrait extends Trait {
this.experienceCost = experienceCost;
}
public void setExperienceCost(int experienceCost, int id) {
commands.get(id).experienceCost = experienceCost;
}
public void setHideErrorMessages(boolean hide) {
this.hideErrorMessages = hide;
}
@ -424,6 +485,11 @@ public class CommandTrait extends Trait {
temporaryPermissions.addAll(permissions);
}
public void setItemCost(List<ItemStack> itemCost, int id) {
commands.get(id).itemCost.clear();
commands.get(id).itemCost.addAll(itemCost);
}
public enum CommandTraitError {
MAXIMUM_TIMES_USED(Setting.NPC_COMMAND_MAXIMUM_TIMES_USED_MESSAGE),
MISSING_EXPERIENCE(Setting.NPC_COMMAND_NOT_ENOUGH_EXPERIENCE_MESSAGE),
@ -463,6 +529,7 @@ public class CommandTrait extends Trait {
public static class ItemRequirementGUI extends InventoryMenuPage {
private Inventory inventory;
private CommandTrait trait;
private int id = -1;
private ItemRequirementGUI() {
throw new UnsupportedOperationException();
@ -472,11 +539,23 @@ public class CommandTrait extends Trait {
this.trait = trait;
}
public ItemRequirementGUI(CommandTrait trait, int id) {
this.trait = trait;
this.id = id;
}
@Override
public void initialise(MenuContext ctx) {
this.inventory = ctx.getInventory();
for (ItemStack stack : trait.itemRequirements) {
inventory.addItem(stack.clone());
if (id == -1) {
for (ItemStack stack : trait.itemRequirements) {
inventory.addItem(stack.clone());
}
}
else {
for (ItemStack stack : trait.commands.get(id).itemCost) {
inventory.addItem(stack.clone());
}
}
}
@ -493,8 +572,13 @@ public class CommandTrait extends Trait {
requirements.add(stack);
}
}
this.trait.itemRequirements.clear();
this.trait.itemRequirements.addAll(requirements);
if (id == -1) {
this.trait.itemRequirements.clear();
this.trait.itemRequirements.addAll(requirements);
}
else {
this.trait.setItemCost(requirements, id);
}
}
}
@ -511,9 +595,13 @@ public class CommandTrait extends Trait {
boolean op;
List<String> perms;
boolean player;
double cost;
int experienceCost;
List<ItemStack> itemCost;
public NPCCommand(int id, String command, Hand hand, boolean player, boolean op, int cooldown,
List<String> perms, int n, int delay, int globalCooldown) {
List<String> perms, int n, int delay, int globalCooldown, double cost,
int experienceCost, List<ItemStack> itemCost) {
this.id = id;
this.command = command;
this.hand = hand;
@ -526,6 +614,9 @@ public class CommandTrait extends Trait {
this.globalCooldown = globalCooldown;
List<String> split = Splitter.on(' ').omitEmptyStrings().trimResults().limit(2).splitToList(command);
this.bungeeServer = split.size() == 2 && split.get(0).equalsIgnoreCase("server") ? split.get(1) : null;
this.cost = cost;
this.experienceCost = experienceCost;
this.itemCost = itemCost;
}
public String getEncodedKey() {
@ -549,6 +640,9 @@ public class CommandTrait extends Trait {
boolean op;
List<String> perms = Lists.newArrayList();
boolean player;
double cost = -1;
int experienceCost = -1;
List<ItemStack> itemCost = Lists.newArrayList();
public NPCCommandBuilder(String command, Hand hand) {
this.command = command;
@ -566,7 +660,7 @@ public class CommandTrait extends Trait {
}
private NPCCommand build(int id) {
return new NPCCommand(id, command, hand, player, op, cooldown, perms, n, delay, globalCooldown);
return new NPCCommand(id, command, hand, player, op, cooldown, perms, n, delay, globalCooldown, cost, experienceCost, itemCost);
}
public NPCCommandBuilder command(String command) {
@ -611,6 +705,21 @@ public class CommandTrait extends Trait {
this.player = player;
return this;
}
public NPCCommandBuilder cost(double cost) {
this.cost = cost;
return this;
}
public NPCCommandBuilder experienceCost(int experienceCost) {
this.experienceCost = experienceCost;
return this;
}
public NPCCommandBuilder itemCost(List<ItemStack> itemCost) {
this.itemCost = itemCost;
return this;
}
}
private static class NPCCommandPersister implements Persister<NPCCommand> {
@ -623,10 +732,16 @@ public class CommandTrait extends Trait {
for (DataKey key : root.getRelative("permissions").getIntegerSubKeys()) {
perms.add(key.getString(""));
}
List<ItemStack> items = Lists.newArrayList();
for (DataKey key : root.getRelative("itemCost").getIntegerSubKeys()) {
items.add(ItemStorage.loadItemStack(key));
}
double cost = root.keyExists("cost") ? root.getDouble("cost") : -1;
int exp = root.keyExists("experienceCost") ? root.getInt("experienceCost") : -1;
return new NPCCommand(Integer.parseInt(root.name()), root.getString("command"),
Hand.valueOf(root.getString("hand")), Boolean.valueOf(root.getString("player")),
Boolean.valueOf(root.getString("op")), root.getInt("cooldown"), perms, root.getInt("n"),
root.getInt("delay"), root.getInt("globalcooldown"));
root.getInt("delay"), root.getInt("globalcooldown"), cost, exp, items);
}
@Override
@ -642,6 +757,11 @@ public class CommandTrait extends Trait {
for (int i = 0; i < instance.perms.size(); i++) {
root.setString("permissions." + i, instance.perms.get(i));
}
root.setDouble("cost", instance.cost);
root.setInt("experienceCost", instance.experienceCost);
for (int i = 0; i < instance.itemCost.size(); i++) {
ItemStorage.saveItem(root.getRelative("itemCost." + i), instance.itemCost.get(i));
}
}
}
@ -774,4 +894,4 @@ public class CommandTrait extends Trait {
return StrSubstitutor.replace(t, map, "{", "}");
}
}
}
}

View File

@ -62,8 +62,11 @@ public class Messages {
public static final String COMMAND_ADDED = "citizens.commands.npc.command.command-added";
public static final String COMMAND_AGE_HELP = "citizens.commands.npc.age.help";
public static final String COMMAND_COST_SET = "citizens.commands.npc.command.cost-set";
public static final String COMMAND_INDIVIDUAL_COST_SET = "citizens.commands.npc.command.individual-cost-set";
public static final String COMMAND_MISSING_COST = "citizens.commands.npc.command.cost-missing";
public static final String COMMAND_DESCRIBE_TEMPLATE = "citizens.commands.npc.command.describe-format";
public static final String COMMAND_EXPERIENCE_COST_SET = "citizens.commands.npc.command.experience-cost-set";
public static final String COMMAND_INDIVIDUAL_EXPERIENCE_COST_SET = "citizens.commands.npc.command.individual-experience-cost-set";
public static final String COMMAND_HELP_HEADER = "citizens.commands.help.header";
public static final String COMMAND_HIDE_ERROR_MESSAGES_SET = "citizens.commands.npc.command.hide-error-messages-set";
public static final String COMMAND_HIDE_ERROR_MESSAGES_UNSET = "citizens.commands.npc.command.hide-error-messages-unset";

View File

@ -58,15 +58,18 @@ citizens.commands.npc.command.persist-sequence-set=Command sequences will now be
citizens.commands.npc.command.persist-sequence-unset=Command sequences will no longer be saved across server restarts.
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.individual-cost-set=Set cost per click to [[{0}]] for command id [[{1}]].
citizens.commands.npc.command.cost-missing=Missing cost to set.
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}]].
citizens.commands.npc.command.individual-experience-cost-set=Set experience cost per click to [[{0}]] for command id [[{1}]].
citizens.commands.npc.command.left-hand-header=Commands to run on [[left click]]:
citizens.commands.npc.command.right-hand-header=Commands to run on [[right click]]:
citizens.commands.npc.command.command-removed=Command [[{0}]] removed.
citizens.commands.npc.command.command-added=Command [[{0}]] added with id [[{1}]].
citizens.commands.npc.command.describe-format=<br> - {0} [{1}s] [<click:run_command:/npc cmd remove {2}><hover:show_text:Remove this command><red><u>-</hover></click>]
citizens.commands.npc.command.describe-format=<br> - {0} [{1}s] [cost:{2}] [exp:{3}] [<click:run_command:/npc cmd remove {4}><hover:show_text:Remove this command><red><u>-</hover></click>]
citizens.commands.npc.command.help=<br>Use the [[-l]] flag to make the command run on left click, [[-r]] on right click (default).<br>Set the per-player cooldown before the command can be used again using [[--cooldown]] (in [[seconds]]).<br>Set the server-wide cooldown in seconds using [[--gcooldown]].<br>[[--delay]] will wait the specified amount in [[ticks]] before executing the command.<br>[[--permissions]] will set the command to require specific permissions (separate multiple with commas).<br>[[--n]] will only let the player run the command that number of times.<br>Use [[-o]] to temporarily execute the command as an op and [[-p]] to run the command as the clicking player instead of the server.<br>To give the player temporary permissions instead of op, use [[/npc command permissions]].<br>Set the cost of each click with [[/npc command cost/expcost/itemcost]].<br>Commands can be executed one by one instead of all at once by using [[/npc command sequential]].
citizens.commands.npc.command.unknown-id=Unknown command id [[{0}]] for this NPC.
citizens.commands.npc.command.temporary-permissions-set=Temporary permissions set to [[{0}]].
@ -494,4 +497,4 @@ citizens.sub-plugins.load=Loading {0}
citizens.traits.age-description={0}''s age is [[{1}]]. Locked is [[{2}]].
citizens.waypoints.available-providers-header=List of available providers
citizens.waypoints.current-provider=The current waypoint provider is [[{0}]].
citizens.waypoints.set-provider=Set the waypoint provider to [[{0}]].
citizens.waypoints.set-provider=Set the waypoint provider to [[{0}]].