2021-07-14 15:23:55 +02:00
|
|
|
package net.citizensnpcs.trait;
|
|
|
|
|
2022-02-19 13:27:41 +01:00
|
|
|
import java.util.List;
|
2021-07-14 15:23:55 +02:00
|
|
|
import java.util.Map;
|
2022-07-17 16:29:11 +02:00
|
|
|
import java.util.function.Consumer;
|
2021-07-14 15:23:55 +02:00
|
|
|
|
2022-07-17 17:01:59 +02:00
|
|
|
import org.bukkit.ChatColor;
|
2022-04-20 05:55:15 +02:00
|
|
|
import org.bukkit.Material;
|
2022-07-17 16:29:11 +02:00
|
|
|
import org.bukkit.entity.HumanEntity;
|
2021-07-14 15:23:55 +02:00
|
|
|
import org.bukkit.entity.Player;
|
2022-04-20 05:55:15 +02:00
|
|
|
import org.bukkit.event.inventory.InventoryType;
|
2022-02-19 13:27:41 +01:00
|
|
|
import org.bukkit.inventory.ItemStack;
|
2022-07-17 16:29:11 +02:00
|
|
|
import org.bukkit.inventory.meta.ItemMeta;
|
2021-07-14 15:23:55 +02:00
|
|
|
|
2022-07-17 16:29:11 +02:00
|
|
|
import com.google.common.base.Joiner;
|
|
|
|
import com.google.common.base.Splitter;
|
2022-02-19 13:27:41 +01:00
|
|
|
import com.google.common.collect.Lists;
|
2021-07-14 15:23:55 +02:00
|
|
|
import com.google.common.collect.Maps;
|
|
|
|
|
2022-04-20 05:55:15 +02:00
|
|
|
import net.citizensnpcs.api.gui.CitizensInventoryClickEvent;
|
|
|
|
import net.citizensnpcs.api.gui.ClickHandler;
|
2022-06-14 15:26:18 +02:00
|
|
|
import net.citizensnpcs.api.gui.InputMenus;
|
|
|
|
import net.citizensnpcs.api.gui.InputMenus.Choice;
|
2022-04-20 05:55:15 +02:00
|
|
|
import net.citizensnpcs.api.gui.InventoryMenu;
|
|
|
|
import net.citizensnpcs.api.gui.InventoryMenuPage;
|
|
|
|
import net.citizensnpcs.api.gui.InventoryMenuSlot;
|
|
|
|
import net.citizensnpcs.api.gui.Menu;
|
|
|
|
import net.citizensnpcs.api.gui.MenuContext;
|
|
|
|
import net.citizensnpcs.api.gui.MenuSlot;
|
2021-07-14 15:23:55 +02:00
|
|
|
import net.citizensnpcs.api.persistence.Persist;
|
|
|
|
import net.citizensnpcs.api.trait.Trait;
|
|
|
|
import net.citizensnpcs.api.trait.TraitName;
|
2022-07-17 17:01:00 +02:00
|
|
|
import net.citizensnpcs.api.util.Colorizer;
|
2021-07-14 15:23:55 +02:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Shop trait for NPC GUI shops.
|
|
|
|
*/
|
|
|
|
@TraitName("shop")
|
|
|
|
public class ShopTrait extends Trait {
|
|
|
|
public ShopTrait() {
|
|
|
|
super("shop");
|
|
|
|
}
|
|
|
|
|
|
|
|
public NPCShop getDefaultShop() {
|
2022-02-19 13:27:41 +01:00
|
|
|
return NPC_SHOPS.computeIfAbsent(npc.getUniqueId().toString(), NPCShop::new);
|
2021-07-14 15:23:55 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
public NPCShop getShop(String name) {
|
2022-02-19 13:27:41 +01:00
|
|
|
return SHOPS.computeIfAbsent(name, NPCShop::new);
|
2021-07-14 15:23:55 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
public static class NPCShop {
|
|
|
|
@Persist
|
2022-02-19 13:27:41 +01:00
|
|
|
private final String name;
|
|
|
|
@Persist(reify = true)
|
|
|
|
private final List<NPCShopPage> pages = Lists.newArrayList();
|
|
|
|
@Persist
|
2022-07-17 16:29:11 +02:00
|
|
|
private String title;
|
|
|
|
@Persist
|
|
|
|
private ShopType type = ShopType.COMMAND;
|
2022-07-16 20:10:38 +02:00
|
|
|
@Persist
|
|
|
|
private String viewPermission;
|
2021-07-14 15:23:55 +02:00
|
|
|
|
|
|
|
private NPCShop(String name) {
|
|
|
|
this.name = name;
|
|
|
|
}
|
|
|
|
|
|
|
|
public void display(Player sender) {
|
2022-07-16 20:10:38 +02:00
|
|
|
if (viewPermission != null && !sender.hasPermission(viewPermission))
|
2022-06-11 19:37:38 +02:00
|
|
|
return;
|
2021-07-14 15:23:55 +02:00
|
|
|
}
|
2022-01-06 12:25:01 +01:00
|
|
|
|
|
|
|
public void displayEditor(Player sender) {
|
2022-06-11 19:37:38 +02:00
|
|
|
InventoryMenu.createSelfRegistered(new NPCShopEditor(this)).present(sender);
|
2022-01-06 12:25:01 +01:00
|
|
|
}
|
2022-02-19 13:27:41 +01:00
|
|
|
|
|
|
|
public String getName() {
|
|
|
|
return name;
|
|
|
|
}
|
2022-06-11 19:37:38 +02:00
|
|
|
|
2022-07-17 16:29:11 +02:00
|
|
|
public NPCShopPage getOrCreatePage(int page) {
|
|
|
|
while (pages.size() <= page) {
|
|
|
|
pages.add(new NPCShopPage(page));
|
|
|
|
}
|
|
|
|
return pages.get(page);
|
|
|
|
}
|
|
|
|
|
2022-06-11 19:37:38 +02:00
|
|
|
public String getRequiredPermission() {
|
2022-07-16 20:10:38 +02:00
|
|
|
return viewPermission;
|
2022-06-11 19:37:38 +02:00
|
|
|
}
|
|
|
|
|
2022-07-17 16:29:11 +02:00
|
|
|
public void removePage(int index) {
|
|
|
|
for (int i = 0; i < pages.size(); i++) {
|
|
|
|
if (pages.get(i).index == index) {
|
|
|
|
pages.remove(i--);
|
|
|
|
index = -1;
|
|
|
|
} else if (index == -1) {
|
|
|
|
pages.get(i).index--;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-06-11 19:37:38 +02:00
|
|
|
public void setPermission(String permission) {
|
2022-07-16 20:10:38 +02:00
|
|
|
this.viewPermission = permission;
|
|
|
|
if (viewPermission != null && viewPermission.isEmpty()) {
|
|
|
|
viewPermission = null;
|
2022-06-11 19:37:38 +02:00
|
|
|
}
|
|
|
|
}
|
2022-02-19 13:27:41 +01:00
|
|
|
}
|
|
|
|
|
2022-07-17 16:29:11 +02:00
|
|
|
@Menu(title = "NPC Shop Contents Editor", type = InventoryType.CHEST, dimensions = { 5, 9 })
|
|
|
|
public static class NPCShopContentsEditor extends InventoryMenuPage {
|
|
|
|
private MenuContext ctx;
|
|
|
|
private int page = 0;
|
|
|
|
private final NPCShop shop;
|
|
|
|
|
|
|
|
public NPCShopContentsEditor(NPCShop shop) {
|
|
|
|
this.shop = shop;
|
|
|
|
}
|
|
|
|
|
|
|
|
public void changePage(int newPage) {
|
|
|
|
this.page = newPage;
|
|
|
|
NPCShopPage sp = shop.getOrCreatePage(page);
|
|
|
|
for (int i = 0; i < ctx.getInventory().getSize(); i++) {
|
|
|
|
ctx.getSlot(i).clear();
|
|
|
|
NPCShopItem item = sp.items.get(i);
|
|
|
|
final int idx = i;
|
|
|
|
ctx.getSlot(i).addClickHandler(evt -> {
|
|
|
|
ctx.clearSlots();
|
|
|
|
NPCShopItem display = item;
|
|
|
|
if (display == null) {
|
|
|
|
display = new NPCShopItem();
|
|
|
|
if (evt.getCursor() != null) {
|
|
|
|
display.display = evt.getCursor().clone();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
ctx.getMenu().transition(new NPCShopItemEditor(display, modified -> {
|
|
|
|
if (modified == null) {
|
|
|
|
sp.items.remove(idx);
|
|
|
|
} else {
|
|
|
|
sp.items.put(idx, modified);
|
|
|
|
}
|
|
|
|
}));
|
|
|
|
});
|
|
|
|
|
|
|
|
if (item == null)
|
|
|
|
continue;
|
|
|
|
|
|
|
|
ctx.getSlot(i).setItemStack(item.display);
|
|
|
|
}
|
|
|
|
InventoryMenuSlot prev = ctx.getSlot(4 * 9 + 3);
|
|
|
|
InventoryMenuSlot edit = ctx.getSlot(4 * 9 + 4);
|
|
|
|
InventoryMenuSlot next = ctx.getSlot(4 * 9 + 5);
|
|
|
|
prev.clear();
|
|
|
|
if (page > 0) {
|
|
|
|
prev.setItemStack(new ItemStack(Material.FEATHER, 1), "Previous page (" + (page) + ")");
|
|
|
|
prev.addClickHandler(evt -> {
|
|
|
|
evt.setCancelled(true);
|
|
|
|
changePage(page - 1);
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
next.clear();
|
|
|
|
next.setItemStack(new ItemStack(Material.FEATHER, 1),
|
|
|
|
page + 1 >= shop.pages.size() ? "New page" : "Next page (" + (page + 1) + ")");
|
|
|
|
next.addClickHandler(evt -> {
|
|
|
|
evt.setCancelled(true);
|
|
|
|
changePage(page + 1);
|
|
|
|
});
|
|
|
|
|
|
|
|
edit.clear();
|
|
|
|
edit.setItemStack(new ItemStack(Material.BOOK), "Edit page");
|
|
|
|
edit.addClickHandler(evt -> {
|
|
|
|
evt.setCancelled(true);
|
|
|
|
ctx.getMenu().transition(new NPCShopPageEditor(shop.getOrCreatePage(page)));
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public void initialise(MenuContext ctx) {
|
|
|
|
this.ctx = ctx;
|
|
|
|
if (ctx.data().containsKey("removePage")) {
|
|
|
|
int index = (int) ctx.data().remove("removePage");
|
|
|
|
shop.removePage(index);
|
|
|
|
page = Math.max(page - 1, 0);
|
|
|
|
}
|
|
|
|
changePage(page);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-06-11 19:37:38 +02:00
|
|
|
@Menu(title = "NPC Shop Editor", type = InventoryType.HOPPER, dimensions = { 0, 5 })
|
2022-04-20 05:55:15 +02:00
|
|
|
public static class NPCShopEditor extends InventoryMenuPage {
|
|
|
|
private MenuContext ctx;
|
|
|
|
private final NPCShop shop;
|
|
|
|
|
|
|
|
public NPCShopEditor(NPCShop shop) {
|
|
|
|
this.shop = shop;
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public void initialise(MenuContext ctx) {
|
|
|
|
this.ctx = ctx;
|
|
|
|
}
|
|
|
|
|
2022-07-17 17:01:00 +02:00
|
|
|
@MenuSlot(slot = { 0, 4 }, material = Material.FEATHER, amount = 1, lore = "Edit shop items")
|
2022-07-16 20:10:38 +02:00
|
|
|
public void onEditItems(InventoryMenuSlot slot, CitizensInventoryClickEvent event) {
|
|
|
|
event.setCancelled(true);
|
2022-07-17 16:29:11 +02:00
|
|
|
ctx.getMenu().transition(new NPCShopContentsEditor(shop));
|
2022-07-16 20:10:38 +02:00
|
|
|
}
|
|
|
|
|
2022-07-17 17:01:00 +02:00
|
|
|
@MenuSlot(slot = { 0, 2 }, material = Material.OAK_SIGN, amount = 1, lore = "Edit shop permission")
|
2022-04-20 05:55:15 +02:00
|
|
|
public void onPermissionChange(InventoryMenuSlot slot, CitizensInventoryClickEvent event) {
|
2022-06-11 19:37:38 +02:00
|
|
|
event.setCancelled(true);
|
2022-07-17 16:29:11 +02:00
|
|
|
ctx.getMenu().transition(InputMenus.stringSetter(shop::getRequiredPermission, shop::setPermission));
|
2022-04-20 05:55:15 +02:00
|
|
|
}
|
|
|
|
|
2022-07-17 17:01:00 +02:00
|
|
|
@MenuSlot(slot = { 0, 0 }, material = Material.BOOK, amount = 1, lore = "Edit shop type")
|
2022-04-20 05:55:15 +02:00
|
|
|
public void onShopTypeChange(InventoryMenuSlot slot, CitizensInventoryClickEvent event) {
|
2022-06-11 19:37:38 +02:00
|
|
|
event.setCancelled(true);
|
2022-07-16 20:10:38 +02:00
|
|
|
ctx.getMenu().transition(InputMenus.<ShopType> picker("Edit shop type", chosen -> {
|
2022-06-14 15:26:18 +02:00
|
|
|
shop.type = chosen.getValue();
|
2022-07-16 20:10:38 +02:00
|
|
|
}, Choice.<ShopType> of(ShopType.BUY, Material.DIAMOND, "Players buy items", shop.type == ShopType.BUY),
|
2022-06-14 15:26:18 +02:00
|
|
|
Choice.of(ShopType.SELL, Material.EMERALD, "Players sell items", shop.type == ShopType.SELL),
|
2022-07-17 16:29:11 +02:00
|
|
|
Choice.of(ShopType.COMMAND, Material.ENDER_EYE, "Clicks trigger commands only",
|
|
|
|
shop.type == ShopType.COMMAND)));
|
2022-04-20 05:55:15 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-07-17 16:29:11 +02:00
|
|
|
public static class NPCShopItem implements Cloneable {
|
2022-02-19 13:27:41 +01:00
|
|
|
@Persist
|
|
|
|
private int cost;
|
|
|
|
@Persist
|
|
|
|
private ItemStack display;
|
2022-07-17 16:29:11 +02:00
|
|
|
|
|
|
|
@Override
|
|
|
|
public NPCShopItem clone() {
|
|
|
|
try {
|
|
|
|
return (NPCShopItem) super.clone();
|
|
|
|
} catch (CloneNotSupportedException e) {
|
|
|
|
throw new Error(e);
|
|
|
|
}
|
|
|
|
}
|
2022-02-19 13:27:41 +01:00
|
|
|
}
|
|
|
|
|
2022-07-16 20:10:38 +02:00
|
|
|
@Menu(title = "NPC Shop Item Editor", type = InventoryType.CHEST, dimensions = { 5, 9 })
|
2022-07-17 17:01:00 +02:00
|
|
|
@MenuSlot(slot = { 0, 4 }, material = Material.DISPENSER, amount = 1, title = "Place display item below")
|
2022-07-16 20:10:38 +02:00
|
|
|
public static class NPCShopItemEditor extends InventoryMenuPage {
|
2022-07-17 16:29:11 +02:00
|
|
|
private final Consumer<NPCShopItem> consumer;
|
2022-07-16 20:10:38 +02:00
|
|
|
private MenuContext ctx;
|
2022-07-17 16:29:11 +02:00
|
|
|
private final NPCShopItem modified;
|
|
|
|
private NPCShopItem original;
|
2022-07-16 20:10:38 +02:00
|
|
|
|
2022-07-17 17:01:00 +02:00
|
|
|
public NPCShopItemEditor(NPCShopItem item, Consumer<NPCShopItem> consumer) {
|
2022-07-17 16:29:11 +02:00
|
|
|
this.original = item;
|
|
|
|
this.modified = original.clone();
|
2022-07-17 17:01:00 +02:00
|
|
|
this.consumer = consumer;
|
2022-07-16 20:10:38 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public void initialise(MenuContext ctx) {
|
|
|
|
this.ctx = ctx;
|
2022-07-17 16:29:11 +02:00
|
|
|
if (modified.display != null) {
|
|
|
|
ctx.getSlot(9 + 4).setItemStack(modified.display);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
@MenuSlot(slot = { 4, 3 }, material = Material.REDSTONE_BLOCK, amount = 1, title = "Cancel")
|
|
|
|
public void onCancel(InventoryMenuSlot slot, CitizensInventoryClickEvent event) {
|
|
|
|
event.setCancelled(true);
|
|
|
|
ctx.getMenu().transitionBack();
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public void onClose(HumanEntity who) {
|
2022-07-17 17:01:00 +02:00
|
|
|
if (original != null && original.display == null) {
|
2022-07-17 16:29:11 +02:00
|
|
|
original = null;
|
|
|
|
}
|
|
|
|
consumer.accept(original);
|
|
|
|
}
|
|
|
|
|
|
|
|
@MenuSlot(slot = { 0, 3 }, material = Material.BOOK, amount = 1, title = "Set description")
|
|
|
|
public void onEditDescription(InventoryMenuSlot slot, CitizensInventoryClickEvent event) {
|
|
|
|
event.setCancelled(true);
|
2022-07-17 17:01:00 +02:00
|
|
|
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);
|
|
|
|
}));
|
2022-07-17 16:29:11 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
@MenuSlot(slot = { 0, 5 }, material = Material.FEATHER, amount = 1, title = "Set name")
|
|
|
|
public void onEditName(InventoryMenuSlot slot, CitizensInventoryClickEvent event) {
|
|
|
|
event.setCancelled(true);
|
2022-07-17 17:01:00 +02:00
|
|
|
if (modified.display == null)
|
|
|
|
return;
|
|
|
|
ctx.getMenu().transition(InputMenus.stringSetter(modified.display.getItemMeta()::getDisplayName, name -> {
|
|
|
|
ItemMeta meta = modified.display.getItemMeta();
|
|
|
|
meta.setDisplayName(ChatColor.RESET + Colorizer.parseColors(name));
|
|
|
|
modified.display.setItemMeta(meta);
|
|
|
|
}));
|
2022-07-17 16:29:11 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
@ClickHandler(slot = { 1, 4 })
|
|
|
|
public void onModifyDisplayItem(InventoryMenuSlot slot, CitizensInventoryClickEvent event) {
|
|
|
|
event.setCancelled(true);
|
|
|
|
if (event.getCursor() != null) {
|
|
|
|
event.setCurrentItem(event.getCursor());
|
|
|
|
modified.display = event.getCursor().clone();
|
|
|
|
} else {
|
|
|
|
event.setCurrentItem(null);
|
|
|
|
modified.display = null;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
@MenuSlot(slot = { 4, 4 }, material = Material.TNT, amount = 1, lore = "<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, lore = "Save")
|
|
|
|
public void onSave(InventoryMenuSlot slot, CitizensInventoryClickEvent event) {
|
|
|
|
original = modified;
|
|
|
|
event.setCancelled(true);
|
|
|
|
ctx.getMenu().transitionBack();
|
2022-07-16 20:10:38 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-02-19 13:27:41 +01:00
|
|
|
public static class NPCShopPage {
|
|
|
|
@Persist("")
|
|
|
|
private int index;
|
|
|
|
@Persist(reify = true)
|
|
|
|
private final Map<Integer, NPCShopItem> items = Maps.newHashMap();
|
|
|
|
@Persist
|
|
|
|
private String title;
|
2022-07-17 16:29:11 +02:00
|
|
|
|
|
|
|
public NPCShopPage(int page) {
|
|
|
|
this.index = page;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
@Menu(title = "NPC Shop Page Editor", type = InventoryType.CHEST, dimensions = { 5, 9 })
|
|
|
|
public static class NPCShopPageEditor extends InventoryMenuPage {
|
|
|
|
private MenuContext ctx;
|
|
|
|
private final NPCShopPage page;
|
|
|
|
|
|
|
|
public NPCShopPageEditor(NPCShopPage page) {
|
|
|
|
this.page = page;
|
|
|
|
}
|
|
|
|
|
|
|
|
@MenuSlot(slot = { 0, 4 }, material = Material.FEATHER, amount = 1)
|
|
|
|
public void editPageTitle(InventoryMenuSlot slot, CitizensInventoryClickEvent event) {
|
|
|
|
event.setCancelled(true);
|
|
|
|
ctx.getMenu().transition(InputMenus.stringSetter(() -> page.title, newTitle -> {
|
|
|
|
page.title = newTitle.isEmpty() ? null : newTitle;
|
|
|
|
}));
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public void initialise(MenuContext ctx) {
|
|
|
|
this.ctx = ctx;
|
|
|
|
ctx.getSlot(4).setDescription("Set page title<br>Currently: " + page.title);
|
|
|
|
}
|
|
|
|
|
|
|
|
@MenuSlot(slot = { 4, 4 }, material = Material.TNT, amount = 1, title = "<c>Remove page")
|
|
|
|
public void removePage(InventoryMenuSlot slot, CitizensInventoryClickEvent event) {
|
|
|
|
event.setCancelled(true);
|
|
|
|
ctx.data().put("removePage", page.index);
|
|
|
|
ctx.getMenu().transitionBack();
|
|
|
|
}
|
2021-07-14 15:23:55 +02:00
|
|
|
}
|
|
|
|
|
2022-04-20 05:55:15 +02:00
|
|
|
public enum ShopType {
|
|
|
|
BUY,
|
2022-07-17 16:29:11 +02:00
|
|
|
COMMAND,
|
|
|
|
SELL;
|
2022-04-20 05:55:15 +02:00
|
|
|
}
|
|
|
|
|
2022-03-04 15:14:28 +01:00
|
|
|
@Persist(value = "npcShops", reify = true, namespace = "shopstrait")
|
2021-07-14 15:23:55 +02:00
|
|
|
private static Map<String, NPCShop> NPC_SHOPS = Maps.newHashMap();
|
2022-03-04 15:14:28 +01:00
|
|
|
@Persist(value = "namedShops", reify = true, namespace = "shopstrait")
|
2021-07-14 15:23:55 +02:00
|
|
|
private static Map<String, NPCShop> SHOPS = Maps.newHashMap();
|
|
|
|
}
|