diff --git a/ChestsPlusPlus_Main/src/main/java/com/jamesdpeters/minecraft/chests/ChestsPlusPlus.java b/ChestsPlusPlus_Main/src/main/java/com/jamesdpeters/minecraft/chests/ChestsPlusPlus.java index c1847f9..1e2a918 100644 --- a/ChestsPlusPlus_Main/src/main/java/com/jamesdpeters/minecraft/chests/ChestsPlusPlus.java +++ b/ChestsPlusPlus_Main/src/main/java/com/jamesdpeters/minecraft/chests/ChestsPlusPlus.java @@ -14,6 +14,8 @@ import com.jamesdpeters.minecraft.chests.maventemplates.BuildConstants; import com.jamesdpeters.minecraft.chests.misc.Permissions; import com.jamesdpeters.minecraft.chests.misc.Stats; import com.jamesdpeters.minecraft.chests.misc.Utils; +import com.jamesdpeters.minecraft.chests.party.PlayerParty; +import com.jamesdpeters.minecraft.chests.party.PlayerPartyStorage; import com.jamesdpeters.minecraft.chests.serialize.Config; import com.jamesdpeters.minecraft.chests.serialize.ConfigStorage; import com.jamesdpeters.minecraft.chests.serialize.LocationInfo; @@ -60,6 +62,9 @@ import org.bukkit.plugin.java.annotation.plugin.author.Author; @Permission(name = Permissions.AUTOCRAFT_OPEN_REMOTE, desc = "Gives permission to remotely open AutoCrafting stations.", defaultValue = PermissionDefault.TRUE) @Permission(name = Permissions.AUTOCRAFT_ADD, desc = "Gives permission to add AutoCrafting stations.", defaultValue = PermissionDefault.TRUE) @Permission(name = Permissions.AUTOCRAFT_REMOVE, desc = "Gives permission to remove AutoCrafting stations.", defaultValue = PermissionDefault.TRUE) +@Permission(name = Permissions.PARTY_CREATE, desc = "Gives permission to create Chests++ parties.", defaultValue = PermissionDefault.TRUE) +@Permission(name = Permissions.PARTY_ACCEPT_INVITE, desc = "Gives permission to accept Chests++ party invites.", defaultValue = PermissionDefault.TRUE) +@Permission(name = Permissions.PARTY_INVITE, desc = "Gives permission to invite players to Chests++ parties.", defaultValue = PermissionDefault.TRUE) public class ChestsPlusPlus extends JavaPlugin { public static JavaPlugin PLUGIN; @@ -73,6 +78,8 @@ public class ChestsPlusPlus extends JavaPlugin { ConfigurationSerialization.registerClass(AutoCraftingStorage.class, "AutoCraftingStorage"); ConfigurationSerialization.registerClass(RecipeSerializable.class, "Recipe"); ConfigurationSerialization.registerClass(LocationInfo.class, "LocationInfo"); + ConfigurationSerialization.registerClass(PlayerPartyStorage.class, "PlayerPartyStorage"); + ConfigurationSerialization.registerClass(PlayerParty.class, "PlayerParty"); } @@ -122,15 +129,15 @@ public class ChestsPlusPlus extends JavaPlugin { }, 0, PluginConfig.UPDATE_CHECKER_PERIOD.get() * 20); } + // Remove armour stands if disabled + Utils.fixEntities(); + //Load storages after load. Bukkit.getScheduler().scheduleSyncDelayedTask(this, () -> { Crafting.load(); new Config(); getLogger().info("Chests++ Successfully Loaded Config and Recipes"); - // Remove armour stands if disabled - Utils.fixEntities(); - //Register event listeners getServer().getPluginManager().registerEvents(new StorageListener(), this); getServer().getPluginManager().registerEvents(new InventoryListener(), this); diff --git a/ChestsPlusPlus_Main/src/main/java/com/jamesdpeters/minecraft/chests/commands/ChestsPlusPlusCommand.java b/ChestsPlusPlus_Main/src/main/java/com/jamesdpeters/minecraft/chests/commands/ChestsPlusPlusCommand.java index 764265d..1d85e67 100644 --- a/ChestsPlusPlus_Main/src/main/java/com/jamesdpeters/minecraft/chests/commands/ChestsPlusPlusCommand.java +++ b/ChestsPlusPlus_Main/src/main/java/com/jamesdpeters/minecraft/chests/commands/ChestsPlusPlusCommand.java @@ -2,7 +2,14 @@ package com.jamesdpeters.minecraft.chests.commands; import com.jamesdpeters.minecraft.chests.ChestsPlusPlus; import com.jamesdpeters.minecraft.chests.api.ApiSpecific; +import com.jamesdpeters.minecraft.chests.inventories.PartyMenu; +import com.jamesdpeters.minecraft.chests.lang.Message; import com.jamesdpeters.minecraft.chests.maventemplates.BuildConstants; +import com.jamesdpeters.minecraft.chests.misc.Messages; +import com.jamesdpeters.minecraft.chests.misc.Permissions; +import com.jamesdpeters.minecraft.chests.misc.Utils; +import com.jamesdpeters.minecraft.chests.party.PartyUtils; +import com.jamesdpeters.minecraft.chests.serialize.Config; import org.bukkit.Bukkit; import org.bukkit.ChatColor; import org.bukkit.command.Command; @@ -17,6 +24,8 @@ public class ChestsPlusPlusCommand extends ServerCommand { private enum OPTIONS { VERSION("/chestsplusplus version", "Display the current version of the plugin."), + PARTY("/chestsplusplus party ", "Create, delete or manage members of your party"), + PARTY_MENU("/chestsplusplus party ", "Create, delete or manage members of your party"), RELOAD("/chestsplusplus reload", "Reloads the plugin."); String description, commandHelp; @@ -45,6 +54,11 @@ public class ChestsPlusPlusCommand extends ServerCommand { @Override public boolean onCommand(CommandSender sender, Command command, String label, String[] args) { + if (!(sender instanceof Player)) { + sender.sendMessage("Only a player can use this command"); + return false; + } + Player player = (Player) sender; if (args != null && args.length > 0) { switch (OPTIONS.valueOf(args[0].toUpperCase())) { case VERSION: @@ -58,6 +72,73 @@ public class ChestsPlusPlusCommand extends ServerCommand { ChestsPlusPlus.PLUGIN.onEnable(); return true; + case PARTY_MENU: + PartyMenu.getMenu(player).getMenu().open(player); + return true; + + case PARTY: + if (args.length > 1) { + if (sender.hasPermission(Permissions.PARTY_ACCEPT_INVITE)) { + if (args[1].toLowerCase().equals("accept-invite")) { + PartyUtils.acceptInvite(player); + return true; + } + } else { + Messages.NO_PERMISSION(player); + return true; + } + if (args[1].toLowerCase().equals("list")) { + // TODO Print list of owned parties. + return true; + } + } + if (args.length > 2) { + if(sender.hasPermission(Permissions.PARTY_CREATE)){ + if (args[1].toLowerCase().equals("create")){ + String partyName = args[2]; + boolean result = PartyUtils.createParty(player, partyName); + if (result){ + sender.sendMessage(ChatColor.GREEN+Message.PARTY_CREATED.getString(ChatColor.WHITE+partyName+ChatColor.GREEN)); + } else { + sender.sendMessage(ChatColor.RED+Message.PARTY_ALREADY_EXISTS.getString(ChatColor.WHITE+partyName+ChatColor.RED)); + } + return true; + } + if (args[1].toLowerCase().equals("delete")){ + String partyName = args[2]; + boolean result = PartyUtils.deleteParty(player, partyName); + if (result){ + sender.sendMessage(ChatColor.GREEN+Message.PARTY_DELETED.getString(ChatColor.WHITE+partyName+ChatColor.GREEN)); + } else { + sender.sendMessage(ChatColor.RED+Message.PARTY_DOESNT_EXIST.getString(ChatColor.WHITE+partyName+ChatColor.RED)); + } + return true; + } + } else { + Messages.NO_PERMISSION(player); + return true; + } + if (args.length > 3) { + if (sender.hasPermission(Permissions.PARTY_INVITE)) { + if (args[1].toLowerCase().equals("invite")) { + String partyName = args[2]; + String playerToInvite = args[3]; + PartyUtils.invitePlayer(player, Bukkit.getOfflinePlayer(playerToInvite), partyName); + return true; + } + } else { + Messages.NO_PERMISSION(player); + return true; + } + if (args[1].toLowerCase().equals("remove-member")) { + String partyName = args[2]; + String playerToInvite = args[3]; + PartyUtils.removePlayer(player, Bukkit.getOfflinePlayer(playerToInvite), partyName); + return true; + } + } + } + default: for (ChestsPlusPlusCommand.OPTIONS option : ChestsPlusPlusCommand.OPTIONS.values()) { sender.sendMessage(ChatColor.RED + option.commandHelp); @@ -72,26 +153,44 @@ public class ChestsPlusPlusCommand extends ServerCommand { @Override public List onTabComplete(CommandSender sender, Command command, String alias, String[] args) { if ((sender instanceof Player)) { + Player player = (Player) sender; if (args.length == 1) { return OPTIONS.valuesList; } if (args.length == 2) { try { switch (OPTIONS.valueOf(args[0].toUpperCase())) { - + case PARTY: + return Stream.of("create", "invite", "delete", "remove-member", "accept-invite", "list").filter(s -> s.contains(args[1])).collect(Collectors.toList()); } } catch (IllegalArgumentException ignored) { } } if (args.length == 3) { try { - + switch (OPTIONS.valueOf(args[0].toUpperCase())) { + case PARTY: { + String arg = args[1]; + if (arg.equals("delete") || arg.equals("invite") || arg.equals("remove-member")) { + List strings = PartyUtils.getPlayerPartyStorage(player).getOwnedPartiesAsStrings(); + return Utils.filterList(strings, args[2]); + } + } + } } catch (IllegalArgumentException ignored) { } } if (args.length == 4) { try { + switch (OPTIONS.valueOf(args[0].toUpperCase())) { + case PARTY: { + String arg = args[2]; + if (arg.equals("invite") || arg.equals("remove-member")) { + return Utils.filterList(Utils.getAllPlayers(), args[3]); + } + } + } } catch (IllegalArgumentException ignored) { } } diff --git a/ChestsPlusPlus_Main/src/main/java/com/jamesdpeters/minecraft/chests/inventories/ChestLinkMenu.java b/ChestsPlusPlus_Main/src/main/java/com/jamesdpeters/minecraft/chests/inventories/ChestLinkMenu.java index 3a84a1e..89246a7 100644 --- a/ChestsPlusPlus_Main/src/main/java/com/jamesdpeters/minecraft/chests/inventories/ChestLinkMenu.java +++ b/ChestsPlusPlus_Main/src/main/java/com/jamesdpeters/minecraft/chests/inventories/ChestLinkMenu.java @@ -20,10 +20,11 @@ import java.util.List; public class ChestLinkMenu implements InventoryProvider { - public static HashMap menus; + public static HashMap menus; private final Collection storages; private final SmartInventory menu; + private int lastPage; // Store the last page the player was on. private ChestLinkMenu(Player player) { this.storages = Config.getChestLink().getStorageMap(player.getUniqueId()).values(); @@ -34,16 +35,17 @@ public class ChestLinkMenu implements InventoryProvider { .manager(ChestsPlusPlus.INVENTORY_MANAGER) .size(6, 9) .build(); + lastPage = 0; //menu.setInsertable(true); } - public static SmartInventory getMenu(Player player) { + public static ChestLinkMenu getMenu(Player player) { if (menus == null) menus = new HashMap<>(); if (menus.containsKey(player)) { return menus.get(player); } else { - menus.put(player, new ChestLinkMenu(player).getMenu()); + menus.put(player, new ChestLinkMenu(player)); return menus.get(player); } } @@ -72,9 +74,23 @@ public class ChestLinkMenu implements InventoryProvider { } contents.set(5, 2, ClickableItem.from(Utils.getNamedItem(new ItemStack(Material.ARROW), "Previous"), - e -> menu.open(player, pagination.previous().getPage()))); + e -> { + lastPage = pagination.previous().getPage(); + menu.open(player, lastPage); + })); contents.set(5, 6, ClickableItem.from(Utils.getNamedItem(new ItemStack(Material.ARROW), "Next"), - e -> menu.open(player, pagination.next().getPage()))); + e -> { + lastPage = pagination.next().getPage(); + menu.open(player, lastPage); + })); + } + + public void openLastPage(Player player){ + menu.open(player, lastPage); + } + + public void open(Player player){ + menu.open(player); } @Override diff --git a/ChestsPlusPlus_Main/src/main/java/com/jamesdpeters/minecraft/chests/inventories/PartyMenu.java b/ChestsPlusPlus_Main/src/main/java/com/jamesdpeters/minecraft/chests/inventories/PartyMenu.java new file mode 100644 index 0000000..f48ffc7 --- /dev/null +++ b/ChestsPlusPlus_Main/src/main/java/com/jamesdpeters/minecraft/chests/inventories/PartyMenu.java @@ -0,0 +1,73 @@ +package com.jamesdpeters.minecraft.chests.inventories; + +import com.jamesdpeters.minecraft.chests.ChestsPlusPlus; +import com.jamesdpeters.minecraft.chests.misc.Utils; +import com.jamesdpeters.minecraft.chests.serialize.Config; +import com.jamesdpeters.minecraft.chests.storage.chestlink.ChestLinkStorage; +import fr.minuskube.inv.ClickableItem; +import fr.minuskube.inv.SmartInventory; +import fr.minuskube.inv.content.InventoryContents; +import fr.minuskube.inv.content.InventoryProvider; +import fr.minuskube.inv.content.Pagination; +import org.bukkit.Material; +import org.bukkit.entity.Player; +import org.bukkit.inventory.ItemStack; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.HashMap; +import java.util.List; + +public class PartyMenu implements InventoryProvider { + + public static HashMap menus; + + private final SmartInventory menu; + + private ClickableItem CREATE = ClickableItem.from(Utils.getNamedItem(new ItemStack(Material.ANVIL), "Create a Party"), itemClickData -> {itemClickData.getPlayer().sendMessage("CREATE");}); + private ClickableItem INVITE = ClickableItem.from(Utils.getNamedItem(new ItemStack(Material.WRITABLE_BOOK), "Invite a Player to a Party"), itemClickData -> {itemClickData.getPlayer().sendMessage("INVITE");}); + private ClickableItem REMOVE_PLAYER = ClickableItem.from(Utils.getNamedItem(new ItemStack(Material.SKELETON_SKULL), "Remove a Player from a Party"), itemClickData -> {itemClickData.getPlayer().sendMessage("INVITE");}); + private ClickableItem LIST = ClickableItem.from(Utils.getNamedItem(new ItemStack(Material.MAP), "List all your parties"), itemClickData -> {itemClickData.getPlayer().sendMessage("LIST");}); + private ClickableItem DELETE = ClickableItem.from(Utils.getNamedItem(new ItemStack(Material.BARRIER), "Delete a Party"), itemClickData -> {itemClickData.getPlayer().sendMessage("DELETE");}); + + private List itemList = Arrays.asList(CREATE, INVITE, LIST, DELETE); + + private PartyMenu() { + menu = SmartInventory.builder() + .id("partyMenu") + .title("Party Menu") + .provider(this) + .manager(ChestsPlusPlus.INVENTORY_MANAGER) + .size(3, 9) + .build(); + //menu.setInsertable(true); + } + + public static PartyMenu getMenu(Player player) { + if (menus == null) menus = new HashMap<>(); + + if (!menus.containsKey(player)) { + menus.put(player, new PartyMenu()); + } + return menus.get(player); + } + + @Override + public void init(Player player, InventoryContents contents) { + + contents.fillBorders(ClickableItem.empty(Utils.getNamedItem(new ItemStack(Material.GRAY_STAINED_GLASS_PANE), " "))); + for (ClickableItem item : itemList) { + contents.add(item); + } + } + + @Override + public void update(Player player, InventoryContents contents) { + + } + + public SmartInventory getMenu() { + return menu; + } +} diff --git a/ChestsPlusPlus_Main/src/main/java/com/jamesdpeters/minecraft/chests/lang/Message.java b/ChestsPlusPlus_Main/src/main/java/com/jamesdpeters/minecraft/chests/lang/Message.java index 0c364ae..6465a2a 100644 --- a/ChestsPlusPlus_Main/src/main/java/com/jamesdpeters/minecraft/chests/lang/Message.java +++ b/ChestsPlusPlus_Main/src/main/java/com/jamesdpeters/minecraft/chests/lang/Message.java @@ -1,8 +1,11 @@ package com.jamesdpeters.minecraft.chests.lang; +import org.bukkit.ChatColor; + import java.text.MessageFormat; public enum Message { + //Messages.java CHEST_HAD_OVERFLOW("Chest item's wouldn't all fit into ChestLink!"), MUST_HOLD_SIGN("You must be holding a sign to do that!"), @@ -67,7 +70,18 @@ public enum Message { ITEM_FRAME_FILTER_DEFAULT("ItemFrame is in default filtering mode. Rotate Item Frame to change mode!"), ITEM_FRAME_FILTER_ALL_TYPES("ItemFrame now filters all types of this item! e.g Enchanted Books."), ITEM_FRAME_FILTER_DENY("ItemFrame now prevents this item from being accepted in the hopper!"), - ITEM_FRAME_FILTER_DENY_ALL_TYPES("ItemFrame now prevents all types of this item from being accepted in the hopper! e.g Enchanted Books."); + ITEM_FRAME_FILTER_DENY_ALL_TYPES("ItemFrame now prevents all types of this item from being accepted in the hopper! e.g Enchanted Books."), + + //Party + PARTY_CREATED("Party {0} has been created!", Tag.PARTY_NAME), + PARTY_DELETED("Party {0} has been deleted!", Tag.PARTY_NAME), + PARTY_ALREADY_EXISTS("Party {0} already exists, unable to create it!", Tag.PARTY_NAME), + PARTY_DOESNT_EXIST("The party {0} doesn't exist!", Tag.PARTY_NAME), + PARTY_INVITE("You have been invited to join {0}''s party: {1}", Tag.PLAYER_NAME, Tag.PARTY_NAME), + PARTY_INVITE_OWNER("You have invited {0} to join your party: {1}", Tag.PLAYER_NAME, Tag.PARTY_NAME), + PARTY_JOINED("You have joined {0}''s party: {1}", Tag.PLAYER_NAME, Tag.PARTY_NAME), + PARTY_NO_INVITE("You currently have no pending party invites!"), + PARTY_ACCEPT_INVITE("Use "+ ChatColor.GREEN+ChatColor.BOLD+"/chestsplusplus party accept-invite"+ChatColor.RESET+" or "+ ChatColor.GREEN+ChatColor.BOLD+"'/c++ party accept-invite'"+ChatColor.RESET+" to accept the invite!"); String message; diff --git a/ChestsPlusPlus_Main/src/main/java/com/jamesdpeters/minecraft/chests/lang/Tag.java b/ChestsPlusPlus_Main/src/main/java/com/jamesdpeters/minecraft/chests/lang/Tag.java index 405f017..efa3204 100644 --- a/ChestsPlusPlus_Main/src/main/java/com/jamesdpeters/minecraft/chests/lang/Tag.java +++ b/ChestsPlusPlus_Main/src/main/java/com/jamesdpeters/minecraft/chests/lang/Tag.java @@ -1,6 +1,7 @@ package com.jamesdpeters.minecraft.chests.lang; public enum Tag { + PARTY_NAME, STORAGE_IDENTIFIER, SORT_METHOD, PLAYER_NAME, diff --git a/ChestsPlusPlus_Main/src/main/java/com/jamesdpeters/minecraft/chests/misc/Permissions.java b/ChestsPlusPlus_Main/src/main/java/com/jamesdpeters/minecraft/chests/misc/Permissions.java index dc18d27..5edcd6a 100644 --- a/ChestsPlusPlus_Main/src/main/java/com/jamesdpeters/minecraft/chests/misc/Permissions.java +++ b/ChestsPlusPlus_Main/src/main/java/com/jamesdpeters/minecraft/chests/misc/Permissions.java @@ -14,4 +14,7 @@ public class Permissions { public static final String AUTOCRAFT_OPEN_REMOTE = "chestlink.autocraft.remote-open"; public static final String AUTOCRAFT_ADD = "chestlink.autocraft.add"; public static final String AUTOCRAFT_REMOVE = "chestlink.autocraft.remove"; + public static final String PARTY_CREATE = "chestslink.party.create"; + public static final String PARTY_INVITE = "chestlink.party.invite"; + public static final String PARTY_ACCEPT_INVITE = "chestlink.party.accept_invite"; } diff --git a/ChestsPlusPlus_Main/src/main/java/com/jamesdpeters/minecraft/chests/party/PartyInvite.java b/ChestsPlusPlus_Main/src/main/java/com/jamesdpeters/minecraft/chests/party/PartyInvite.java new file mode 100644 index 0000000..d6207ea --- /dev/null +++ b/ChestsPlusPlus_Main/src/main/java/com/jamesdpeters/minecraft/chests/party/PartyInvite.java @@ -0,0 +1,43 @@ +package com.jamesdpeters.minecraft.chests.party; + +import com.jamesdpeters.minecraft.chests.lang.Message; +import org.bukkit.ChatColor; +import org.bukkit.OfflinePlayer; +import org.bukkit.entity.Player; + +public class PartyInvite { + + OfflinePlayer owner, player; + PlayerParty party; + boolean pending; + + public PartyInvite(OfflinePlayer owner, OfflinePlayer player, PlayerParty party) { + this.owner = owner; + this.player = player; + this.party = party; + } + + public void sendInvite() { + Player onlinePlayer = player.getPlayer(); + if(onlinePlayer != null) { + onlinePlayer.sendMessage(ChatColor.GREEN+Message.PARTY_INVITE.getString(ChatColor.WHITE+player.getName()+ChatColor.GREEN, ChatColor.WHITE+party.getPartyName()+ChatColor.GREEN)); + onlinePlayer.sendMessage(Message.PARTY_ACCEPT_INVITE.getString()); + } + Player onlineOwner = owner.getPlayer(); + if (onlineOwner != null){ + onlineOwner.sendMessage(ChatColor.GREEN+Message.PARTY_INVITE_OWNER.getString(ChatColor.WHITE+player.getName()+ChatColor.GREEN, ChatColor.WHITE+party.getPartyName()+ChatColor.GREEN)); + } + pending = true; + } + + public void acceptInvite(){ + if (pending){ + party.addMember(player); + Player onlinePlayer = player.getPlayer(); + if (onlinePlayer != null){ + onlinePlayer.sendMessage(ChatColor.GREEN+Message.PARTY_JOINED.getString(ChatColor.WHITE+owner.getName()+ChatColor.GREEN, ChatColor.WHITE+party.getPartyName()+ChatColor.GREEN)); + } + pending = false; + } + } +} diff --git a/ChestsPlusPlus_Main/src/main/java/com/jamesdpeters/minecraft/chests/party/PartyUtils.java b/ChestsPlusPlus_Main/src/main/java/com/jamesdpeters/minecraft/chests/party/PartyUtils.java new file mode 100644 index 0000000..fbb8ea3 --- /dev/null +++ b/ChestsPlusPlus_Main/src/main/java/com/jamesdpeters/minecraft/chests/party/PartyUtils.java @@ -0,0 +1,125 @@ +package com.jamesdpeters.minecraft.chests.party; + +import com.jamesdpeters.minecraft.chests.lang.Message; +import com.jamesdpeters.minecraft.chests.serialize.Config; +import org.bukkit.ChatColor; +import org.bukkit.OfflinePlayer; +import org.bukkit.entity.Player; + +import java.util.HashMap; +import java.util.UUID; + +public class PartyUtils { + + public enum PARTY_STATUS { + PARTY_ALREADY_EXISTS, + PARTY_CREATED + } + + // Stores the last party invite sent to this player. + private static HashMap latestPartyInvite = new HashMap<>(); + + /** + * Invites a player to the owners given party. The last pending invite is overwritten. + * @param owner + * @param playerToInvite + * @param partyName + */ + public static void invitePlayer(OfflinePlayer owner, OfflinePlayer playerToInvite, String partyName){ + PlayerPartyStorage storage = getPlayerPartyStorage(owner); + + PlayerParty party = storage.getOwnedParties().get(partyName); + if (party == null){ + Player onlineOwner = owner.getPlayer(); + if(onlineOwner != null) { + onlineOwner.sendMessage(Message.PARTY_DOESNT_EXIST.getString(partyName)); + } + return; + } + + PartyInvite invite = new PartyInvite(owner, playerToInvite, storage.getOwnedParties().get(partyName)); + latestPartyInvite.put(playerToInvite.getUniqueId(), invite); + + invite.sendInvite(); + } + + public static void removePlayer(OfflinePlayer owner, OfflinePlayer playerToRemove, String partyName){ + PlayerPartyStorage storage = getPlayerPartyStorage(owner); + + PlayerParty party = storage.getOwnedParties().get(partyName); + if (party == null){ + Player onlineOwner = owner.getPlayer(); + if(onlineOwner != null) { + onlineOwner.sendMessage(Message.PARTY_DOESNT_EXIST.getString(partyName)); + } + return; + } + + party.removeMember(playerToRemove); + } + + /** + * Accepts the current pending invite for this player. + * @param player + */ + public static void acceptInvite(OfflinePlayer player){ + PartyInvite invite = latestPartyInvite.get(player.getUniqueId()); + if (invite == null) { + Player onlinePlayer = player.getPlayer(); + if (onlinePlayer != null) { + onlinePlayer.sendMessage(Message.PARTY_NO_INVITE.getString()); + } + return; + } + invite.acceptInvite(); + latestPartyInvite.remove(player.getUniqueId()); + } + + /** + * Creates a party. + * @param owner + * @param partyName + * @return + */ + public static boolean createParty(OfflinePlayer owner, String partyName){ + PlayerPartyStorage storage = getPlayerPartyStorage(owner); + + // Check if party already exists. + if (storage.getOwnedParties().containsKey(partyName)) return false; + + storage.getOwnedParties().put(partyName, new PlayerParty(owner, partyName)); + return true; + } + + public static boolean deleteParty(OfflinePlayer owner, String partyName){ + HashMap map = Config.getStore().parties; + PlayerPartyStorage storage = map.get(owner.getUniqueId().toString()); + if (storage == null) { + storage = new PlayerPartyStorage(owner); + map.put(owner.getUniqueId().toString(), storage); + } + + // Check if party already exists. + if (!storage.getOwnedParties().containsKey(partyName)) { + Player onlineOwner = owner.getPlayer(); + if (onlineOwner != null) { + onlineOwner.sendMessage(ChatColor.RED + Message.PARTY_DOESNT_EXIST.getString(partyName)); + } + return false; + } + + // Remove party + storage.getOwnedParties().remove(partyName); + return true; + } + + public static PlayerPartyStorage getPlayerPartyStorage(OfflinePlayer owner) { + HashMap map = Config.getStore().parties; + PlayerPartyStorage storage = map.get(owner.getUniqueId().toString()); + if (storage == null){ + storage = new PlayerPartyStorage(owner); + map.put(owner.getUniqueId().toString(), storage); + } + return storage; + } +} diff --git a/ChestsPlusPlus_Main/src/main/java/com/jamesdpeters/minecraft/chests/party/PlayerParty.java b/ChestsPlusPlus_Main/src/main/java/com/jamesdpeters/minecraft/chests/party/PlayerParty.java new file mode 100644 index 0000000..82eb18b --- /dev/null +++ b/ChestsPlusPlus_Main/src/main/java/com/jamesdpeters/minecraft/chests/party/PlayerParty.java @@ -0,0 +1,89 @@ +package com.jamesdpeters.minecraft.chests.party; + +import org.bukkit.Bukkit; +import org.bukkit.OfflinePlayer; +import org.bukkit.configuration.serialization.ConfigurationSerializable; +import org.bukkit.configuration.serialization.SerializableAs; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.UUID; + +@SerializableAs("PlayerParty") +public class PlayerParty implements ConfigurationSerializable { + + private OfflinePlayer owner; + private UUID ownerUUID; + private String partyName; + private List members; + private ArrayList memberUUIDs; + + public PlayerParty(OfflinePlayer owner, String partyName) { + this.owner = owner; + this.ownerUUID = owner.getUniqueId(); + this.partyName = partyName; + this.members = new ArrayList<>(); + } + + @SuppressWarnings("unchecked") + public PlayerParty(Map map){ + ownerUUID = UUID.fromString((String) map.get("owner")); + owner = Bukkit.getOfflinePlayer(ownerUUID); + + partyName = (String) map.get("partyName"); + + Object o = map.get("members"); + if (o != null) { + memberUUIDs = (ArrayList) o; + members = new ArrayList<>(); + for (String uuid : memberUUIDs){ + members.add(Bukkit.getOfflinePlayer(UUID.fromString(uuid))); + } + } + + } + + + @Override + public Map serialize() { + HashMap map = new LinkedHashMap<>(); + map.put("owner", owner.getUniqueId().toString()); + map.put("partyName", partyName); + + memberUUIDs = new ArrayList<>(); + members.forEach(player -> memberUUIDs.add(player.getUniqueId().toString())); + map.put("members", memberUUIDs); + return map; + } + + public OfflinePlayer getOwner() { + return owner; + } + + public String getPartyName() { + return partyName; + } + + public List getMembers() { + return members; + } + + public void addMember(OfflinePlayer player) { + if (members == null){ + members = new ArrayList<>(); + } + members.add(player); + } + + public void removeMember(OfflinePlayer player) { + if (members != null) members.remove(player); + } + + public boolean isMember(OfflinePlayer player) { + return getMembers().stream().anyMatch(p -> p.getUniqueId().equals(player.getUniqueId())); + } + +} diff --git a/ChestsPlusPlus_Main/src/main/java/com/jamesdpeters/minecraft/chests/party/PlayerPartyStorage.java b/ChestsPlusPlus_Main/src/main/java/com/jamesdpeters/minecraft/chests/party/PlayerPartyStorage.java new file mode 100644 index 0000000..2ea313f --- /dev/null +++ b/ChestsPlusPlus_Main/src/main/java/com/jamesdpeters/minecraft/chests/party/PlayerPartyStorage.java @@ -0,0 +1,75 @@ +package com.jamesdpeters.minecraft.chests.party; + +import com.jamesdpeters.minecraft.chests.serialize.Config; +import org.bukkit.Bukkit; +import org.bukkit.OfflinePlayer; +import org.bukkit.configuration.serialization.ConfigurationSerializable; +import org.bukkit.configuration.serialization.SerializableAs; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.UUID; + +@SerializableAs("PlayerPartyStorage") +public class PlayerPartyStorage implements ConfigurationSerializable { + + private OfflinePlayer owner; + private HashMap ownedParties; + + public PlayerPartyStorage(OfflinePlayer owner){ + this.owner = owner; + this.ownedParties = new HashMap<>(); + } + + @SuppressWarnings("unchecked") + public PlayerPartyStorage(Map map){ + UUID ownerUUID = UUID.fromString((String) map.get("owner")); + owner = Bukkit.getOfflinePlayer(ownerUUID); + + ownedParties = (HashMap) map.get("ownedParties"); + if (ownedParties == null) ownedParties = new HashMap<>(); + } + + @Override + public Map serialize() { + HashMap map = new LinkedHashMap<>(); + map.put("owner", owner.getUniqueId().toString()); + map.put("ownedParties", ownedParties); + return map; + } + + /** + * Checks the owners parties to see if the given player is a member. + */ + public static boolean doPlayersShareParty(OfflinePlayer owner, OfflinePlayer player){ + PlayerPartyStorage storage = Config.getStore().parties.get(owner.getUniqueId().toString()); + if (storage != null){ + for (PlayerParty ownedParty : storage.ownedParties.values()) { + if (ownedParty.getMembers().contains(player)) return true; + } + } + return false; + } + + public OfflinePlayer getOwner() { + return owner; + } + + public HashMap getOwnedParties() { + return ownedParties; + } + + public Collection getOwnedPartiesCollection() { + return ownedParties.values(); + } + + public List getOwnedPartiesAsStrings(){ + List strings = new ArrayList<>(); + ownedParties.values().forEach(party -> strings.add(party.getPartyName())); + return strings; + } +} diff --git a/ChestsPlusPlus_Main/src/main/java/com/jamesdpeters/minecraft/chests/serialize/ConfigStorage.java b/ChestsPlusPlus_Main/src/main/java/com/jamesdpeters/minecraft/chests/serialize/ConfigStorage.java index 02fe4cb..9788eb8 100644 --- a/ChestsPlusPlus_Main/src/main/java/com/jamesdpeters/minecraft/chests/serialize/ConfigStorage.java +++ b/ChestsPlusPlus_Main/src/main/java/com/jamesdpeters/minecraft/chests/serialize/ConfigStorage.java @@ -1,5 +1,6 @@ package com.jamesdpeters.minecraft.chests.serialize; +import com.jamesdpeters.minecraft.chests.party.PlayerPartyStorage; import com.jamesdpeters.minecraft.chests.storage.autocraft.AutoCraftingStorage; import com.jamesdpeters.minecraft.chests.storage.chestlink.ChestLinkStorage; import org.bukkit.configuration.serialization.ConfigurationSerializable; @@ -15,12 +16,14 @@ public class ConfigStorage implements ConfigurationSerializable { public HashMap> chests; public HashMap> autocraftingtables; + public HashMap parties; @Override public Map serialize() { LinkedHashMap hashMap = new LinkedHashMap<>(); hashMap.put("chests", chests); hashMap.put("autocraftingtables", autocraftingtables); + hashMap.put("parties", parties); return hashMap; } @@ -28,25 +31,31 @@ public class ConfigStorage implements ConfigurationSerializable { public ConfigStorage(Map map) { //Legacy handling - chests = (HashMap>) map.get("chests"); if (chests == null) chests = new HashMap<>(); -// } autocraftingtables = (HashMap>) map.get("autocraftingtables"); if (autocraftingtables == null) autocraftingtables = new HashMap<>(); + + parties = (HashMap) map.get("parties"); + if (parties == null) parties = new HashMap<>(); + validate(); } private void validate() { - if (chests != null) chests.forEach((s, invMap) -> invMap.values().removeIf(Objects::isNull)); + if (chests != null) + chests.forEach((s, invMap) -> invMap.values().removeIf(Objects::isNull)); if (autocraftingtables != null) autocraftingtables.forEach((s, craftMap) -> craftMap.values().removeIf(Objects::isNull)); + if (parties != null) + parties.values().removeIf(Objects::isNull); } public ConfigStorage() { chests = new HashMap<>(); autocraftingtables = new HashMap<>(); + parties = new HashMap<>(); } } diff --git a/ChestsPlusPlus_Main/src/main/java/com/jamesdpeters/minecraft/chests/storage/abstracts/AbstractStorage.java b/ChestsPlusPlus_Main/src/main/java/com/jamesdpeters/minecraft/chests/storage/abstracts/AbstractStorage.java index c0f11ba..1c0b2e7 100644 --- a/ChestsPlusPlus_Main/src/main/java/com/jamesdpeters/minecraft/chests/storage/abstracts/AbstractStorage.java +++ b/ChestsPlusPlus_Main/src/main/java/com/jamesdpeters/minecraft/chests/storage/abstracts/AbstractStorage.java @@ -5,6 +5,7 @@ import com.jamesdpeters.minecraft.chests.api.ApiSpecific; import com.jamesdpeters.minecraft.chests.misc.Permissions; import com.jamesdpeters.minecraft.chests.misc.Utils; import com.jamesdpeters.minecraft.chests.misc.Values; +import com.jamesdpeters.minecraft.chests.party.PlayerPartyStorage; import com.jamesdpeters.minecraft.chests.serialize.LocationInfo; import org.bukkit.Bukkit; import org.bukkit.ChatColor; @@ -350,6 +351,7 @@ public abstract class AbstractStorage implements ConfigurationSerializable { public boolean hasPermission(OfflinePlayer player) { if (isPublic) return true; if (player.getUniqueId().equals(playerUUID)) return true; + if (PlayerPartyStorage.doPlayersShareParty(getOwner(), player)) return true; if (members != null) { for (String uuid : members) { if (player.getUniqueId().toString().equals(uuid)) return true; diff --git a/ChestsPlusPlus_Main/src/main/java/com/jamesdpeters/minecraft/chests/storage/abstracts/StorageType.java b/ChestsPlusPlus_Main/src/main/java/com/jamesdpeters/minecraft/chests/storage/abstracts/StorageType.java index 2d7e0fa..a23bae9 100644 --- a/ChestsPlusPlus_Main/src/main/java/com/jamesdpeters/minecraft/chests/storage/abstracts/StorageType.java +++ b/ChestsPlusPlus_Main/src/main/java/com/jamesdpeters/minecraft/chests/storage/abstracts/StorageType.java @@ -5,6 +5,7 @@ import com.jamesdpeters.minecraft.chests.ChestsPlusPlus; import com.jamesdpeters.minecraft.chests.misc.Messages; import com.jamesdpeters.minecraft.chests.misc.Utils; import com.jamesdpeters.minecraft.chests.misc.Values; +import com.jamesdpeters.minecraft.chests.party.PartyUtils; import com.jamesdpeters.minecraft.chests.serialize.Config; import com.jamesdpeters.minecraft.chests.serialize.ConfigStorage; import com.jamesdpeters.minecraft.chests.serialize.LocationInfo; @@ -142,6 +143,8 @@ public abstract class StorageType implements Listener public List getStorageMemberOf(Player player) { return getMap().entrySet().stream().flatMap(map -> map.getValue().values().stream().filter(storage -> { + if (PartyUtils.getPlayerPartyStorage(storage.getOwner()).getOwnedPartiesCollection().stream().anyMatch(party -> party.isMember(player))) return true; // Uses party to match. + if (storage.isPublic()) return false; if (storage.getOwner().getUniqueId().equals(player.getUniqueId())) return false; if (storage.getMembers() == null) return false; @@ -243,6 +246,7 @@ public abstract class StorageType implements Listener public T removeBlock(T storage, Location location, boolean hasPickedUp) { if (storage != null) { storage.removeLocation(location); + storageCache.remove(location); if (storage.getLocationsSize() == 0 && !hasPickedUp) { storage.dropInventory(location); getStorageMap(storage.getOwner().getUniqueId()).remove(storage.getIdentifier()); diff --git a/ChestsPlusPlus_Main/src/main/java/com/jamesdpeters/minecraft/chests/storage/chestlink/ChestLinkStorage.java b/ChestsPlusPlus_Main/src/main/java/com/jamesdpeters/minecraft/chests/storage/chestlink/ChestLinkStorage.java index 132b38f..db352cf 100644 --- a/ChestsPlusPlus_Main/src/main/java/com/jamesdpeters/minecraft/chests/storage/chestlink/ChestLinkStorage.java +++ b/ChestsPlusPlus_Main/src/main/java/com/jamesdpeters/minecraft/chests/storage/chestlink/ChestLinkStorage.java @@ -151,7 +151,7 @@ public class ChestLinkStorage extends AbstractStorage implements ConfigurationSe InventoryHolder inventoryHolder = getInventory().getHolder(); if (inventoryHolder instanceof VirtualInventoryHolder) { ((VirtualInventoryHolder) inventoryHolder).setPreviousInventory(() -> { - Bukkit.getScheduler().runTask(ChestsPlusPlus.PLUGIN, () -> ChestLinkMenu.getMenu(player).open(player)); + Bukkit.getScheduler().runTask(ChestsPlusPlus.PLUGIN, () -> ChestLinkMenu.getMenu(player).openLastPage(player)); }); } Utils.openChestInventory(player, getInventory()); diff --git a/ChestsPlusPlus_Main/src/main/resources/lang/en_GB.properties b/ChestsPlusPlus_Main/src/main/resources/lang/en_GB.properties index caf56d2..d22f9db 100644 --- a/ChestsPlusPlus_Main/src/main/resources/lang/en_GB.properties +++ b/ChestsPlusPlus_Main/src/main/resources/lang/en_GB.properties @@ -4,6 +4,8 @@ # It should be located in the 'lang' folder # Then in config.yml 'language-file: default' would be renamed to 'language-file: en_US' # To help contribute to the plugin and provide new language files you can create a pull-request at https://github.com/JamesPeters98/ChestsPlusPlus or join our Discord https://discord.gg/YRs3mP5 +PARTY_CREATED=Party {party_name} has been created\! +PARTY_DELETED=Party {party_name} has been deleted\! COMMAND_CHESTLINK_LIST=Lists all ChestLinks that you own\! COMMAND_MEMBER=Add, remove or list members of a group COMMAND_AUTOCRAFT_LIST=Lists all AutoCraft groups that you own\! @@ -20,6 +22,7 @@ SORT=Sort method for {storage_identifier} has been set to {sort_method} ITEM_FRAME_FILTER_DENY_ALL_TYPES=ItemFrame now prevents all types of this item from being accepted in the hopper\! e.g Enchanted Books. NO_PERMISSION=You don't have permission to do that\! COMMAND_CHESTLINK_SETPUBLIC=Set a ChestLink to be accessible by anyone. +PARTY_NO_INVITE=You currently have no pending party invites\! CURRENT_MEMBERS=Current Members\: {player_list} CANNOT_RENAME_GROUP_DOESNT_EXIST=Error renaming group\! {storage_identifier} doesn't exist\! STORAGE_ADDED=Successfully added {storage_type} to group\: {storage_group} for {player_name} @@ -32,6 +35,7 @@ LIST_MEMBERS_OF_GROUP=Members of {storage_type} group {storage_identifier}\: {pl REMOVE_MEMBER_FROM_ALL=Successfully removed {player_name} from all {storage_type} groups COMMAND_CHESTLINK_RENAME=Rename a ChestLink. STORAGE_REMOVED=Successfully removed {storage_type} from group\: {storage_group} for {player_name} +PARTY_INVITE=You have been invited to join {player_name}''s party\: {party_name} COMMAND_CHESTLINK_REMOVE=Delete a ChestLink and drop its inventory at your feet\! COMMAND_CHESTLINK_MENU=Open the ChestLink menu to display all groups\! GROUP_DOESNT_EXIST={storage_group} isn't a valid {storage_type} group to remove\! @@ -41,11 +45,14 @@ CANNOT_RENAME_GROUP_ALREADY_EXISTS=Error renaming group\! {storage_identifier} a NO_ADDITIONAL_MEMBERS=There are no additional members in the group\: {storage_identifier} CHEST_HAD_OVERFLOW=Chest item's wouldn't all fit into ChestLink\! COMMAND_AUTOCRAFT_ADD=Create/add a Crafting Table to an AutoCraft group +PARTY_ALREADY_EXISTS=Party {party_name} already exists, unable to create it\! MUST_HOLD_SIGN=You must be holding a sign to do that\! COMMAND_CHESTLINK_ADD=Create/add a chest to a ChestLink group COMMAND_HELP=List of commands and their uses\! UNABLE_TO_ADD_MEMBER_TO_ALL=Unable to add player {player_name} to {storage_type}\! +PARTY_JOINED=You have joined {player_name}''s party\: {party_name} ITEM_FRAME_FILTER_ALL_TYPES=ItemFrame now filters all types of this item\! e.g Enchanted Books. +PARTY_INVITE_OWNER=You have invited {player_name} to join your party\: {party_name} COMMAND_AUTOCRAFT_RENAME=Rename an AutoCraft group. MUST_LOOK_AT_CRAFTING_TABLE=You must be looking at the Crafting Table you want to AutoCraft with\! COMMAND_AUTOCRAFT_REMOVE=Delete an AutoCraft group and drop all the Crafting Tables\! @@ -53,4 +60,6 @@ UNABLE_TO_REMOVE_MEMBER=Unable to remove player {player_name} from {storage_type COMMAND_AUTOCRAFT_SETPUBLIC=Set an AutoCraft group to be accessible by anyone. ALREADY_PART_OF_GROUP=This {storage_type} is already a part of a group\! INVALID_ID=Invalid {storage_type} ID\! Must not contain a colon '\:' unless you are referencing another players group that you are a member of +PARTY_ACCEPT_INVITE=Use §a§l/chestsplusplus party accept-invite§r or §a§l'/c++ party accept-invite'§r to accept the invite\! FOUND_UNLINKED_STORAGE=This {storage_type} wasn't linked to your system\! It has been added under the {storage_identifier} group\! +PARTY_DOESNT_EXIST=The party {party_name} doesn't exist\!