284 lines
9.4 KiB
Java
284 lines
9.4 KiB
Java
package tc.oc.pgm.blitz;
|
|
|
|
import com.google.common.collect.ImmutableSet;
|
|
import net.md_5.bungee.api.ChatColor;
|
|
import net.md_5.bungee.api.chat.TranslatableComponent;
|
|
import org.bukkit.Particle;
|
|
import org.bukkit.World;
|
|
import org.bukkit.event.EventException;
|
|
import org.bukkit.event.EventHandler;
|
|
import org.bukkit.event.EventPriority;
|
|
import org.bukkit.event.Listener;
|
|
import tc.oc.api.docs.PlayerId;
|
|
import tc.oc.commons.core.chat.Component;
|
|
import tc.oc.commons.core.chat.Components;
|
|
import tc.oc.pgm.events.ListenerScope;
|
|
import tc.oc.pgm.events.MatchPlayerDeathEvent;
|
|
import tc.oc.pgm.events.PartyAddEvent;
|
|
import tc.oc.pgm.events.PlayerChangePartyEvent;
|
|
import tc.oc.pgm.join.JoinDenied;
|
|
import tc.oc.pgm.join.JoinHandler;
|
|
import tc.oc.pgm.join.JoinMatchModule;
|
|
import tc.oc.pgm.join.JoinMethod;
|
|
import tc.oc.pgm.join.JoinRequest;
|
|
import tc.oc.pgm.join.JoinResult;
|
|
import tc.oc.pgm.listeners.MatchAnnouncer;
|
|
import tc.oc.pgm.match.Competitor;
|
|
import tc.oc.pgm.match.Match;
|
|
import tc.oc.pgm.match.MatchModule;
|
|
import tc.oc.pgm.match.MatchPlayer;
|
|
import tc.oc.pgm.match.MatchScope;
|
|
import tc.oc.pgm.spawns.events.ParticipantReleaseEvent;
|
|
import tc.oc.pgm.teams.TeamMatchModule;
|
|
import tc.oc.pgm.victory.VictoryMatchModule;
|
|
|
|
import javax.annotation.Nullable;
|
|
import javax.inject.Inject;
|
|
import java.util.EnumSet;
|
|
import java.util.HashSet;
|
|
import java.util.Optional;
|
|
import java.util.Set;
|
|
|
|
@ListenerScope(MatchScope.LOADED)
|
|
public class BlitzMatchModuleImpl extends MatchModule implements BlitzMatchModule, Listener, JoinHandler {
|
|
|
|
private final Match match;
|
|
private final World world;
|
|
private final JoinMatchModule join;
|
|
private final VictoryMatchModule victory;
|
|
private final Optional<TeamMatchModule> teams;
|
|
private BlitzProperties properties;
|
|
|
|
private final Set<Lives> lives = new HashSet<>();
|
|
private final Set<PlayerId> eliminated = new HashSet<>();
|
|
private boolean activated = false;
|
|
private int competitors = 0;
|
|
|
|
@Inject BlitzMatchModuleImpl(Match match, World world, JoinMatchModule join, VictoryMatchModule victory, Optional<TeamMatchModule> teams, BlitzProperties properties) {
|
|
this.match = match;
|
|
this.world = world;
|
|
this.join = join;
|
|
this.victory = victory;
|
|
this.teams = teams;
|
|
this.properties = properties;
|
|
}
|
|
|
|
private void preload() {
|
|
if(!properties().empty()) {
|
|
activate();
|
|
}
|
|
}
|
|
|
|
protected int competitors() {
|
|
return competitors;
|
|
}
|
|
|
|
protected int remainingCompetitors() {
|
|
return (int) match.getCompetitors()
|
|
.stream()
|
|
.filter(c -> !c.getPlayers().isEmpty())
|
|
.count();
|
|
}
|
|
|
|
private void updateCompetitors() {
|
|
competitors = Math.max(competitors(), remainingCompetitors());
|
|
}
|
|
|
|
private void setup(MatchPlayer player, boolean force) {
|
|
if(force) {
|
|
eliminated.remove(player.getPlayerId());
|
|
lives.removeIf(life -> life.owner(player.getPlayerId()));
|
|
}
|
|
switch(properties().type) {
|
|
case INDIVIDUAL:
|
|
properties().individuals.forEach((filter, count) -> {
|
|
if(filter.allows(player)) {
|
|
lives.add(new LivesIndividual(player, count));
|
|
}
|
|
}); break;
|
|
case TEAM:
|
|
properties().teams.forEach((teamFactory, count) -> {
|
|
if(teams.get().team(teamFactory).equals(player.getCompetitor())) {
|
|
lives.add(new LivesTeam(player.getCompetitor(), count));
|
|
}
|
|
}); break;
|
|
}
|
|
}
|
|
|
|
private void showLives(MatchPlayer player, boolean release, boolean activate) {
|
|
final Optional<Lives> lives = lives(player);
|
|
if(activated() && lives.isPresent()) {
|
|
player.showTitle(
|
|
release ? MatchAnnouncer.GO
|
|
: activate ? new Component(new TranslatableComponent("blitz.activated"), ChatColor.GREEN)
|
|
: Components.blank(),
|
|
lives.get().remaining(),
|
|
0, 60, 20
|
|
);
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public boolean activated() {
|
|
return activated;
|
|
}
|
|
|
|
@Override
|
|
public void activate(@Nullable BlitzProperties newProperties) {
|
|
if(!activated) {
|
|
activated = true;
|
|
if(newProperties != null) {
|
|
properties = newProperties;
|
|
}
|
|
load();
|
|
if(match.hasStarted()) {
|
|
enable();
|
|
}
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public void deactivate() {
|
|
activated = false;
|
|
lives.clear();
|
|
eliminated.clear();
|
|
}
|
|
|
|
@Override
|
|
public void load() {
|
|
if(activated()) {
|
|
join.registerHandler(this);
|
|
victory.setVictoryCondition(new BlitzVictoryCondition(this));
|
|
} else {
|
|
preload();
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public void enable() {
|
|
if(activated()) {
|
|
updateCompetitors();
|
|
match.participants().forEach(player -> {
|
|
setup(player, false);
|
|
if(match.hasStarted()) {
|
|
showLives(player, false, true);
|
|
}
|
|
});
|
|
match.callEvent(new BlitzEvent(match, this));
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public BlitzProperties properties() {
|
|
return properties;
|
|
}
|
|
|
|
@Override
|
|
public boolean increment(MatchPlayer player, int lives, boolean notify, boolean immediate) {
|
|
if(!eliminated(player)) {
|
|
return lives(player).map(life -> {
|
|
life.add(player.getPlayerId(), lives);
|
|
if(notify) {
|
|
player.showTitle(Components.blank(), life.change(lives), 0, 40, 10);
|
|
}
|
|
if(life.empty() && immediate) {
|
|
eliminate(player);
|
|
return true;
|
|
}
|
|
return false;
|
|
}).orElse(false);
|
|
}
|
|
return true;
|
|
}
|
|
|
|
@Override
|
|
public int livesCount(MatchPlayer player) {
|
|
return lives(player).map(Lives::current)
|
|
.orElseThrow(() -> new IllegalStateException(player + " has no lives present to count"));
|
|
}
|
|
|
|
@Override
|
|
public Optional<Lives> lives(MatchPlayer player) {
|
|
return lives.stream()
|
|
.filter(lives -> lives.applicableTo(player.getPlayerId()))
|
|
.findFirst();
|
|
}
|
|
|
|
@Override
|
|
public Optional<Lives> lives(Competitor competitor) {
|
|
return lives.stream()
|
|
.filter(lives -> lives.type().equals(Lives.Type.TEAM) && lives.competitor().equals(competitor))
|
|
.findFirst();
|
|
}
|
|
|
|
@Override
|
|
public boolean eliminated(MatchPlayer player) {
|
|
return eliminated.contains(player.getPlayerId());
|
|
}
|
|
|
|
@Override
|
|
public void eliminate(MatchPlayer player) {
|
|
if(activated() && !eliminated(player)) {
|
|
eliminated.add(player.getPlayerId());
|
|
// Process eliminations within the same tick simultaneously, so that ties are properly detected
|
|
match.getScheduler(MatchScope.RUNNING).debounceTask(() -> {
|
|
ImmutableSet.copyOf(getMatch().getParticipatingPlayers())
|
|
.stream()
|
|
.filter(this::eliminated)
|
|
.forEach(participating -> {
|
|
match.setPlayerParty(participating, match.getDefaultParty());
|
|
world.spawnParticle(Particle.SMOKE_LARGE, player.getLocation(), 5);
|
|
});
|
|
victory.invalidateAndCheckEnd();
|
|
});
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public JoinResult queryJoin(MatchPlayer joining, JoinRequest request) {
|
|
if(activated() &&
|
|
match.hasStarted() &&
|
|
!EnumSet.of(JoinMethod.FORCE, JoinMethod.REMOTE).contains(request.method())) {
|
|
// This message should NOT look like an error, because remotely joining players will see it often.
|
|
// It also should not say "Blitz" because not all maps that use this module want to be labelled "Blitz".
|
|
return JoinDenied.friendly("command.gameplay.join.matchStarted");
|
|
}
|
|
return null;
|
|
}
|
|
|
|
@EventHandler
|
|
public void onPartyAdd(PartyAddEvent event) {
|
|
if(event.getParty() instanceof Competitor) {
|
|
updateCompetitors();
|
|
}
|
|
}
|
|
|
|
@EventHandler(priority = EventPriority.HIGH, ignoreCancelled = true)
|
|
public void onPartyChange(PlayerChangePartyEvent event) throws EventException {
|
|
final MatchPlayer player = event.getPlayer();
|
|
if(event.getNewParty() == null) {
|
|
if(event.getOldParty() instanceof Competitor && match.hasStarted() && !increment(player, -1, false, true)) {
|
|
eliminate(player);
|
|
}
|
|
} else if(event.getNewParty() instanceof Competitor) {
|
|
event.yield();
|
|
setup(player, true);
|
|
updateCompetitors();
|
|
}
|
|
}
|
|
|
|
@EventHandler(priority = EventPriority.HIGH, ignoreCancelled = true)
|
|
public void onDeath(MatchPlayerDeathEvent event) {
|
|
final MatchPlayer player = event.getVictim();
|
|
if(player.competitor().isPresent()) {
|
|
increment(player, -1, false, true);
|
|
}
|
|
}
|
|
|
|
@EventHandler
|
|
public void onRelease(ParticipantReleaseEvent event) {
|
|
showLives(event.getPlayer(), event.wasFrozen(), false);
|
|
}
|
|
|
|
}
|