ProjectAres/PGM/src/main/java/tc/oc/pgm/goals/TouchableGoal.java

285 lines
9.9 KiB
Java

package tc.oc.pgm.goals;
import java.util.Collection;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;
import javax.annotation.Nullable;
import com.google.common.base.Function;
import com.google.common.collect.Collections2;
import com.google.common.collect.ImmutableSet;
import net.md_5.bungee.api.ChatColor;
import net.md_5.bungee.api.chat.BaseComponent;
import net.md_5.bungee.api.chat.TranslatableComponent;
import org.bukkit.Bukkit;
import org.bukkit.event.EventHandler;
import org.bukkit.event.EventPriority;
import org.bukkit.event.Listener;
import tc.oc.api.docs.virtual.MatchDoc;
import tc.oc.commons.core.chat.Component;
import tc.oc.commons.bukkit.chat.ComponentRenderers;
import tc.oc.pgm.events.ListenerScope;
import tc.oc.pgm.match.Competitor;
import tc.oc.pgm.match.Match;
import tc.oc.pgm.match.MatchPlayer;
import tc.oc.pgm.match.MatchScope;
import tc.oc.pgm.match.ParticipantState;
import tc.oc.pgm.match.Party;
import tc.oc.pgm.events.CompetitorRemoveEvent;
import tc.oc.pgm.goals.events.GoalCompleteEvent;
import tc.oc.pgm.goals.events.GoalTouchEvent;
import tc.oc.pgm.spawns.events.ParticipantDespawnEvent;
/**
* A {@link Goal} that may be 'touched' by players, meaning the player has
* made some tangible progress in completing the goal.
*/
@ListenerScope(MatchScope.RUNNING)
public abstract class TouchableGoal<T extends ProximityGoalDefinition> extends ProximityGoal<T> implements Listener {
public static final ChatColor COLOR_TOUCHED = ChatColor.YELLOW;
public static final String SYMBOL_TOUCHED = "\u2733"; // ✳
protected boolean touched;
protected final Set<Competitor> touchingCompetitors = new HashSet<>();
protected final Set<ParticipantState> touchingPlayers = new HashSet<>();
protected final Set<ParticipantState> recentTouchingPlayers = new HashSet<>();
public TouchableGoal(T definition, Match match) {
super(definition, match);
match.registerEvents(this);
}
/**
* Should touches NOT be credited until the goal is completed?
*/
public boolean getDeferTouches() {
return false;
}
/**
* Gets a formatted message designed to be broadcast when a player touches the goal.
*
* @param toucher The player
* @param self is the message for the toucher?
*/
public abstract BaseComponent getTouchMessage(@Nullable ParticipantState toucher, boolean self);
@Override
public net.md_5.bungee.api.ChatColor renderProximityColor(Competitor team, Party viewer) {
return hasTouched(team) ? net.md_5.bungee.api.ChatColor.YELLOW : super.renderProximityColor(team, viewer);
}
@Override
public ChatColor renderSidebarStatusColor(@Nullable Competitor competitor, Party viewer) {
return shouldShowTouched(competitor, viewer) ? COLOR_TOUCHED
: super.renderSidebarStatusColor(competitor, viewer);
}
@Override
public String renderSidebarStatusText(@Nullable Competitor competitor, Party viewer) {
return shouldShowTouched(competitor, viewer) ? SYMBOL_TOUCHED
: super.renderSidebarStatusText(competitor, viewer);
}
public boolean isTouched() {
return touched;
}
/** Gets whether or not the specified team has touched the goal since the last reset. */
public boolean hasTouched(Competitor team) {
return touchingCompetitors.contains(team);
}
public boolean hasTouched(ParticipantState player) {
return touchingPlayers.contains(player);
}
public ImmutableSet<ParticipantState> getTouchingPlayers() {
return ImmutableSet.copyOf(touchingPlayers);
}
/** Gets whether or not the specified player has recently (in their current lifetime) touched the goal. */
public boolean hasTouchedRecently(final ParticipantState player) {
return recentTouchingPlayers.contains(player);
}
/**
* Gets whether or not the specified player touching the goal has any significance at this moment.
*/
public boolean canTouch(final ParticipantState player) {
return canComplete(player.getParty()) &&
!isCompleted(player.getParty()) &&
!hasTouchedRecently(player);
}
public void touch(final @Nullable ParticipantState toucher) {
// TODO: support playerless touches (deduce which team to give the touch to based on objective owner etc)
if(toucher == null) return;
touched = true;
GoalTouchEvent event;
if(toucher == null) {
event = new GoalTouchEvent(this, getMatch().getClock().now().instant);
} else {
if(!canTouch(toucher)) return;
boolean firstForCompetitor = touchingCompetitors.add(toucher.getParty());
boolean firstForPlayer = touchingPlayers.add(toucher);
boolean firstForPlayerLife = recentTouchingPlayers.add(toucher);
event = new GoalTouchEvent(this,
toucher.getParty(), firstForCompetitor,
toucher, firstForPlayer, firstForPlayerLife,
getMatch().getClock().now().instant);
}
getMatch().callEvent(event);
sendTouchMessage(toucher, !event.getCancelToucherMessage());
playTouchEffects(toucher);
}
public void resetTouches() {
touched = false;
touchingCompetitors.clear();
touchingPlayers.clear();
recentTouchingPlayers.clear();
}
public void resetTouches(Competitor team) {
if(touchingCompetitors.remove(team)) {
for(Iterator<ParticipantState> iterator = touchingPlayers.iterator(); iterator.hasNext(); ) {
if(iterator.next().getParty() == team) iterator.remove();;
}
for(Iterator<ParticipantState> iterator = recentTouchingPlayers.iterator(); iterator.hasNext(); ) {
if(iterator.next().getParty() == team) iterator.remove();;
}
}
}
@Override
public @Nullable ProximityMetric getProximityMetric(Competitor team) {
if(hasTouched(team)) {
return getDefinition().getPostTouchMetric();
} else {
return super.getProximityMetric(team);
}
}
public boolean showEnemyTouches() {
return false;
}
public boolean shouldShowTouched(@Nullable Competitor team, Party viewer) {
return team != null &&
!isCompleted(team) &&
hasTouched(team) &&
(team == viewer || showEnemyTouches() || viewer.isObservingType() || getMatch().isFinished());
}
protected void sendTouchMessage(@Nullable ParticipantState toucher, boolean includeToucher) {
if(!isVisible()) return;
BaseComponent message = getTouchMessage(toucher, false);
ComponentRenderers.send(Bukkit.getConsoleSender(), message);
if(!showEnemyTouches()) {
message = new Component(toucher.getParty().getChatPrefix(), message);
}
for(MatchPlayer viewer : getMatch().getPlayers()) {
if(shouldShowTouched(toucher.getParty(), viewer.getParty()) && (toucher == null || !toucher.isPlayer(viewer))) {
viewer.sendMessage(message);
}
}
if(toucher != null) {
if(includeToucher) {
toucher.sendMessage(getTouchMessage(toucher, true));
}
if(getDeferTouches()) {
toucher.sendMessage(new TranslatableComponent("match.touch.destroyable.deferredNotice"));
}
}
}
protected void playTouchEffects(@Nullable ParticipantState toucher) {
if(toucher == null || !isVisible()) return;
MatchPlayer onlineToucher = toucher.getMatchPlayer();
if(onlineToucher == null) return;
onlineToucher.playSparks();
}
@EventHandler(priority = EventPriority.MONITOR)
public void onPlayerDeath(ParticipantDespawnEvent event) {
ParticipantState victim = event.getPlayer().getParticipantState();
if(victim != null) recentTouchingPlayers.remove(victim);
}
@EventHandler(priority = EventPriority.MONITOR)
public void onCompetitorRemove(CompetitorRemoveEvent event) {
resetTouches(event.getCompetitor());
}
@EventHandler(priority = EventPriority.MONITOR)
public void onComplete(GoalCompleteEvent event) {
if(this == event.getGoal()) {
resetTouches();
}
}
@Override
public MatchDoc.TouchableGoal getDocument() {
return new Document();
}
public class Document extends OwnedGoal.Document implements MatchDoc.TouchableGoal {
@Override
public Collection<? extends MatchDoc.TouchableGoal.Proximity> proximities() {
return Collections2.transform(
getMatch().getCompetitors(),
new Function<Competitor, MatchDoc.TouchableGoal.Proximity>() {
@Override
public MatchDoc.TouchableGoal.Proximity apply(Competitor competitor) {
return new Proximity(competitor);
}
}
);
}
public class Proximity implements MatchDoc.TouchableGoal.Proximity {
private final Competitor competitor;
@Override
public String _id() {
return competitor.getId();
}
public Proximity(Competitor competitor) {
this.competitor = competitor;
}
@Override
public boolean touched() {
return hasTouched(competitor);
}
@Override
public Metric metric() {
final ProximityMetric metric = getProximityMetric(competitor);
return metric == null ? null : metric.apiValue();
}
@Override
public double distance() {
return getMinimumDistance(competitor);
}
}
}
}