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.