ProjectAres/PGM/src/main/java/tc/oc/pgm/blitz/BlitzMatchModuleImpl.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);
}
}