added team fights up to 4 vs 4 to the arena
This commit is contained in:
1 parent
4add446940
commit
ae3bde626f
10 files changed
+664
-363
No files matched your search
@@ -225,14 +225,13 @@ public class Arena extends Appliance {
|
|||||||
return this.slots.stream().anyMatch(slot -> slot.state == ArenaSlot.State.FREE);
|
return this.slots.stream().anyMatch(slot -> slot.state == ArenaSlot.State.FREE);
|
||||||
}
|
}
|
||||||
|
|
||||||
void startFight(Player red, Player blue, UUID challenger, FightMode mode) {
|
void startFight(List<List<Player>> teams, FightMode mode, Map<UUID, UUID> kits) {
|
||||||
ArenaSlot slot = this.slots.stream().filter(s -> s.state == ArenaSlot.State.FREE).findFirst().orElse(null);
|
ArenaSlot slot = this.slots.stream().filter(s -> s.state == ArenaSlot.State.FREE).findFirst().orElse(null);
|
||||||
if(slot == null) {
|
if(slot == null) {
|
||||||
this.toTribune(red);
|
teams.forEach(team -> team.forEach(this::toTribune));
|
||||||
this.toTribune(blue);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
new Fight(this, slot, red, blue, challenger, mode).start();
|
new Fight(this, slot, teams, mode, kits).start();
|
||||||
}
|
}
|
||||||
|
|
||||||
void resetSlot(ArenaSlot slot) {
|
void resetSlot(ArenaSlot slot) {
|
||||||
@@ -314,7 +313,7 @@ public class Arena extends Appliance {
|
|||||||
|
|
||||||
public void handleQuit(Player player) {
|
public void handleQuit(Player player) {
|
||||||
Fight fight = this.fightOf(player);
|
Fight fight = this.fightOf(player);
|
||||||
if(fight != null && this.state(player) == PlayerState.FIGHT) fight.forfeit(player.getUniqueId());
|
if(fight != null && fight.isAlive(player.getUniqueId())) fight.forfeit(player.getUniqueId());
|
||||||
if(this.lobby != null) this.lobby.leave(player);
|
if(this.lobby != null) this.lobby.leave(player);
|
||||||
this.teams.leave(player);
|
this.teams.leave(player);
|
||||||
this.states.remove(player.getUniqueId());
|
this.states.remove(player.getUniqueId());
|
||||||
@@ -348,7 +347,7 @@ public class Arena extends Appliance {
|
|||||||
|
|
||||||
public @Nullable Region arenaRegionOf(Player player) {
|
public @Nullable Region arenaRegionOf(Player player) {
|
||||||
Fight fight = this.fightOf(player);
|
Fight fight = this.fightOf(player);
|
||||||
return fight == null ? null : fight.slot().settings.region();
|
return fight == null ? null : fight.region();
|
||||||
}
|
}
|
||||||
|
|
||||||
public @Nullable Lobby lobby() {
|
public @Nullable Lobby lobby() {
|
||||||
|
|||||||
@@ -1,9 +1,11 @@
|
|||||||
package eu.mhsl.craftattack.spawn.arena.appliances.arena;
|
package eu.mhsl.craftattack.spawn.arena.appliances.arena;
|
||||||
|
|
||||||
import eu.mhsl.craftattack.spawn.arena.appliances.arena.logic.FightMode;
|
import eu.mhsl.craftattack.spawn.arena.appliances.arena.logic.FightMode;
|
||||||
|
import eu.mhsl.craftattack.spawn.arena.appliances.arena.logic.Region;
|
||||||
import eu.mhsl.craftattack.spawn.core.Main;
|
import eu.mhsl.craftattack.spawn.core.Main;
|
||||||
import net.kyori.adventure.bossbar.BossBar;
|
import net.kyori.adventure.bossbar.BossBar;
|
||||||
import net.kyori.adventure.text.Component;
|
import net.kyori.adventure.text.Component;
|
||||||
|
import net.kyori.adventure.text.JoinConfiguration;
|
||||||
import net.kyori.adventure.text.format.NamedTextColor;
|
import net.kyori.adventure.text.format.NamedTextColor;
|
||||||
import net.kyori.adventure.title.Title;
|
import net.kyori.adventure.title.Title;
|
||||||
import org.bukkit.Bukkit;
|
import org.bukkit.Bukkit;
|
||||||
@@ -18,37 +20,44 @@ import org.jetbrains.annotations.Nullable;
|
|||||||
|
|
||||||
import java.time.Duration;
|
import java.time.Duration;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
import java.util.HashMap;
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
|
import java.util.LinkedHashSet;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A running fight in one arena. Fighter 0 comes from the red pad and uses spawn 1, fighter 1 the blue pad and spawn 2.
|
* A running fight in one arena between team 0 (red pad, spawn 1) and team 1 (blue pad, spawn 2).
|
||||||
|
* A team loses once none of its players is left in the arena.
|
||||||
*/
|
*/
|
||||||
final class Fight {
|
final class Fight {
|
||||||
private static final int CELEBRATION_TICKS = 60;
|
private static final int CELEBRATION_TICKS = 60;
|
||||||
private static final double SPECTATOR_RANGE = 32;
|
private static final double SPECTATOR_RANGE = 32;
|
||||||
|
private static final double SPAWN_SPREAD = 2;
|
||||||
|
|
||||||
private final Arena arena;
|
private final Arena arena;
|
||||||
private final ArenaSlot slot;
|
private final ArenaSlot slot;
|
||||||
private final UUID[] fighters;
|
private final List<List<UUID>> teams;
|
||||||
private final String[] names;
|
private final Map<UUID, String> names = new HashMap<>();
|
||||||
private final UUID challenger;
|
private final List<Set<UUID>> alive;
|
||||||
private final FightMode mode;
|
private final FightMode mode;
|
||||||
|
private final Map<UUID, UUID> kits;
|
||||||
private final BossBar bossBar;
|
private final BossBar bossBar;
|
||||||
private final Set<UUID> bossBarViewers = new HashSet<>();
|
private final Set<UUID> bossBarViewers = new HashSet<>();
|
||||||
private int secondsLeft;
|
private int secondsLeft;
|
||||||
private boolean over;
|
private boolean over;
|
||||||
private @Nullable BukkitTask timer;
|
private @Nullable BukkitTask timer;
|
||||||
|
|
||||||
Fight(Arena arena, ArenaSlot slot, Player red, Player blue, UUID challenger, FightMode mode) {
|
Fight(Arena arena, ArenaSlot slot, List<List<Player>> teams, FightMode mode, Map<UUID, UUID> kits) {
|
||||||
this.arena = arena;
|
this.arena = arena;
|
||||||
this.slot = slot;
|
this.slot = slot;
|
||||||
this.fighters = new UUID[]{red.getUniqueId(), blue.getUniqueId()};
|
this.teams = teams.stream().map(team -> team.stream().map(Player::getUniqueId).toList()).toList();
|
||||||
this.names = new String[]{red.getName(), blue.getName()};
|
teams.forEach(team -> team.forEach(player -> this.names.put(player.getUniqueId(), player.getName())));
|
||||||
this.challenger = challenger;
|
this.alive = this.teams.stream().map(team -> (Set<UUID>) new LinkedHashSet<>(team)).toList();
|
||||||
this.mode = mode;
|
this.mode = mode;
|
||||||
|
this.kits = kits;
|
||||||
this.secondsLeft = arena.settings().fightSeconds();
|
this.secondsLeft = arena.settings().fightSeconds();
|
||||||
this.bossBar = BossBar.bossBar(Component.empty(), 1f, BossBar.Color.RED, BossBar.Overlay.NOTCHED_10);
|
this.bossBar = BossBar.bossBar(Component.empty(), 1f, BossBar.Color.RED, BossBar.Overlay.NOTCHED_10);
|
||||||
}
|
}
|
||||||
@@ -57,44 +66,72 @@ final class Fight {
|
|||||||
this.slot.state = ArenaSlot.State.FIGHT;
|
this.slot.state = ArenaSlot.State.FIGHT;
|
||||||
this.slot.fight = this;
|
this.slot.fight = this;
|
||||||
|
|
||||||
FightMode.Role[] roles = new FightMode.Role[2];
|
for(int team = 0; team < 2; team++) {
|
||||||
for(int i = 0; i < 2; i++) {
|
List<UUID> members = this.teams.get(team);
|
||||||
roles[i] = this.fighters[i].equals(this.challenger) ? FightMode.Role.CHALLENGER : FightMode.Role.OPPONENT;
|
for(int i = 0; i < members.size(); i++) {
|
||||||
}
|
Player player = Bukkit.getPlayer(members.get(i));
|
||||||
UUID challengerId = this.challenger;
|
if(player == null) continue;
|
||||||
UUID opponentId = this.fighters[0].equals(challengerId) ? this.fighters[1] : this.fighters[0];
|
this.arena.setState(player, Arena.PlayerState.FIGHT);
|
||||||
|
this.arena.teams().join(player, Teams.fight(team));
|
||||||
for(int i = 0; i < 2; i++) {
|
player.closeInventory();
|
||||||
Player player = Bukkit.getPlayer(this.fighters[i]);
|
player.teleport(this.spawnFor(team, i, members.size()));
|
||||||
if(player == null) continue;
|
Arena.restore(player, GameMode.SURVIVAL);
|
||||||
UUID kitOwner = this.mode.kitSource(roles[i]) == FightMode.Role.CHALLENGER ? challengerId : opponentId;
|
InventoryStore.apply(player, this.kits.getOrDefault(player.getUniqueId(), player.getUniqueId()));
|
||||||
|
|
||||||
this.arena.setState(player, Arena.PlayerState.FIGHT);
|
player.showTitle(Title.title(
|
||||||
this.arena.teams().join(player, Teams.fight(i));
|
Component.text("Kampf!", NamedTextColor.GOLD),
|
||||||
player.closeInventory();
|
Component.text("gegen ", NamedTextColor.GRAY).append(this.teamNames(1 - team)),
|
||||||
player.teleport(this.slot.settings.spawns()[i]);
|
Title.Times.times(Duration.ZERO, Duration.ofMillis(1500), Duration.ofMillis(500))
|
||||||
Arena.restore(player, GameMode.SURVIVAL);
|
));
|
||||||
InventoryStore.apply(player, kitOwner);
|
Style.play(player, Sound.EVENT_RAID_HORN, 1.2f);
|
||||||
|
}
|
||||||
player.showTitle(Title.title(
|
|
||||||
Component.text("Kampf!", NamedTextColor.GOLD),
|
|
||||||
Component.text("gegen ", NamedTextColor.GRAY).append(Component.text(this.names[1 - i], Style.PAD_TEXT_COLORS[1 - i])),
|
|
||||||
Title.Times.times(Duration.ZERO, Duration.ofMillis(1500), Duration.ofMillis(500))
|
|
||||||
));
|
|
||||||
Style.play(player, Sound.EVENT_RAID_HORN, 1.2f);
|
|
||||||
}
|
}
|
||||||
|
// players who left during the countdown never arrived
|
||||||
|
for(int team = 0; team < 2; team++) this.alive.get(team).removeIf(uuid -> Bukkit.getPlayer(uuid) == null);
|
||||||
|
|
||||||
Bukkit.broadcast(Style.message(
|
Bukkit.broadcast(Style.message(
|
||||||
this.headline().append(Component.text(" in Arena " + this.slot.settings.name() + ". Modus: " + this.mode.displayName(), NamedTextColor.GRAY))
|
this.teamNames(0).append(Component.text(" gegen ", NamedTextColor.GRAY)).append(this.teamNames(1))
|
||||||
|
.append(Component.text(" in Arena " + this.slot.settings.name() + ". Modus: " + this.mode.displayName(this.isTeamFight()), NamedTextColor.GRAY))
|
||||||
));
|
));
|
||||||
this.updateBossBar();
|
this.updateBossBar();
|
||||||
this.timer = Bukkit.getScheduler().runTaskTimer(Main.instance(), this::tickSecond, 20, 20);
|
this.timer = Bukkit.getScheduler().runTaskTimer(Main.instance(), this::tickSecond, 20, 20);
|
||||||
|
if(this.alive.get(0).isEmpty() || this.alive.get(1).isEmpty()) this.checkEnd();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Team members stand in a small circle around the spawn, facing the same way.
|
||||||
|
*/
|
||||||
|
private Location spawnFor(int team, int index, int count) {
|
||||||
|
Location center = this.slot.settings.spawns()[team];
|
||||||
|
if(count == 1) return center;
|
||||||
|
double angle = 2 * Math.PI * index / count;
|
||||||
|
Location spot = center.clone().add(Math.cos(angle) * SPAWN_SPREAD, 0, Math.sin(angle) * SPAWN_SPREAD);
|
||||||
|
boolean free = spot.getBlock().isPassable() && spot.clone().add(0, 1, 0).getBlock().isPassable()
|
||||||
|
&& !spot.clone().subtract(0, 1, 0).getBlock().isPassable();
|
||||||
|
return free ? spot : center;
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean isTeamFight() {
|
||||||
|
return this.teams.get(0).size() > 1 || this.teams.get(1).size() > 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Component teamNames(int team) {
|
||||||
|
return Component.join(
|
||||||
|
JoinConfiguration.separator(Component.text(", ", NamedTextColor.GRAY)),
|
||||||
|
this.teams.get(team).stream().map(uuid -> Component.text(this.names.get(uuid), Style.PAD_TEXT_COLORS[team])).toList()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Short form for bossbar and displays: names in a duel, "Rot 2/3" in a team fight.
|
||||||
|
*/
|
||||||
Component headline() {
|
Component headline() {
|
||||||
return Component.text(this.names[0], Style.PAD_TEXT_COLORS[0])
|
if(!this.isTeamFight()) {
|
||||||
|
return this.teamNames(0).append(Component.text(" gegen ", NamedTextColor.GRAY)).append(this.teamNames(1));
|
||||||
|
}
|
||||||
|
return Component.text("Rot " + this.alive.get(0).size() + "/" + this.teams.get(0).size(), Style.PAD_TEXT_COLORS[0])
|
||||||
.append(Component.text(" gegen ", NamedTextColor.GRAY))
|
.append(Component.text(" gegen ", NamedTextColor.GRAY))
|
||||||
.append(Component.text(this.names[1], Style.PAD_TEXT_COLORS[1]));
|
.append(Component.text("Blau " + this.alive.get(1).size() + "/" + this.teams.get(1).size(), Style.PAD_TEXT_COLORS[1]));
|
||||||
}
|
}
|
||||||
|
|
||||||
int secondsLeft() {
|
int secondsLeft() {
|
||||||
@@ -106,7 +143,20 @@ final class Fight {
|
|||||||
}
|
}
|
||||||
|
|
||||||
boolean involves(UUID player) {
|
boolean involves(UUID player) {
|
||||||
return this.fighters[0].equals(player) || this.fighters[1].equals(player);
|
return this.teams.get(0).contains(player) || this.teams.get(1).contains(player);
|
||||||
|
}
|
||||||
|
|
||||||
|
boolean isAlive(UUID player) {
|
||||||
|
return this.alive.get(0).contains(player) || this.alive.get(1).contains(player);
|
||||||
|
}
|
||||||
|
|
||||||
|
boolean sameTeam(UUID a, UUID b) {
|
||||||
|
int team = this.teamOf(a);
|
||||||
|
return team != -1 && team == this.teamOf(b);
|
||||||
|
}
|
||||||
|
|
||||||
|
Region region() {
|
||||||
|
return this.slot.settings.region();
|
||||||
}
|
}
|
||||||
|
|
||||||
ArenaSlot slot() {
|
ArenaSlot slot() {
|
||||||
@@ -116,11 +166,11 @@ final class Fight {
|
|||||||
private void tickSecond() {
|
private void tickSecond() {
|
||||||
if(this.over) return;
|
if(this.over) return;
|
||||||
if(--this.secondsLeft <= 0) {
|
if(--this.secondsLeft <= 0) {
|
||||||
this.finish(-1, Component.text("Die Zeit ist abgelaufen.", NamedTextColor.GRAY));
|
this.timeUp();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if(this.secondsLeft == 60 || this.secondsLeft == 10) {
|
if(this.secondsLeft == 60 || this.secondsLeft == 10) {
|
||||||
this.fighterPlayers().forEach(player -> {
|
this.alivePlayers().forEach(player -> {
|
||||||
player.sendActionBar(Component.text("Noch " + this.secondsLeft + " Sekunden", NamedTextColor.YELLOW));
|
player.sendActionBar(Component.text("Noch " + this.secondsLeft + " Sekunden", NamedTextColor.YELLOW));
|
||||||
Style.play(player, Sound.BLOCK_NOTE_BLOCK_HAT, 0.8f);
|
Style.play(player, Sound.BLOCK_NOTE_BLOCK_HAT, 0.8f);
|
||||||
});
|
});
|
||||||
@@ -128,13 +178,40 @@ final class Fight {
|
|||||||
this.updateBossBar();
|
this.updateBossBar();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* More survivors win, then more hearts left, otherwise a draw.
|
||||||
|
*/
|
||||||
|
private void timeUp() {
|
||||||
|
int red = this.alive.get(0).size();
|
||||||
|
int blue = this.alive.get(1).size();
|
||||||
|
if(red != blue) {
|
||||||
|
this.finish(red > blue ? 0 : 1, Component.text("(Zeit abgelaufen, mehr Überlebende)", NamedTextColor.GRAY));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
double redHealth = this.health(0);
|
||||||
|
double blueHealth = this.health(1);
|
||||||
|
if(Math.abs(redHealth - blueHealth) >= 0.5) {
|
||||||
|
this.finish(redHealth > blueHealth ? 0 : 1, Component.text("(Zeit abgelaufen, mehr Herzen übrig)", NamedTextColor.GRAY));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.finish(-1, Component.text("Die Zeit ist abgelaufen.", NamedTextColor.GRAY));
|
||||||
|
}
|
||||||
|
|
||||||
|
private double health(int team) {
|
||||||
|
return this.alive.get(team).stream()
|
||||||
|
.map(Bukkit::getPlayer)
|
||||||
|
.filter(player -> player != null)
|
||||||
|
.mapToDouble(player -> player.getHealth() + player.getAbsorptionAmount())
|
||||||
|
.sum();
|
||||||
|
}
|
||||||
|
|
||||||
private void updateBossBar() {
|
private void updateBossBar() {
|
||||||
this.bossBar.name(this.headline().append(Component.text(" " + Style.time(this.secondsLeft), NamedTextColor.WHITE)));
|
this.bossBar.name(this.headline().append(Component.text(" " + Style.time(this.secondsLeft), NamedTextColor.WHITE)));
|
||||||
this.bossBar.progress(Math.clamp((float) this.secondsLeft / this.arena.settings().fightSeconds(), 0f, 1f));
|
this.bossBar.progress(Math.clamp((float) this.secondsLeft / this.arena.settings().fightSeconds(), 0f, 1f));
|
||||||
|
|
||||||
Set<UUID> viewers = new HashSet<>();
|
Set<UUID> viewers = new HashSet<>();
|
||||||
for(Player player : this.arena.worlds().play().getPlayers()) {
|
for(Player player : this.arena.worlds().play().getPlayers()) {
|
||||||
if(this.involves(player.getUniqueId()) || this.isSpectating(player)) viewers.add(player.getUniqueId());
|
if(this.isAlive(player.getUniqueId()) || this.isSpectating(player)) viewers.add(player.getUniqueId());
|
||||||
}
|
}
|
||||||
for(UUID uuid : this.bossBarViewers) {
|
for(UUID uuid : this.bossBarViewers) {
|
||||||
Player player = Bukkit.getPlayer(uuid);
|
Player player = Bukkit.getPlayer(uuid);
|
||||||
@@ -150,32 +227,61 @@ final class Fight {
|
|||||||
|
|
||||||
private boolean isSpectating(Player player) {
|
private boolean isSpectating(Player player) {
|
||||||
if(this.arena.state(player) != Arena.PlayerState.TRIBUNE && this.arena.state(player) != Arena.PlayerState.PAD) return false;
|
if(this.arena.state(player) != Arena.PlayerState.TRIBUNE && this.arena.state(player) != Arena.PlayerState.PAD) return false;
|
||||||
var region = this.slot.settings.region();
|
Region region = this.slot.settings.region();
|
||||||
double dx = Math.max(0, Math.max(region.minX() - player.getX(), player.getX() - (region.maxX() + 1)));
|
double dx = Math.max(0, Math.max(region.minX() - player.getX(), player.getX() - (region.maxX() + 1)));
|
||||||
double dz = Math.max(0, Math.max(region.minZ() - player.getZ(), player.getZ() - (region.maxZ() + 1)));
|
double dz = Math.max(0, Math.max(region.minZ() - player.getZ(), player.getZ() - (region.maxZ() + 1)));
|
||||||
return Math.hypot(dx, dz) <= SPECTATOR_RANGE;
|
return Math.hypot(dx, dz) <= SPECTATOR_RANGE;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The player took lethal damage.
|
* The player took lethal damage: out of the fight and back to the tribune.
|
||||||
*/
|
*/
|
||||||
void defeated(Player loser) {
|
void defeated(Player player) {
|
||||||
int index = this.indexOf(loser.getUniqueId());
|
if(this.over || !this.eliminate(player.getUniqueId())) return;
|
||||||
if(index == -1 || this.over) return;
|
player.showTitle(Title.title(Component.text("Ausgeschieden", NamedTextColor.RED), Component.empty()));
|
||||||
Player winner = Bukkit.getPlayer(this.fighters[1 - index]);
|
Style.play(player, Sound.BLOCK_BEACON_DEACTIVATE, 1f);
|
||||||
Component detail = winner == null
|
this.arena.toTribune(player);
|
||||||
? Component.empty()
|
this.announceElimination(player.getUniqueId(), "ist ausgeschieden");
|
||||||
: Component.text("(" + this.mode.displayName() + ", " + Style.hearts(winner.getHealth() + winner.getAbsorptionAmount()) + " ❤ übrig)", NamedTextColor.GRAY);
|
this.checkEnd();
|
||||||
this.finish(1 - index, detail);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The player left the server during the fight.
|
* The player left the server during the fight.
|
||||||
*/
|
*/
|
||||||
void forfeit(UUID quitter) {
|
void forfeit(UUID quitter) {
|
||||||
int index = this.indexOf(quitter);
|
if(this.over || !this.eliminate(quitter)) return;
|
||||||
if(index == -1 || this.over) return;
|
this.announceElimination(quitter, "hat die Arena verlassen");
|
||||||
this.finish(1 - index, Component.text("(" + this.names[index] + " hat die Arena verlassen)", NamedTextColor.GRAY));
|
this.checkEnd();
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean eliminate(UUID player) {
|
||||||
|
int team = this.teamOf(player);
|
||||||
|
return team != -1 && this.alive.get(team).remove(player);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void announceElimination(UUID player, String what) {
|
||||||
|
if(!this.isTeamFight()) return;
|
||||||
|
int team = this.teamOf(player);
|
||||||
|
Component message = Component.text(this.names.get(player), Style.PAD_TEXT_COLORS[team])
|
||||||
|
.append(Component.text(" " + what + ". ", NamedTextColor.GRAY))
|
||||||
|
.append(this.headline());
|
||||||
|
this.alivePlayers().forEach(fighter -> fighter.sendActionBar(message));
|
||||||
|
this.updateBossBar();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void checkEnd() {
|
||||||
|
if(this.over) return;
|
||||||
|
boolean redOut = this.alive.get(0).isEmpty();
|
||||||
|
boolean blueOut = this.alive.get(1).isEmpty();
|
||||||
|
if(redOut && blueOut) this.finish(-1, Component.text("Niemand ist mehr übrig.", NamedTextColor.GRAY));
|
||||||
|
else if(redOut) this.finish(1, this.detail(1));
|
||||||
|
else if(blueOut) this.finish(0, this.detail(0));
|
||||||
|
}
|
||||||
|
|
||||||
|
private Component detail(int winner) {
|
||||||
|
String hearts = Style.hearts(this.health(winner));
|
||||||
|
String survivors = this.isTeamFight() ? this.alive.get(winner).size() + " übrig, " : "";
|
||||||
|
return Component.text("(" + this.mode.displayName(this.isTeamFight()) + ", " + survivors + hearts + " ❤)", NamedTextColor.GRAY);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -186,23 +292,20 @@ final class Fight {
|
|||||||
this.over = true;
|
this.over = true;
|
||||||
this.stopTimer();
|
this.stopTimer();
|
||||||
this.hideBossBar();
|
this.hideBossBar();
|
||||||
this.fighterPlayers().forEach(this.arena::toTribune);
|
this.alivePlayers().forEach(this.arena::toTribune);
|
||||||
this.slot.fight = null;
|
this.slot.fight = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void finish(int winnerIndex, Component detail) {
|
private void finish(int winner, Component detail) {
|
||||||
this.over = true;
|
this.over = true;
|
||||||
this.stopTimer();
|
this.stopTimer();
|
||||||
|
|
||||||
if(winnerIndex == -1) {
|
if(winner == -1) {
|
||||||
Bukkit.broadcast(Style.message(
|
Bukkit.broadcast(Style.message(
|
||||||
Component.text(this.names[0], Style.PAD_TEXT_COLORS[0])
|
this.teamNames(0).append(Component.text(" und ", NamedTextColor.GRAY)).append(this.teamNames(1))
|
||||||
.append(Component.text(" und ", NamedTextColor.GRAY))
|
.append(Component.text(" trennen sich unentschieden. ", NamedTextColor.GRAY)).append(detail)
|
||||||
.append(Component.text(this.names[1], Style.PAD_TEXT_COLORS[1]))
|
|
||||||
.append(Component.text(" trennen sich unentschieden. ", NamedTextColor.GRAY))
|
|
||||||
.append(detail)
|
|
||||||
));
|
));
|
||||||
this.fighterPlayers().forEach(player -> {
|
this.alivePlayers().forEach(player -> {
|
||||||
player.showTitle(Title.title(Component.text("Unentschieden", NamedTextColor.YELLOW), detail));
|
player.showTitle(Title.title(Component.text("Unentschieden", NamedTextColor.YELLOW), detail));
|
||||||
Style.play(player, Sound.BLOCK_BEACON_DEACTIVATE, 1f);
|
Style.play(player, Sound.BLOCK_BEACON_DEACTIVATE, 1f);
|
||||||
this.arena.toTribune(player);
|
this.arena.toTribune(player);
|
||||||
@@ -211,33 +314,31 @@ final class Fight {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
int loserIndex = 1 - winnerIndex;
|
int loser = 1 - winner;
|
||||||
|
boolean plural = this.teams.get(winner).size() > 1;
|
||||||
Bukkit.broadcast(Style.message(
|
Bukkit.broadcast(Style.message(
|
||||||
Component.text(this.names[winnerIndex], Style.PAD_TEXT_COLORS[winnerIndex])
|
this.teamNames(winner)
|
||||||
.append(Component.text(" hat gegen ", NamedTextColor.GRAY))
|
.append(Component.text(plural ? " gewinnen gegen " : " hat gegen ", NamedTextColor.GRAY))
|
||||||
.append(Component.text(this.names[loserIndex], Style.PAD_TEXT_COLORS[loserIndex]))
|
.append(this.teamNames(loser))
|
||||||
.append(Component.text(" gewonnen ", NamedTextColor.GRAY))
|
.append(Component.text(plural ? " " : " gewonnen ", NamedTextColor.GRAY))
|
||||||
.append(detail)
|
.append(detail)
|
||||||
));
|
));
|
||||||
|
|
||||||
Player loser = Bukkit.getPlayer(this.fighters[loserIndex]);
|
for(UUID uuid : this.teams.get(loser)) {
|
||||||
if(loser != null) {
|
Player player = Bukkit.getPlayer(uuid);
|
||||||
loser.showTitle(Title.title(
|
if(player == null) continue;
|
||||||
Component.text("Verloren", NamedTextColor.RED),
|
player.showTitle(Title.title(Component.text("Verloren", NamedTextColor.RED), Component.empty()));
|
||||||
Component.text(this.names[winnerIndex] + " hat gewonnen", NamedTextColor.GRAY)
|
if(this.alive.get(loser).contains(uuid)) this.arena.toTribune(player);
|
||||||
));
|
|
||||||
Style.play(loser, Sound.BLOCK_BEACON_DEACTIVATE, 1f);
|
|
||||||
this.arena.toTribune(loser);
|
|
||||||
}
|
}
|
||||||
|
for(UUID uuid : this.teams.get(winner)) {
|
||||||
Player winner = Bukkit.getPlayer(this.fighters[winnerIndex]);
|
Player player = Bukkit.getPlayer(uuid);
|
||||||
if(winner != null) {
|
if(player == null) continue;
|
||||||
winner.showTitle(Title.title(
|
player.showTitle(Title.title(
|
||||||
Component.text("Gewonnen!", NamedTextColor.GOLD),
|
Component.text("Gewonnen!", NamedTextColor.GOLD),
|
||||||
Component.text("gegen " + this.names[loserIndex], NamedTextColor.GRAY)
|
Component.text("gegen ", NamedTextColor.GRAY).append(this.teamNames(loser))
|
||||||
));
|
));
|
||||||
Style.play(winner, Sound.UI_TOAST_CHALLENGE_COMPLETE, 1f);
|
Style.play(player, Sound.UI_TOAST_CHALLENGE_COMPLETE, 1f);
|
||||||
this.celebrate(winner.getLocation(), Style.PAD_PARTICLE_COLORS[winnerIndex]);
|
if(this.alive.get(winner).contains(uuid)) this.celebrate(player.getLocation(), Style.PAD_PARTICLE_COLORS[winner]);
|
||||||
}
|
}
|
||||||
this.afterCelebration();
|
this.afterCelebration();
|
||||||
}
|
}
|
||||||
@@ -261,9 +362,8 @@ final class Fight {
|
|||||||
private void afterCelebration() {
|
private void afterCelebration() {
|
||||||
this.hideBossBar();
|
this.hideBossBar();
|
||||||
Bukkit.getScheduler().runTaskLater(Main.instance(), () -> {
|
Bukkit.getScheduler().runTaskLater(Main.instance(), () -> {
|
||||||
for(UUID uuid : this.fighters) {
|
for(Player player : this.alivePlayers()) {
|
||||||
Player player = Bukkit.getPlayer(uuid);
|
if(this.arena.fightOf(player) == this && this.arena.state(player) == Arena.PlayerState.FIGHT) this.arena.toTribune(player);
|
||||||
if(player != null && this.arena.fightOf(player) == this) this.arena.toTribune(player);
|
|
||||||
}
|
}
|
||||||
this.slot.fight = null;
|
this.slot.fight = null;
|
||||||
this.arena.resetSlot(this.slot);
|
this.arena.resetSlot(this.slot);
|
||||||
@@ -275,17 +375,19 @@ final class Fight {
|
|||||||
this.timer = null;
|
this.timer = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
private int indexOf(UUID player) {
|
private int teamOf(UUID player) {
|
||||||
if(this.fighters[0].equals(player)) return 0;
|
if(this.teams.get(0).contains(player)) return 0;
|
||||||
if(this.fighters[1].equals(player)) return 1;
|
if(this.teams.get(1).contains(player)) return 1;
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
private List<Player> fighterPlayers() {
|
private List<Player> alivePlayers() {
|
||||||
List<Player> players = new ArrayList<>();
|
List<Player> players = new ArrayList<>();
|
||||||
for(UUID uuid : this.fighters) {
|
for(Set<UUID> team : this.alive) {
|
||||||
Player player = Bukkit.getPlayer(uuid);
|
for(UUID uuid : team) {
|
||||||
if(player != null) players.add(player);
|
Player player = Bukkit.getPlayer(uuid);
|
||||||
|
if(player != null) players.add(player);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return players;
|
return players;
|
||||||
}
|
}
|
||||||
|
|||||||
+8
-4
@@ -22,19 +22,23 @@ import org.bukkit.inventory.ItemStack;
|
|||||||
class FightListener extends ApplianceListener<Arena> {
|
class FightListener extends ApplianceListener<Arena> {
|
||||||
private boolean isFighting(Player player) {
|
private boolean isFighting(Player player) {
|
||||||
Fight fight = this.getAppliance().fightOf(player);
|
Fight fight = this.getAppliance().fightOf(player);
|
||||||
return fight != null && !fight.isOver() && this.getAppliance().state(player) == Arena.PlayerState.FIGHT;
|
return fight != null && !fight.isOver() && fight.isAlive(player.getUniqueId());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Only fighters hit each other, and never their own team directly. Explosions still hurt everyone.
|
||||||
|
*/
|
||||||
@EventHandler(priority = EventPriority.HIGH, ignoreCancelled = true)
|
@EventHandler(priority = EventPriority.HIGH, ignoreCancelled = true)
|
||||||
public void onDamageByPlayer(EntityDamageByEntityEvent event) {
|
public void onDamageByPlayer(EntityDamageByEntityEvent event) {
|
||||||
if(!(event.getEntity() instanceof Player)) return;
|
if(!(event.getEntity() instanceof Player victim)) return;
|
||||||
Player attacker = switch(event.getDamager()) {
|
Player attacker = switch(event.getDamager()) {
|
||||||
case Player player -> player;
|
case Player player -> player;
|
||||||
case Projectile projectile when projectile.getShooter() instanceof Player shooter -> shooter;
|
case Projectile projectile when projectile.getShooter() instanceof Player shooter -> shooter;
|
||||||
default -> null;
|
default -> null;
|
||||||
};
|
};
|
||||||
if(attacker == null || attacker == event.getEntity()) return;
|
if(attacker == null || attacker == victim) return;
|
||||||
if(!this.isFighting(attacker)) event.setCancelled(true);
|
Fight fight = this.getAppliance().fightOf(attacker);
|
||||||
|
if(!this.isFighting(attacker) || fight.sameTeam(attacker.getUniqueId(), victim.getUniqueId())) event.setCancelled(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
@EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true)
|
@EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true)
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import eu.mhsl.craftattack.spawn.arena.appliances.arena.logic.PadLobby;
|
|||||||
import eu.mhsl.craftattack.spawn.arena.appliances.arena.logic.Region;
|
import eu.mhsl.craftattack.spawn.arena.appliances.arena.logic.Region;
|
||||||
import eu.mhsl.craftattack.spawn.core.util.world.InteractSounds;
|
import eu.mhsl.craftattack.spawn.core.util.world.InteractSounds;
|
||||||
import net.kyori.adventure.text.Component;
|
import net.kyori.adventure.text.Component;
|
||||||
|
import net.kyori.adventure.text.JoinConfiguration;
|
||||||
import net.kyori.adventure.text.format.NamedTextColor;
|
import net.kyori.adventure.text.format.NamedTextColor;
|
||||||
import net.kyori.adventure.title.Title;
|
import net.kyori.adventure.title.Title;
|
||||||
import org.bukkit.Bukkit;
|
import org.bukkit.Bukkit;
|
||||||
@@ -15,11 +16,12 @@ import org.bukkit.Sound;
|
|||||||
import org.bukkit.entity.Player;
|
import org.bukkit.entity.Player;
|
||||||
import org.bukkit.entity.TextDisplay;
|
import org.bukkit.entity.TextDisplay;
|
||||||
import org.bukkit.util.Vector;
|
import org.bukkit.util.Vector;
|
||||||
import org.jetbrains.annotations.Nullable;
|
|
||||||
|
|
||||||
import java.time.Duration;
|
import java.time.Duration;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
import java.util.Objects;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -69,7 +71,7 @@ final class Lobby implements PadLobby.Listener {
|
|||||||
|
|
||||||
private void checkEnter(Player player) {
|
private void checkEnter(Player player) {
|
||||||
for(int pad = 0; pad < PadLobby.PADS; pad++) {
|
for(int pad = 0; pad < PadLobby.PADS; pad++) {
|
||||||
if(this.pads.occupant(pad) != null) {
|
if(this.pads.isPadFull(pad)) {
|
||||||
if(this.isOn(player, pad, PUSH_MARGIN)) this.pushAway(player, pad);
|
if(this.isOn(player, pad, PUSH_MARGIN)) this.pushAway(player, pad);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@@ -79,6 +81,10 @@ final class Lobby implements PadLobby.Listener {
|
|||||||
this.arena.setState(player, Arena.PlayerState.PAD);
|
this.arena.setState(player, Arena.PlayerState.PAD);
|
||||||
this.arena.teams().join(player, Teams.pad(pad));
|
this.arena.teams().join(player, Teams.pad(pad));
|
||||||
Style.play(player, Sound.BLOCK_NOTE_BLOCK_PLING, 1.4f);
|
Style.play(player, Sound.BLOCK_NOTE_BLOCK_PLING, 1.4f);
|
||||||
|
if(!player.getUniqueId().equals(this.pads.challenger())) {
|
||||||
|
player.sendActionBar(Component.text(Style.PAD_NAMES[pad] + " " + this.pads.players(pad).size() + "/" + this.pads.size(pad)
|
||||||
|
+ ", Modus: " + this.modeName(), Style.PAD_TEXT_COLORS[pad]));
|
||||||
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -94,14 +100,42 @@ final class Lobby implements PadLobby.Listener {
|
|||||||
this.arena.setState(player, Arena.PlayerState.TRIBUNE);
|
this.arena.setState(player, Arena.PlayerState.TRIBUNE);
|
||||||
}
|
}
|
||||||
|
|
||||||
void chooseMode(Player player, FightMode mode) {
|
void handleMenuClick(Player player, ModeMenu menu, ModeMenu.Click click) {
|
||||||
if(!this.pads.setMode(player.getUniqueId(), mode)) {
|
if(!this.pads.canConfigure(player.getUniqueId())) {
|
||||||
player.closeInventory();
|
player.closeInventory();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
int ownSize = menu.ownSize;
|
||||||
|
int opponentSize = menu.opponentSize;
|
||||||
|
FightMode mode = menu.mode;
|
||||||
|
switch(click) {
|
||||||
|
case ModeMenu.OwnSize(int size) -> ownSize = size;
|
||||||
|
case ModeMenu.OpponentSize(int size) -> opponentSize = size;
|
||||||
|
case ModeMenu.Mode(FightMode chosen) -> mode = chosen;
|
||||||
|
case ModeMenu.Done() -> {
|
||||||
|
InteractSounds.of(player).success();
|
||||||
|
player.closeInventory();
|
||||||
|
player.sendActionBar(Component.text(this.pads.size(this.pads.challengerPad()) + " gegen " + this.pads.size(1 - this.pads.challengerPad())
|
||||||
|
+ ", Modus: " + this.modeName(), NamedTextColor.GOLD));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// switching to teams drops the opponent's inventory mode
|
||||||
|
if(!mode.isAllowed(ownSize, opponentSize)) mode = FightMode.OWN;
|
||||||
|
if(!this.pads.configure(player.getUniqueId(), ownSize, opponentSize, mode)) return;
|
||||||
|
menu.ownSize = ownSize;
|
||||||
|
menu.opponentSize = opponentSize;
|
||||||
|
menu.mode = mode;
|
||||||
|
menu.draw();
|
||||||
InteractSounds.of(player).click();
|
InteractSounds.of(player).click();
|
||||||
player.closeInventory();
|
}
|
||||||
player.sendActionBar(Component.text("Modus: " + mode.displayName(), NamedTextColor.GOLD));
|
|
||||||
|
private boolean isTeamFight() {
|
||||||
|
return this.pads.size(0) > 1 || this.pads.size(1) > 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
private String modeName() {
|
||||||
|
return this.pads.mode().displayName(this.isTeamFight());
|
||||||
}
|
}
|
||||||
|
|
||||||
boolean isIdle() {
|
boolean isIdle() {
|
||||||
@@ -109,11 +143,7 @@ final class Lobby implements PadLobby.Listener {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void shutdown() {
|
void shutdown() {
|
||||||
for(int pad = 0; pad < PadLobby.PADS; pad++) {
|
for(int pad = 0; pad < PadLobby.PADS; pad++) this.playersOn(pad).forEach(this::leave);
|
||||||
UUID occupant = this.pads.occupant(pad);
|
|
||||||
Player player = occupant == null ? null : Bukkit.getPlayer(occupant);
|
|
||||||
if(player != null) this.leave(player);
|
|
||||||
}
|
|
||||||
for(TextDisplay display : this.displays) display.remove();
|
for(TextDisplay display : this.displays) display.remove();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -134,121 +164,119 @@ final class Lobby implements PadLobby.Listener {
|
|||||||
if(now - this.lastPushSound.getOrDefault(player.getUniqueId(), 0L) < 1000) return;
|
if(now - this.lastPushSound.getOrDefault(player.getUniqueId(), 0L) < 1000) return;
|
||||||
this.lastPushSound.put(player.getUniqueId(), now);
|
this.lastPushSound.put(player.getUniqueId(), now);
|
||||||
Style.play(player, Sound.BLOCK_NOTE_BLOCK_BASS, 0.6f);
|
Style.play(player, Sound.BLOCK_NOTE_BLOCK_BASS, 0.6f);
|
||||||
player.sendActionBar(Component.text("Dieses Feld ist besetzt", NamedTextColor.RED));
|
player.sendActionBar(Component.text("Dieses Feld ist voll", NamedTextColor.RED));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void challengerSelected(UUID challenger) {
|
public void challengerSelected(UUID challenger, boolean canConfigure) {
|
||||||
Player player = Bukkit.getPlayer(challenger);
|
Player player = Bukkit.getPlayer(challenger);
|
||||||
if(player == null) return;
|
if(player == null) return;
|
||||||
player.sendMessage(Style.message("Du forderst heraus. Wähle den Modus. Wer sich auf das andere Feld stellt, nimmt an."));
|
if(!canConfigure) {
|
||||||
InteractSounds.of(player).open();
|
player.sendMessage(Style.message("Du führst jetzt die Herausforderung an. Teams und Modus bleiben wie gewählt."));
|
||||||
ModeMenu.open(player, this.pads.mode());
|
return;
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void kitsApplied(UUID challenger, UUID opponent, FightMode mode) {
|
|
||||||
Player challengerPlayer = Bukkit.getPlayer(challenger);
|
|
||||||
Player opponentPlayer = Bukkit.getPlayer(opponent);
|
|
||||||
if(challengerPlayer == null || opponentPlayer == null) return;
|
|
||||||
if(ModeMenu.isOpen(challengerPlayer)) challengerPlayer.closeInventory();
|
|
||||||
|
|
||||||
InventoryStore.apply(challengerPlayer, mode.kitSource(FightMode.Role.CHALLENGER) == FightMode.Role.CHALLENGER ? challenger : opponent);
|
|
||||||
InventoryStore.apply(opponentPlayer, mode.kitSource(FightMode.Role.OPPONENT) == FightMode.Role.CHALLENGER ? challenger : opponent);
|
|
||||||
|
|
||||||
String kitText = switch(mode) {
|
|
||||||
case OWN -> "Jeder kämpft mit seinem eigenen Inventar.";
|
|
||||||
case CHALLENGER -> "Ihr kämpft beide mit dem Inventar von " + challengerPlayer.getName() + ".";
|
|
||||||
case OPPONENT -> "Ihr kämpft beide mit dem Inventar von " + opponentPlayer.getName() + ".";
|
|
||||||
};
|
|
||||||
opponentPlayer.sendMessage(Style.message("Du nimmst die Herausforderung von " + challengerPlayer.getName() + " an. " + kitText + " Verlasse das Feld, um abzubrechen."));
|
|
||||||
challengerPlayer.sendMessage(Style.message(opponentPlayer.getName() + " nimmt deine Herausforderung an. " + kitText + " Verlasse das Feld, um abzubrechen."));
|
|
||||||
for(Player player : new Player[]{challengerPlayer, opponentPlayer}) {
|
|
||||||
Style.play(player, Sound.ITEM_ARMOR_EQUIP_NETHERITE, 1f);
|
|
||||||
}
|
}
|
||||||
|
player.sendMessage(Style.message("Du forderst heraus. Wähle Teamgröße und Modus. Wer sich auf die Felder stellt, macht mit."));
|
||||||
|
InteractSounds.of(player).open();
|
||||||
|
int pad = this.pads.challengerPad();
|
||||||
|
ModeMenu.open(player, pad, this.pads.size(pad), this.pads.size(1 - pad), this.pads.mode());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void kitReverted(UUID uuid) {
|
public void kitAssigned(UUID uuid, UUID kitOwner) {
|
||||||
Player player = Bukkit.getPlayer(uuid);
|
Player player = Bukkit.getPlayer(uuid);
|
||||||
if(player != null) InventoryStore.apply(player, uuid);
|
if(player == null) return;
|
||||||
|
if(ModeMenu.isOpen(player)) player.closeInventory();
|
||||||
|
InventoryStore.apply(player, kitOwner);
|
||||||
|
if(uuid.equals(kitOwner)) return;
|
||||||
|
Player owner = Bukkit.getPlayer(kitOwner);
|
||||||
|
player.sendMessage(Style.message("Du kämpfst mit dem Inventar von " + (owner == null ? "jemand anderem" : owner.getName()) + ". Verlasse das Feld, um abzubrechen."));
|
||||||
|
Style.play(player, Sound.ITEM_ARMOR_EQUIP_NETHERITE, 1f);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void countdown(int secondsLeft) {
|
public void countdown(int secondsLeft) {
|
||||||
for(int pad = 0; pad < PadLobby.PADS; pad++) {
|
for(int pad = 0; pad < PadLobby.PADS; pad++) {
|
||||||
Player player = this.occupantPlayer(pad);
|
Component opponents = this.namesOn(1 - pad);
|
||||||
Player other = this.occupantPlayer(1 - pad);
|
for(Player player : this.playersOn(pad)) {
|
||||||
if(player == null || other == null) continue;
|
player.showTitle(Title.title(
|
||||||
player.showTitle(Title.title(
|
Component.text(secondsLeft, NamedTextColor.GOLD),
|
||||||
Component.text(secondsLeft, NamedTextColor.GOLD),
|
Component.text("Kampf gegen ", NamedTextColor.GRAY).append(opponents),
|
||||||
Component.text("Kampf gegen ", NamedTextColor.GRAY).append(Component.text(other.getName(), Style.PAD_TEXT_COLORS[1 - pad])),
|
Title.Times.times(Duration.ZERO, Duration.ofMillis(1100), Duration.ZERO)
|
||||||
Title.Times.times(Duration.ZERO, Duration.ofMillis(1100), Duration.ZERO)
|
));
|
||||||
));
|
Style.play(player, Sound.BLOCK_NOTE_BLOCK_PLING, 0.6f + (this.arena.settings().countdownSeconds() - secondsLeft) * 0.2f);
|
||||||
Style.play(player, Sound.BLOCK_NOTE_BLOCK_PLING, 0.6f + (this.arena.settings().countdownSeconds() - secondsLeft) * 0.2f);
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void countdownCancelled() {
|
public void countdownCancelled() {
|
||||||
for(int pad = 0; pad < PadLobby.PADS; pad++) {
|
for(int pad = 0; pad < PadLobby.PADS; pad++) {
|
||||||
Player player = this.occupantPlayer(pad);
|
for(Player player : this.playersOn(pad)) {
|
||||||
if(player == null) continue;
|
player.clearTitle();
|
||||||
player.clearTitle();
|
player.sendActionBar(Component.text("Abgebrochen", NamedTextColor.RED));
|
||||||
player.sendActionBar(Component.text("Abgebrochen", NamedTextColor.RED));
|
Style.play(player, Sound.BLOCK_NOTE_BLOCK_BASS, 0.5f);
|
||||||
Style.play(player, Sound.BLOCK_NOTE_BLOCK_BASS, 0.5f);
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void start(UUID challenger, UUID opponent, FightMode mode, int challengerPad) {
|
public void start(List<List<UUID>> teams, UUID challenger, FightMode mode, Map<UUID, UUID> kits) {
|
||||||
Player challengerPlayer = Bukkit.getPlayer(challenger);
|
List<List<Player>> players = teams.stream()
|
||||||
Player opponentPlayer = Bukkit.getPlayer(opponent);
|
.map(team -> team.stream().map(Bukkit::getPlayer).filter(Objects::nonNull).toList())
|
||||||
if(challengerPlayer == null || opponentPlayer == null) {
|
.toList();
|
||||||
if(challengerPlayer != null) this.arena.toTribune(challengerPlayer);
|
if(players.get(0).isEmpty() || players.get(1).isEmpty()) {
|
||||||
if(opponentPlayer != null) this.arena.toTribune(opponentPlayer);
|
players.forEach(team -> team.forEach(this.arena::toTribune));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
Player red = challengerPad == 0 ? challengerPlayer : opponentPlayer;
|
this.arena.startFight(players, mode, kits);
|
||||||
Player blue = challengerPad == 0 ? opponentPlayer : challengerPlayer;
|
|
||||||
this.arena.startFight(red, blue, challenger, mode);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private @Nullable Player occupantPlayer(int pad) {
|
private List<Player> playersOn(int pad) {
|
||||||
UUID occupant = this.pads.occupant(pad);
|
return this.pads.players(pad).stream().map(Bukkit::getPlayer).filter(Objects::nonNull).toList();
|
||||||
return occupant == null ? null : Bukkit.getPlayer(occupant);
|
}
|
||||||
|
|
||||||
|
private Component namesOn(int pad) {
|
||||||
|
return Component.join(
|
||||||
|
JoinConfiguration.separator(Component.text(", ", NamedTextColor.GRAY)),
|
||||||
|
this.playersOn(pad).stream().map(player -> Component.text(player.getName(), Style.PAD_TEXT_COLORS[pad])).toList()
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void updateDisplays() {
|
private void updateDisplays() {
|
||||||
boolean waiting = this.pads.isFull() && !this.arena.hasFreeSlot();
|
boolean waiting = this.pads.isFull() && !this.arena.hasFreeSlot();
|
||||||
Player challenger = this.pads.challenger() == null ? null : Bukkit.getPlayer(this.pads.challenger());
|
Player challenger = this.pads.challenger() == null ? null : Bukkit.getPlayer(this.pads.challenger());
|
||||||
String modeText = "Modus: " + this.pads.mode().displayName();
|
int challengerPad = this.pads.challengerPad();
|
||||||
|
String modeText = "Modus: " + this.modeName();
|
||||||
|
|
||||||
for(int pad = 0; pad < PadLobby.PADS; pad++) {
|
for(int pad = 0; pad < PadLobby.PADS; pad++) {
|
||||||
|
List<Player> players = this.playersOn(pad);
|
||||||
Component title = Component.text(Style.PAD_NAMES[pad], Style.PAD_TEXT_COLORS[pad]);
|
Component title = Component.text(Style.PAD_NAMES[pad], Style.PAD_TEXT_COLORS[pad]);
|
||||||
Player occupant = this.occupantPlayer(pad);
|
if(challenger != null) title = title.append(Component.text(" " + players.size() + "/" + this.pads.size(pad), NamedTextColor.WHITE));
|
||||||
|
|
||||||
Component body;
|
Component body;
|
||||||
if(waiting) {
|
if(waiting) {
|
||||||
body = Component.text("Alle Arenen belegt", NamedTextColor.GRAY).appendNewline()
|
body = this.namesOn(pad).appendNewline()
|
||||||
.append(Component.text("Gleich geht es los", NamedTextColor.WHITE));
|
.append(Component.text("Alle Arenen belegt, gleich geht es los", NamedTextColor.GRAY));
|
||||||
} else if(this.pads.isCountingDown()) {
|
} else if(this.pads.isCountingDown()) {
|
||||||
body = Component.text(occupant == null ? "" : occupant.getName(), Style.PAD_TEXT_COLORS[pad]).appendNewline()
|
body = this.namesOn(pad).appendNewline()
|
||||||
.append(Component.text("Kampf beginnt in " + this.pads.secondsLeft(), NamedTextColor.GOLD));
|
.append(Component.text("Kampf beginnt in " + this.pads.secondsLeft(), NamedTextColor.GOLD));
|
||||||
} else if(occupant != null) {
|
} else if(challenger == null) {
|
||||||
body = Component.text(occupant.getName(), Style.PAD_TEXT_COLORS[pad]).appendNewline()
|
body = !this.arena.hasFreeSlot()
|
||||||
.append(Component.text("fordert heraus", NamedTextColor.GRAY)).appendNewline()
|
? Component.text("Alle Arenen belegt", NamedTextColor.GRAY).appendNewline()
|
||||||
|
.append(Component.text("Du kannst dich trotzdem anstellen", NamedTextColor.WHITE))
|
||||||
|
: Component.text("Stell dich hierhin,", NamedTextColor.WHITE).appendNewline()
|
||||||
|
.append(Component.text("um herauszufordern", NamedTextColor.WHITE));
|
||||||
|
} else if(pad == challengerPad) {
|
||||||
|
body = this.namesOn(pad).appendNewline()
|
||||||
|
.append(Component.text(players.size() == 1 ? "fordert heraus" : "fordern heraus", NamedTextColor.GRAY)).appendNewline()
|
||||||
.append(Component.text(modeText, NamedTextColor.WHITE));
|
.append(Component.text(modeText, NamedTextColor.WHITE));
|
||||||
} else if(challenger != null) {
|
|
||||||
body = Component.text("Stell dich hierhin, um gegen", NamedTextColor.WHITE).appendNewline()
|
|
||||||
.append(Component.text(challenger.getName(), Style.PAD_TEXT_COLORS[1 - pad]))
|
|
||||||
.append(Component.text(" zu kämpfen", NamedTextColor.WHITE)).appendNewline()
|
|
||||||
.append(Component.text(modeText, NamedTextColor.GRAY));
|
|
||||||
} else if(!this.arena.hasFreeSlot()) {
|
|
||||||
body = Component.text("Alle Arenen belegt", NamedTextColor.GRAY).appendNewline()
|
|
||||||
.append(Component.text("Du kannst dich trotzdem anstellen", NamedTextColor.WHITE));
|
|
||||||
} else {
|
} else {
|
||||||
body = Component.text("Stell dich hierhin,", NamedTextColor.WHITE).appendNewline()
|
Component who = players.isEmpty()
|
||||||
.append(Component.text("um herauszufordern", NamedTextColor.WHITE));
|
? Component.text("Stell dich hierhin, um gegen", NamedTextColor.WHITE).appendNewline()
|
||||||
|
.append(Component.text(challenger.getName(), Style.PAD_TEXT_COLORS[challengerPad]))
|
||||||
|
.append(Component.text(" zu kämpfen", NamedTextColor.WHITE))
|
||||||
|
: this.namesOn(pad).appendNewline().append(Component.text("nehmen an", NamedTextColor.GRAY));
|
||||||
|
body = who.appendNewline().append(Component.text(modeText, NamedTextColor.GRAY));
|
||||||
}
|
}
|
||||||
this.displays[pad].text(title.appendNewline().append(body));
|
this.displays[pad].text(title.appendNewline().append(body));
|
||||||
}
|
}
|
||||||
@@ -260,18 +288,18 @@ final class Lobby implements PadLobby.Listener {
|
|||||||
for(int pad = 0; pad < PadLobby.PADS; pad++) {
|
for(int pad = 0; pad < PadLobby.PADS; pad++) {
|
||||||
Region region = this.regions[pad];
|
Region region = this.regions[pad];
|
||||||
Color color = waiting ? Style.WAITING_PARTICLE_COLOR : Style.PAD_PARTICLE_COLORS[pad];
|
Color color = waiting ? Style.WAITING_PARTICLE_COLOR : Style.PAD_PARTICLE_COLORS[pad];
|
||||||
boolean taken = this.pads.occupant(pad) != null;
|
boolean full = this.pads.isPadFull(pad);
|
||||||
double spacing = countdown ? 0.25 : taken ? 0.4 : 0.6;
|
double spacing = countdown ? 0.25 : full ? 0.4 : 0.6;
|
||||||
this.drawOutline(region, new Particle.DustOptions(color, taken ? 1.2f : 0.9f), spacing);
|
this.drawOutline(region, new Particle.DustOptions(color, full ? 1.2f : 0.9f), spacing);
|
||||||
|
|
||||||
Player occupant = this.occupantPlayer(pad);
|
|
||||||
if(occupant == null) continue;
|
|
||||||
double speed = countdown ? 0.6 : 0.3;
|
double speed = countdown ? 0.6 : 0.3;
|
||||||
for(int strand = 0; strand < 2; strand++) {
|
for(Player occupant : this.playersOn(pad)) {
|
||||||
double angle = this.ticks * speed + strand * Math.PI;
|
for(int strand = 0; strand < 2; strand++) {
|
||||||
double height = (this.ticks % 40) / 20.0;
|
double angle = this.ticks * speed + strand * Math.PI;
|
||||||
Location point = occupant.getLocation().add(Math.cos(angle) * 0.7, height, Math.sin(angle) * 0.7);
|
double height = (this.ticks % 40) / 20.0;
|
||||||
Particle.DUST.builder().location(point).data(new Particle.DustOptions(color, 1f)).receivers(48).spawn();
|
Location point = occupant.getLocation().add(Math.cos(angle) * 0.7, height, Math.sin(angle) * 0.7);
|
||||||
|
Particle.DUST.builder().location(point).data(new Particle.DustOptions(color, 1f)).receivers(48).spawn();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+97
-25
@@ -1,6 +1,7 @@
|
|||||||
package eu.mhsl.craftattack.spawn.arena.appliances.arena;
|
package eu.mhsl.craftattack.spawn.arena.appliances.arena;
|
||||||
|
|
||||||
import eu.mhsl.craftattack.spawn.arena.appliances.arena.logic.FightMode;
|
import eu.mhsl.craftattack.spawn.arena.appliances.arena.logic.FightMode;
|
||||||
|
import eu.mhsl.craftattack.spawn.arena.appliances.arena.logic.PadLobby;
|
||||||
import eu.mhsl.craftattack.spawn.core.util.inventory.ItemBuilder;
|
import eu.mhsl.craftattack.spawn.core.util.inventory.ItemBuilder;
|
||||||
import eu.mhsl.craftattack.spawn.core.util.inventory.PlaceholderItems;
|
import eu.mhsl.craftattack.spawn.core.util.inventory.PlaceholderItems;
|
||||||
import net.kyori.adventure.text.Component;
|
import net.kyori.adventure.text.Component;
|
||||||
@@ -14,55 +15,126 @@ import org.bukkit.inventory.InventoryHolder;
|
|||||||
import org.bukkit.inventory.ItemStack;
|
import org.bukkit.inventory.ItemStack;
|
||||||
import org.bukkit.inventory.meta.SkullMeta;
|
import org.bukkit.inventory.meta.SkullMeta;
|
||||||
import org.jetbrains.annotations.NotNull;
|
import org.jetbrains.annotations.NotNull;
|
||||||
import org.jetbrains.annotations.Nullable;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Chest menu in which the challenger picks the mode. Works for Bedrock players as well.
|
* Chest menu in which the challenger picks team sizes and mode. Every click applies at once.
|
||||||
|
* Works for Bedrock players as well.
|
||||||
*/
|
*/
|
||||||
final class ModeMenu implements InventoryHolder {
|
final class ModeMenu implements InventoryHolder {
|
||||||
private static final int[] SLOTS = {11, 13, 15};
|
private static final int OWN_ROW = 10;
|
||||||
|
private static final int OPPONENT_ROW = 19;
|
||||||
|
private static final int[] MODE_SLOTS = {29, 31, 33};
|
||||||
private static final FightMode[] MODES = {FightMode.OWN, FightMode.CHALLENGER, FightMode.OPPONENT};
|
private static final FightMode[] MODES = {FightMode.OWN, FightMode.CHALLENGER, FightMode.OPPONENT};
|
||||||
|
private static final int DONE_SLOT = 40;
|
||||||
|
|
||||||
private final Inventory inventory;
|
sealed interface Click {
|
||||||
|
|
||||||
private ModeMenu(Player challenger, FightMode current) {
|
|
||||||
this.inventory = Bukkit.createInventory(this, 27, Component.text("Wähle den Kampfmodus"));
|
|
||||||
for(int i = 0; i < this.inventory.getSize(); i++) this.inventory.setItem(i, PlaceholderItems.grayStainedGlassPane);
|
|
||||||
for(int i = 0; i < MODES.length; i++) this.inventory.setItem(SLOTS[i], this.item(MODES[i], challenger, MODES[i] == current));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static void open(Player challenger, FightMode current) {
|
record OwnSize(int size) implements Click {
|
||||||
challenger.openInventory(new ModeMenu(challenger, current).getInventory());
|
}
|
||||||
|
|
||||||
|
record OpponentSize(int size) implements Click {
|
||||||
|
}
|
||||||
|
|
||||||
|
record Mode(FightMode mode) implements Click {
|
||||||
|
}
|
||||||
|
|
||||||
|
record Done() implements Click {
|
||||||
|
}
|
||||||
|
|
||||||
|
private final Inventory inventory;
|
||||||
|
private final Player challenger;
|
||||||
|
private final int ownPad;
|
||||||
|
int ownSize;
|
||||||
|
int opponentSize;
|
||||||
|
FightMode mode;
|
||||||
|
|
||||||
|
private ModeMenu(Player challenger, int ownPad, int ownSize, int opponentSize, FightMode mode) {
|
||||||
|
this.challenger = challenger;
|
||||||
|
this.ownPad = ownPad;
|
||||||
|
this.ownSize = ownSize;
|
||||||
|
this.opponentSize = opponentSize;
|
||||||
|
this.mode = mode;
|
||||||
|
this.inventory = Bukkit.createInventory(this, 45, Component.text("Teams und Kampfmodus"));
|
||||||
|
this.draw();
|
||||||
|
}
|
||||||
|
|
||||||
|
static void open(Player challenger, int ownPad, int ownSize, int opponentSize, FightMode mode) {
|
||||||
|
challenger.openInventory(new ModeMenu(challenger, ownPad, ownSize, opponentSize, mode).getInventory());
|
||||||
}
|
}
|
||||||
|
|
||||||
static boolean isOpen(Player player) {
|
static boolean isOpen(Player player) {
|
||||||
return player.getOpenInventory().getTopInventory().getHolder(false) instanceof ModeMenu;
|
return player.getOpenInventory().getTopInventory().getHolder(false) instanceof ModeMenu;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Nullable FightMode modeAt(int slot) {
|
Click clickAt(int slot) {
|
||||||
for(int i = 0; i < SLOTS.length; i++) {
|
if(slot > OWN_ROW && slot <= OWN_ROW + PadLobby.MAX_TEAM_SIZE) return new OwnSize(slot - OWN_ROW);
|
||||||
if(SLOTS[i] == slot) return MODES[i];
|
if(slot > OPPONENT_ROW && slot <= OPPONENT_ROW + PadLobby.MAX_TEAM_SIZE) return new OpponentSize(slot - OPPONENT_ROW);
|
||||||
|
for(int i = 0; i < MODE_SLOTS.length; i++) {
|
||||||
|
if(MODE_SLOTS[i] == slot) return new Mode(MODES[i]);
|
||||||
}
|
}
|
||||||
|
if(slot == DONE_SLOT) return new Done();
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
private ItemStack item(FightMode mode, Player challenger, boolean selected) {
|
void draw() {
|
||||||
|
for(int i = 0; i < this.inventory.getSize(); i++) this.inventory.setItem(i, PlaceholderItems.grayStainedGlassPane);
|
||||||
|
int opponentPad = 1 - this.ownPad;
|
||||||
|
boolean teams = this.ownSize > 1 || this.opponentSize > 1;
|
||||||
|
|
||||||
|
this.inventory.setItem(OWN_ROW, this.label(Material.PLAYER_HEAD, "Dein Team", this.ownPad, "So viele stehen mit dir auf dem " + Style.PAD_NAMES[this.ownPad] + ".")
|
||||||
|
.withMeta(meta -> ((SkullMeta) meta).setOwningPlayer(this.challenger)).build());
|
||||||
|
this.inventory.setItem(OPPONENT_ROW, this.label(Material.SKELETON_SKULL, "Gegner", opponentPad, "So viele stellen sich auf das " + Style.PAD_NAMES[opponentPad] + ".").build());
|
||||||
|
for(int size = 1; size <= PadLobby.MAX_TEAM_SIZE; size++) {
|
||||||
|
this.inventory.setItem(OWN_ROW + size, this.sizeItem(size, this.ownPad, size == this.ownSize));
|
||||||
|
this.inventory.setItem(OPPONENT_ROW + size, this.sizeItem(size, opponentPad, size == this.opponentSize));
|
||||||
|
}
|
||||||
|
|
||||||
|
for(int i = 0; i < MODES.length; i++) {
|
||||||
|
FightMode mode = MODES[i];
|
||||||
|
this.inventory.setItem(MODE_SLOTS[i], this.modeItem(mode, teams, mode == this.mode, mode.isAllowed(this.ownSize, this.opponentSize)));
|
||||||
|
}
|
||||||
|
|
||||||
|
this.inventory.setItem(DONE_SLOT, ItemBuilder.of(Material.LIME_CONCRETE)
|
||||||
|
.displayName(Component.text("Fertig", NamedTextColor.GREEN).decoration(TextDecoration.ITALIC, false))
|
||||||
|
.lore(this.ownSize + " gegen " + this.opponentSize + ", " + this.mode.displayName(teams) + ". Jetzt fehlen nur noch die anderen auf den Feldern.")
|
||||||
|
.build());
|
||||||
|
}
|
||||||
|
|
||||||
|
private ItemBuilder label(Material material, String name, int pad, String text) {
|
||||||
|
return ItemBuilder.of(material)
|
||||||
|
.displayName(Component.text(name, Style.PAD_TEXT_COLORS[pad]).decoration(TextDecoration.ITALIC, false))
|
||||||
|
.lore(text);
|
||||||
|
}
|
||||||
|
|
||||||
|
private ItemStack sizeItem(int size, int pad, boolean selected) {
|
||||||
|
ItemBuilder builder = ItemBuilder.of(pad == 0 ? Material.RED_CONCRETE : Material.BLUE_CONCRETE)
|
||||||
|
.amount(size)
|
||||||
|
.displayName(Component.text(size + " Spieler", Style.PAD_TEXT_COLORS[pad]).decoration(TextDecoration.ITALIC, false));
|
||||||
|
builder.lore(selected ? "Ausgewählt" : "Klicken zum Auswählen", selected ? NamedTextColor.GREEN : NamedTextColor.YELLOW);
|
||||||
|
if(selected) builder.glint();
|
||||||
|
return builder.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
private ItemStack modeItem(FightMode mode, boolean teams, boolean selected, boolean allowed) {
|
||||||
ItemBuilder builder = switch(mode) {
|
ItemBuilder builder = switch(mode) {
|
||||||
case OWN -> ItemBuilder.of(Material.IRON_SWORD)
|
case OWN -> ItemBuilder.of(Material.IRON_SWORD)
|
||||||
.lore("Ihr kämpft beide mit eurem eigenen Inventar.");
|
.lore("Jeder kämpft mit seinem eigenen Inventar.");
|
||||||
case CHALLENGER -> ItemBuilder.of(Material.PLAYER_HEAD)
|
case CHALLENGER -> ItemBuilder.of(Material.PLAYER_HEAD)
|
||||||
.withMeta(meta -> ((SkullMeta) meta).setOwningPlayer(challenger))
|
.withMeta(meta -> ((SkullMeta) meta).setOwningPlayer(this.challenger))
|
||||||
.lore("Ihr kämpft beide mit deinem Inventar.");
|
.lore(teams ? "Alle kämpfen mit deinem Inventar." : "Ihr kämpft beide mit deinem Inventar.");
|
||||||
case OPPONENT -> ItemBuilder.of(Material.CHEST)
|
case OPPONENT -> ItemBuilder.of(allowed ? Material.CHEST : Material.BARRIER)
|
||||||
.lore("Ihr kämpft beide mit dem Inventar von dem, der sich auf das andere Feld stellt.");
|
.lore(allowed
|
||||||
|
? "Ihr kämpft beide mit dem Inventar von dem, der sich auf das andere Feld stellt."
|
||||||
|
: "Geht nur bei 1 gegen 1.");
|
||||||
};
|
};
|
||||||
builder.displayName(Component.text(mode.displayName(), NamedTextColor.GOLD).decoration(TextDecoration.ITALIC, false));
|
builder.displayName(Component.text(mode.displayName(teams), allowed ? NamedTextColor.GOLD : NamedTextColor.DARK_GRAY).decoration(TextDecoration.ITALIC, false));
|
||||||
|
if(!allowed) return builder.build();
|
||||||
|
builder.appendLore(Component.empty());
|
||||||
if(selected) {
|
if(selected) {
|
||||||
builder.glint().appendLore(Component.empty())
|
builder.glint().appendLore(Component.text("Ausgewählt", NamedTextColor.GREEN).decoration(TextDecoration.ITALIC, false));
|
||||||
.appendLore(Component.text("Ausgewählt", NamedTextColor.GREEN).decoration(TextDecoration.ITALIC, false));
|
|
||||||
} else {
|
} else {
|
||||||
builder.appendLore(Component.empty())
|
builder.appendLore(Component.text("Klicken zum Auswählen", NamedTextColor.YELLOW).decoration(TextDecoration.ITALIC, false));
|
||||||
.appendLore(Component.text("Klicken zum Auswählen", NamedTextColor.YELLOW).decoration(TextDecoration.ITALIC, false));
|
|
||||||
}
|
}
|
||||||
return builder.build();
|
return builder.build();
|
||||||
}
|
}
|
||||||
|
|||||||
+2
-3
@@ -1,6 +1,5 @@
|
|||||||
package eu.mhsl.craftattack.spawn.arena.appliances.arena;
|
package eu.mhsl.craftattack.spawn.arena.appliances.arena;
|
||||||
|
|
||||||
import eu.mhsl.craftattack.spawn.arena.appliances.arena.logic.FightMode;
|
|
||||||
import eu.mhsl.craftattack.spawn.core.appliance.ApplianceListener;
|
import eu.mhsl.craftattack.spawn.core.appliance.ApplianceListener;
|
||||||
import org.bukkit.entity.Player;
|
import org.bukkit.entity.Player;
|
||||||
import org.bukkit.event.EventHandler;
|
import org.bukkit.event.EventHandler;
|
||||||
@@ -14,8 +13,8 @@ class ModeMenuListener extends ApplianceListener<Arena> {
|
|||||||
if(!(event.getWhoClicked() instanceof Player player)) return;
|
if(!(event.getWhoClicked() instanceof Player player)) return;
|
||||||
if(event.getRawSlot() >= event.getView().getTopInventory().getSize()) return;
|
if(event.getRawSlot() >= event.getView().getTopInventory().getSize()) return;
|
||||||
|
|
||||||
FightMode mode = menu.modeAt(event.getRawSlot());
|
ModeMenu.Click click = menu.clickAt(event.getRawSlot());
|
||||||
Lobby lobby = this.getAppliance().lobby();
|
Lobby lobby = this.getAppliance().lobby();
|
||||||
if(mode != null && lobby != null) lobby.chooseMode(player, mode);
|
if(click != null && lobby != null) lobby.handleMenuClick(player, menu, click);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
+13
-24
@@ -1,36 +1,25 @@
|
|||||||
package eu.mhsl.craftattack.spawn.arena.appliances.arena.logic;
|
package eu.mhsl.craftattack.spawn.arena.appliances.arena.logic;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Which inventory the two fighters use. The challenger chooses it.
|
* Which inventory the fighters use. The challenger chooses it.
|
||||||
*/
|
*/
|
||||||
public enum FightMode {
|
public enum FightMode {
|
||||||
OWN("Jeder sein eigenes"),
|
OWN,
|
||||||
CHALLENGER("Beide mit meinem"),
|
CHALLENGER,
|
||||||
OPPONENT("Beide mit dem des Gegners");
|
OPPONENT;
|
||||||
|
|
||||||
public enum Role {
|
|
||||||
CHALLENGER,
|
|
||||||
OPPONENT
|
|
||||||
}
|
|
||||||
|
|
||||||
private final String displayName;
|
|
||||||
|
|
||||||
FightMode(String displayName) {
|
|
||||||
this.displayName = displayName;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String displayName() {
|
|
||||||
return this.displayName;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return whose inventory the fighter with the given role gets
|
* With more than one player on a side, "the opponent's inventory" would be ambiguous.
|
||||||
*/
|
*/
|
||||||
public Role kitSource(Role fighter) {
|
public boolean isAllowed(int ownSize, int opponentSize) {
|
||||||
|
return this != OPPONENT || (ownSize == 1 && opponentSize == 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
public String displayName(boolean teams) {
|
||||||
return switch(this) {
|
return switch(this) {
|
||||||
case OWN -> fighter;
|
case OWN -> "Jeder sein eigenes";
|
||||||
case CHALLENGER -> Role.CHALLENGER;
|
case CHALLENGER -> teams ? "Alle mit meinem" : "Beide mit meinem";
|
||||||
case OPPONENT -> Role.OPPONENT;
|
case OPPONENT -> "Beide mit dem des Gegners";
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
+107
-53
@@ -2,36 +2,46 @@ package eu.mhsl.craftattack.spawn.arena.appliances.arena.logic;
|
|||||||
|
|
||||||
import org.jetbrains.annotations.Nullable;
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* State of the two start pads. The first player on a pad becomes the challenger and picks the mode,
|
* State of the two start pads. The first player on a pad becomes the challenger and picks team sizes and mode
|
||||||
* stepping on the other pad means accepting. Once both stand there long enough, the fight starts.
|
* while standing there alone. Everyone else joins a team by stepping on a pad. Once both pads hold exactly
|
||||||
|
* the chosen number of players long enough, the fight starts.
|
||||||
*/
|
*/
|
||||||
public class PadLobby {
|
public class PadLobby {
|
||||||
public static final int PADS = 2;
|
public static final int PADS = 2;
|
||||||
|
public static final int MAX_TEAM_SIZE = 4;
|
||||||
|
|
||||||
public interface Listener {
|
public interface Listener {
|
||||||
/** The player is now the challenger and should pick a mode. */
|
/** The player is now the challenger. Only when alone on the pads the options can be changed. */
|
||||||
void challengerSelected(UUID challenger);
|
void challengerSelected(UUID challenger, boolean canConfigure);
|
||||||
|
|
||||||
/** Both pads are taken: give both the inventories of the chosen mode. */
|
/** The player should now hold the inventory of {@code kitOwner} (the own one to revert). */
|
||||||
void kitsApplied(UUID challenger, UUID opponent, FightMode mode);
|
void kitAssigned(UUID player, UUID kitOwner);
|
||||||
|
|
||||||
/** The player gets the own inventory back. */
|
|
||||||
void kitReverted(UUID player);
|
|
||||||
|
|
||||||
void countdown(int secondsLeft);
|
void countdown(int secondsLeft);
|
||||||
|
|
||||||
void countdownCancelled();
|
void countdownCancelled();
|
||||||
|
|
||||||
void start(UUID challenger, UUID opponent, FightMode mode, int challengerPad);
|
/**
|
||||||
|
* @param teams players per pad
|
||||||
|
* @param kits whose inventory each player fights with
|
||||||
|
*/
|
||||||
|
void start(List<List<UUID>> teams, UUID challenger, FightMode mode, Map<UUID, UUID> kits);
|
||||||
}
|
}
|
||||||
|
|
||||||
private final Listener listener;
|
private final Listener listener;
|
||||||
private final int countdownTicks;
|
private final int countdownTicks;
|
||||||
private final UUID[] occupants = new UUID[PADS];
|
private final List<List<UUID>> pads = List.of(new ArrayList<>(), new ArrayList<>());
|
||||||
private int challengerPad = -1;
|
/** Everyone on a pad, in the order they stepped on. The first one is the challenger. */
|
||||||
|
private final List<UUID> order = new ArrayList<>();
|
||||||
|
private final Map<UUID, UUID> kits = new HashMap<>();
|
||||||
|
private final int[] sizes = {1, 1};
|
||||||
private FightMode mode = FightMode.OWN;
|
private FightMode mode = FightMode.OWN;
|
||||||
private int ticksLeft = -1;
|
private int ticksLeft = -1;
|
||||||
|
|
||||||
@@ -41,22 +51,26 @@ public class PadLobby {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return false if the pad is already taken
|
* @return false if the pad is already full or the player stands on a pad
|
||||||
*/
|
*/
|
||||||
public boolean enter(int pad, UUID player) {
|
public boolean enter(int pad, UUID player) {
|
||||||
if(this.occupants[pad] != null) return false;
|
|
||||||
if(this.padOf(player) != -1) return false;
|
if(this.padOf(player) != -1) return false;
|
||||||
|
boolean first = this.order.isEmpty();
|
||||||
|
if(!first && this.pads.get(pad).size() >= this.sizes[pad]) return false;
|
||||||
|
|
||||||
this.occupants[pad] = player;
|
this.pads.get(pad).add(player);
|
||||||
if(this.challengerPad == -1) {
|
this.order.add(player);
|
||||||
this.challengerPad = pad;
|
this.kits.put(player, player);
|
||||||
|
if(first) {
|
||||||
|
this.sizes[0] = 1;
|
||||||
|
this.sizes[1] = 1;
|
||||||
this.mode = FightMode.OWN;
|
this.mode = FightMode.OWN;
|
||||||
this.listener.challengerSelected(player);
|
this.listener.challengerSelected(player, true);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.listener.kitsApplied(this.challenger(), this.opponent(), this.mode);
|
this.refreshKits();
|
||||||
this.ticksLeft = this.countdownTicks;
|
if(this.isFull()) this.ticksLeft = this.countdownTicks;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -64,35 +78,47 @@ public class PadLobby {
|
|||||||
int pad = this.padOf(player);
|
int pad = this.padOf(player);
|
||||||
if(pad == -1) return;
|
if(pad == -1) return;
|
||||||
|
|
||||||
int other = 1 - pad;
|
boolean wasFull = this.isFull();
|
||||||
boolean bothThere = this.isFull();
|
boolean wasChallenger = player.equals(this.challenger());
|
||||||
this.occupants[pad] = null;
|
this.pads.get(pad).remove(player);
|
||||||
this.listener.kitReverted(player);
|
this.order.remove(player);
|
||||||
if(bothThere) {
|
this.kits.remove(player);
|
||||||
|
this.listener.kitAssigned(player, player);
|
||||||
|
|
||||||
|
if(wasFull) {
|
||||||
this.ticksLeft = -1;
|
this.ticksLeft = -1;
|
||||||
this.listener.kitReverted(this.occupants[other]);
|
|
||||||
this.listener.countdownCancelled();
|
this.listener.countdownCancelled();
|
||||||
}
|
}
|
||||||
|
|
||||||
if(pad != this.challengerPad) return;
|
this.refreshKits();
|
||||||
if(this.occupants[other] == null) {
|
// the longest waiting player takes over; team sizes and mode stay as chosen
|
||||||
this.challengerPad = -1;
|
if(wasChallenger && !this.order.isEmpty()) {
|
||||||
return;
|
this.listener.challengerSelected(this.order.getFirst(), this.order.size() == 1);
|
||||||
}
|
}
|
||||||
this.challengerPad = other;
|
|
||||||
this.mode = FightMode.OWN;
|
|
||||||
this.listener.challengerSelected(this.occupants[other]);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Only the challenger can change the mode, and only while nobody stands on the other pad.
|
* Only the challenger can change the options, and only while standing alone on the pads.
|
||||||
|
*
|
||||||
|
* @param ownSize players on the challenger's pad
|
||||||
|
* @param opponentSize players on the other pad
|
||||||
*/
|
*/
|
||||||
public boolean setMode(UUID player, FightMode mode) {
|
public boolean configure(UUID player, int ownSize, int opponentSize, FightMode mode) {
|
||||||
if(!player.equals(this.challenger()) || this.isFull()) return false;
|
if(!this.canConfigure(player)) return false;
|
||||||
|
if(ownSize < 1 || ownSize > MAX_TEAM_SIZE || opponentSize < 1 || opponentSize > MAX_TEAM_SIZE) return false;
|
||||||
|
if(!mode.isAllowed(ownSize, opponentSize)) return false;
|
||||||
|
int own = this.challengerPad();
|
||||||
|
this.sizes[own] = ownSize;
|
||||||
|
this.sizes[1 - own] = opponentSize;
|
||||||
this.mode = mode;
|
this.mode = mode;
|
||||||
|
this.refreshKits();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean canConfigure(UUID player) {
|
||||||
|
return player.equals(this.challenger()) && this.order.size() == 1;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Called every server tick. The countdown only runs while an arena is free.
|
* Called every server tick. The countdown only runs while an arena is free.
|
||||||
*/
|
*/
|
||||||
@@ -106,39 +132,67 @@ public class PadLobby {
|
|||||||
if(this.ticksLeft % 20 == 0 && this.ticksLeft > 0) this.listener.countdown(this.ticksLeft / 20);
|
if(this.ticksLeft % 20 == 0 && this.ticksLeft > 0) this.listener.countdown(this.ticksLeft / 20);
|
||||||
if(this.ticksLeft-- > 0) return;
|
if(this.ticksLeft-- > 0) return;
|
||||||
|
|
||||||
|
List<List<UUID>> teams = List.of(List.copyOf(this.pads.get(0)), List.copyOf(this.pads.get(1)));
|
||||||
UUID challenger = this.challenger();
|
UUID challenger = this.challenger();
|
||||||
UUID opponent = this.opponent();
|
|
||||||
FightMode mode = this.mode;
|
FightMode mode = this.mode;
|
||||||
int challengerPad = this.challengerPad;
|
Map<UUID, UUID> kits = Map.copyOf(this.kits);
|
||||||
this.occupants[0] = null;
|
this.pads.forEach(List::clear);
|
||||||
this.occupants[1] = null;
|
this.order.clear();
|
||||||
this.challengerPad = -1;
|
this.kits.clear();
|
||||||
|
this.sizes[0] = 1;
|
||||||
|
this.sizes[1] = 1;
|
||||||
this.mode = FightMode.OWN;
|
this.mode = FightMode.OWN;
|
||||||
this.ticksLeft = -1;
|
this.ticksLeft = -1;
|
||||||
this.listener.start(challenger, opponent, mode, challengerPad);
|
this.listener.start(teams, challenger, mode, kits);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Works out whose inventory everyone should hold and reports only the changes.
|
||||||
|
*/
|
||||||
|
private void refreshKits() {
|
||||||
|
UUID challenger = this.challenger();
|
||||||
|
if(challenger == null) return;
|
||||||
|
int challengerPad = this.challengerPad();
|
||||||
|
UUID firstOpponent = this.pads.get(1 - challengerPad).isEmpty() ? null : this.pads.get(1 - challengerPad).getFirst();
|
||||||
|
|
||||||
|
for(UUID player : this.order) {
|
||||||
|
UUID wanted = switch(this.mode) {
|
||||||
|
case OWN -> player;
|
||||||
|
case CHALLENGER -> challenger;
|
||||||
|
case OPPONENT -> player.equals(challenger) && firstOpponent != null ? firstOpponent : player;
|
||||||
|
};
|
||||||
|
if(wanted.equals(this.kits.get(player))) continue;
|
||||||
|
this.kits.put(player, wanted);
|
||||||
|
this.listener.kitAssigned(player, wanted);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public int padOf(UUID player) {
|
public int padOf(UUID player) {
|
||||||
for(int i = 0; i < PADS; i++) {
|
for(int i = 0; i < PADS; i++) {
|
||||||
if(player.equals(this.occupants[i])) return i;
|
if(this.pads.get(i).contains(player)) return i;
|
||||||
}
|
}
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
public @Nullable UUID occupant(int pad) {
|
public List<UUID> players(int pad) {
|
||||||
return this.occupants[pad];
|
return List.copyOf(this.pads.get(pad));
|
||||||
|
}
|
||||||
|
|
||||||
|
public int size(int pad) {
|
||||||
|
return this.sizes[pad];
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isPadFull(int pad) {
|
||||||
|
return !this.order.isEmpty() && this.pads.get(pad).size() >= this.sizes[pad];
|
||||||
}
|
}
|
||||||
|
|
||||||
public @Nullable UUID challenger() {
|
public @Nullable UUID challenger() {
|
||||||
return this.challengerPad == -1 ? null : this.occupants[this.challengerPad];
|
return this.order.isEmpty() ? null : this.order.getFirst();
|
||||||
}
|
|
||||||
|
|
||||||
public @Nullable UUID opponent() {
|
|
||||||
return this.challengerPad == -1 ? null : this.occupants[1 - this.challengerPad];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public int challengerPad() {
|
public int challengerPad() {
|
||||||
return this.challengerPad;
|
UUID challenger = this.challenger();
|
||||||
|
return challenger == null ? -1 : this.padOf(challenger);
|
||||||
}
|
}
|
||||||
|
|
||||||
public FightMode mode() {
|
public FightMode mode() {
|
||||||
@@ -146,7 +200,7 @@ public class PadLobby {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public boolean isFull() {
|
public boolean isFull() {
|
||||||
return this.occupants[0] != null && this.occupants[1] != null;
|
return this.pads.get(0).size() == this.sizes[0] && this.pads.get(1).size() == this.sizes[1];
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isCountingDown() {
|
public boolean isCountingDown() {
|
||||||
@@ -154,7 +208,7 @@ public class PadLobby {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public boolean isIdle() {
|
public boolean isIdle() {
|
||||||
return this.occupants[0] == null && this.occupants[1] == null;
|
return this.order.isEmpty();
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Seconds until the start, rounded up; -1 without countdown. */
|
/** Seconds until the start, rounded up; -1 without countdown. */
|
||||||
|
|||||||
+10
-15
@@ -2,26 +2,21 @@ package eu.mhsl.craftattack.spawn.arena.appliances.arena.logic;
|
|||||||
|
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
import static eu.mhsl.craftattack.spawn.arena.appliances.arena.logic.FightMode.Role.CHALLENGER;
|
import static org.junit.jupiter.api.Assertions.*;
|
||||||
import static eu.mhsl.craftattack.spawn.arena.appliances.arena.logic.FightMode.Role.OPPONENT;
|
|
||||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
|
||||||
|
|
||||||
class FightModeTest {
|
class FightModeTest {
|
||||||
@Test
|
@Test
|
||||||
void ownKeepsEveryonesInventory() {
|
void opponentInventoryOnlyInOneVersusOne() {
|
||||||
assertEquals(CHALLENGER, FightMode.OWN.kitSource(CHALLENGER));
|
assertTrue(FightMode.OPPONENT.isAllowed(1, 1));
|
||||||
assertEquals(OPPONENT, FightMode.OWN.kitSource(OPPONENT));
|
assertFalse(FightMode.OPPONENT.isAllowed(1, 2));
|
||||||
|
assertFalse(FightMode.OPPONENT.isAllowed(3, 1));
|
||||||
|
assertTrue(FightMode.CHALLENGER.isAllowed(4, 3));
|
||||||
|
assertTrue(FightMode.OWN.isAllowed(2, 4));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void challengerKitForBoth() {
|
void namesForTeams() {
|
||||||
assertEquals(CHALLENGER, FightMode.CHALLENGER.kitSource(CHALLENGER));
|
assertEquals("Beide mit meinem", FightMode.CHALLENGER.displayName(false));
|
||||||
assertEquals(CHALLENGER, FightMode.CHALLENGER.kitSource(OPPONENT));
|
assertEquals("Alle mit meinem", FightMode.CHALLENGER.displayName(true));
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
void opponentKitForBoth() {
|
|
||||||
assertEquals(OPPONENT, FightMode.OPPONENT.kitSource(CHALLENGER));
|
|
||||||
assertEquals(OPPONENT, FightMode.OPPONENT.kitSource(OPPONENT));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
+107
-48
@@ -5,7 +5,9 @@ import org.junit.jupiter.api.Test;
|
|||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
import static org.junit.jupiter.api.Assertions.*;
|
import static org.junit.jupiter.api.Assertions.*;
|
||||||
|
|
||||||
@@ -13,22 +15,21 @@ class PadLobbyTest {
|
|||||||
private final UUID a = UUID.randomUUID();
|
private final UUID a = UUID.randomUUID();
|
||||||
private final UUID b = UUID.randomUUID();
|
private final UUID b = UUID.randomUUID();
|
||||||
private final UUID c = UUID.randomUUID();
|
private final UUID c = UUID.randomUUID();
|
||||||
|
private final UUID d = UUID.randomUUID();
|
||||||
|
private final UUID e = UUID.randomUUID();
|
||||||
private final List<String> events = new ArrayList<>();
|
private final List<String> events = new ArrayList<>();
|
||||||
|
private Map<UUID, UUID> startedKits;
|
||||||
private PadLobby lobby;
|
private PadLobby lobby;
|
||||||
|
|
||||||
@BeforeEach
|
@BeforeEach
|
||||||
void setUp() {
|
void setUp() {
|
||||||
this.lobby = new PadLobby(new PadLobby.Listener() {
|
this.lobby = new PadLobby(new PadLobby.Listener() {
|
||||||
public void challengerSelected(UUID challenger) {
|
public void challengerSelected(UUID challenger, boolean canConfigure) {
|
||||||
PadLobbyTest.this.events.add("challenger " + PadLobbyTest.this.name(challenger));
|
PadLobbyTest.this.events.add("challenger " + PadLobbyTest.this.name(challenger) + (canConfigure ? " configurable" : ""));
|
||||||
}
|
}
|
||||||
|
|
||||||
public void kitsApplied(UUID challenger, UUID opponent, FightMode mode) {
|
public void kitAssigned(UUID player, UUID kitOwner) {
|
||||||
PadLobbyTest.this.events.add("kits " + PadLobbyTest.this.name(challenger) + PadLobbyTest.this.name(opponent) + " " + mode);
|
PadLobbyTest.this.events.add("kit " + PadLobbyTest.this.name(player) + "<-" + PadLobbyTest.this.name(kitOwner));
|
||||||
}
|
|
||||||
|
|
||||||
public void kitReverted(UUID player) {
|
|
||||||
PadLobbyTest.this.events.add("revert " + PadLobbyTest.this.name(player));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void countdown(int secondsLeft) {
|
public void countdown(int secondsLeft) {
|
||||||
@@ -39,8 +40,10 @@ class PadLobbyTest {
|
|||||||
PadLobbyTest.this.events.add("cancelled");
|
PadLobbyTest.this.events.add("cancelled");
|
||||||
}
|
}
|
||||||
|
|
||||||
public void start(UUID challenger, UUID opponent, FightMode mode, int challengerPad) {
|
public void start(List<List<UUID>> teams, UUID challenger, FightMode mode, Map<UUID, UUID> kits) {
|
||||||
PadLobbyTest.this.events.add("start " + PadLobbyTest.this.name(challenger) + PadLobbyTest.this.name(opponent) + " " + mode + " " + challengerPad);
|
PadLobbyTest.this.startedKits = kits;
|
||||||
|
PadLobbyTest.this.events.add("start " + PadLobbyTest.this.names(teams.get(0)) + " vs " + PadLobbyTest.this.names(teams.get(1))
|
||||||
|
+ " by " + PadLobbyTest.this.name(challenger) + " " + mode);
|
||||||
}
|
}
|
||||||
}, 2);
|
}, 2);
|
||||||
}
|
}
|
||||||
@@ -48,50 +51,99 @@ class PadLobbyTest {
|
|||||||
private String name(UUID uuid) {
|
private String name(UUID uuid) {
|
||||||
if(uuid.equals(this.a)) return "a";
|
if(uuid.equals(this.a)) return "a";
|
||||||
if(uuid.equals(this.b)) return "b";
|
if(uuid.equals(this.b)) return "b";
|
||||||
return "c";
|
if(uuid.equals(this.c)) return "c";
|
||||||
|
if(uuid.equals(this.d)) return "d";
|
||||||
|
return "e";
|
||||||
|
}
|
||||||
|
|
||||||
|
private String names(List<UUID> uuids) {
|
||||||
|
return uuids.stream().map(this::name).collect(Collectors.joining());
|
||||||
|
}
|
||||||
|
|
||||||
|
private void run(int ticks) {
|
||||||
|
for(int i = 0; i < ticks; i++) this.lobby.tick(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void firstOnPadBecomesChallenger() {
|
void firstOnPadBecomesChallengerAndMayConfigure() {
|
||||||
assertTrue(this.lobby.enter(1, this.a));
|
assertTrue(this.lobby.enter(1, this.a));
|
||||||
assertEquals(List.of("challenger a"), this.events);
|
assertEquals(List.of("challenger a configurable"), this.events);
|
||||||
assertEquals(this.a, this.lobby.challenger());
|
assertEquals(this.a, this.lobby.challenger());
|
||||||
assertNull(this.lobby.opponent());
|
assertTrue(this.lobby.canConfigure(this.a));
|
||||||
assertFalse(this.lobby.isCountingDown());
|
assertFalse(this.lobby.isCountingDown());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void takenPadIsRefused() {
|
void oneVersusOneStartsAfterCountdown() {
|
||||||
this.lobby.enter(0, this.a);
|
this.lobby.enter(0, this.a);
|
||||||
assertFalse(this.lobby.enter(0, this.b));
|
assertTrue(this.lobby.configure(this.a, 1, 1, FightMode.CHALLENGER));
|
||||||
assertFalse(this.lobby.enter(1, this.a), "a player can only take one pad");
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
void secondPlayerAppliesKitsAndStartsAfterCountdown() {
|
|
||||||
this.lobby.enter(0, this.a);
|
|
||||||
assertTrue(this.lobby.setMode(this.a, FightMode.CHALLENGER));
|
|
||||||
this.lobby.enter(1, this.b);
|
this.lobby.enter(1, this.b);
|
||||||
assertFalse(this.lobby.setMode(this.a, FightMode.OWN), "mode is fixed once the opponent stands there");
|
assertFalse(this.lobby.configure(this.a, 1, 1, FightMode.OWN), "options are fixed once someone else joined");
|
||||||
|
this.run(41);
|
||||||
for(int i = 0; i < 41; i++) this.lobby.tick(true);
|
|
||||||
|
|
||||||
assertEquals(List.of(
|
assertEquals(List.of(
|
||||||
"challenger a",
|
"challenger a configurable",
|
||||||
"kits ab CHALLENGER",
|
"kit b<-a",
|
||||||
"countdown 2",
|
"countdown 2",
|
||||||
"countdown 1",
|
"countdown 1",
|
||||||
"start ab CHALLENGER 0"
|
"start a vs b by a CHALLENGER"
|
||||||
), this.events);
|
), this.events);
|
||||||
|
assertEquals(Map.of(this.a, this.a, this.b, this.a), this.startedKits);
|
||||||
assertTrue(this.lobby.isIdle());
|
assertTrue(this.lobby.isIdle());
|
||||||
assertNull(this.lobby.challenger());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void onlyChallengerMayChangeMode() {
|
void fullPadIsRefusedAndPlayerOnlyTakesOnePad() {
|
||||||
this.lobby.enter(0, this.a);
|
this.lobby.enter(0, this.a);
|
||||||
assertFalse(this.lobby.setMode(this.b, FightMode.OPPONENT));
|
this.lobby.enter(1, this.b);
|
||||||
assertEquals(FightMode.OWN, this.lobby.mode());
|
assertFalse(this.lobby.enter(1, this.c), "1v1: the blue pad is full");
|
||||||
|
assertFalse(this.lobby.enter(0, this.b), "a player can only stand on one pad");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void unevenTeamsStartWhenBothPadsAreExactlyFull() {
|
||||||
|
this.lobby.enter(0, this.a);
|
||||||
|
assertTrue(this.lobby.configure(this.a, 2, 3, FightMode.OWN));
|
||||||
|
assertTrue(this.lobby.enter(0, this.b));
|
||||||
|
assertFalse(this.lobby.enter(0, this.c), "red is full with 2");
|
||||||
|
this.lobby.enter(1, this.c);
|
||||||
|
this.lobby.enter(1, this.d);
|
||||||
|
assertFalse(this.lobby.isCountingDown());
|
||||||
|
this.lobby.enter(1, this.e);
|
||||||
|
assertTrue(this.lobby.isCountingDown());
|
||||||
|
this.run(41);
|
||||||
|
assertEquals("start ab vs cde by a OWN", this.events.getLast());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void challengerKitGoesToEveryone() {
|
||||||
|
this.lobby.enter(0, this.a);
|
||||||
|
this.lobby.configure(this.a, 2, 2, FightMode.CHALLENGER);
|
||||||
|
this.lobby.enter(0, this.b);
|
||||||
|
this.lobby.enter(1, this.c);
|
||||||
|
this.lobby.enter(1, this.d);
|
||||||
|
assertEquals(List.of("challenger a configurable", "kit b<-a", "kit c<-a", "kit d<-a"), this.events);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void opponentModeOnlyForOneVersusOne() {
|
||||||
|
this.lobby.enter(0, this.a);
|
||||||
|
assertFalse(this.lobby.configure(this.a, 2, 1, FightMode.OPPONENT));
|
||||||
|
assertTrue(this.lobby.configure(this.a, 1, 1, FightMode.OPPONENT));
|
||||||
|
this.lobby.enter(1, this.b);
|
||||||
|
assertEquals("kit a<-b", this.events.getLast());
|
||||||
|
|
||||||
|
this.lobby.leave(this.b);
|
||||||
|
assertTrue(this.events.contains("kit a<-a"), "challenger gets the own inventory back");
|
||||||
|
assertTrue(this.events.contains("cancelled"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void onlyChallengerMayConfigure() {
|
||||||
|
this.lobby.enter(0, this.a);
|
||||||
|
assertFalse(this.lobby.configure(this.b, 2, 2, FightMode.OWN));
|
||||||
|
assertFalse(this.lobby.configure(this.a, 5, 1, FightMode.OWN));
|
||||||
|
assertFalse(this.lobby.configure(this.a, 0, 1, FightMode.OWN));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -101,42 +153,49 @@ class PadLobbyTest {
|
|||||||
for(int i = 0; i < 100; i++) this.lobby.tick(false);
|
for(int i = 0; i < 100; i++) this.lobby.tick(false);
|
||||||
assertFalse(this.events.contains("countdown 2"));
|
assertFalse(this.events.contains("countdown 2"));
|
||||||
assertEquals(2, this.lobby.secondsLeft());
|
assertEquals(2, this.lobby.secondsLeft());
|
||||||
|
this.run(41);
|
||||||
for(int i = 0; i < 41; i++) this.lobby.tick(true);
|
assertEquals("start a vs b by a OWN", this.events.getLast());
|
||||||
assertTrue(this.events.contains("start ab OWN 0"));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void opponentLeavingCancelsAndRevertsBoth() {
|
void leavingCancelsCountdownAndRevertsKit() {
|
||||||
this.lobby.enter(0, this.a);
|
this.lobby.enter(0, this.a);
|
||||||
this.lobby.setMode(this.a, FightMode.OPPONENT);
|
this.lobby.configure(this.a, 1, 1, FightMode.CHALLENGER);
|
||||||
this.lobby.enter(1, this.b);
|
this.lobby.enter(1, this.b);
|
||||||
this.events.clear();
|
this.events.clear();
|
||||||
|
|
||||||
this.lobby.leave(this.b);
|
this.lobby.leave(this.b);
|
||||||
|
|
||||||
assertEquals(List.of("revert b", "revert a", "cancelled"), this.events);
|
assertEquals(List.of("kit b<-b", "cancelled"), this.events);
|
||||||
assertEquals(this.a, this.lobby.challenger());
|
assertEquals(this.a, this.lobby.challenger());
|
||||||
assertEquals(FightMode.OPPONENT, this.lobby.mode(), "challenger keeps the chosen mode");
|
assertEquals(FightMode.CHALLENGER, this.lobby.mode());
|
||||||
assertFalse(this.lobby.isCountingDown());
|
assertFalse(this.lobby.isCountingDown());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void challengerLeavingHandsOverRole() {
|
void longestWaitingTakesOverAndKeepsTeamSizes() {
|
||||||
this.lobby.enter(0, this.a);
|
this.lobby.enter(0, this.a);
|
||||||
this.lobby.setMode(this.a, FightMode.CHALLENGER);
|
this.lobby.configure(this.a, 2, 2, FightMode.CHALLENGER);
|
||||||
this.lobby.enter(1, this.b);
|
this.lobby.enter(1, this.b);
|
||||||
|
this.lobby.enter(0, this.c);
|
||||||
this.events.clear();
|
this.events.clear();
|
||||||
|
|
||||||
this.lobby.leave(this.a);
|
this.lobby.leave(this.a);
|
||||||
|
|
||||||
assertEquals(List.of("revert a", "revert b", "cancelled", "challenger b"), this.events);
|
assertEquals(List.of("kit a<-a", "kit b<-b", "kit c<-b", "challenger b"), this.events);
|
||||||
assertEquals(this.b, this.lobby.challenger());
|
assertEquals(this.b, this.lobby.challenger());
|
||||||
assertEquals(1, this.lobby.challengerPad());
|
assertFalse(this.lobby.canConfigure(this.b), "others are still there");
|
||||||
assertEquals(FightMode.OWN, this.lobby.mode());
|
assertEquals(2, this.lobby.size(0));
|
||||||
|
assertEquals(2, this.lobby.size(1));
|
||||||
|
}
|
||||||
|
|
||||||
this.lobby.enter(0, this.c);
|
@Test
|
||||||
assertEquals("kits bc OWN", this.events.getLast());
|
void aloneAgainMeansConfigurableAgain() {
|
||||||
|
this.lobby.enter(0, this.a);
|
||||||
|
this.lobby.enter(1, this.b);
|
||||||
|
this.lobby.leave(this.a);
|
||||||
|
assertEquals("challenger b configurable", this.events.getLast());
|
||||||
|
assertTrue(this.lobby.canConfigure(this.b));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -146,6 +205,6 @@ class PadLobbyTest {
|
|||||||
assertTrue(this.lobby.isIdle());
|
assertTrue(this.lobby.isIdle());
|
||||||
assertNull(this.lobby.challenger());
|
assertNull(this.lobby.challenger());
|
||||||
this.lobby.leave(this.b);
|
this.lobby.leave(this.b);
|
||||||
assertEquals(List.of("challenger a", "revert a"), this.events);
|
assertEquals(List.of("challenger a configurable", "kit a<-a"), this.events);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Reference in new issue
Block a user