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

481 lines
17 KiB
Java
Raw Normal View History

package net.citizensnpcs.trait;
import java.util.Arrays;
2020-06-10 20:24:03 +02:00
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.Map;
import org.bukkit.Bukkit;
import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player;
import org.bukkit.permissions.PermissionAttachment;
2020-06-30 09:20:02 +02:00
import org.bukkit.plugin.RegisteredServiceProvider;
2020-06-10 20:24:03 +02:00
import com.google.common.base.Predicate;
import com.google.common.base.Splitter;
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.io.BaseEncoding;
import com.google.common.io.ByteArrayDataOutput;
import com.google.common.io.ByteStreams;
2020-04-15 21:04:42 +02:00
import net.citizensnpcs.api.CitizensAPI;
import net.citizensnpcs.api.command.CommandMessages;
2020-06-30 09:20:02 +02:00
import net.citizensnpcs.api.event.NPCCommandDispatchEvent;
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;
2020-02-22 05:57:03 +01:00
import net.citizensnpcs.api.persistence.PersistenceLoader;
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.Messaging;
2020-01-16 12:40:32 +01:00
import net.citizensnpcs.api.util.Placeholders;
import net.citizensnpcs.util.Messages;
import net.citizensnpcs.util.StringHelper;
import net.citizensnpcs.util.Util;
2020-06-30 09:20:02 +02:00
import net.milkbowl.vault.economy.Economy;
@TraitName("commandtrait")
public class CommandTrait extends Trait {
@Persist
2019-10-01 08:16:00 +02:00
@DelegatePersistence(NPCCommandPersister.class)
private final Map<String, NPCCommand> commands = Maps.newHashMap();
2020-02-22 05:57:03 +01:00
@Persist
@DelegatePersistence(PlayerNPCCommandPersister.class)
2020-03-07 05:43:16 +01:00
private final Map<String, PlayerNPCCommand> cooldowns = Maps.newHashMap();
@Persist
2020-06-30 09:20:02 +02:00
private double cost = -1;
@Persist
private ExecutionMode executionMode = ExecutionMode.LINEAR;
2020-06-10 20:24:03 +02:00
@Persist
private final List<String> temporaryPermissions = Lists.newArrayList();
public CommandTrait() {
super("commandtrait");
}
public int addCommand(NPCCommandBuilder builder) {
int id = getNewId();
2020-06-10 20:24:03 +02:00
commands.put(String.valueOf(id), builder.build(id));
return id;
}
2020-06-30 09:20:02 +02:00
private boolean checkPreconditions(Player player, Hand hand) {
if (cost > 0) {
try {
RegisteredServiceProvider<Economy> provider = Bukkit.getServicesManager()
.getRegistration(Economy.class);
if (provider != null && provider.getProvider() != null) {
Economy economy = provider.getProvider();
if (!economy.has(player, cost))
return false;
economy.withdrawPlayer(player, cost);
}
} catch (NoClassDefFoundError e) {
2020-07-09 17:37:45 +02:00
Messaging.severe("Unable to find Vault when checking command cost - is it installed?");
2020-06-30 09:20:02 +02:00
}
}
return true;
}
/**
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()) {
2020-01-17 10:11:23 +01:00
if (command.hand == Hand.LEFT || command.hand == Hand.BOTH) {
left.add(command);
2020-01-17 10:11:23 +01:00
}
if (command.hand == Hand.RIGHT || command.hand == Hand.BOTH) {
right.add(command);
}
}
String output = "";
if (left.size() > 0) {
output += Messaging.tr(Messages.COMMAND_LEFT_HAND_HEADER);
for (NPCCommand command : left) {
2020-02-22 05:57:03 +01:00
output += describe(command);
}
}
if (right.size() > 0) {
output += Messaging.tr(Messages.COMMAND_RIGHT_HAND_HEADER);
for (NPCCommand command : right) {
2020-02-22 05:57:03 +01:00
output += describe(command);
}
}
if (output.isEmpty()) {
output = Messaging.tr(Messages.COMMAND_NO_COMMANDS_ADDED);
}
Messaging.send(sender, output);
}
2020-02-22 05:57:03 +01:00
private String describe(NPCCommand command) {
String output = "<br> - [" + StringHelper.wrap(command.id) + "]: " + command.command + " ["
+ StringHelper.wrap(command.cooldown + "s") + "]";
2020-02-22 05:57:03 +01:00
if (command.op) {
output += " -o";
}
if (command.player) {
output += " -p";
}
return output;
}
2020-04-15 21:04:42 +02:00
public void dispatch(final Player player, final Hand hand) {
2020-06-30 09:20:02 +02:00
NPCCommandDispatchEvent event = new NPCCommandDispatchEvent(npc, player);
event.setCancelled(!checkPreconditions(player, hand));
Bukkit.getServer().getPluginManager().callEvent(event);
if (event.isCancelled()) {
return;
}
2020-04-15 21:04:42 +02:00
Runnable task = new Runnable() {
@Override
public void run() {
List<NPCCommand> commandList = Lists
.newArrayList(Iterables.filter(commands.values(), new Predicate<NPCCommand>() {
@Override
public boolean apply(NPCCommand command) {
return command.hand == hand || command.hand == Hand.BOTH;
}
}));
2020-07-15 14:19:43 +02:00
if (executionMode == ExecutionMode.RANDOM) {
if (commandList.size() > 0) {
runCommand(player, commandList.get(Util.getFastRandom().nextInt(commandList.size())));
}
return;
}
2020-06-10 20:24:03 +02:00
int max = -1;
if (executionMode == ExecutionMode.SEQUENTIAL) {
Collections.sort(commandList, new Comparator<NPCCommand>() {
2020-06-10 20:24:03 +02:00
@Override
public int compare(NPCCommand o1, NPCCommand o2) {
return Integer.compare(o1.id, o2.id);
}
});
max = commandList.size() > 0 ? commandList.get(commandList.size() - 1).id : -1;
2020-06-10 20:24:03 +02:00
}
for (NPCCommand command : commandList) {
if (executionMode == ExecutionMode.SEQUENTIAL) {
2020-06-10 20:24:03 +02:00
PlayerNPCCommand info = cooldowns.get(player.getUniqueId().toString());
2020-06-11 10:23:48 +02:00
if (info != null && command.id <= info.lastUsedId) {
2020-06-10 20:24:03 +02:00
if (info.lastUsedId == max) {
info.lastUsedId = -1;
2020-06-11 10:23:48 +02:00
} else {
continue;
2020-06-10 20:24:03 +02:00
}
}
}
runCommand(player, command);
2020-10-03 09:40:53 +02:00
if (executionMode == ExecutionMode.SEQUENTIAL) {
break;
}
}
}
private void runCommand(final Player player, NPCCommand command) {
Runnable runnable = new Runnable() {
@Override
public void run() {
PlayerNPCCommand info = cooldowns.get(player.getUniqueId().toString());
if (info == null && (executionMode == ExecutionMode.SEQUENTIAL
|| PlayerNPCCommand.requiresTracking(command))) {
cooldowns.put(player.getUniqueId().toString(), info = new PlayerNPCCommand());
}
if (info != null && !info.canUse(player, command)) {
return;
}
PermissionAttachment attachment = player.addAttachment(CitizensAPI.getPlugin());
if (temporaryPermissions.size() > 0) {
for (String permission : temporaryPermissions) {
attachment.setPermission(permission, true);
}
}
command.run(npc, player);
attachment.remove();
2020-04-15 21:04:42 +02:00
}
};
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);
}
}
public ExecutionMode getExecutionMode() {
return executionMode;
}
private int getNewId() {
int i = 0;
while (commands.containsKey(String.valueOf(i))) {
i++;
}
return i;
}
public boolean hasCommandId(int id) {
return commands.containsKey(String.valueOf(id));
}
public void removeCommandById(int id) {
commands.remove(String.valueOf(id));
}
2020-06-30 09:20:02 +02:00
public void setCost(double cost) {
this.cost = cost;
}
public void setExecutionMode(ExecutionMode mode) {
this.executionMode = mode;
2020-06-10 20:24:03 +02:00
}
public void setTemporaryPermissions(List<String> permissions) {
temporaryPermissions.clear();
temporaryPermissions.addAll(permissions);
}
public enum ExecutionMode {
LINEAR,
RANDOM,
SEQUENTIAL;
}
2019-10-01 08:16:00 +02:00
public static enum Hand {
2020-02-14 15:48:40 +01:00
BOTH,
LEFT,
RIGHT;
}
private static class NPCCommand {
String bungeeServer;
String command;
2020-02-22 05:57:03 +01:00
int cooldown;
int delay;
Hand hand;
2020-06-10 20:24:03 +02:00
int id;
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,
List<String> perms, int n, int delay) {
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;
List<String> split = Splitter.on(' ').omitEmptyStrings().trimResults().splitToList(command);
this.bungeeServer = split.size() == 2 && split.get(0).equalsIgnoreCase("server") ? split.get(1) : null;
}
public void run(NPC npc, Player clicker) {
2020-01-16 12:40:32 +01:00
String interpolatedCommand = Placeholders.replace(command, clicker, npc);
2020-06-12 13:35:30 +02:00
if (Messaging.isDebugging()) {
Messaging.debug(
"Running command " + interpolatedCommand + " on NPC " + npc.getId() + " clicker " + clicker);
}
if (!player) {
Bukkit.getServer().dispatchCommand(Bukkit.getConsoleSender(), interpolatedCommand);
return;
}
boolean wasOp = clicker.isOp();
if (op) {
clicker.setOp(true);
}
if (bungeeServer != null) {
ByteArrayDataOutput out = ByteStreams.newDataOutput();
out.writeUTF("Connect");
out.writeUTF(bungeeServer);
clicker.sendPluginMessage(CitizensAPI.getPlugin(), "BungeeCord", out.toByteArray());
} else {
try {
clicker.chat("/" + interpolatedCommand);
} catch (Throwable t) {
t.printStackTrace();
}
}
if (op) {
clicker.setOp(wasOp);
}
}
}
public static class NPCCommandBuilder {
String command;
int cooldown;
int delay;
Hand hand;
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) {
this.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) {
return new NPCCommand(id, command, hand, player, op, cooldown, perms, n, delay);
}
public NPCCommandBuilder command(String command) {
this.command = command;
return this;
}
public NPCCommandBuilder cooldown(int cooldown) {
this.cooldown = cooldown;
return this;
}
public NPCCommandBuilder delay(int delay) {
this.delay = delay;
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(""));
}
2020-06-10 20:24:03 +02:00
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"));
}
@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);
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));
}
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
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() {
}
2020-03-02 16:40:13 +01:00
public boolean canUse(Player player, NPCCommand command) {
for (String perm : command.perms) {
2020-03-03 16:31:04 +01:00
if (!player.hasPermission(perm)) {
Messaging.sendErrorTr(player, CommandMessages.NO_PERMISSION);
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
}
2020-02-22 05:57:03 +01:00
long currentTimeSec = System.currentTimeMillis() / 1000;
String commandKey = BaseEncoding.base64().encode(command.command.getBytes());
// TODO: remove this in 2.0.28
for (Map map : Arrays.asList(lastUsed, nUsed)) {
if (map.containsKey(command)) {
Object value = map.remove(command);
map.put(commandKey, value);
}
}
if (lastUsed.containsKey(commandKey)) {
if (currentTimeSec < lastUsed.get(commandKey) + command.cooldown) {
2020-02-22 05:57:03 +01:00
return false;
}
lastUsed.remove(commandKey);
2020-02-22 05:57:03 +01:00
}
int previouslyUsed = nUsed.getOrDefault(commandKey, 0);
if (command.n > 0 && command.n <= previouslyUsed) {
return false;
}
2020-02-22 05:57:03 +01:00
if (command.cooldown > 0) {
lastUsed.put(commandKey, currentTimeSec);
2020-02-22 05:57:03 +01:00
}
if (command.n > 0) {
nUsed.put(commandKey, previouslyUsed + 1);
}
2020-06-10 20:24:03 +02:00
lastUsedId = command.id;
2020-02-22 05:57:03 +01:00
return true;
}
public static boolean requiresTracking(NPCCommand command) {
return command.cooldown > 0 || command.n > 0 || (command.perms != null && command.perms.size() > 0);
}
2020-02-22 05:57:03 +01:00
}
private static class PlayerNPCCommandPersister implements Persister<PlayerNPCCommand> {
public PlayerNPCCommandPersister() {
}
@Override
public PlayerNPCCommand create(DataKey root) {
return PersistenceLoader.load(PlayerNPCCommand.class, root);
}
@Override
public void save(PlayerNPCCommand instance, DataKey root) {
PersistenceLoader.save(instance, root);
}
}
}