diff --git a/README.md b/README.md index 62d9d58..57a9653 100644 --- a/README.md +++ b/README.md @@ -44,3 +44,4 @@ cancelSelect - Cancels selecting a chest and cancels previously typed ChestNetwo [ ] YAML translation file [ ] Proper help [ ] Online manual +[ ] Integrations (LWC, protection, ...) diff --git a/build.gradle b/build.gradle index 9cc5b5a..85d1028 100644 --- a/build.gradle +++ b/build.gradle @@ -26,9 +26,8 @@ description = 'chestnetworks' spigot { authors 'Zhincore' - softDepends 'ChestSort' apiVersion '1.18' - excludeLibraries('acf-paper', 'ChestSortAPI') + excludeLibraries('acf-paper') } shadowJar { diff --git a/src/main/java/eu/zhincore/chestnetworks/ChestNetCommand.java b/src/main/java/eu/zhincore/chestnetworks/ChestNetCommand.java index 5e9fd07..ecc6ac6 100644 --- a/src/main/java/eu/zhincore/chestnetworks/ChestNetCommand.java +++ b/src/main/java/eu/zhincore/chestnetworks/ChestNetCommand.java @@ -3,17 +3,56 @@ import org.bukkit.entity.Player; import co.aikar.commands.BaseCommand; import co.aikar.commands.MessageType; +import co.aikar.commands.PaperCommandManager; import co.aikar.commands.annotation.*; +import co.aikar.locales.MessageKey; +import eu.zhincore.chestnetworks.networks.ChestNetworkManager; -@CommandAlias("cnet|chestnetworks") +@CommandAlias("cnet|chestnet|chestnetworks") @CommandPermission("chestnetworks.command") public class ChestNetCommand extends BaseCommand { @Dependency - private ChestNetworksPlugin plugin; + private PaperCommandManager cmdManager; + + @Dependency + private ChestNetworkManager networkManager; @Subcommand("list") - public void listNets(Player player) { - var networks = plugin.database.networkManager.networks.row(player.getUniqueId()); - ; + public void list(Player player) { + var networks = networkManager.listNetworks(player.getUniqueId()); + + if (networks.isEmpty()) { + cmdManager.sendMessage(player, MessageType.ERROR, MessageKey.of("chestnet.no_networks")); + } else { + var replacements = new String[] { "{networks}", String.join(", ", networks) }; + cmdManager.sendMessage(player, MessageType.INFO, MessageKey.of("chestnet.list"), replacements); + } + } + + @Subcommand("create") + public void create(Player player, String name) { + if (networkManager.create(player.getUniqueId(), name)) { + cmdManager.sendMessage(player, MessageType.INFO, MessageKey.of("chestnet.created"), "{name}", name); + } else { + cmdManager.sendMessage(player, MessageType.ERROR, MessageKey.of("chestnet.exists"), "{name}", name); + } + } + + @Subcommand("deleteNetwork") + public void deleteNetwork(Player player, @Values("@network") String name) { + if (networkManager.delete(player.getUniqueId(), name)) { + cmdManager.sendMessage(player, MessageType.INFO, MessageKey.of("chestnet.deleted"), "{name}", name); + } else { + cmdManager.sendMessage(player, MessageType.ERROR, MessageKey.of("chestnet.not_exists"), "{name}", name); + } + } + + @Subcommand("addChest|setChest") + public void setChest(Player player, String name) { + if (networkManager.create(player.getUniqueId(), name)) { + cmdManager.sendMessage(player, MessageType.INFO, MessageKey.of("chestnet.created"), "{name}", name); + } else { + cmdManager.sendMessage(player, MessageType.ERROR, MessageKey.of("chestnet.exists"), "{name}", name); + } } } diff --git a/src/main/java/eu/zhincore/chestnetworks/ChestNetListener.java b/src/main/java/eu/zhincore/chestnetworks/ChestNetListener.java new file mode 100644 index 0000000..8fdba13 --- /dev/null +++ b/src/main/java/eu/zhincore/chestnetworks/ChestNetListener.java @@ -0,0 +1,95 @@ +package eu.zhincore.chestnetworks; + +import java.util.HashMap; +import java.util.UUID; +import org.bukkit.Location; +import org.bukkit.Material; +import org.bukkit.event.EventHandler; +import org.bukkit.event.player.PlayerInteractEvent; +import co.aikar.commands.MessageType; +import co.aikar.commands.PaperCommandManager; +import co.aikar.locales.MessageKey; +import eu.zhincore.chestnetworks.networks.NetworkChest; + +public class ChestNetListener { + private PaperCommandManager cmdManager; + private Database database; + private HashMap playerSelect = new HashMap<>(); + + public ChestNetListener(ChestNetworksPlugin plugin, Database database) { + this.cmdManager = plugin.commandManager; + this.database = database; + } + + public void startAddChest(UUID playerId, String netName, NetworkChest chest) { + var entry = new SelectionEntry(SelectionEntry.Type.ADD, netName, chest); + playerSelect.put(playerId.toString(), entry); + } + + public void startCheckChest(UUID playerId) { + var entry = new SelectionEntry(SelectionEntry.Type.CHECK); + playerSelect.put(playerId.toString(), entry); + } + + @EventHandler + public void onPlayerInteract(PlayerInteractEvent ev) { + var player = ev.getPlayer(); + var playerId = player.getUniqueId().toString(); + var entry = playerSelect.get(playerId); + if (entry == null) return; + + var block = ev.getClickedBlock(); + if (block.getType() != Material.CHEST) { + cmdManager.sendMessage(player, MessageType.ERROR, MessageKey.of("chestnet.not_chest")); + return; + } + playerSelect.remove(playerId); + var location = block.getLocation(); + + var chestData = database.networkManager.getChestData(location); + + switch (entry.type) { + case ADD: + if (chestData != null) { + cmdManager.sendMessage(player, MessageType.ERROR, MessageKey.of("chestnet.chest_exists")); + return; + } + + var network = database.networkManager.get(playerId, entry.netName); + if (network == null) return; + + entry.chest.location = location; + network.addChest(entry.chest); + cmdManager.sendMessage(player, MessageType.INFO, MessageKey.of("chestnet.chest_added")); + break; + + case CHECK: + if (chestData == null) { + cmdManager.sendMessage(player, MessageType.ERROR, MessageKey.of("chestnet.chest_not_exists")); + return; + } + cmdManager.sendMessage(player, MessageType.ERROR, MessageKey.of("chestnet.chest_info"), "network"); + break; + } + } + + private class SelectionEntry { + public Type type; + public String netName; + public NetworkChest chest; + + public SelectionEntry(Type type) { + this.type = type; + } + + public SelectionEntry(Type type, String netName, NetworkChest chest) { + this.type = type; + this.netName = netName; + this.chest = chest; + } + + public enum Type { + ADD, CHECK + } + } +} diff --git a/src/main/java/eu/zhincore/chestnetworks/ChestNetworksPlugin.java b/src/main/java/eu/zhincore/chestnetworks/ChestNetworksPlugin.java index 42898b1..44d9026 100644 --- a/src/main/java/eu/zhincore/chestnetworks/ChestNetworksPlugin.java +++ b/src/main/java/eu/zhincore/chestnetworks/ChestNetworksPlugin.java @@ -3,31 +3,61 @@ import java.io.IOException; import java.util.Locale; import org.bukkit.Bukkit; +import org.bukkit.ChatColor; import org.bukkit.configuration.InvalidConfigurationException; +import org.bukkit.configuration.file.YamlConfiguration; import org.bukkit.plugin.java.JavaPlugin; +import com.google.common.collect.Lists; +import co.aikar.commands.MessageType; import co.aikar.commands.PaperCommandManager; -import eu.zhincore.chestnetworks.util.ChestNetMessages; +import eu.zhincore.chestnetworks.networks.ChestNetworkManager; public class ChestNetworksPlugin extends JavaPlugin { - public Database database = new Database(this); - public PaperCommandManager manager = new PaperCommandManager(this); - public ChestNetMessages messages; + public Database database; + public PaperCommandManager commandManager; @Override public void onEnable() { + // Initialize chestnet database + database = new Database(this); database.load(); - var locales = manager.getLocales(); + // Initialize ACF + commandManager = new PaperCommandManager(this); + commandManager.enableUnstableAPI("help"); + commandManager.usePerIssuerLocale(false); + commandManager.setFormat(MessageType.INFO, 1, ChatColor.GREEN); + + commandManager.registerDependency(PaperCommandManager.class, commandManager); + commandManager.registerDependency(ChestNetworkManager.class, database.networkManager); + + // Initialize ACF locales + var locales = commandManager.getLocales(); + locales.setDefaultLocale(Locale.ENGLISH); try { - locales.loadYamlLanguageFile("lang_en.yml", Locale.ENGLISH); + loadInternalYamlLanguageFile("lang_en.yml", Locale.ENGLISH); } catch (IOException | InvalidConfigurationException e) { e.printStackTrace(); Bukkit.getPluginManager().disablePlugin(this); return; } - messages = new ChestNetMessages(locales); - manager.registerCommand(new ChestNetCommand()); + // Initialize completions + var completions = commandManager.getCommandCompletions(); + completions.registerCompletion("network", c -> { + var networks = database.networkManager.listNetworks(c.getPlayer().getUniqueId()); + return Lists.newArrayList(networks); + }); + + // Initialize ACF commands + commandManager.registerCommand(new ChestNetCommand()); + } + + public boolean loadInternalYamlLanguageFile(String name, Locale locale) + throws IOException, InvalidConfigurationException { + var yamlConfiguration = new YamlConfiguration(); + yamlConfiguration.load(getTextResource(name)); + return commandManager.getLocales().loadLanguage(yamlConfiguration, locale); } @Override diff --git a/src/main/java/eu/zhincore/chestnetworks/Database.java b/src/main/java/eu/zhincore/chestnetworks/Database.java index f18bb40..d08f4d4 100644 --- a/src/main/java/eu/zhincore/chestnetworks/Database.java +++ b/src/main/java/eu/zhincore/chestnetworks/Database.java @@ -6,11 +6,13 @@ import java.io.InputStreamReader; import java.io.Reader; import org.bukkit.Location; +import com.google.common.collect.HashBasedTable; import com.google.gson.Gson; import com.google.gson.GsonBuilder; import eu.zhincore.chestnetworks.networks.ChestNetworkManager; import eu.zhincore.chestnetworks.util.BukkitLocationSerializer; import eu.zhincore.chestnetworks.util.ChestNetworksPluginSerializer; +import eu.zhincore.chestnetworks.util.HashBasedTableSerializer; import eu.zhincore.chestnetworks.util.SchedulableTask; public class Database { @@ -18,14 +20,16 @@ public class Database { private Gson gson; public ChestNetworksPlugin plugin; public ChestNetworkManager networkManager; - private SchedulableTask saveTask = new SchedulableTask(plugin, () -> this.save()); + private SchedulableTask saveTask; public Database(ChestNetworksPlugin plugin) { this.plugin = plugin; + saveTask = new SchedulableTask(plugin, () -> this.save()); file = new File(plugin.getDataFolder(), "data.json"); gson = new GsonBuilder().enableComplexMapKeySerialization() - .registerTypeAdapter(Location.class, new BukkitLocationSerializer()) - .registerTypeAdapter(ChestNetworksPlugin.class, new ChestNetworksPluginSerializer(plugin)).create(); + .registerTypeAdapter(ChestNetworksPlugin.class, new ChestNetworksPluginSerializer(plugin)) + .registerTypeAdapter(HashBasedTable.class, new HashBasedTableSerializer()) + .registerTypeAdapter(Location.class, new BukkitLocationSerializer()).create(); } public ChestNetworkManager load() { diff --git a/src/main/java/eu/zhincore/chestnetworks/networks/ChestNetwork.java b/src/main/java/eu/zhincore/chestnetworks/networks/ChestNetwork.java index 8690ca7..668b87f 100644 --- a/src/main/java/eu/zhincore/chestnetworks/networks/ChestNetwork.java +++ b/src/main/java/eu/zhincore/chestnetworks/networks/ChestNetwork.java @@ -3,7 +3,6 @@ import java.util.ArrayList; import java.util.HashSet; import java.util.List; -import java.util.UUID; import org.bukkit.inventory.ItemStack; import com.google.common.collect.ArrayListMultimap; import eu.zhincore.chestnetworks.ChestNetworksPlugin; @@ -12,19 +11,12 @@ import eu.zhincore.chestnetworks.util.SchedulableTask; public class ChestNetwork { - public UUID ownerId; - public String name; public boolean sort = true; public List chests = new ArrayList<>(); private ChestNetworksPlugin plugin; private transient ArrayListMultimap, NetworkChest> indexByContent = ArrayListMultimap.create(); - private transient SchedulableTask indexingTask = new SchedulableTask(plugin, () -> this.rebuildIndex()); - - public ChestNetwork(UUID ownerId, String name) { - this.ownerId = ownerId; - this.name = name; - } + private transient SchedulableTask indexingTask = new SchedulableTask(plugin, () -> this.rebuildIndex(), true); public void addChest(NetworkChest chest) { chests.add(chest); diff --git a/src/main/java/eu/zhincore/chestnetworks/networks/ChestNetworkManager.java b/src/main/java/eu/zhincore/chestnetworks/networks/ChestNetworkManager.java index c8d5e49..20adee5 100644 --- a/src/main/java/eu/zhincore/chestnetworks/networks/ChestNetworkManager.java +++ b/src/main/java/eu/zhincore/chestnetworks/networks/ChestNetworkManager.java @@ -2,29 +2,53 @@ import java.util.ArrayList; import java.util.List; +import java.util.Set; import java.util.UUID; +import org.bukkit.Location; import org.bukkit.inventory.Inventory; import com.google.common.collect.HashBasedTable; +import com.google.common.collect.ImmutableSet; import eu.zhincore.chestnetworks.ChestNetworksPlugin; public class ChestNetworkManager { - public HashBasedTable networks = HashBasedTable.create(); + private HashBasedTable networks = HashBasedTable.create(); private ChestNetworksPlugin plugin; public ChestNetworkManager(ChestNetworksPlugin plugin) { this.plugin = plugin; } + public Set listNetworks(UUID playerId) { + try { + if (networks.containsRow(playerId.toString())) return networks.row(playerId.toString()).keySet(); + } catch (java.lang.NullPointerException e) { + // Ignored + } + return ImmutableSet.of(); + } + public boolean create(UUID playerId, String netName) { - var playerNets = networks.row(playerId); + var playerNets = networks.row(playerId.toString()); if (playerNets.containsKey(netName)) return false; - playerNets.put(netName, new ChestNetwork(playerId, netName)); + playerNets.put(netName, new ChestNetwork()); plugin.database.scheduleSave(); return true; } + public ChestNetwork get(UUID playerId, String netName) { + return get(playerId.toString(), netName); + } + + public ChestNetwork get(String playerId, String netName) { + return networks.get(playerId, netName); + } + + public boolean delete(UUID playerId, String netName) { + return networks.remove(playerId.toString(), netName) != null; + } + public boolean update(UUID playerId, String netName) { - var network = networks.row(playerId).get(netName); + var network = networks.row(playerId.toString()).get(netName); if (network == null) return false; network.update(); return true; @@ -37,9 +61,11 @@ public void update(Inventory inventory) { chest.network.update(chest); } - private NetworkChest getChestData(Inventory inventory) { - var location = inventory.getLocation(); + public NetworkChest getChestData(Inventory inventory) { + return getChestData(inventory.getLocation()); + } + public NetworkChest getChestData(Location location) { for (var network : networks.values()) { for (var chest : network.chests) { if (chest.location.equals(location)) return chest; diff --git a/src/main/java/eu/zhincore/chestnetworks/networks/NetworkChest.java b/src/main/java/eu/zhincore/chestnetworks/networks/NetworkChest.java index a5faad0..239e08c 100644 --- a/src/main/java/eu/zhincore/chestnetworks/networks/NetworkChest.java +++ b/src/main/java/eu/zhincore/chestnetworks/networks/NetworkChest.java @@ -13,9 +13,8 @@ public class NetworkChest { public List content; public int priority = 0; - public NetworkChest(ChestType type, Location location, ChestNetwork network, List content) { + public NetworkChest(ChestType type, ChestNetwork network, List content) { this.type = type; - this.location = location; this.network = network; this.content = content; } @@ -28,7 +27,7 @@ public Inventory getInventory() { return chest.getInventory(); } - static enum ChestType { + public static enum ChestType { STORAGE, INPUT } } diff --git a/src/main/java/eu/zhincore/chestnetworks/util/ChestNetMessages.java b/src/main/java/eu/zhincore/chestnetworks/util/ChestNetMessages.java deleted file mode 100644 index 041fcdb..0000000 --- a/src/main/java/eu/zhincore/chestnetworks/util/ChestNetMessages.java +++ /dev/null @@ -1,13 +0,0 @@ -package eu.zhincore.chestnetworks.util; - -import co.aikar.commands.BukkitLocales; - -public class ChestNetMessages { - BukkitLocales locales; - - public ChestNetMessages(BukkitLocales locales) { - this.locales = locales; - } - - -} diff --git a/src/main/java/eu/zhincore/chestnetworks/util/ChestNetworksPluginSerializer.java b/src/main/java/eu/zhincore/chestnetworks/util/ChestNetworksPluginSerializer.java index 33be9dc..d2f25c8 100644 --- a/src/main/java/eu/zhincore/chestnetworks/util/ChestNetworksPluginSerializer.java +++ b/src/main/java/eu/zhincore/chestnetworks/util/ChestNetworksPluginSerializer.java @@ -13,8 +13,8 @@ public ChestNetworksPluginSerializer(ChestNetworksPlugin plugin) { } @Override - public JsonElement serialize(ChestNetworksPlugin location, Type typeOfSrc, JsonSerializationContext context) { - return JsonNull.INSTANCE; + public JsonElement serialize(ChestNetworksPlugin plugin, Type typeOfSrc, JsonSerializationContext context) { + return new JsonPrimitive("ChestNetworks"); } @Override diff --git a/src/main/java/eu/zhincore/chestnetworks/util/HashBasedTableSerializer.java b/src/main/java/eu/zhincore/chestnetworks/util/HashBasedTableSerializer.java new file mode 100644 index 0000000..71d6c81 --- /dev/null +++ b/src/main/java/eu/zhincore/chestnetworks/util/HashBasedTableSerializer.java @@ -0,0 +1,28 @@ +package eu.zhincore.chestnetworks.util; + +import java.lang.reflect.Type; +import java.util.HashMap; +import com.google.common.collect.HashBasedTable; +import com.google.gson.*; + +public class HashBasedTableSerializer implements JsonSerializer>, + JsonDeserializer> { + @Override + public JsonElement serialize(HashBasedTable table, Type typeOfSrc, + JsonSerializationContext context) { + return context.serialize(table.rowMap()); + } + + @Override + public HashBasedTable deserialize(JsonElement json, Type typeOfT, + JsonDeserializationContext context) { + HashMap> map = context.deserialize(json, HashMap.class); + var table = HashBasedTable.create(); + + for (var row : map.entrySet()) { + table.row(row.getKey()).putAll(row.getValue()); + } + + return table; + } +} diff --git a/src/main/java/eu/zhincore/chestnetworks/util/SchedulableTask.java b/src/main/java/eu/zhincore/chestnetworks/util/SchedulableTask.java index 4ab553c..bd080cb 100644 --- a/src/main/java/eu/zhincore/chestnetworks/util/SchedulableTask.java +++ b/src/main/java/eu/zhincore/chestnetworks/util/SchedulableTask.java @@ -8,12 +8,23 @@ public class SchedulableTask { public ChestNetworksPlugin plugin; private BukkitTask task; public Runnable runner; + public boolean sync = false; public int delay; public SchedulableTask(ChestNetworksPlugin plugin, Runnable runner) { this(plugin, runner, 1); } + public SchedulableTask(ChestNetworksPlugin plugin, Runnable runner, boolean sync, int delay) { + this(plugin, runner, delay); + this.sync = sync; + } + + public SchedulableTask(ChestNetworksPlugin plugin, Runnable runner, boolean sync) { + this(plugin, runner); + this.sync = sync; + } + public SchedulableTask(ChestNetworksPlugin plugin, Runnable runner, int delay) { this.plugin = plugin; this.runner = runner; @@ -22,7 +33,11 @@ public SchedulableTask(ChestNetworksPlugin plugin, Runnable runner, int delay) { public void schedule() { cancel(); - task = Bukkit.getScheduler().runTaskLaterAsynchronously(plugin, runner, delay); + if (sync) { + task = Bukkit.getScheduler().runTaskLater(plugin, runner, delay); + } else { + task = Bukkit.getScheduler().runTaskLaterAsynchronously(plugin, runner, delay); + } } public void cancel() { diff --git a/src/main/resources/lang_en.yml b/src/main/resources/lang_en.yml index 47739e3..ba0c900 100644 --- a/src/main/resources/lang_en.yml +++ b/src/main/resources/lang_en.yml @@ -1 +1,17 @@ -network_list: "You have created these networks:" +chestnet: + no_networks: You haven't created any networks yet. + list: "You have created these networks: {networks}." + created: Network '{name}' created. + exists: Network '{name}' already exists. + not_exists: Network '{name}' doesn't exists! + deleted: Network '{name}' deleted. + not_chest: The selected block is not a chest! + chest_added: Chest has been successfully added to the network! + chest_exists: This chest is already registered in a network! + chest_not_exists: This chest isn't registered in any network! + chest_info: + "Chest info:" + " Network: {network}" + " Owner: {owner}" + " Type: {type}" + " Content: {content}"