Work on /npc shop, QoL tweaks, fix scoreboard team removal

This commit is contained in:
fullwall 2022-07-26 23:50:28 +08:00
parent ddf5660c66
commit 9cf7779ff4
10 changed files with 438 additions and 62 deletions

View File

@ -34,7 +34,6 @@ import net.citizensnpcs.api.CitizensPlugin;
import net.citizensnpcs.api.InventoryHelper;
import net.citizensnpcs.api.SkullMetaProvider;
import net.citizensnpcs.api.ai.speech.SpeechFactory;
import net.citizensnpcs.api.command.CommandContext;
import net.citizensnpcs.api.command.CommandManager;
import net.citizensnpcs.api.command.CommandManager.CommandInfo;
import net.citizensnpcs.api.command.Injector;
@ -71,6 +70,7 @@ import net.citizensnpcs.npc.ai.speech.Chat;
import net.citizensnpcs.npc.ai.speech.CitizensSpeechFactory;
import net.citizensnpcs.npc.profile.ProfileFetcher;
import net.citizensnpcs.npc.skin.Skin;
import net.citizensnpcs.trait.ShopTrait;
import net.citizensnpcs.util.Messages;
import net.citizensnpcs.util.NMS;
import net.citizensnpcs.util.PlayerUpdateTask;
@ -103,6 +103,7 @@ public class Citizens extends JavaPlugin implements CitizensPlugin {
private boolean saveOnDisable = true;
private NPCDataStore saves;
private NPCSelector selector;
private Storage shops;
private final SkullMetaProvider skullMetaProvider = new SkullMetaProvider() {
@Override
public String getTexture(SkullMeta meta) {
@ -305,8 +306,7 @@ public class Citizens extends JavaPlugin implements CitizensPlugin {
}
public void onDependentPluginDisable() {
storeNPCs();
saves.saveToDiskImmediate();
storeNPCs(false);
saveOnDisable = false;
}
@ -347,7 +347,8 @@ public class Citizens extends JavaPlugin implements CitizensPlugin {
registerScriptHelpers();
saves = createStorage(getDataFolder());
if (saves == null) {
shops = new YamlStorage(new File(getDataFolder(), "shops.yml"));
if (saves == null || !shops.load()) {
Messaging.severeTr(Messages.FAILED_LOAD_SAVES);
Bukkit.getPluginManager().disablePlugin(this);
return;
@ -420,6 +421,9 @@ public class Citizens extends JavaPlugin implements CitizensPlugin {
saves.reloadFromSource();
saves.loadInto(npcRegistry);
shops.load();
ShopTrait.loadShops(shops.getKey(""));
getServer().getPluginManager().callEvent(new CitizensReloadEvent());
}
@ -504,17 +508,21 @@ public class Citizens extends JavaPlugin implements CitizensPlugin {
}
public void storeNPCs() {
storeNPCs(false);
}
public void storeNPCs(boolean async) {
if (saves == null)
return;
saves.storeAll(npcRegistry);
}
public void storeNPCs(CommandContext args) {
storeNPCs();
boolean async = args.hasFlag('a');
ShopTrait.saveShops(shops.getKey(""));
if (async) {
saves.saveToDisk();
new Thread(() -> {
shops.save();
}).start();
} else {
shops.save();
saves.saveToDiskImmediate();
}
}
@ -536,6 +544,8 @@ public class Citizens extends JavaPlugin implements CitizensPlugin {
Messaging.logTr(Messages.NUM_LOADED_NOTIFICATION, Iterables.size(npcRegistry), "?");
startMetrics();
scheduleSaveTask(Setting.SAVE_TASK_DELAY.asInt());
shops.load();
ShopTrait.loadShops(shops.getKey(""));
Bukkit.getPluginManager().callEvent(new CitizensEnableEvent());
new PlayerUpdateTask().runTaskTimer(Citizens.this, 0, 1);
enabled = true;
@ -545,8 +555,7 @@ public class Citizens extends JavaPlugin implements CitizensPlugin {
private class CitizensSaveTask implements Runnable {
@Override
public void run() {
storeNPCs();
saves.saveToDisk();
storeNPCs(false);
}
}
}

View File

@ -73,7 +73,7 @@ public class AdminCommands {
permission = "citizens.admin")
public void save(CommandContext args, CommandSender sender, NPC npc) {
Messaging.sendTr(sender, Messages.CITIZENS_SAVING);
plugin.storeNPCs(args);
plugin.storeNPCs(args.hasFlag('a'));
Messaging.sendTr(sender, Messages.CITIZENS_SAVED);
}
}

View File

@ -1191,7 +1191,7 @@ public class NPCCommands {
int id = npcs.get(i).getId();
String line = StringHelper.wrap(id) + " " + npcs.get(i).getName() + " (<<[[tp:command(/npc tp --id " + id
+ "):Teleport to this NPC>>) (<<[[summon:command(/npc tph --id " + id
+ "):Teleport NPC to me>> (<<<c>-:command(/npc remove " + id + "):Remove this NPC>>)";
+ "):Teleport NPC to me>>) (<<<c>-:command(/npc remove " + id + "):Remove this NPC>>)";
paginator.addLine(line);
}
@ -2567,11 +2567,12 @@ public class NPCCommands {
@Command(
aliases = { "npc" },
usage = "tp",
desc = "Teleport to a NPC",
usage = "tp (-e(xact))",
desc = "Teleport in front of an NPC",
modifiers = { "tp", "teleport" },
min = 1,
max = 1,
flags = "e",
permission = "citizens.npc.tp")
public void tp(CommandContext args, Player player, NPC npc) {
Location to = npc.getOrAddTrait(CurrentLocation.class).getLocation();
@ -2579,6 +2580,10 @@ public class NPCCommands {
Messaging.sendError(player, Messages.TELEPORT_NPC_LOCATION_NOT_FOUND);
return;
}
if (!args.hasFlag('e')) {
to = to.clone().add(to.getDirection().setY(0));
to.setDirection(to.getDirection().multiply(-1)).setPitch(0);
}
player.teleport(to, TeleportCause.COMMAND);
Messaging.sendTr(player, Messages.TELEPORTED_TO_NPC, npc.getName());
}

View File

@ -1,5 +1,6 @@
package net.citizensnpcs.trait;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.EnumSet;
@ -28,6 +29,7 @@ import com.google.common.base.Splitter;
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 com.google.common.io.ByteArrayDataOutput;
import com.google.common.io.ByteStreams;
@ -324,6 +326,14 @@ public class CommandTrait extends Trait {
commands.remove(id);
}
@Override
public void save(DataKey key) {
Collection<NPCCommand> commands = this.commands.values();
for (PlayerNPCCommand playerCommand : cooldowns.values()) {
playerCommand.prune(globalCooldowns, commands);
}
}
private void sendErrorMessage(Player player, CommandTraitMessages msg, Function<String, String> transform,
Object... objects) {
if (hideErrorMessages) {
@ -445,6 +455,7 @@ public class CommandTrait extends Trait {
int globalCooldown;
Hand hand;
int id;
String key;
int n;
boolean op;
List<String> perms;
@ -466,6 +477,12 @@ public class CommandTrait extends Trait {
this.bungeeServer = split.size() == 2 && split.get(0).equalsIgnoreCase("server") ? split.get(1) : null;
}
public String getEncodedKey() {
if (key != null)
return key;
return key = BaseEncoding.base64().encode(command.getBytes());
}
public void run(NPC npc, Player clicker) {
String cmd = command;
if (command.startsWith("say")) {
@ -628,7 +645,7 @@ public class CommandTrait extends Trait {
}
}
long currentTimeSec = System.currentTimeMillis() / 1000;
String commandKey = BaseEncoding.base64().encode(command.command.getBytes());
String commandKey = command.getEncodedKey();
if (lastUsed.containsKey(commandKey)) {
long deadline = ((Number) lastUsed.get(commandKey)).longValue() + command.cooldown;
if (currentTimeSec < deadline) {
@ -667,6 +684,34 @@ public class CommandTrait extends Trait {
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 <= currentTimeSec) {
lastUsed.remove(commandKey);
}
if (globalCooldowns != null) {
number = globalCooldowns.get(commandKey);
if (number != null && number.longValue() + command.globalCooldown <= currentTimeSec) {
globalCooldowns.remove(commandKey);
}
}
}
for (String key : Sets.difference(Sets.newHashSet(lastUsed.keySet()), commandKeys)) {
lastUsed.remove(key);
nUsed.remove(key);
}
if (globalCooldowns != null) {
for (String key : Sets.difference(Sets.newHashSet(globalCooldowns.keySet()), commandKeys)) {
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);

View File

@ -87,6 +87,7 @@ public class ScoreboardTrait extends Trait {
if (team.hasEntry(name)) {
if (team.getSize() == 1) {
for (Player player : Bukkit.getOnlinePlayers()) {
SENT_TEAMS.remove(player.getUniqueId(), team.getName());
NMS.sendTeamPacket(player, team, 1);
}
team.unregister();

View File

@ -3,6 +3,7 @@ package net.citizensnpcs.trait;
import java.util.List;
import java.util.Map;
import java.util.function.Consumer;
import java.util.function.Function;
import org.bukkit.Bukkit;
import org.bukkit.ChatColor;
@ -26,15 +27,20 @@ import net.citizensnpcs.api.gui.InputMenus;
import net.citizensnpcs.api.gui.InputMenus.Choice;
import net.citizensnpcs.api.gui.InventoryMenu;
import net.citizensnpcs.api.gui.InventoryMenuPage;
import net.citizensnpcs.api.gui.InventoryMenuPattern;
import net.citizensnpcs.api.gui.InventoryMenuSlot;
import net.citizensnpcs.api.gui.Menu;
import net.citizensnpcs.api.gui.MenuContext;
import net.citizensnpcs.api.gui.MenuPattern;
import net.citizensnpcs.api.gui.MenuSlot;
import net.citizensnpcs.api.persistence.Persist;
import net.citizensnpcs.api.persistence.PersistenceLoader;
import net.citizensnpcs.api.trait.Trait;
import net.citizensnpcs.api.trait.TraitName;
import net.citizensnpcs.api.util.Colorizer;
import net.citizensnpcs.api.util.DataKey;
import net.citizensnpcs.trait.shop.NPCShopAction;
import net.citizensnpcs.trait.shop.NPCShopAction.GUI;
/**
* Shop trait for NPC GUI shops.
@ -46,11 +52,11 @@ public class ShopTrait extends Trait {
}
public NPCShop getDefaultShop() {
return NPC_SHOPS.computeIfAbsent(npc.getUniqueId().toString(), NPCShop::new);
return StoredShops.NPC_SHOPS.computeIfAbsent(npc.getUniqueId().toString(), NPCShop::new);
}
public NPCShop getShop(String name) {
return SHOPS.computeIfAbsent(name, NPCShop::new);
return StoredShops.GLOBAL_SHOPS.computeIfAbsent(name, NPCShop::new);
}
public static class NPCShop {
@ -200,11 +206,36 @@ public class ShopTrait extends Trait {
public static class NPCShopItem implements Cloneable {
@Persist
private List<NPCShopAction> cost;
private final List<NPCShopAction> cost = Lists.newArrayList();
@Persist
private ItemStack display;
@Persist
private List<NPCShopAction> result;
private final List<NPCShopAction> result = Lists.newArrayList();
private void changeAction(List<NPCShopAction> source, Function<NPCShopAction, Boolean> filter,
NPCShopAction delta) {
for (int i = 0; i < source.size(); i++) {
if (filter.apply(source.get(i))) {
if (delta == null) {
source.remove(i);
} else {
source.set(i, delta);
}
return;
}
}
if (delta != null) {
source.add(delta);
}
}
public void changeCost(Function<NPCShopAction, Boolean> filter, NPCShopAction cost) {
changeAction(this.cost, filter, cost);
}
public void changeResult(Function<NPCShopAction, Boolean> filter, NPCShopAction result) {
changeAction(this.result, filter, result);
}
@Override
public NPCShopItem clone() {
@ -220,9 +251,19 @@ public class ShopTrait extends Trait {
}
@Menu(title = "NPC Shop Item Editor", type = InventoryType.CHEST, dimensions = { 6, 9 })
@MenuSlot(slot = { 0, 4 }, material = Material.DISPENSER, amount = 1, title = "Place display item below")
@MenuSlot(slot = { 3, 4 }, material = Material.DISPENSER, amount = 1, title = "<f>Place display item below")
public static class NPCShopItemEditor extends InventoryMenuPage {
@MenuPattern(
offset = { 0, 0 },
slots = { @MenuSlot(pat = 'x', material = Material.AIR) },
value = "x x\n x \nx x")
private InventoryMenuPattern actionItems;
private final Consumer<NPCShopItem> callback;
@MenuPattern(
offset = { 0, 6 },
slots = { @MenuSlot(pat = 'x', material = Material.AIR) },
value = "x x\n x \nx x")
private InventoryMenuPattern costItems;
private MenuContext ctx;
private final NPCShopItem modified;
private NPCShopItem original;
@ -239,9 +280,34 @@ public class ShopTrait extends Trait {
if (modified.display != null) {
ctx.getSlot(9 + 4).setItemStack(modified.display);
}
int pos = 0;
for (GUI template : NPCShopAction.getGUIs()) {
ItemStack item = template.createMenuItem();
if (item == null)
continue;
costItems.getSlots().get(pos).setItemStack(item);
costItems.getSlots().get(pos).addClickHandler(event -> {
event.setCancelled(true);
ctx.getMenu()
.transition(template.createEditor(
modified.cost.stream().filter(template::manages).findFirst().orElse(null),
cost -> modified.changeCost(template::manages, cost)));
});
actionItems.getSlots().get(pos).setItemStack(item);
actionItems.getSlots().get(pos).addClickHandler(event -> {
event.setCancelled(true);
ctx.getMenu()
.transition(template.createEditor(
modified.result.stream().filter(template::manages).findFirst().orElse(null),
result -> modified.changeResult(template::manages, result)));
});
pos++;
}
}
@MenuSlot(slot = { 4, 3 }, material = Material.REDSTONE_BLOCK, amount = 1, title = "Cancel")
@MenuSlot(slot = { 5, 3 }, material = Material.REDSTONE_BLOCK, amount = 1, title = "<7>Cancel")
public void onCancel(InventoryMenuSlot slot, CitizensInventoryClickEvent event) {
event.setCancelled(true);
ctx.getMenu().transitionBack();
@ -255,20 +321,23 @@ public class ShopTrait extends Trait {
callback.accept(original);
}
@MenuSlot(slot = { 1, 5 }, material = Material.BOOK, amount = 1, title = "Set description")
@MenuSlot(slot = { 4, 5 }, material = Material.BOOK, amount = 1, title = "<f>Set description")
public void onEditDescription(InventoryMenuSlot slot, CitizensInventoryClickEvent event) {
event.setCancelled(true);
if (modified.display == null)
return;
ctx.getMenu().transition(InputMenus.stringSetter(
() -> Joiner.on("<br>").skipNulls().join(modified.display.getItemMeta().getLore()), description -> {
ItemMeta meta = modified.display.getItemMeta();
meta.setLore(Lists.newArrayList(Splitter.on("<br>").split(Colorizer.parseColors(description))));
modified.display.setItemMeta(meta);
}));
ctx.getMenu()
.transition(InputMenus.stringSetter(() -> modified.display.getItemMeta().hasLore()
? Joiner.on("<br>").skipNulls().join(modified.display.getItemMeta().getLore())
: "", description -> {
ItemMeta meta = modified.display.getItemMeta();
meta.setLore(Lists
.newArrayList(Splitter.on("<br>").split(Colorizer.parseColors(description))));
modified.display.setItemMeta(meta);
}));
}
@MenuSlot(slot = { 1, 3 }, material = Material.FEATHER, amount = 1, title = "Set name")
@MenuSlot(slot = { 4, 3 }, material = Material.FEATHER, amount = 1, title = "<f>Set name")
public void onEditName(InventoryMenuSlot slot, CitizensInventoryClickEvent event) {
event.setCancelled(true);
if (modified.display == null)
@ -280,7 +349,7 @@ public class ShopTrait extends Trait {
}));
}
@ClickHandler(slot = { 1, 4 })
@ClickHandler(slot = { 4, 4 })
public void onModifyDisplayItem(InventoryMenuSlot slot, CitizensInventoryClickEvent event) {
event.setCancelled(true);
if (event.getCursor() != null) {
@ -292,14 +361,14 @@ public class ShopTrait extends Trait {
}
}
@MenuSlot(slot = { 4, 4 }, material = Material.TNT, amount = 1, title = "<c>Remove")
@MenuSlot(slot = { 5, 4 }, material = Material.TNT, amount = 1, title = "<c>Remove")
public void onRemove(InventoryMenuSlot slot, CitizensInventoryClickEvent event) {
original = null;
event.setCancelled(true);
ctx.getMenu().transitionBack();
}
@MenuSlot(slot = { 4, 5 }, material = Material.EMERALD_BLOCK, amount = 1, title = "Save")
@MenuSlot(slot = { 5, 5 }, material = Material.EMERALD_BLOCK, amount = 1, title = "<a>Save")
public void onSave(InventoryMenuSlot slot, CitizensInventoryClickEvent event) {
original = modified;
event.setCancelled(true);
@ -485,8 +554,20 @@ public class ShopTrait extends Trait {
SELL;
}
@Persist(value = "npcShops", reify = true, namespace = "shopstrait")
private static Map<String, NPCShop> NPC_SHOPS = Maps.newHashMap();
@Persist(value = "globalShops", reify = true, namespace = "shopstrait")
private static Map<String, NPCShop> SHOPS = Maps.newHashMap();
private static class StoredShops {
@Persist(value = "global", reify = true)
private static Map<String, NPCShop> GLOBAL_SHOPS = Maps.newHashMap();
@Persist(value = "npc", reify = true)
private static Map<String, NPCShop> NPC_SHOPS = Maps.newHashMap();
}
public static void loadShops(DataKey root) {
SAVED = PersistenceLoader.load(StoredShops.class, root);
}
public static void saveShops(DataKey root) {
PersistenceLoader.save(SAVED, root);
}
private static StoredShops SAVED = new StoredShops();
}

View File

@ -95,7 +95,6 @@ public class SkinTrait extends Trait {
textureRaw = npc.data().get(NPC.PLAYER_SKIN_TEXTURE_PROPERTIES_METADATA);
npc.data().remove(NPC.PLAYER_SKIN_TEXTURE_PROPERTIES_METADATA);
update = true;
}
if (npc.data().has(NPC.PLAYER_SKIN_TEXTURE_PROPERTIES_SIGN_METADATA)) {
signature = npc.data().get(NPC.PLAYER_SKIN_TEXTURE_PROPERTIES_SIGN_METADATA);

View File

@ -2,15 +2,31 @@ package net.citizensnpcs.trait.shop;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.WeakHashMap;
import java.util.function.Consumer;
import java.util.function.Supplier;
import org.bukkit.entity.HumanEntity;
import org.bukkit.Bukkit;
import org.bukkit.Material;
import org.bukkit.OfflinePlayer;
import org.bukkit.entity.Entity;
import org.bukkit.entity.Player;
import org.bukkit.inventory.ItemStack;
import com.google.common.collect.Lists;
import net.citizensnpcs.api.gui.InputMenus;
import net.citizensnpcs.api.gui.InventoryMenuPage;
import net.citizensnpcs.api.persistence.Persist;
import net.citizensnpcs.api.persistence.PersistenceLoader;
import net.citizensnpcs.api.persistence.PersisterRegistry;
import net.citizensnpcs.trait.shop.NPCShopAction.ItemAction.ItemActionGUI;
import net.citizensnpcs.trait.shop.NPCShopAction.MoneyAction.MoneyActionGUI;
import net.citizensnpcs.trait.shop.NPCShopAction.PermissionAction.PermissionActionGUI;
import net.citizensnpcs.util.Util;
import net.milkbowl.vault.economy.Economy;
import net.milkbowl.vault.permission.Permission;
public abstract class NPCShopAction implements Cloneable {
@Override
@ -22,9 +38,17 @@ public abstract class NPCShopAction implements Cloneable {
}
}
public abstract boolean grant(HumanEntity entity);
public abstract PendingAction grant(Entity entity);
public abstract boolean take(HumanEntity entity);
public abstract PendingAction take(Entity entity);
public static interface GUI {
public InventoryMenuPage createEditor(NPCShopAction previous, Consumer<NPCShopAction> callback);
public ItemStack createMenuItem();
public boolean manages(NPCShopAction action);
}
public static class ItemAction extends NPCShopAction {
@Persist
@ -42,35 +66,151 @@ public abstract class NPCShopAction implements Cloneable {
}
@Override
public boolean grant(HumanEntity entity) {
return false;
public PendingAction grant(Entity entity) {
return PendingAction.create(() -> {
return true;
}, () -> {
}, () -> {
});
}
@Override
public boolean take(HumanEntity entity) {
return false;
public PendingAction take(Entity entity) {
return PendingAction.create(() -> {
return true;
}, () -> {
}, () -> {
});
}
public static class ItemActionGUI implements GUI {
@Override
public InventoryMenuPage createEditor(NPCShopAction previous, Consumer<NPCShopAction> callback) {
return null;
}
@Override
public ItemStack createMenuItem() {
return Util.createItem(Material.CHEST, "Item");
}
@Override
public boolean manages(NPCShopAction action) {
return action instanceof ItemAction;
}
}
}
public static class MoneyAction extends NPCShopAction {
@Persist
public int money;
public double money;
public MoneyAction() {
}
public MoneyAction(int money) {
this.money = money;
@Override
public PendingAction grant(Entity entity) {
if (!(entity instanceof OfflinePlayer))
return PendingAction.fail();
Economy economy = Bukkit.getServicesManager().getRegistration(Economy.class).getProvider();
OfflinePlayer player = (OfflinePlayer) entity;
return PendingAction.create(() -> {
return true;
}, () -> {
economy.depositPlayer(player, money);
}, () -> {
economy.withdrawPlayer(player, money);
});
}
@Override
public boolean grant(HumanEntity entity) {
return false;
public PendingAction take(Entity entity) {
if (!(entity instanceof OfflinePlayer))
return PendingAction.fail();
Economy economy = Bukkit.getServicesManager().getRegistration(Economy.class).getProvider();
OfflinePlayer player = (OfflinePlayer) entity;
return PendingAction.create(() -> {
return economy.has(player, money);
}, () -> {
economy.withdrawPlayer(player, money);
}, () -> {
economy.depositPlayer(player, money);
});
}
@Override
public boolean take(HumanEntity entity) {
return false;
public static class MoneyActionGUI implements GUI {
private Boolean supported;
@Override
public InventoryMenuPage createEditor(NPCShopAction previous, Consumer<NPCShopAction> callback) {
final MoneyAction action = previous == null ? new MoneyAction() : (MoneyAction) previous;
return InputMenus.filteredStringSetter(() -> Double.toString(action.money), (s) -> {
try {
double result = Double.parseDouble(s);
if (result < 0)
return false;
action.money = result;
} catch (NumberFormatException nfe) {
return false;
}
callback.accept(action);
return true;
});
}
@Override
public ItemStack createMenuItem() {
if (supported == null) {
try {
supported = Bukkit.getServicesManager().getRegistration(Economy.class).getProvider() != null;
} catch (Throwable t) {
supported = false;
}
}
if (!supported) {
return null;
}
return Util.createItem(Material.GOLD_INGOT, "Money");
}
@Override
public boolean manages(NPCShopAction action) {
return action instanceof MoneyAction;
}
}
}
public static class PendingAction {
private final Runnable execute;
private final Supplier<Boolean> possible;
private final Runnable rollback;
public PendingAction(Supplier<Boolean> isPossible, Runnable execute, Runnable rollback) {
this.possible = isPossible;
this.execute = execute;
this.rollback = rollback;
}
public boolean isPossible() {
return possible.get();
}
public void rollback() {
rollback.run();
}
public void run() {
execute.run();
}
public static PendingAction create(Supplier<Boolean> isPossible, Runnable execute, Runnable rollback) {
return new PendingAction(isPossible, execute, rollback);
}
public static PendingAction fail() {
return new PendingAction(() -> false, () -> {
}, () -> {
});
}
}
@ -86,20 +226,93 @@ public abstract class NPCShopAction implements Cloneable {
}
@Override
public boolean grant(HumanEntity entity) {
return false;
public PendingAction grant(Entity entity) {
if (!(entity instanceof Player))
return PendingAction.fail();
Player player = (Player) entity;
Permission perm = Bukkit.getServicesManager().getRegistration(Permission.class).getProvider();
return PendingAction.create(() -> {
return true;
}, () -> {
for (String permission : permissions) {
perm.playerAdd(player, permission);
}
}, () -> {
for (String permission : permissions) {
perm.playerRemove(player, permission);
}
});
}
@Override
public boolean take(HumanEntity entity) {
return false;
public PendingAction take(Entity entity) {
if (!(entity instanceof Player))
return PendingAction.fail();
Player player = (Player) entity;
Permission perm = Bukkit.getServicesManager().getRegistration(Permission.class).getProvider();
return PendingAction.create(() -> {
for (String permission : permissions) {
if (!perm.playerHas(player, permission)) {
return false;
}
}
return true;
}, () -> {
for (String permission : permissions) {
perm.playerRemove(player, permission);
}
}, () -> {
for (String permission : permissions) {
perm.playerAdd(player, permission);
}
});
}
public static class PermissionActionGUI implements GUI {
private Boolean supported;
@Override
public InventoryMenuPage createEditor(NPCShopAction previous, Consumer<NPCShopAction> callback) {
return null;
}
@Override
public ItemStack createMenuItem() {
if (supported == null) {
try {
supported = Bukkit.getServicesManager().getRegistration(Permission.class).getProvider() != null;
} catch (Throwable t) {
supported = false;
}
}
if (!supported) {
return null;
}
return Util.createItem(Util.getFallbackMaterial("OAK_SIGN", "SIGN"), "Permission");
}
@Override
public boolean manages(NPCShopAction action) {
return action instanceof PermissionAction;
}
}
}
public static PersisterRegistry<NPCShopAction> REGISTRY = PersistenceLoader.createRegistry(NPCShopAction.class);
public static Iterable<GUI> getGUIs() {
return GUI.values();
}
public static void register(Class<? extends NPCShopAction> clazz, String type, GUI gui) {
REGISTRY.register(type, clazz);
GUI.put(clazz, gui);
}
private static final Map<Class<? extends NPCShopAction>, GUI> GUI = new WeakHashMap<>();
private static final PersisterRegistry<NPCShopAction> REGISTRY = PersistenceLoader
.createRegistry(NPCShopAction.class);
static {
REGISTRY.register("items", ItemAction.class);
REGISTRY.register("permissions", PermissionAction.class);
REGISTRY.register("money", MoneyAction.class);
register(ItemAction.class, "items", new ItemActionGUI());
register(PermissionAction.class, "permissions", new PermissionActionGUI());
register(MoneyAction.class, "money", new MoneyActionGUI());
}
}

View File

@ -90,7 +90,8 @@ public class FoxTrait extends Trait {
if (args.hasValueFlag("type")) {
Fox.Type type = Util.matchEnum(Fox.Type.values(), args.getFlag("type"));
if (type == null) {
throw new CommandUsageException(Messages.INVALID_FOX_TYPE, Util.listValuesPretty(Fox.Type.values()));
throw new CommandUsageException(
Messaging.tr(Messages.INVALID_FOX_TYPE, Util.listValuesPretty(Fox.Type.values())), null);
}
trait.setType(type);
output += ' ' + Messaging.tr(Messages.FOX_TYPE_SET, args.getFlag("type"));

View File

@ -1,5 +1,6 @@
package net.citizensnpcs.util;
import java.util.Arrays;
import java.util.EnumSet;
import java.util.Random;
import java.util.Set;
@ -16,6 +17,9 @@ import org.bukkit.entity.LivingEntity;
import org.bukkit.entity.Player;
import org.bukkit.event.player.PlayerInteractEntityEvent;
import org.bukkit.event.player.PlayerInteractEvent;
import org.bukkit.inventory.ItemFlag;
import org.bukkit.inventory.ItemStack;
import org.bukkit.inventory.meta.ItemMeta;
import org.bukkit.scoreboard.Scoreboard;
import org.bukkit.util.Vector;
@ -26,7 +30,9 @@ import net.citizensnpcs.api.event.NPCCollisionEvent;
import net.citizensnpcs.api.event.NPCPushEvent;
import net.citizensnpcs.api.npc.NPC;
import net.citizensnpcs.api.util.BoundingBox;
import net.citizensnpcs.api.util.Colorizer;
import net.citizensnpcs.api.util.SpigotUtil;
import net.md_5.bungee.api.ChatColor;
public class Util {
private Util() {
@ -69,6 +75,22 @@ public class Util {
return angle;
}
public static ItemStack createItem(Material mat, String name) {
return createItem(mat, name, null);
}
public static ItemStack createItem(Material mat, String name, String description) {
ItemStack stack = new ItemStack(mat, 1);
ItemMeta meta = stack.getItemMeta();
meta.setDisplayName(ChatColor.RESET + Colorizer.parseColors(name));
if (description != null) {
meta.setLore(Arrays.asList(Colorizer.parseColors(description).split("\n")));
}
meta.addItemFlags(ItemFlag.HIDE_ATTRIBUTES);
stack.setItemMeta(meta);
return stack;
}
public static void face(Entity entity, float yaw, float pitch) {
double pitchCos = Math.cos(Math.toRadians(pitch));
Vector vector = new Vector(Math.sin(Math.toRadians(yaw)) * -pitchCos, -Math.sin(Math.toRadians(pitch)),