diff --git a/module/hologram-integration/src/main/java/org/battleplugins/arena/module/hologram/HologramIntegration.java b/module/hologram-integration/src/main/java/org/battleplugins/arena/module/hologram/HologramIntegration.java index 035631a2..f3346f61 100644 --- a/module/hologram-integration/src/main/java/org/battleplugins/arena/module/hologram/HologramIntegration.java +++ b/module/hologram-integration/src/main/java/org/battleplugins/arena/module/hologram/HologramIntegration.java @@ -10,7 +10,7 @@ import org.bukkit.event.EventHandler; /** - * A module that allows for hooking into various hologram plugin. + * A module that allows for hooking into various hologram plugins. */ @ArenaModule(id = HologramIntegration.ID, name = "Hologram", description = "Adds support for hooking into various Hologram plugins.", authors = "BattlePlugins") public class HologramIntegration implements ArenaModuleInitializer { diff --git a/module/party-integration/build.gradle.kts b/module/party-integration/build.gradle.kts new file mode 100644 index 00000000..47349194 --- /dev/null +++ b/module/party-integration/build.gradle.kts @@ -0,0 +1,10 @@ +repositories { + maven("https://repo.alessiodp.com/releases/") + maven("https://simonsator.de/repo/") +} + +dependencies { + compileOnly("com.alessiodp.parties:parties-api:3.2.15") + compileOnly("de.simonsator:Party-and-Friends-MySQL-Edition-Spigot-API:1.6.2-RELEASE") + compileOnly("de.simonsator:spigot-party-api-for-party-and-friends:1.0.7-RELEASE") +} diff --git a/module/party-integration/src/main/java/org/battleplugins/arena/module/party/PartyIntegration.java b/module/party-integration/src/main/java/org/battleplugins/arena/module/party/PartyIntegration.java new file mode 100644 index 00000000..85f76f32 --- /dev/null +++ b/module/party-integration/src/main/java/org/battleplugins/arena/module/party/PartyIntegration.java @@ -0,0 +1,33 @@ +package org.battleplugins.arena.module.party; + +import org.battleplugins.arena.event.BattleArenaPostInitializeEvent; +import org.battleplugins.arena.feature.party.Parties; +import org.battleplugins.arena.module.ArenaModule; +import org.battleplugins.arena.module.ArenaModuleInitializer; +import org.battleplugins.arena.module.party.paf.PAFPartiesFeature; +import org.battleplugins.arena.module.party.parties.PartiesPartiesFeature; +import org.bukkit.Bukkit; +import org.bukkit.event.EventHandler; + +/** + * A module that allows for hooking into various party plugins. + */ +@ArenaModule(id = PartyIntegration.ID, name = "Party", description = "Adds support for hooking into various Party plugins.", authors = "BattlePlugins") +public class PartyIntegration implements ArenaModuleInitializer { + public static final String ID = "party"; + + @EventHandler + public void onPostInitialize(BattleArenaPostInitializeEvent event) { + if (Bukkit.getPluginManager().isPluginEnabled("Spigot-Party-API-PAF")) { + Parties.register(new PAFPartiesFeature()); + + event.getBattleArena().info("Parties for Friends (API) found. Using Spigot-Party-API-PAF for party integration."); + } + + if (Bukkit.getPluginManager().isPluginEnabled("Parties")) { + Parties.register(new PartiesPartiesFeature()); + + event.getBattleArena().info("Parties found. Using Parties for party integration."); + } + } +} diff --git a/module/party-integration/src/main/java/org/battleplugins/arena/module/party/paf/PAFPartiesFeature.java b/module/party-integration/src/main/java/org/battleplugins/arena/module/party/paf/PAFPartiesFeature.java new file mode 100644 index 00000000..4c4bf57a --- /dev/null +++ b/module/party-integration/src/main/java/org/battleplugins/arena/module/party/paf/PAFPartiesFeature.java @@ -0,0 +1,28 @@ +package org.battleplugins.arena.module.party.paf; + +import de.simonsator.partyandfriends.spigot.api.pafplayers.PAFPlayer; +import de.simonsator.partyandfriends.spigot.api.pafplayers.PAFPlayerManager; +import de.simonsator.partyandfriends.spigot.api.party.PartyManager; +import org.battleplugins.arena.feature.PluginFeature; +import org.battleplugins.arena.feature.party.PartiesFeature; +import org.battleplugins.arena.feature.party.Party; +import org.jetbrains.annotations.Nullable; + +import java.util.UUID; + +public class PAFPartiesFeature extends PluginFeature implements PartiesFeature { + + public PAFPartiesFeature() { + super("Spigot-Party-API-PAF"); + } + + @Override + public @Nullable Party getParty(UUID uuid) { + PAFPlayer player = PAFPlayerManager.getInstance().getPlayer(uuid); + if (player == null) { + return null; + } + + return new PAFParty(PartyManager.getInstance().getParty(player)); + } +} diff --git a/module/party-integration/src/main/java/org/battleplugins/arena/module/party/paf/PAFParty.java b/module/party-integration/src/main/java/org/battleplugins/arena/module/party/paf/PAFParty.java new file mode 100644 index 00000000..36901e03 --- /dev/null +++ b/module/party-integration/src/main/java/org/battleplugins/arena/module/party/paf/PAFParty.java @@ -0,0 +1,28 @@ +package org.battleplugins.arena.module.party.paf; + +import de.simonsator.partyandfriends.spigot.api.party.PlayerParty; +import org.battleplugins.arena.feature.party.Party; +import org.battleplugins.arena.feature.party.PartyMember; + +import java.util.Set; +import java.util.stream.Collectors; + +public class PAFParty implements Party { + private final PlayerParty impl; + + public PAFParty(PlayerParty impl) { + this.impl = impl; + } + + @Override + public PartyMember getLeader() { + return new PAFPartyMember(this.impl.getLeader()); + } + + @Override + public Set getMembers() { + return this.impl.getPlayers().stream() + .map(PAFPartyMember::new) + .collect(Collectors.toSet()); + } +} diff --git a/module/party-integration/src/main/java/org/battleplugins/arena/module/party/paf/PAFPartyMember.java b/module/party-integration/src/main/java/org/battleplugins/arena/module/party/paf/PAFPartyMember.java new file mode 100644 index 00000000..5ef10b06 --- /dev/null +++ b/module/party-integration/src/main/java/org/battleplugins/arena/module/party/paf/PAFPartyMember.java @@ -0,0 +1,31 @@ +package org.battleplugins.arena.module.party.paf; + +import de.simonsator.partyandfriends.spigot.api.pafplayers.PAFPlayer; +import de.simonsator.partyandfriends.spigot.api.party.PartyManager; +import org.battleplugins.arena.feature.party.Party; +import org.battleplugins.arena.feature.party.PartyMember; + +import java.util.UUID; + +public class PAFPartyMember implements PartyMember { + private final PAFPlayer impl; + + public PAFPartyMember(PAFPlayer impl) { + this.impl = impl; + } + + @Override + public String getName() { + return this.impl.getName(); + } + + @Override + public UUID getUniqueId() { + return this.impl.getUniqueId(); + } + + @Override + public Party getParty() { + return new PAFParty(PartyManager.getInstance().getParty(this.impl)); + } +} diff --git a/module/party-integration/src/main/java/org/battleplugins/arena/module/party/parties/PartiesPartiesFeature.java b/module/party-integration/src/main/java/org/battleplugins/arena/module/party/parties/PartiesPartiesFeature.java new file mode 100644 index 00000000..4131cf26 --- /dev/null +++ b/module/party-integration/src/main/java/org/battleplugins/arena/module/party/parties/PartiesPartiesFeature.java @@ -0,0 +1,26 @@ +package org.battleplugins.arena.module.party.parties; + +import com.alessiodp.parties.api.Parties; +import org.battleplugins.arena.feature.PluginFeature; +import org.battleplugins.arena.feature.party.PartiesFeature; +import org.battleplugins.arena.feature.party.Party; +import org.jetbrains.annotations.Nullable; + +import java.util.UUID; + +public class PartiesPartiesFeature extends PluginFeature implements PartiesFeature { + + public PartiesPartiesFeature() { + super("Parties"); + } + + @Override + public @Nullable Party getParty(UUID uuid) { + com.alessiodp.parties.api.interfaces.Party party = Parties.getApi().getPartyOfPlayer(uuid); + if (party == null) { + return null; + } + + return new PartiesParty(party); + } +} diff --git a/module/party-integration/src/main/java/org/battleplugins/arena/module/party/parties/PartiesParty.java b/module/party-integration/src/main/java/org/battleplugins/arena/module/party/parties/PartiesParty.java new file mode 100644 index 00000000..f0e8c10f --- /dev/null +++ b/module/party-integration/src/main/java/org/battleplugins/arena/module/party/parties/PartiesParty.java @@ -0,0 +1,46 @@ +package org.battleplugins.arena.module.party.parties; + +import com.alessiodp.parties.api.Parties; +import com.alessiodp.parties.api.interfaces.PartyPlayer; +import org.battleplugins.arena.feature.party.Party; +import org.battleplugins.arena.feature.party.PartyMember; + +import java.util.Set; +import java.util.stream.Collectors; + +public class PartiesParty implements Party { + private final com.alessiodp.parties.api.interfaces.Party impl; + + public PartiesParty(com.alessiodp.parties.api.interfaces.Party impl) { + this.impl = impl; + } + + @Override + public PartyMember getLeader() { + if (this.impl.getLeader() == null) { + // Return the first member of the party if the leader is null + Set members = this.getMembers(); + if (!members.isEmpty()) { + return members.iterator().next(); + } + + return null; + } + + PartyPlayer player = Parties.getApi().getPartyPlayer(this.impl.getLeader()); + if (player == null) { + return null; + } + + return new PartiesPartyMember(player); + } + + @Override + public Set getMembers() { + return this.impl.getOnlineMembers() + .stream() + .filter(m -> !m.getPlayerUUID().equals(this.impl.getLeader())) + .map(PartiesPartyMember::new) + .collect(Collectors.toSet()); + } +} diff --git a/module/party-integration/src/main/java/org/battleplugins/arena/module/party/parties/PartiesPartyMember.java b/module/party-integration/src/main/java/org/battleplugins/arena/module/party/parties/PartiesPartyMember.java new file mode 100644 index 00000000..41d3e981 --- /dev/null +++ b/module/party-integration/src/main/java/org/battleplugins/arena/module/party/parties/PartiesPartyMember.java @@ -0,0 +1,36 @@ +package org.battleplugins.arena.module.party.parties; + +import com.alessiodp.parties.api.Parties; +import com.alessiodp.parties.api.interfaces.PartyPlayer; +import org.battleplugins.arena.feature.party.Party; +import org.battleplugins.arena.feature.party.PartyMember; + +import java.util.UUID; + +public class PartiesPartyMember implements PartyMember { + private final PartyPlayer impl; + + public PartiesPartyMember(PartyPlayer impl) { + this.impl = impl; + } + + @Override + public String getName() { + return this.impl.getName(); + } + + @Override + public UUID getUniqueId() { + return this.impl.getPlayerUUID(); + } + + @Override + public Party getParty() { + com.alessiodp.parties.api.interfaces.Party party = Parties.getApi().getPartyOfPlayer(this.impl.getPlayerUUID()); + if (party == null) { + return null; + } + + return new PartiesParty(party); + } +} diff --git a/plugin/src/main/java/org/battleplugins/arena/BattleArena.java b/plugin/src/main/java/org/battleplugins/arena/BattleArena.java index 0ff9bc49..aa0815bc 100644 --- a/plugin/src/main/java/org/battleplugins/arena/BattleArena.java +++ b/plugin/src/main/java/org/battleplugins/arena/BattleArena.java @@ -45,6 +45,7 @@ import java.nio.file.Files; import java.nio.file.Path; import java.util.ArrayList; +import java.util.Collection; import java.util.Comparator; import java.util.HashMap; import java.util.List; @@ -465,6 +466,21 @@ public CompletableFuture getOrCreateCompetition(Arena arena, return this.competitionManager.getOrCreateCompetition(arena, player, role, name); } + /** + * Returns a currently active {@link Competition} for the given {@link Arena}, + * {@link Player}s, {@link PlayerRole} and map name. If no competition is found, + * a new one is created if applicable. + * + * @param arena the arena to get the competition for + * @param players the players to get the competition for + * @param role the role of the player + * @param name the name of the competition + * @return the competition result + */ + public CompletableFuture getOrCreateCompetition(Arena arena, Collection players, PlayerRole role, @Nullable String name) { + return this.competitionManager.getOrCreateCompetition(arena, players, role, name); + } + /** * Finds a joinable {@link Competition} for the given {@link Player} and {@link PlayerRole}. * @@ -477,6 +493,18 @@ public CompletableFuture findJoinableCompetition(List findJoinableCompetition(List> competitions, Collection players, PlayerRole role) { + return this.competitionManager.findJoinableCompetition(competitions, players, role); + } + /** * Adds a new {@link Competition} to the given {@link Arena}. * diff --git a/plugin/src/main/java/org/battleplugins/arena/command/ArenaCommandExecutor.java b/plugin/src/main/java/org/battleplugins/arena/command/ArenaCommandExecutor.java index b1d049e0..f0c27e54 100644 --- a/plugin/src/main/java/org/battleplugins/arena/command/ArenaCommandExecutor.java +++ b/plugin/src/main/java/org/battleplugins/arena/command/ArenaCommandExecutor.java @@ -19,6 +19,9 @@ import org.battleplugins.arena.editor.context.MapCreateContext; import org.battleplugins.arena.editor.type.MapOption; import org.battleplugins.arena.event.player.ArenaLeaveEvent; +import org.battleplugins.arena.feature.party.Parties; +import org.battleplugins.arena.feature.party.Party; +import org.battleplugins.arena.feature.party.PartyMember; import org.battleplugins.arena.messages.Messages; import org.battleplugins.arena.options.ArenaOptionType; import org.battleplugins.arena.options.TeamSelection; @@ -29,7 +32,9 @@ import org.bukkit.entity.Player; import org.jetbrains.annotations.Nullable; +import java.util.ArrayList; import java.util.HashMap; +import java.util.HashSet; import java.util.List; import java.util.Locale; import java.util.Map; @@ -79,8 +84,47 @@ public void join(Player player, @Argument(name = "map") CompetitionMap map) { return; } + Set players; + Party party = Parties.getParty(player.getUniqueId()); + if (party != null) { + players = new HashSet<>(); + PartyMember leader = party.getLeader(); + if (leader != null) { + // If player is not the leader, deny them entry + if (!leader.getUniqueId().equals(player.getUniqueId())) { + Messages.CANNOT_JOIN_ARENA_NOT_PARTY_LEADER.send(player); + return; + } + + players.add(player); + } + + for (PartyMember member : party.getMembers()) { + Player memberPlayer = Bukkit.getPlayer(member.getUniqueId()); + if (memberPlayer != null) { + players.add(memberPlayer); + } + } + + // If we get into a weird state where the party is empty, + // just add the player + if (players.isEmpty()) { + players.add(player); + } + } else { + players = Set.of(player); + } + + // If any player is already in an arena, deny them entry + for (Player toJoin : players) { + if (ArenaPlayer.getArenaPlayer(toJoin) != null) { + Messages.CANNOT_JOIN_ARENA_MEMBER_IN_ARENA.send(player); + return; + } + } + List> competitions = map == RANDOM_MAP_MARKER ? this.arena.getPlugin().getCompetitions(this.arena) : this.arena.getPlugin().getCompetitions(this.arena, map.getName()); - this.arena.getPlugin().findJoinableCompetition(competitions, player, PlayerRole.PLAYING).whenCompleteAsync((result, e) -> { + this.arena.getPlugin().findJoinableCompetition(competitions, players, PlayerRole.PLAYING).whenCompleteAsync((result, e) -> { if (e != null) { Messages.ARENA_ERROR.send(player, e.getMessage()); this.arena.getPlugin().error("An error occurred while joining the arena", e); @@ -89,9 +133,11 @@ public void join(Player player, @Argument(name = "map") CompetitionMap map) { Competition competition = result.competition(); if (competition != null) { - competition.join(player, PlayerRole.PLAYING); + competition.join(players, PlayerRole.PLAYING); - Messages.ARENA_JOINED.send(player, competition.getMap().getName()); + for (Player toJoin : players) { + Messages.ARENA_JOINED.send(toJoin, competition.getMap().getName()); + } } else { List maps = this.arena.getPlugin().getMaps(this.arena); if (maps.isEmpty()) { @@ -107,7 +153,7 @@ public void join(Player player, @Argument(name = "map") CompetitionMap map) { // Try and create a dynamic competition if possible this.arena.getPlugin() - .getOrCreateCompetition(this.arena, player, PlayerRole.PLAYING, mapName) + .getOrCreateCompetition(this.arena, players, PlayerRole.PLAYING, mapName) .whenComplete((newResult, ex) -> { if (ex != null) { Messages.ARENA_ERROR.send(player, ex.getMessage()); @@ -130,8 +176,10 @@ public void join(Player player, @Argument(name = "map") CompetitionMap map) { return; } - newResult.competition().join(player, PlayerRole.PLAYING); - Messages.ARENA_JOINED.send(player, newResult.competition().getMap().getName()); + for (Player toJoin : players) { + newResult.competition().join(toJoin, PlayerRole.PLAYING); + Messages.ARENA_JOINED.send(toJoin, newResult.competition().getMap().getName()); + } }); } }, Bukkit.getScheduler().getMainThreadExecutor(this.arena.getPlugin())); diff --git a/plugin/src/main/java/org/battleplugins/arena/competition/Competition.java b/plugin/src/main/java/org/battleplugins/arena/competition/Competition.java index 137b24a5..d73fe97b 100644 --- a/plugin/src/main/java/org/battleplugins/arena/competition/Competition.java +++ b/plugin/src/main/java/org/battleplugins/arena/competition/Competition.java @@ -6,6 +6,7 @@ import org.battleplugins.arena.event.player.ArenaLeaveEvent; import org.bukkit.entity.Player; +import java.util.Collection; import java.util.concurrent.CompletableFuture; /** @@ -46,10 +47,19 @@ public interface Competition> extends CompetitionLike canJoin(Player player, PlayerRole role); + /** + * Gets whether the players can join the competition. + * + * @param players the players to join + * @param role the role of the player + */ + CompletableFuture canJoin(Collection players, PlayerRole role); + /** * Adds the player to the competition. * @@ -58,6 +68,14 @@ public interface Competition> extends CompetitionLike players, PlayerRole role); + /** * Removes the player from the competition. * diff --git a/plugin/src/main/java/org/battleplugins/arena/competition/CompetitionManager.java b/plugin/src/main/java/org/battleplugins/arena/competition/CompetitionManager.java index aa17036d..5a1f9463 100644 --- a/plugin/src/main/java/org/battleplugins/arena/competition/CompetitionManager.java +++ b/plugin/src/main/java/org/battleplugins/arena/competition/CompetitionManager.java @@ -18,6 +18,7 @@ import java.nio.file.Files; import java.nio.file.Path; import java.util.ArrayList; +import java.util.Collection; import java.util.Collections; import java.util.Comparator; import java.util.HashMap; @@ -49,9 +50,13 @@ public List> getCompetitions(Arena arena, String name) { } public CompletableFuture getOrCreateCompetition(Arena arena, Player player, PlayerRole role, @Nullable String name) { + return this.getOrCreateCompetition(arena, List.of(player), role, name); + } + + public CompletableFuture getOrCreateCompetition(Arena arena, Collection players, PlayerRole role, @Nullable String name) { // See if we can join any already open competitions List> openCompetitions = this.getCompetitions(arena, name); - CompletableFuture joinableCompetition = this.findJoinableCompetition(openCompetitions, player, role); + CompletableFuture joinableCompetition = this.findJoinableCompetition(openCompetitions, players, role); return joinableCompetition.thenApplyAsync(result -> { if (result.competition() != null) { return result; @@ -118,10 +123,18 @@ public CompletableFuture getOrCreateCompetition(Arena arena, } public CompletableFuture findJoinableCompetition(List> competitions, Player player, PlayerRole role) { - return this.findJoinableCompetition(competitions, player, role, null); + return this.findJoinableCompetition(competitions, List.of(player), role); + } + + public CompletableFuture findJoinableCompetition(List> competitions, Collection players, PlayerRole role) { + return this.findJoinableCompetition(competitions, players, role, null); } private CompletableFuture findJoinableCompetition(List> competitions, Player player, PlayerRole role, @Nullable JoinResult lastResult) { + return this.findJoinableCompetition(competitions, List.of(player), role, lastResult); + } + + private CompletableFuture findJoinableCompetition(List> competitions, Collection players, PlayerRole role, @Nullable JoinResult lastResult) { if (competitions.isEmpty()) { return CompletableFuture.completedFuture(new CompetitionResult(null, lastResult == null ? JoinResult.NOT_JOINABLE : lastResult)); } @@ -134,9 +147,9 @@ private CompletableFuture findJoinableCompetition(List competition = competitions.stream() .max(Comparator.comparingInt(Competition::getAlivePlayerCount)) - .orElse(null); + .orElseThrow(); // Should never throw but just in case - CompletableFuture result = competition.canJoin(player, role); + CompletableFuture result = competition.canJoin(players, role); JoinResult joinResult = result.join(); if (joinResult == JoinResult.SUCCESS) { return CompletableFuture.completedFuture(new CompetitionResult(competition, JoinResult.SUCCESS)); @@ -144,7 +157,7 @@ private CompletableFuture findJoinableCompetition(List> remainingCompetitions = new ArrayList<>(competitions); remainingCompetitions.remove(competition); - return this.findJoinableCompetition(remainingCompetitions, player, role, joinResult); + return this.findJoinableCompetition(remainingCompetitions, players, role, joinResult); } } diff --git a/plugin/src/main/java/org/battleplugins/arena/competition/LiveCompetition.java b/plugin/src/main/java/org/battleplugins/arena/competition/LiveCompetition.java index afe850f0..e84017be 100644 --- a/plugin/src/main/java/org/battleplugins/arena/competition/LiveCompetition.java +++ b/plugin/src/main/java/org/battleplugins/arena/competition/LiveCompetition.java @@ -28,6 +28,7 @@ import org.bukkit.entity.Player; import org.jetbrains.annotations.Nullable; +import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; @@ -84,6 +85,11 @@ public LiveCompetition(Arena arena, CompetitionType type, LiveCompetitionMap map @Override public CompletableFuture canJoin(Player player, PlayerRole role) { + return this.canJoin(Set.of(player), role); + } + + @Override + public CompletableFuture canJoin(Collection players, PlayerRole role) { CompetitionPhase currentPhase = this.phaseManager.getCurrentPhase(); // Check if the player can join the competition in its current state @@ -100,20 +106,24 @@ public CompletableFuture canJoin(Player player, PlayerRole role) { // the overall maximum number of players this competition can have if (teams.getTeamSelection() == TeamSelection.PICK || teams.isNonTeamGame()) { // Player cannot join - arena is full - if ((this.getPlayers().size() + 1) > this.maxPlayers) { + if ((this.getPlayers().size() + this.players.size()) > this.maxPlayers) { return CompletableFuture.completedFuture(JoinResult.ARENA_FULL); } } else { List availableTeams = teams.getAvailableTeams(); // Otherwise, we need to go through all teams and see if there is room for the player + + int spaceAvailable = 0; for (ArenaTeam availableTeam : availableTeams) { - // If we have less than the minimum amount of players on the team, then we can - // assume that this team has room and break - if (this.teamManager.canJoinTeam(availableTeam)) { - break; + // Get the amount of space remaining on the team + int remainingSpace = this.teamManager.getRemainingSpace(availableTeam); + if (remainingSpace > 0) { + spaceAvailable += remainingSpace; } + } - // No available teams - return that the arena is full + // No available teams - return that the arena is full + if (spaceAvailable < players.size()) { return CompletableFuture.completedFuture(JoinResult.ARENA_FULL); } } @@ -124,9 +134,15 @@ public CompletableFuture canJoin(Player player, PlayerRole role) { return CompletableFuture.completedFuture(JoinResult.NOT_SPECTATABLE); } - // Call the ArenaPreJoinEvent - ArenaPreJoinEvent event = this.arena.getEventManager().callEvent(new ArenaPreJoinEvent(this.arena, this, role, JoinResult.SUCCESS, player)); - return CompletableFuture.completedFuture(event.getResult()); + for (Player player : players) { + // Call the ArenaPreJoinEvent + ArenaPreJoinEvent event = this.arena.getEventManager().callEvent(new ArenaPreJoinEvent(this.arena, this, role, JoinResult.SUCCESS, player)); + if (event.getResult() != JoinResult.SUCCESS) { + return CompletableFuture.completedFuture(event.getResult()); + } + } + + return CompletableFuture.completedFuture(JoinResult.SUCCESS); } /** @@ -174,7 +190,12 @@ public final LiveCompetitionMap getMap() { @Override public final void join(Player player, PlayerRole role) { - this.join(player, role, null); + this.join(Set.of(player), role); + } + + @Override + public final void join(Collection players, PlayerRole role) { + this.join(players, role, null); } /** @@ -186,14 +207,29 @@ public final void join(Player player, PlayerRole role) { * @param team the team to join */ public final void join(Player player, PlayerRole role, @Nullable ArenaTeam team) { - if (this.arena.getPlugin().isInArena(player)) { - throw new IllegalStateException("Player is already in an arena!"); - } + this.join(Set.of(player), role, team); + } - ArenaPlayer arenaPlayer = this.createPlayer(player); - arenaPlayer.setRole(role); + /** + * Makes the players join the competition with the specified {@link PlayerRole} + * and {@link ArenaTeam}. + * + * @param players the players to join + * @param role the role of the player + * @param team the team to join + */ + public final void join(Collection players, PlayerRole role, @Nullable ArenaTeam team) { + for (Player player : players) { + if (this.arena.getPlugin().isInArena(player)) { + this.arena.getPlugin().error("Player {} is already in an arena! Please report this as it is a bug!", player.getName(), new IllegalStateException()); + continue; + } - this.join(arenaPlayer, team); + ArenaPlayer arenaPlayer = this.createPlayer(player); + arenaPlayer.setRole(role); + + this.join(arenaPlayer, team); + } } private void join(ArenaPlayer player, @Nullable ArenaTeam team) { diff --git a/plugin/src/main/java/org/battleplugins/arena/competition/team/TeamManager.java b/plugin/src/main/java/org/battleplugins/arena/competition/team/TeamManager.java index 6ee978f3..1d69a11a 100644 --- a/plugin/src/main/java/org/battleplugins/arena/competition/team/TeamManager.java +++ b/plugin/src/main/java/org/battleplugins/arena/competition/team/TeamManager.java @@ -169,8 +169,29 @@ public StatHolder getStats(ArenaTeam team) { * @return whether the player can join the team */ public boolean canJoinTeam(ArenaTeam team) { + return this.canJoinTeam(team, 1); + } + + /** + * Returns whether the player can join the given {@link ArenaTeam}. + * + * @param team the team to check if the player can join + * @param playersToJoin the amount of players to join + * @return whether the player can join the team + */ + public boolean canJoinTeam(ArenaTeam team, int playersToJoin) { int playersOnTeam = this.getNumberOfPlayersOnTeam(team); - return playersOnTeam < this.getMaximumTeamSize(team); + return playersOnTeam + playersToJoin <= this.getMaximumTeamSize(team); + } + + /** + * Returns the remaining space on the given {@link ArenaTeam}. + * + * @param team the team to get the remaining space for + * @return the remaining space on the team + */ + public int getRemainingSpace(ArenaTeam team) { + return this.getMaximumTeamSize(team) - this.getNumberOfPlayersOnTeam(team); } /** diff --git a/plugin/src/main/java/org/battleplugins/arena/config/CustomEffectParser.java b/plugin/src/main/java/org/battleplugins/arena/config/CustomEffectParser.java index b07dedd4..0c5defa4 100644 --- a/plugin/src/main/java/org/battleplugins/arena/config/CustomEffectParser.java +++ b/plugin/src/main/java/org/battleplugins/arena/config/CustomEffectParser.java @@ -6,6 +6,7 @@ import java.util.HashMap; import java.util.Map; +@DocumentationSource("https://docs.battleplugins.org/books/user-guide/page/custom-effect-format") public class CustomEffectParser> implements ArenaConfigParser.Parser { @SuppressWarnings("unchecked") diff --git a/plugin/src/main/java/org/battleplugins/arena/feature/PluginFeature.java b/plugin/src/main/java/org/battleplugins/arena/feature/PluginFeature.java index e7151545..f72cf690 100644 --- a/plugin/src/main/java/org/battleplugins/arena/feature/PluginFeature.java +++ b/plugin/src/main/java/org/battleplugins/arena/feature/PluginFeature.java @@ -1,5 +1,6 @@ package org.battleplugins.arena.feature; +import org.bukkit.Bukkit; import org.bukkit.plugin.Plugin; /** @@ -10,6 +11,13 @@ public abstract class PluginFeature implements FeatureInstance { private final Plugin plugin; + public PluginFeature(String pluginName) { + this.plugin = Bukkit.getPluginManager().getPlugin(pluginName); + if (this.plugin == null) { + throw new IllegalArgumentException("Plugin " + pluginName + " does not exist!"); + } + } + public PluginFeature(Plugin plugin) { this.plugin = plugin; } diff --git a/plugin/src/main/java/org/battleplugins/arena/feature/party/Parties.java b/plugin/src/main/java/org/battleplugins/arena/feature/party/Parties.java new file mode 100644 index 00000000..0429b476 --- /dev/null +++ b/plugin/src/main/java/org/battleplugins/arena/feature/party/Parties.java @@ -0,0 +1,43 @@ +package org.battleplugins.arena.feature.party; + +import org.battleplugins.arena.feature.FeatureController; +import org.jetbrains.annotations.ApiStatus; +import org.jetbrains.annotations.Nullable; + +import java.util.UUID; + +/** + * API for holograms used in Parties. + */ +@ApiStatus.Experimental +public class Parties extends FeatureController { + private static PartiesFeature instance; + + /** + * Gets the party of the given player UUID. + * + * @param uuid the uuid of the party + * @return the party of the given player UUID + */ + @Nullable + public static Party getParty(UUID uuid) { + PartiesFeature instance = instance(); + if (instance == null) { + return null; + } + + return instance.getParty(uuid); + } + + public static PartiesFeature instance() { + if (instance == null) { + instance = createInstance(PartiesFeature.class); + } + + return instance; + } + + public static void register(PartiesFeature feature) { + registerFeature(PartiesFeature.class, feature); + } +} diff --git a/plugin/src/main/java/org/battleplugins/arena/feature/party/PartiesFeature.java b/plugin/src/main/java/org/battleplugins/arena/feature/party/PartiesFeature.java new file mode 100644 index 00000000..3941643e --- /dev/null +++ b/plugin/src/main/java/org/battleplugins/arena/feature/party/PartiesFeature.java @@ -0,0 +1,25 @@ +package org.battleplugins.arena.feature.party; + +import org.battleplugins.arena.feature.FeatureInstance; +import org.jetbrains.annotations.ApiStatus; +import org.jetbrains.annotations.Nullable; + +import java.util.UUID; + +/** + * Main entrypoint for parties. Implementing plugins + * should implement this interface to provide party + * support. + */ +@ApiStatus.Experimental +public interface PartiesFeature extends FeatureInstance { + + /** + * Gets the party of the given player UUID. + * + * @param uuid the uuid of the party + * @return the party of the given player UUID + */ + @Nullable + Party getParty(UUID uuid); +} diff --git a/plugin/src/main/java/org/battleplugins/arena/feature/party/Party.java b/plugin/src/main/java/org/battleplugins/arena/feature/party/Party.java new file mode 100644 index 00000000..3c80c01c --- /dev/null +++ b/plugin/src/main/java/org/battleplugins/arena/feature/party/Party.java @@ -0,0 +1,26 @@ +package org.battleplugins.arena.feature.party; + +import org.jetbrains.annotations.Nullable; + +import java.util.Set; + +/** + * Represents a party. + */ +public interface Party { + + /** + * Gets the leader of the party. + * + * @return the leader of the party + */ + @Nullable + PartyMember getLeader(); + + /** + * Gets the members of the party. + * + * @return the members of the party + */ + Set getMembers(); +} diff --git a/plugin/src/main/java/org/battleplugins/arena/feature/party/PartyMember.java b/plugin/src/main/java/org/battleplugins/arena/feature/party/PartyMember.java new file mode 100644 index 00000000..d1102786 --- /dev/null +++ b/plugin/src/main/java/org/battleplugins/arena/feature/party/PartyMember.java @@ -0,0 +1,30 @@ +package org.battleplugins.arena.feature.party; + +import java.util.UUID; + +/** + * Represents a party member. + */ +public interface PartyMember { + + /** + * Gets the name of the party member. + * + * @return the name of the party member + */ + String getName(); + + /** + * Gets the unique id of the party member. + * + * @return the unique id of the party member + */ + UUID getUniqueId(); + + /** + * Gets the party the member is in. + * + * @return the party the member is in + */ + Party getParty(); +} diff --git a/plugin/src/main/java/org/battleplugins/arena/messages/Messages.java b/plugin/src/main/java/org/battleplugins/arena/messages/Messages.java index fe7e780e..5fbaa29e 100644 --- a/plugin/src/main/java/org/battleplugins/arena/messages/Messages.java +++ b/plugin/src/main/java/org/battleplugins/arena/messages/Messages.java @@ -82,6 +82,8 @@ public final class Messages { public static final Message NO_TEAMS = error("arena-no-teams", "There are no teams in this arena!"); public static final Message CANNOT_JOIN_TEAM_SOLO = error("arena-cannot-join-team-solo", "You cannot join a team in a solo arena!"); public static final Message TEAM_SELECTION_NOT_AVAILABLE = error("arena-team-selection-not-available", "Team selection is not available at this time!"); + public static final Message CANNOT_JOIN_ARENA_NOT_PARTY_LEADER = error("arena-cannot-join-arena-not-party-leader", "You must be the party leader to join an arena!"); + public static final Message CANNOT_JOIN_ARENA_MEMBER_IN_ARENA = error("arena-cannot-join-arena-member-in-arena", "You cannot join an arena while a party member is in an arena!"); public static final Message ARENA_STARTS_IN = info("arena-starts-in", "{} will start in {}!"); public static final Message ARENA_START_CANCELLED = error("arena-starts-cancelled", "Countdown cancelled as there is not enough players to start!"); diff --git a/plugin/src/main/resources/config.yml b/plugin/src/main/resources/config.yml index 627846dd..9fba4fca 100644 --- a/plugin/src/main/resources/config.yml +++ b/plugin/src/main/resources/config.yml @@ -25,6 +25,8 @@ randomized-arena-join: false # Modules that are disabled by default. BattleArena comes pre-installed with # multiple modules that can be disabled below if their behavior is not desired +# Example for disabling the parties module: +# disabled-modules: [parties] disabled-modules: [] # Event configurations diff --git a/settings.gradle.kts b/settings.gradle.kts index 5b6230be..eade35f1 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -24,6 +24,7 @@ include("module:classes") include("module:duels") include("module:hologram-integration") include("module:one-in-the-chamber") +include("module:party-integration") include("module:placeholderapi-integration") include("module:scoreboards") include("module:team-colors")