Citizens2/main/src/main/java/net/citizensnpcs/trait/CommandTrait.java

861 lines
33 KiB
Java
Raw Normal View History

package net.citizensnpcs.trait;
import java.time.Duration;
import java.util.Collection;
2020-06-10 20:24:03 +02:00
import java.util.Collections;
import java.util.Comparator;
import java.util.EnumSet;
2022-11-26 17:32:05 +01:00
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
2022-07-21 17:13:51 +02:00
import java.util.UUID;
import java.util.concurrent.TimeUnit;
import java.util.function.Function;
import org.apache.logging.log4j.core.lookup.StrSubstitutor;
import org.bukkit.Bukkit;
2021-02-01 15:35:28 +01:00
import org.bukkit.Material;
import org.bukkit.command.CommandSender;
2021-02-01 15:35:28 +01:00
import org.bukkit.entity.HumanEntity;
import org.bukkit.entity.Player;
import org.bukkit.event.inventory.InventoryClickEvent;
2021-02-01 15:35:28 +01:00
import org.bukkit.event.inventory.InventoryType;
import org.bukkit.inventory.Inventory;
import org.bukkit.inventory.ItemStack;
import org.bukkit.permissions.PermissionAttachment;
2020-06-10 20:24:03 +02:00
import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.common.collect.Sets;
import com.google.common.io.BaseEncoding;
import net.citizensnpcs.Settings.Setting;
2020-04-15 21:04:42 +02:00
import net.citizensnpcs.api.CitizensAPI;
2020-06-30 09:20:02 +02:00
import net.citizensnpcs.api.event.NPCCommandDispatchEvent;
2021-02-01 15:35:28 +01:00
import net.citizensnpcs.api.gui.InventoryMenuPage;
import net.citizensnpcs.api.gui.InventoryMenuSlot;
2021-02-01 15:35:28 +01:00
import net.citizensnpcs.api.gui.Menu;
import net.citizensnpcs.api.gui.MenuContext;
import net.citizensnpcs.api.npc.NPC;
2019-10-01 08:16:00 +02:00
import net.citizensnpcs.api.persistence.DelegatePersistence;
import net.citizensnpcs.api.persistence.Persist;
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;
import net.citizensnpcs.trait.shop.ItemAction;
import net.citizensnpcs.trait.shop.MoneyAction;
import net.citizensnpcs.trait.shop.NPCShopAction;
import net.citizensnpcs.trait.shop.NPCShopAction.Transaction;
import net.citizensnpcs.util.Messages;
import net.citizensnpcs.util.StringHelper;
import net.citizensnpcs.util.Util;
@TraitName("commandtrait")
public class CommandTrait extends Trait {
2022-07-21 17:13:51 +02:00
@Persist(keyType = Integer.class)
2019-10-01 08:16:00 +02:00
@DelegatePersistence(NPCCommandPersister.class)
2022-07-21 17:13:51 +02:00
private final Map<Integer, NPCCommand> commands = Maps.newHashMap();
@Persist
2020-06-30 09:20:02 +02:00
private double cost = -1;
@Persist
2022-10-24 16:01:40 +02:00
private final Map<CommandTraitError, String> customErrorMessages = Maps.newEnumMap(CommandTraitError.class);
private final Map<String, Set<CommandTraitError>> executionErrors = Maps.newHashMap();
2020-06-30 09:20:02 +02:00
@Persist
private ExecutionMode executionMode = ExecutionMode.LINEAR;
2020-06-10 20:24:03 +02:00
@Persist
private int experienceCost = -1;
@Persist(valueType = Long.class)
2020-12-16 11:12:37 +01:00
private final Map<String, Long> globalCooldowns = Maps.newHashMap();
@Persist
2021-11-19 16:32:28 +01:00
private boolean hideErrorMessages;
@Persist
private final List<ItemStack> itemRequirements = Lists.newArrayList();
2024-02-04 17:05:51 +01:00
private int lastUsedId = -1;
2022-11-28 16:10:50 +01:00
@Persist
private boolean persistSequence = false;
@Persist(keyType = UUID.class, reify = true, value = "cooldowns")
private final Map<UUID, PlayerNPCCommand> playerTracking = Maps.newHashMap();
2021-02-01 15:35:28 +01:00
@Persist
private final List<String> temporaryPermissions = Lists.newArrayList();
public CommandTrait() {
super("commandtrait");
}
public int addCommand(NPCCommandBuilder builder) {
int id = getNewId();
2022-07-21 17:13:51 +02:00
commands.put(id, builder.build(id));
return id;
}
2023-07-11 05:25:26 +02:00
private Transaction chargeCommandCosts(Player player, Hand hand, NPCCommand command) {
NPCShopAction action = null;
if (player.hasPermission("citizens.npc.command.ignoreerrors.*"))
return Transaction.success();
if (cost > 0 && !player.hasPermission("citizens.npc.command.ignoreerrors.cost")) {
action = new MoneyAction(cost);
if (!action.take(player, null, 1).isPossible()) {
sendErrorMessage(player, CommandTraitError.MISSING_MONEY, null, cost);
2020-06-30 09:20:02 +02:00
}
}
if (experienceCost > 0 && !player.hasPermission("citizens.npc.command.ignoreerrors.expcost")) {
action = new ExperienceAction(experienceCost);
if (!action.take(player, null, 1).isPossible()) {
2022-10-24 16:01:40 +02:00
sendErrorMessage(player, CommandTraitError.MISSING_EXPERIENCE, null, experienceCost);
2021-11-01 13:56:54 +01:00
}
}
if (itemRequirements.size() > 0 && !player.hasPermission("citizens.npc.command.ignoreerrors.itemcost")) {
action = new ItemAction(itemRequirements);
if (!action.take(player, null, 1).isPossible()) {
ItemStack stack = itemRequirements.get(0);
sendErrorMessage(player, CommandTraitError.MISSING_ITEM, null, Util.prettyEnum(stack.getType()),
stack.getAmount());
2021-02-01 15:35:28 +01:00
}
}
2023-07-11 05:25:26 +02:00
if (command.cost != -1 && !player.hasPermission("citizens.npc.command.ignoreerrors.cost")) {
action = new MoneyAction(command.cost);
if (!action.take(player, null, 1).isPossible()) {
2023-07-11 05:25:26 +02:00
sendErrorMessage(player, CommandTraitError.MISSING_MONEY, null, command.cost);
}
}
2023-07-11 05:25:26 +02:00
if (command.experienceCost != -1 && !player.hasPermission("citizens.npc.command.ignoreerrors.expcost")) {
action = new ExperienceAction(command.experienceCost);
if (!action.take(player, null, 1).isPossible()) {
2023-07-11 05:25:26 +02:00
sendErrorMessage(player, CommandTraitError.MISSING_EXPERIENCE, null, command.experienceCost);
}
}
2023-07-11 05:25:26 +02:00
if (command.itemCost != null && command.itemCost.size() > 0
&& !player.hasPermission("citizens.npc.command.ignoreerrors.itemcost")) {
action = new ItemAction(command.itemCost);
if (!action.take(player, null, 1).isPossible()) {
2023-07-11 05:25:26 +02:00
ItemStack stack = command.itemCost.get(0);
sendErrorMessage(player, CommandTraitError.MISSING_ITEM, null, Util.prettyEnum(stack.getType()),
stack.getAmount());
}
}
return action == null ? Transaction.success() : action.take(player, null, 1);
2020-06-30 09:20:02 +02:00
}
public void clear() {
commands.clear();
}
public void clearHistory(CommandTraitError which, UUID who) {
2022-10-24 16:01:40 +02:00
Collection<PlayerNPCCommand> toClear = Lists.newArrayList();
if (who != null) {
toClear.add(playerTracking.get(who));
2022-10-24 16:01:40 +02:00
} else {
toClear.addAll(playerTracking.values());
}
switch (which) {
case MAXIMUM_TIMES_USED:
for (PlayerNPCCommand tracked : toClear) {
tracked.nUsed.clear();
}
break;
case ON_COOLDOWN:
for (PlayerNPCCommand tracked : toClear) {
tracked.lastUsed.clear();
}
break;
case ON_GLOBAL_COOLDOWN:
globalCooldowns.clear();
break;
default:
return;
}
}
public void clearPlayerHistory(UUID who) {
if (who == null) {
playerTracking.clear();
} else {
playerTracking.remove(who);
}
}
/**
2020-02-14 15:48:40 +01:00
* Send a brief description of the current state of the trait to the supplied {@link CommandSender}.
*/
public void describe(CommandSender sender) {
List<NPCCommand> left = Lists.newArrayList();
List<NPCCommand> right = Lists.newArrayList();
for (NPCCommand command : commands.values()) {
2022-11-28 16:10:50 +01:00
if (command.hand == Hand.LEFT || command.hand == Hand.SHIFT_LEFT || command.hand == Hand.BOTH) {
left.add(command);
2020-01-17 10:11:23 +01:00
}
2022-11-28 16:10:50 +01:00
if (command.hand == Hand.RIGHT || command.hand == Hand.SHIFT_RIGHT || command.hand == Hand.BOTH) {
right.add(command);
}
}
List<String> outputList = Lists.newArrayList();
2022-02-19 07:36:45 +01:00
if (cost > 0) {
outputList.add("Cost: " + StringHelper.wrap(cost));
2022-02-19 07:36:45 +01:00
}
if (experienceCost > 0) {
outputList.add("XP cost: " + StringHelper.wrap(experienceCost));
2022-02-19 07:36:45 +01:00
}
if (left.size() > 0) {
outputList.add(Messaging.tr(Messages.COMMAND_LEFT_HAND_HEADER));
for (NPCCommand command : left) {
outputList.add(describe(command));
}
}
if (right.size() > 0) {
outputList.add(Messaging.tr(Messages.COMMAND_RIGHT_HAND_HEADER));
for (NPCCommand command : right) {
outputList.add(describe(command));
}
}
if (outputList.isEmpty()) {
outputList.add(Messaging.tr(Messages.COMMAND_NO_COMMANDS_ADDED));
2022-06-02 14:15:45 +02:00
} else {
outputList.add(0, executionMode.toString());
}
StringBuilder output = new StringBuilder();
for (String item : outputList) {
output.append(item);
output.append(" ");
}
Messaging.send(sender, output.toString().trim());
}
2020-02-22 05:57:03 +01:00
private String describe(NPCCommand command) {
2023-07-11 05:25:26 +02:00
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(command.cost > 0 ? command.cost : "default"),
StringHelper.wrap(command.experienceCost > 0 ? command.experienceCost : "default"), command.id);
2022-02-13 10:33:44 +01:00
if (command.globalCooldown > 0) {
output += "[global " + StringHelper.wrap(command.globalCooldown) + "s]";
}
if (command.delay > 0) {
output += "[delay " + StringHelper.wrap(command.delay) + "t]";
}
if (command.n > 0) {
output += "[" + StringHelper.wrap(command.n) + " uses]";
}
2020-02-22 05:57:03 +01:00
if (command.op) {
output += " -o";
}
if (command.player) {
output += " -p";
}
return output;
}
public void dispatch(Player player, Hand handIn) {
Hand hand = player.isSneaking()
? handIn == CommandTrait.Hand.LEFT ? CommandTrait.Hand.SHIFT_LEFT : CommandTrait.Hand.SHIFT_RIGHT
2022-11-28 16:10:50 +01:00
: handIn;
2020-06-30 09:20:02 +02:00
NPCCommandDispatchEvent event = new NPCCommandDispatchEvent(npc, player);
Bukkit.getServer().getPluginManager().callEvent(event);
2023-09-03 17:41:38 +02:00
if (event.isCancelled())
2020-06-30 09:20:02 +02:00
return;
2023-09-03 17:41:38 +02:00
2020-04-15 21:04:42 +02:00
Runnable task = new Runnable() {
Boolean charged = null;
2020-04-15 21:04:42 +02:00
@Override
public void run() {
List<NPCCommand> commandList = Lists.newArrayList(Iterables.filter(commands.values(),
command -> (command.hand == hand || command.hand == Hand.BOTH)));
2020-07-15 14:19:43 +02:00
if (executionMode == ExecutionMode.RANDOM) {
if (commandList.size() > 0) {
2024-01-26 16:43:50 +01:00
runCommand(player, hand, commandList.get(Util.getFastRandom().nextInt(commandList.size())));
2020-07-15 14:19:43 +02:00
}
return;
}
2020-06-10 20:24:03 +02:00
int max = -1;
2024-02-04 17:05:51 +01:00
if (executionMode == ExecutionMode.SEQUENTIAL || executionMode == ExecutionMode.CYCLE) {
Collections.sort(commandList, Comparator.comparing(o1 -> o1.id));
max = commandList.size() > 0 ? commandList.get(commandList.size() - 1).id : -1;
2020-06-10 20:24:03 +02:00
}
if (executionMode == ExecutionMode.LINEAR) {
2022-10-24 16:01:40 +02:00
executionErrors.put(player.getUniqueId().toString(), EnumSet.noneOf(CommandTraitError.class));
}
2020-06-10 20:24:03 +02:00
for (NPCCommand command : commandList) {
2024-01-26 11:55:11 +01:00
PlayerNPCCommand info = null;
2024-02-04 17:05:51 +01:00
if (executionMode == ExecutionMode.CYCLE) {
if (command.id <= lastUsedId) {
if (lastUsedId != max)
continue;
lastUsedId = -1;
}
}
2024-01-26 11:55:11 +01:00
if (executionMode == ExecutionMode.SEQUENTIAL
&& (info = playerTracking.get(player.getUniqueId())) != null) {
if (info.lastUsedHand != hand) {
info.lastUsedHand = hand;
info.lastUsedId = -1;
}
2024-01-26 11:55:11 +01:00
if (command.id <= info.lastUsedId) {
if (info.lastUsedId != max)
2020-06-11 10:23:48 +02:00
continue;
2024-01-26 11:55:11 +01:00
info.lastUsedId = -1;
2020-06-10 20:24:03 +02:00
}
}
2024-01-26 16:43:50 +01:00
runCommand(player, hand, command);
2024-02-04 17:05:51 +01:00
if (executionMode == ExecutionMode.SEQUENTIAL || executionMode == ExecutionMode.CYCLE
|| (charged != null && !charged))
2024-01-26 11:55:11 +01:00
break;
}
}
2024-01-26 16:43:50 +01:00
private void runCommand(Player player, Hand hand, NPCCommand command) {
2022-11-26 17:32:05 +01:00
Runnable runnable = () -> {
PlayerNPCCommand info = playerTracking.get(player.getUniqueId());
if (info == null && (executionMode == ExecutionMode.SEQUENTIAL
|| PlayerNPCCommand.requiresTracking(command))) {
playerTracking.put(player.getUniqueId(), info = new PlayerNPCCommand());
}
Transaction charge = null;
2022-11-26 17:32:05 +01:00
if (charged == null) {
2023-07-11 05:25:26 +02:00
charge = chargeCommandCosts(player, hand, command);
if (!charge.isPossible()) {
2022-11-26 17:32:05 +01:00
charged = false;
return;
}
2022-11-26 17:32:05 +01:00
}
2024-01-26 16:43:50 +01:00
if (info != null && !info.canUse(CommandTrait.this, player, hand, command))
return;
if (charged == null) {
charge.run();
}
2022-11-26 17:32:05 +01:00
if (temporaryPermissions.size() > 0) {
PermissionAttachment attachment = player.addAttachment(CitizensAPI.getPlugin());
if (attachment != null) {
for (String permission : temporaryPermissions) {
attachment.setPermission(permission, true);
}
command.run(npc, player);
attachment.remove();
return;
}
2020-04-15 21:04:42 +02:00
}
2022-11-26 17:32:05 +01:00
command.run(npc, player);
};
if (command.delay <= 0) {
runnable.run();
} else {
Bukkit.getScheduler().scheduleSyncDelayedTask(CitizensAPI.getPlugin(), runnable, command.delay);
2020-04-15 21:04:42 +02:00
}
2020-02-22 05:57:03 +01:00
}
2020-04-15 21:04:42 +02:00
};
if (Bukkit.isPrimaryThread()) {
task.run();
} else {
Bukkit.getScheduler().scheduleSyncDelayedTask(CitizensAPI.getPlugin(), task);
}
}
2020-10-07 13:16:41 +02:00
public double getCost() {
return cost;
}
public ExecutionMode getExecutionMode() {
return executionMode;
}
public int getExperienceCost() {
2021-11-01 13:56:54 +01:00
return experienceCost;
}
private int getNewId() {
int i = 0;
2022-07-21 17:13:51 +02:00
while (commands.containsKey(i)) {
i++;
}
return i;
}
public boolean hasCommandId(int id) {
2022-07-21 17:13:51 +02:00
return commands.containsKey(id);
}
2021-11-19 16:32:28 +01:00
public boolean isHideErrorMessages() {
return hideErrorMessages;
}
2022-11-28 16:10:50 +01:00
public boolean persistSequence() {
return persistSequence;
}
public void removeCommandById(int id) {
2022-07-21 17:13:51 +02:00
commands.remove(id);
}
@Override
public void save(DataKey key) {
Collection<NPCCommand> commands = this.commands.values();
2022-11-26 17:32:05 +01:00
for (Iterator<PlayerNPCCommand> itr = playerTracking.values().iterator(); itr.hasNext();) {
PlayerNPCCommand playerCommand = itr.next();
playerCommand.prune(globalCooldowns, commands);
2022-11-28 16:10:50 +01:00
if (playerCommand.lastUsed.isEmpty() && playerCommand.nUsed.isEmpty()
&& (!persistSequence || playerCommand.lastUsedId == -1)) {
2022-11-26 17:32:05 +01:00
itr.remove();
}
}
}
2023-07-11 05:25:26 +02:00
private void sendErrorMessage(Player player, CommandTraitError msg, Function<String, String> transform,
Object... objects) {
if (hideErrorMessages)
2021-11-19 16:32:28 +01:00
return;
2022-10-24 16:01:40 +02:00
Set<CommandTraitError> sent = executionErrors.get(player.getUniqueId().toString());
if (sent != null) {
if (sent.contains(msg))
return;
sent.add(msg);
}
String messageRaw = customErrorMessages.getOrDefault(msg, msg.setting.asString());
if (transform != null) {
messageRaw = transform.apply(messageRaw);
}
if (messageRaw != null && messageRaw.trim().length() > 0) {
Messaging.send(player, Translator.format(messageRaw, objects));
}
}
2020-06-30 09:20:02 +02:00
public void setCost(double cost) {
this.cost = cost;
}
2022-10-24 16:01:40 +02:00
public void setCustomErrorMessage(CommandTraitError which, String message) {
customErrorMessages.put(which, message);
}
public void setExecutionMode(ExecutionMode mode) {
executionMode = mode;
2020-06-10 20:24:03 +02:00
}
public void setExperienceCost(int experienceCost) {
2021-11-01 13:56:54 +01:00
this.experienceCost = experienceCost;
}
2021-11-19 16:32:28 +01:00
public void setHideErrorMessages(boolean hide) {
hideErrorMessages = hide;
2021-11-19 16:32:28 +01:00
}
2022-11-28 16:10:50 +01:00
public void setPersistSequence(boolean persistSequence) {
this.persistSequence = persistSequence;
}
public void setTemporaryPermissions(List<String> permissions) {
temporaryPermissions.clear();
temporaryPermissions.addAll(permissions);
}
2022-10-24 16:01:40 +02:00
public enum CommandTraitError {
MAXIMUM_TIMES_USED(Setting.NPC_COMMAND_MAXIMUM_TIMES_USED_MESSAGE),
2021-11-01 13:56:54 +01:00
MISSING_EXPERIENCE(Setting.NPC_COMMAND_NOT_ENOUGH_EXPERIENCE_MESSAGE),
2021-02-01 15:35:28 +01:00
MISSING_ITEM(Setting.NPC_COMMAND_MISSING_ITEM_MESSAGE),
MISSING_MONEY(Setting.NPC_COMMAND_NOT_ENOUGH_MONEY_MESSAGE),
NO_PERMISSION(Setting.NPC_COMMAND_NO_PERMISSION_MESSAGE),
2020-12-16 11:12:37 +01:00
ON_COOLDOWN(Setting.NPC_COMMAND_ON_COOLDOWN_MESSAGE),
ON_GLOBAL_COOLDOWN(Setting.NPC_COMMAND_ON_GLOBAL_COOLDOWN_MESSAGE);
private final Setting setting;
2022-10-24 16:01:40 +02:00
CommandTraitError(Setting setting) {
this.setting = setting;
}
}
public enum ExecutionMode {
2024-02-04 17:05:51 +01:00
CYCLE,
LINEAR,
RANDOM,
SEQUENTIAL;
@Override
public String toString() {
2022-06-02 14:15:45 +02:00
return name().charAt(0) + name().substring(1).toLowerCase();
}
}
2019-10-01 08:16:00 +02:00
public static enum Hand {
2020-02-14 15:48:40 +01:00
BOTH,
LEFT,
2022-11-28 16:10:50 +01:00
RIGHT,
SHIFT_LEFT,
SHIFT_RIGHT;
}
2021-02-01 15:35:28 +01:00
@Menu(title = "Drag items for requirements", type = InventoryType.CHEST, dimensions = { 5, 9 })
public static class ItemRequirementGUI extends InventoryMenuPage {
2023-07-11 05:25:26 +02:00
private int id = -1;
2021-02-01 15:35:28 +01:00
private Inventory inventory;
private CommandTrait trait;
private ItemRequirementGUI() {
throw new UnsupportedOperationException();
}
public ItemRequirementGUI(CommandTrait trait) {
this.trait = trait;
}
public ItemRequirementGUI(CommandTrait trait, int id) {
this.trait = trait;
this.id = id;
}
2021-02-01 15:35:28 +01:00
@Override
public void initialise(MenuContext ctx) {
inventory = ctx.getInventory();
if (id == -1) {
for (ItemStack stack : trait.itemRequirements) {
inventory.addItem(stack.clone());
}
2023-07-11 05:25:26 +02:00
} else {
for (ItemStack stack : trait.commands.get(id).itemCost) {
inventory.addItem(stack.clone());
}
2021-02-01 15:35:28 +01:00
}
}
@Override
public void onClick(InventoryMenuSlot slot, InventoryClickEvent event) {
event.setCancelled(false);
}
2021-02-01 15:35:28 +01:00
@Override
public void onClose(HumanEntity player) {
List<ItemStack> requirements = Lists.newArrayList();
for (ItemStack stack : inventory.getContents()) {
if (stack != null && stack.getType() != Material.AIR) {
requirements.add(stack);
}
}
if (id == -1) {
trait.itemRequirements.clear();
trait.itemRequirements.addAll(requirements);
2023-07-11 05:25:26 +02:00
} else {
trait.commands.get(id).itemCost.clear();
trait.commands.get(id).itemCost.addAll(requirements);
}
2021-02-01 15:35:28 +01:00
}
}
private static class NPCCommand {
String command;
2020-02-22 05:57:03 +01:00
int cooldown;
2023-07-11 05:25:26 +02:00
double cost;
int delay;
2023-07-11 05:25:26 +02:00
int experienceCost;
2020-12-16 11:12:37 +01:00
int globalCooldown;
Hand hand;
2020-06-10 20:24:03 +02:00
int id;
2023-07-11 05:25:26 +02:00
List<ItemStack> itemCost;
String key;
int n;
2019-12-29 14:14:38 +01:00
boolean op;
2020-03-02 16:40:13 +01:00
List<String> perms;
2020-02-22 05:57:03 +01:00
boolean player;
2020-06-10 20:24:03 +02:00
public NPCCommand(int id, String command, Hand hand, boolean player, boolean op, int cooldown,
2023-07-11 05:25:26 +02:00
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;
this.player = player;
2019-12-29 14:14:38 +01:00
this.op = op;
2020-02-22 05:57:03 +01:00
this.cooldown = cooldown;
2020-03-02 16:40:13 +01:00
this.perms = perms;
this.n = n;
this.delay = delay;
2020-12-16 11:12:37 +01:00
this.globalCooldown = globalCooldown;
this.cost = cost;
this.experienceCost = experienceCost;
this.itemCost = itemCost;
}
public String getEncodedKey() {
if (key != null)
return key;
return key = BaseEncoding.base64().encode(command.getBytes());
}
public void run(NPC npc, Player clicker) {
2022-12-10 06:42:07 +01:00
Util.runCommand(npc, clicker, command, op, player);
}
}
public static class NPCCommandBuilder {
String command;
int cooldown;
2023-07-11 05:25:26 +02:00
double cost = -1;
int delay;
2023-07-11 05:25:26 +02:00
int experienceCost = -1;
int globalCooldown;
Hand hand;
2023-07-11 05:25:26 +02:00
List<ItemStack> itemCost = Lists.newArrayList();
int n = -1;
boolean op;
List<String> perms = Lists.newArrayList();
boolean player;
public NPCCommandBuilder(String command, Hand hand) {
this.command = command;
this.hand = hand;
}
public NPCCommandBuilder addPerm(String permission) {
perms.add(permission);
return this;
}
public NPCCommandBuilder addPerms(List<String> perms) {
this.perms.addAll(perms);
return this;
}
2020-06-10 20:24:03 +02:00
private NPCCommand build(int id) {
2023-07-11 05:25:26 +02:00
return new NPCCommand(id, command, hand, player, op, cooldown, perms, n, delay, globalCooldown, cost,
experienceCost, itemCost);
}
public NPCCommandBuilder command(String command) {
this.command = command;
return this;
}
public NPCCommandBuilder cooldown(Duration cd) {
2023-03-14 07:31:07 +01:00
return cooldown(Util.convert(TimeUnit.SECONDS, cd));
}
public NPCCommandBuilder cooldown(int cooldown) {
this.cooldown = cooldown;
return this;
}
2023-07-11 05:25:26 +02:00
public NPCCommandBuilder cost(double cost) {
this.cost = cost;
return this;
}
2023-10-13 15:49:21 +02:00
public NPCCommandBuilder delay(Duration delay) {
this.delay = Util.toTicks(delay);
return this;
}
2023-07-11 05:25:26 +02:00
public NPCCommandBuilder experienceCost(int experienceCost) {
this.experienceCost = experienceCost;
return this;
}
public NPCCommandBuilder globalCooldown(Duration cd) {
2023-03-14 07:31:07 +01:00
return globalCooldown(Util.convert(TimeUnit.SECONDS, cd));
}
2020-12-16 11:12:37 +01:00
public NPCCommandBuilder globalCooldown(int cooldown) {
globalCooldown = cooldown;
2020-12-16 11:12:37 +01:00
return this;
}
2023-07-11 05:25:26 +02:00
public NPCCommandBuilder itemCost(List<ItemStack> itemCost) {
this.itemCost = itemCost;
return this;
}
public NPCCommandBuilder n(int n) {
this.n = n;
return this;
}
public NPCCommandBuilder op(boolean op) {
this.op = op;
return this;
}
public NPCCommandBuilder player(boolean player) {
this.player = player;
return this;
}
}
private static class NPCCommandPersister implements Persister<NPCCommand> {
public NPCCommandPersister() {
}
@Override
public NPCCommand create(DataKey root) {
2020-03-02 16:40:13 +01:00
List<String> perms = Lists.newArrayList();
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;
2020-06-10 20:24:03 +02:00
return new NPCCommand(Integer.parseInt(root.name()), root.getString("command"),
Hand.valueOf(root.getString("hand")), Boolean.parseBoolean(root.getString("player")),
Boolean.parseBoolean(root.getString("op")), root.getInt("cooldown"), perms, root.getInt("n"),
root.getInt("delay"), root.getInt("globalcooldown"), cost, exp, items);
}
@Override
public void save(NPCCommand instance, DataKey root) {
root.setString("command", instance.command);
root.setString("hand", instance.hand.name());
root.setBoolean("player", instance.player);
2019-12-29 14:14:38 +01:00
root.setBoolean("op", instance.op);
2020-02-22 05:57:03 +01:00
root.setInt("cooldown", instance.cooldown);
2020-12-16 11:12:37 +01:00
root.setInt("globalcooldown", instance.globalCooldown);
root.setInt("n", instance.n);
root.setInt("delay", instance.delay);
2020-03-02 16:40:13 +01:00
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));
}
2020-02-22 05:57:03 +01:00
}
}
private static class PlayerNPCCommand {
2020-04-26 06:53:00 +02:00
@Persist(valueType = Long.class)
2020-02-22 05:57:03 +01:00
Map<String, Long> lastUsed = Maps.newHashMap();
@Persist
Hand lastUsedHand;
@Persist
2020-06-10 20:24:03 +02:00
int lastUsedId = -1;
@Persist
Map<String, Integer> nUsed = Maps.newHashMap();
2020-02-22 05:57:03 +01:00
public PlayerNPCCommand() {
}
2024-01-26 16:43:50 +01:00
public boolean canUse(CommandTrait trait, Player player, Hand hand, NPCCommand command) {
2020-03-02 16:40:13 +01:00
for (String perm : command.perms) {
2020-03-03 16:31:04 +01:00
if (!player.hasPermission(perm)) {
2022-10-24 16:01:40 +02:00
trait.sendErrorMessage(player, CommandTraitError.NO_PERMISSION, null);
2020-03-02 16:40:13 +01:00
return false;
2020-03-03 16:31:04 +01:00
}
2020-03-02 16:40:13 +01:00
}
long globalDelay = Setting.NPC_COMMAND_GLOBAL_COMMAND_COOLDOWN.asSeconds();
2020-02-22 05:57:03 +01:00
long currentTimeSec = System.currentTimeMillis() / 1000;
String commandKey = command.getEncodedKey();
if (!player.hasPermission("citizens.npc.command.ignoreerrors.cooldown")
&& lastUsed.containsKey(commandKey)) {
long deadline = ((Number) lastUsed.get(commandKey)).longValue()
+ (command.cooldown != 0 ? command.cooldown : globalDelay);
2021-06-13 08:53:19 +02:00
if (currentTimeSec < deadline) {
long seconds = deadline - currentTimeSec;
2022-10-24 16:01:40 +02:00
trait.sendErrorMessage(player, CommandTraitError.ON_COOLDOWN,
new TimeVariableFormatter(seconds, TimeUnit.SECONDS), seconds);
2020-02-22 05:57:03 +01:00
return false;
}
lastUsed.remove(commandKey);
2020-02-22 05:57:03 +01:00
}
if (!player.hasPermission("citizens.npc.command.ignoreerrors.globalcooldown") && command.globalCooldown > 0
&& trait.globalCooldowns.containsKey(commandKey)) {
2022-02-13 10:33:44 +01:00
long deadline = ((Number) trait.globalCooldowns.get(commandKey)).longValue() + command.globalCooldown;
2021-06-13 08:53:19 +02:00
if (currentTimeSec < deadline) {
long seconds = deadline - currentTimeSec;
2022-10-24 16:01:40 +02:00
trait.sendErrorMessage(player, CommandTraitError.ON_GLOBAL_COOLDOWN,
new TimeVariableFormatter(seconds, TimeUnit.SECONDS), seconds);
2020-12-16 11:12:37 +01:00
return false;
}
trait.globalCooldowns.remove(commandKey);
}
2023-02-10 16:44:54 +01:00
int timesUsed = nUsed.getOrDefault(commandKey, 0);
if (!player.hasPermission("citizens.npc.command.ignoreerrors.nused") && command.n > 0
&& command.n <= timesUsed) {
2022-10-24 16:01:40 +02:00
trait.sendErrorMessage(player, CommandTraitError.MAXIMUM_TIMES_USED, null, command.n);
return false;
}
if (command.cooldown > 0 || globalDelay > 0) {
lastUsed.put(commandKey, currentTimeSec);
2020-02-22 05:57:03 +01:00
}
2020-12-16 11:12:37 +01:00
if (command.globalCooldown > 0) {
trait.globalCooldowns.put(commandKey, currentTimeSec);
}
if (command.n > 0) {
2023-02-10 16:44:54 +01:00
nUsed.put(commandKey, timesUsed + 1);
}
2020-06-10 20:24:03 +02:00
lastUsedId = command.id;
2024-01-26 16:43:50 +01:00
lastUsedHand = hand;
2020-02-22 05:57:03 +01:00
return true;
}
public void prune(Map<String, Long> globalCooldowns, Collection<NPCCommand> commands) {
long currentTimeSec = System.currentTimeMillis() / 1000;
Set<String> commandKeys = Sets.newHashSet();
for (NPCCommand command : commands) {
String commandKey = command.getEncodedKey();
commandKeys.add(commandKey);
Number number = lastUsed.get(commandKey);
if (number != null && number.longValue() + (command.cooldown != 0 ? command.cooldown
2023-10-13 15:49:21 +02:00
: Setting.NPC_COMMAND_GLOBAL_COMMAND_COOLDOWN.asSeconds()) < currentTimeSec) {
lastUsed.remove(commandKey);
}
if (globalCooldowns != null) {
number = globalCooldowns.get(commandKey);
2023-10-13 15:49:21 +02:00
if (number != null && number.longValue() + command.globalCooldown < currentTimeSec) {
globalCooldowns.remove(commandKey);
}
}
}
Set<String> diff = Sets.newHashSet(lastUsed.keySet());
diff.removeAll(commandKeys);
for (String key : diff) {
lastUsed.remove(key);
nUsed.remove(key);
}
if (globalCooldowns != null) {
diff = Sets.newHashSet(globalCooldowns.keySet());
diff.removeAll(commandKeys);
for (String key : diff) {
globalCooldowns.remove(key);
}
}
}
public static boolean requiresTracking(NPCCommand command) {
return command.globalCooldown > 0 || command.cooldown > 0 || command.n > 0
|| command.perms != null && command.perms.size() > 0
|| Setting.NPC_COMMAND_GLOBAL_COMMAND_COOLDOWN.asSeconds() > 0;
}
2020-02-22 05:57:03 +01:00
}
private static class TimeVariableFormatter implements Function<String, String> {
private final Map<String, String> map = Maps.newHashMapWithExpectedSize(5);
public TimeVariableFormatter(long source, TimeUnit unit) {
long seconds = TimeUnit.SECONDS.convert(source, unit);
long minutes = TimeUnit.MINUTES.convert(source, unit);
long hours = TimeUnit.HOURS.convert(source, unit);
long days = TimeUnit.DAYS.convert(source, unit);
map.put("seconds", "" + seconds);
map.put("seconds_over", "" + (seconds - TimeUnit.SECONDS.convert(minutes, TimeUnit.MINUTES)));
map.put("minutes", "" + minutes);
map.put("minutes_over", "" + (minutes - TimeUnit.MINUTES.convert(hours, TimeUnit.HOURS)));
map.put("hours", "" + hours);
map.put("hours_over", "" + (hours - TimeUnit.HOURS.convert(days, TimeUnit.DAYS)));
map.put("days", "" + days);
}
@Override
public String apply(String t) {
return StrSubstitutor.replace(t, map, "{", "}");
}
}
}