diff --git a/api/src/main/java/de/erethon/dungeonsxl/api/player/GroupAdapter.java b/api/src/main/java/de/erethon/dungeonsxl/api/player/GroupAdapter.java index b07b4f5b..352e2aed 100644 --- a/api/src/main/java/de/erethon/dungeonsxl/api/player/GroupAdapter.java +++ b/api/src/main/java/de/erethon/dungeonsxl/api/player/GroupAdapter.java @@ -14,10 +14,16 @@ */ package de.erethon.dungeonsxl.api.player; +import de.erethon.dungeonsxl.api.DungeonsAPI; +import java.util.HashMap; +import java.util.Map; +import java.util.Map.Entry; import org.bukkit.entity.Player; /** * Implement and register in order to track a group. + *

+ * See implementation classes in de.erethon.dungeonsxl.player.groupadapter for reference. * * @param the external group object * @author Daniel Saukel @@ -44,12 +50,50 @@ public abstract class GroupAdapter { ONLINE } + public class ExternalGroupData { + + private T eGroup; + private boolean createdByDXL; + + public ExternalGroupData(T eGroup, boolean createdByDXL) { + this.eGroup = eGroup; + this.createdByDXL = createdByDXL; + } + + /** + * Returns the wrapped external group object. + * + * @return the wrapped external group object + */ + public T get() { + return eGroup; + } + + /** + * Returns if the external group was created by DungeonsXL. + *

+ * Groups may be created by DungeonsXL, for example through a command, a group sign or automatically if a dungeon is entered. + * The integration implementation should give dungeon groups equivalent groups from the external group plugin. + * External groups created to mirror dungeon groups should be removed when their dungeon group is deleted, but those created intentionally should not. + * + * @return if the external group was created by DungeonsXL. + */ + public boolean isCreatedByDXL() { + return createdByDXL; + } + + } + + protected DungeonsAPI dxl; private Philosophy philosophy; + protected Map> groups = new HashMap<>(); /** + * @param dxl the DungeonsAPI instance * @param philosophy the player handling philosophy */ - protected GroupAdapter(Philosophy philosophy) { + protected GroupAdapter(DungeonsAPI dxl, Philosophy philosophy) { + this.dxl = dxl; this.philosophy = philosophy; } @@ -68,11 +112,10 @@ public abstract class GroupAdapter { * @param eGroup the external group * @return a dungeon group {@link #areCorresponding(PlayerGroup, Object) corresponding} with the external group */ - public abstract PlayerGroup createPlayerGroup(T eGroup); + public abstract PlayerGroup createDungeonGroup(T eGroup); /** - * Creates an external group {@link #areCorresponding(PlayerGroup, Object) corresponding} with the dungeon - * group. + * Creates an external group {@link #areCorresponding(PlayerGroup, Object) corresponding} with the dungeon group. * * @param dGroup the dungeon group * @return an external group {@link #areCorresponding(PlayerGroup, Object) corresponding} with the dungeon group @@ -80,24 +123,30 @@ public abstract class GroupAdapter { public abstract T createExternalGroup(PlayerGroup dGroup); /** - * Returns the dungeon group {@link #areCorresponding(PlayerGroup, Object) corresponding} with the external - * group or null of none exists. + * Returns the dungeon group {@link #areCorresponding(PlayerGroup, Object) corresponding} with the external group or null of none exists. * * @param eGroup the external group - * @return the dungeon group {@link #areCorresponding(PlayerGroup, Object) corresponding} with the external - * group + * @return the dungeon group {@link #areCorresponding(PlayerGroup, Object) corresponding} with the external group */ - public abstract PlayerGroup getPlayerGroup(T eGroup); + public PlayerGroup getDungeonGroup(T eGroup) { + for (Entry> entry : groups.entrySet()) { + if (entry.getValue().get().equals(eGroup)) { + return entry.getKey(); + } + } + return null; + } /** - * Returns the external group {@link #areCorresponding(PlayerGroup, Object) corresponding} with the dungeon - * group. + * Returns the external group {@link #areCorresponding(PlayerGroup, Object) corresponding} with the dungeon group. * * @param dGroup the dungeon group - * @return the external group {@link #areCorresponding(PlayerGroup, Object) corresponding} with the dungeon - * group + * @return the external group {@link #areCorresponding(PlayerGroup, Object) corresponding} with the dungeon group */ - public abstract T getExternalGroup(PlayerGroup dGroup); + public T getExternalGroup(PlayerGroup dGroup) { + ExternalGroupData data = groups.get(dGroup); + return data != null ? data.get() : null; + } /** * Returns the dungeon group that mirrors the external group. @@ -107,10 +156,10 @@ public abstract class GroupAdapter { * @param eGroup the dungeon group * @return the dungeon group that mirrors the dungeon group */ - public PlayerGroup getOrCreatePlayerGroup(T eGroup) { - PlayerGroup dGroup = getPlayerGroup(eGroup); + public PlayerGroup getOrCreateDungeonGroup(T eGroup) { + PlayerGroup dGroup = getDungeonGroup(eGroup); if (dGroup == null) { - dGroup = createPlayerGroup(eGroup); + dGroup = createDungeonGroup(eGroup); } return dGroup; } @@ -131,6 +180,14 @@ public abstract class GroupAdapter { return eGroup; } + /** + * Returns the external group of the given group member. + * + * @param member the group member + * @return the external group of the given group member + */ + public abstract T getExternalGroup(Player member); + /** * Checks if two groups are corresponding. *

@@ -142,7 +199,30 @@ public abstract class GroupAdapter { * @param eGroup the external group * @return if the two groups are corresponding */ - public abstract boolean areCorresponding(PlayerGroup dGroup, T eGroup); + public boolean areCorresponding(PlayerGroup dGroup, T eGroup) { + if (dGroup == null || eGroup == null) { + return false; + } + ExternalGroupData data = groups.get(dGroup); + return data != null && eGroup.equals(data.get()); + } + + /** + * Deletes the external group corresponding with the given dungeon group. + * + * @param dGroup the dungeon group corresponding with the external one to delete + * @return if the deletion was successful + */ + public abstract boolean deleteCorrespondingGroup(PlayerGroup dGroup); + + /** + * Checks if the two groups have the same members. + * + * @param dGroup the dungeon group + * @param eGroup the external group + * @return if the two groups have the same members + */ + public abstract boolean areSimilar(PlayerGroup dGroup, T eGroup); /** * Ensures that the player is in {@link #areCorresponding(PlayerGroup, Object) corresponding} groups. @@ -151,13 +231,28 @@ public abstract class GroupAdapter { * If no dungeon group exists, it is created automatically. Switching dungeon groups forces the player to leave their dungeon. *

* If the player is in a dungeon group but not in an external group, the player is added to the corresponding external group if it exists. - * If no corresponding external group exists, a new one is only created if the {@link #getPhilosophy() philosophy} is either - * {@link Philosophy#RUNTIME} or {@link Philosophy#ONLINE}. + * If no corresponding external group exists, a new one is created. * * @param player the player */ public void syncPlayer(Player player) { - throw new UnsupportedOperationException("TODO"); + T eGroup = getExternalGroup(player); + PlayerGroup dGroup = dxl.getPlayerGroup(player); + + if (eGroup != null && !areCorresponding(dGroup, eGroup)) { + if (areSimilar(dGroup, eGroup)) { + // The groups are not yet marked as corresponding because one of them is still being created. + return; + } + if (dGroup != null) { + dGroup.removePlayer(player, false); + return; + } + dGroup = createDungeonGroup(eGroup); + + } else if (eGroup == null && dGroup != null) { + createExternalGroup(dGroup); + } } } diff --git a/core/pom.xml b/core/pom.xml index 7f9be455..e1a0d0fc 100644 --- a/core/pom.xml +++ b/core/pom.xml @@ -89,6 +89,11 @@ 2.10.2 provided + + com.alessiodp.parties + parties-api + 2.6.14 + @@ -107,5 +112,9 @@ placeholderapi http://repo.extendedclip.com/content/repositories/placeholderapi/ + + codemc-repo + https://repo.codemc.org/repository/maven-public/ + diff --git a/core/src/main/java/de/erethon/dungeonsxl/DungeonsXL.java b/core/src/main/java/de/erethon/dungeonsxl/DungeonsXL.java index 4759b386..ab11142b 100644 --- a/core/src/main/java/de/erethon/dungeonsxl/DungeonsXL.java +++ b/core/src/main/java/de/erethon/dungeonsxl/DungeonsXL.java @@ -68,6 +68,7 @@ import de.erethon.dungeonsxl.player.DGroup; import de.erethon.dungeonsxl.player.DPermission; import de.erethon.dungeonsxl.player.DPlayerListener; import de.erethon.dungeonsxl.player.SecureModeTask; +import de.erethon.dungeonsxl.player.groupadapter.*; import de.erethon.dungeonsxl.requirement.*; import de.erethon.dungeonsxl.reward.*; import de.erethon.dungeonsxl.sign.DSignListener; @@ -137,6 +138,7 @@ public class DungeonsXL extends DREPlugin implements DungeonsAPI { private Registry gameRuleRegistry = new GameRuleRegistry(); private Registry externalMobProviderRegistry = new Registry<>(); private Registry playerGroupCache = new Registry<>(); + private Collection groupAdapters = new ArrayList<>(); @Deprecated private class SignRegistry extends Registry> { @@ -213,6 +215,9 @@ public class DungeonsXL extends DREPlugin implements DungeonsAPI { if (manager.isPluginEnabled("PlaceholderAPI")) { new PlaceholderUtil(this, "dxl").register(); } + if (manager.isPluginEnabled("Parties")) { + registerGroupAdapter(new PartiesAdapter(this)); + } VignetteAPI.init(this); } @@ -530,7 +535,16 @@ public class DungeonsXL extends DREPlugin implements DungeonsAPI { @Override public void registerGroupAdapter(GroupAdapter groupAdapter) { - throw new UnsupportedOperationException("Not supported yet."); + groupAdapters.add(groupAdapter); + } + + /** + * Returns a collection of the loadedGroupAdapters + * + * @return a collection of GroupAdapters + */ + public Collection getGroupAdapters() { + return groupAdapters; } /** diff --git a/core/src/main/java/de/erethon/dungeonsxl/player/DGroup.java b/core/src/main/java/de/erethon/dungeonsxl/player/DGroup.java index 11d31235..b789ad05 100644 --- a/core/src/main/java/de/erethon/dungeonsxl/player/DGroup.java +++ b/core/src/main/java/de/erethon/dungeonsxl/player/DGroup.java @@ -107,6 +107,8 @@ public class DGroup implements PlayerGroup { floorCount = 0; id = counter++; + + plugin.getGroupAdapters().forEach(a -> a.syncPlayer(player)); } public DGroup(DungeonsXL plugin, Player player, Dungeon dungeon) { @@ -143,6 +145,8 @@ public class DGroup implements PlayerGroup { floorCount = 0; id = counter++; + + plugin.getGroupAdapters().forEach(a -> a.syncPlayer(captain)); } // Getters and setters @@ -216,6 +220,8 @@ public class DGroup implements PlayerGroup { players.add(player.getUniqueId()); } + + plugin.getGroupAdapters().forEach(a -> a.syncPlayer(player)); } @Override @@ -238,8 +244,11 @@ public class DGroup implements PlayerGroup { if (!event.isCancelled()) { delete(); + return; } } + + plugin.getGroupAdapters().forEach(a -> a.syncPlayer(player)); } @Override @@ -662,6 +671,8 @@ public class DGroup implements PlayerGroup { } plugin.getGlobalProtectionCache().updateGroupSigns(this); + + plugin.getGroupAdapters().forEach(a -> a.deleteCorrespondingGroup(this)); } public boolean startGame(Game game) { diff --git a/core/src/main/java/de/erethon/dungeonsxl/player/groupadapter/PartiesAdapter.java b/core/src/main/java/de/erethon/dungeonsxl/player/groupadapter/PartiesAdapter.java new file mode 100644 index 00000000..1407a1ef --- /dev/null +++ b/core/src/main/java/de/erethon/dungeonsxl/player/groupadapter/PartiesAdapter.java @@ -0,0 +1,193 @@ +/* + * Copyright (C) 2012-2020 Frank Baumann + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package de.erethon.dungeonsxl.player.groupadapter; + +import com.alessiodp.parties.api.Parties; +import com.alessiodp.parties.api.events.bukkit.party.BukkitPartiesPartyPostCreateEvent; +import com.alessiodp.parties.api.events.bukkit.party.BukkitPartiesPartyPreDeleteEvent; +import com.alessiodp.parties.api.events.bukkit.party.BukkitPartiesPartyRenameEvent; +import com.alessiodp.parties.api.events.bukkit.player.BukkitPartiesPlayerPostJoinEvent; +import com.alessiodp.parties.api.events.bukkit.player.BukkitPartiesPlayerPostLeaveEvent; +import com.alessiodp.parties.api.interfaces.PartiesAPI; +import com.alessiodp.parties.api.interfaces.Party; +import com.alessiodp.parties.api.interfaces.PartyPlayer; +import de.erethon.commons.chat.MessageUtil; +import de.erethon.dungeonsxl.api.DungeonsAPI; +import de.erethon.dungeonsxl.api.player.GroupAdapter; +import de.erethon.dungeonsxl.api.player.PlayerGroup; +import de.erethon.dungeonsxl.config.DMessage; +import java.util.ArrayList; +import java.util.Collection; +import java.util.UUID; +import org.bukkit.Bukkit; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.Listener; +import org.bukkit.scheduler.BukkitRunnable; + +/** + * This class may be used as a reference for implementations of the GroupAdapter API. + * + * @author Daniel Saukel + */ +public class PartiesAdapter extends GroupAdapter implements Listener { + + private PartiesAPI partiesAPI; + + public PartiesAdapter(DungeonsAPI api) { + super(api, Philosophy.PERSISTENT); + Bukkit.getPluginManager().registerEvents(this, api); + partiesAPI = Parties.getApi(); + } + + @Override + public Party createExternalGroup(PlayerGroup dGroup) { + if (!partiesAPI.createParty(dGroup.getRawName(), partiesAPI.getPartyPlayer(dGroup.getLeader().getUniqueId()))) { + return null; + } + Party eGroup = partiesAPI.getParty(dGroup.getRawName()); + groups.put(dGroup, new ExternalGroupData<>(eGroup, true)); + return eGroup; + } + + @Override + public PlayerGroup createDungeonGroup(Party eGroup) { + PlayerGroup dGroup = dxl.createGroup(Bukkit.getPlayer(eGroup.getLeader()), eGroup.getName()); + eGroup.getMembers().forEach(uuid -> dGroup.addPlayer(Bukkit.getPlayer(uuid), false)); + groups.put(dGroup, new ExternalGroupData<>(eGroup, false)); + return dGroup; + } + + @Override + public Party getExternalGroup(Player member) { + PartyPlayer pPlayer = getPartyPlayer(member); + if (pPlayer == null) { + return null; + } + return partiesAPI.getParty(pPlayer.getPartyName()); + } + + public boolean addExternalGroupMember(Party eGroup, Player member) { + return eGroup.addMember(getPartyPlayer(member)); + } + + public boolean removeExternalGroupMember(Player member) { + PartyPlayer pPlayer = getPartyPlayer(member); + if (pPlayer == null) { + return false; + } + Party eGroup = partiesAPI.getParty(pPlayer.getPartyName()); + if (eGroup == null) { + return false; + } + eGroup.removeMember(pPlayer); + if (eGroup.getMembers().isEmpty()) { + eGroup.delete(); + } + return true; + } + + @Override + public boolean deleteCorrespondingGroup(PlayerGroup dGroup) { + ExternalGroupData data = groups.get(dGroup); + if (data == null || !data.isCreatedByDXL()) { + return false; + } + data.get().delete(); + groups.remove(dGroup); + return true; + } + + @Override + public boolean areSimilar(PlayerGroup dGroup, Party eGroup) { + if (dGroup == null || eGroup == null) { + return false; + } + + Collection members = new ArrayList<>(dGroup.getMembers().getUniqueIds()); + for (UUID member : eGroup.getMembers()) { + if (!members.contains(member)) { + return false; + } + members.remove(member); + } + return members.isEmpty(); + } + + @EventHandler + public void onCreation(BukkitPartiesPartyPostCreateEvent event) { + // Event is called asynchronously + new BukkitRunnable() { + @Override + public void run() { + createDungeonGroup(event.getParty()); + } + }.runTask(dxl); + } + + @EventHandler + public void onDeletion(BukkitPartiesPartyPreDeleteEvent event) { + PlayerGroup dGroup = getDungeonGroup(event.getParty()); + if (dGroup != null) { + groups.remove(dGroup); // This avoids circular deleting of groups + dGroup.delete(); + } + } + + @EventHandler + public void onRename(BukkitPartiesPartyRenameEvent event) { + PlayerGroup dGroup = getDungeonGroup(event.getParty()); + if (dGroup != null) { + dGroup.delete(); + } + if (dxl.getPlayerGroupCache().get(event.getNewPartyName()) != null) { + MessageUtil.sendMessage(getPlayer(event.getPartyPlayer()), DMessage.ERROR_NAME_IN_USE.getMessage(event.getNewPartyName())); + event.setCancelled(true); + return; + } + dGroup.setName(event.getNewPartyName()); + } + + @EventHandler + public void onJoin(BukkitPartiesPlayerPostJoinEvent event) { + new BukkitRunnable() { + @Override + public void run() { + syncPlayer(getPlayer(event.getPartyPlayer())); + } + }.runTask(dxl); + } + + @EventHandler + public void onLeave(BukkitPartiesPlayerPostLeaveEvent event) { + new BukkitRunnable() { + @Override + public void run() { + syncPlayer(getPlayer(event.getPartyPlayer())); + } + }.runTask(dxl); + } + + private Player getPlayer(PartyPlayer player) { + return Bukkit.getPlayer(player.getPlayerUUID()); + } + + private PartyPlayer getPartyPlayer(Player player) { + return partiesAPI.getPartyPlayer(player.getUniqueId()); + } + +} diff --git a/core/src/main/resources/plugin.yml b/core/src/main/resources/plugin.yml index cc514927..25ef7142 100644 --- a/core/src/main/resources/plugin.yml +++ b/core/src/main/resources/plugin.yml @@ -4,7 +4,7 @@ version: ${project.parent.version}${buildNo} authors: [Frank Baumann, Milan Albrecht, Tobias Schmitz, Daniel Saukel] description: ${project.parent.description} website: ${project.parent.url} -softdepend: [CommandsXL, ItemsXL, Vault, Citizens, CustomMobs, InsaneMobs, MythicMobs, HolographicDisplays, LWC, PlaceholderAPI] +softdepend: [CommandsXL, ItemsXL, Vault, Citizens, CustomMobs, InsaneMobs, MythicMobs, HolographicDisplays, LWC, PlaceholderAPI, Parties] commands: dungeonsxl: description: Reference command for DungeonsXL.