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

225 lines
8.3 KiB
Java

package tc.oc.pgm.goals;
import java.util.Map;
import javax.annotation.Nullable;
import com.google.common.collect.ImmutableSet;
import net.md_5.bungee.api.ChatColor;
import org.bukkit.Location;
import org.bukkit.block.BlockState;
import org.bukkit.event.EventHandler;
import org.bukkit.event.EventPriority;
import org.bukkit.event.Listener;
import tc.oc.commons.bukkit.util.BlockUtils;
import tc.oc.commons.core.chat.ChatUtils;
import tc.oc.commons.core.localization.Locales;
import tc.oc.commons.core.util.DefaultMapAdapter;
import tc.oc.pgm.events.ListenerScope;
import tc.oc.pgm.match.Competitor;
import tc.oc.pgm.Config;
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.commons.bukkit.event.CoarsePlayerMoveEvent;
import tc.oc.pgm.events.CompetitorRemoveEvent;
import tc.oc.pgm.events.MatchPlayerDeathEvent;
import tc.oc.pgm.events.ParticipantBlockTransformEvent;
import tc.oc.pgm.goals.events.GoalCompleteEvent;
import tc.oc.pgm.goals.events.GoalProximityChangeEvent;
import tc.oc.pgm.goals.events.GoalTouchEvent;
@ListenerScope(MatchScope.RUNNING)
public abstract class ProximityGoal<T extends ProximityGoalDefinition> extends OwnedGoal<T> implements Listener {
private final Map<Competitor, Integer> proximity = new DefaultMapAdapter<>(Integer.MAX_VALUE);
public ProximityGoal(T definition, Match match) {
super(definition, match);
match.registerEvents(this);
}
/**
* Get the locations from which proximity can be measured relative to.
* The shortest measurement will be used.
*/
public abstract Iterable<Location> getProximityLocations(ParticipantState player);
public @Nullable ProximityMetric getProximityMetric(Competitor team) {
return getDefinition().getPreTouchMetric();
}
public @Nullable ProximityMetric.Type getProximityMetricType(Competitor team) {
ProximityMetric metric = getProximityMetric(team);
return metric == null ? null : metric.type;
}
/**
* Is proximity relevant at the present moment for the given team?
* That is, can it be measured and affect the outcome of te match?
*/
public boolean isProximityRelevant(Competitor team) {
return canComplete(team) && !isCompleted() && getProximityMetric(team) != null;
}
protected boolean canPlayerUpdateProximity(ParticipantState player) {
return canComplete(player.getParty());
}
protected boolean canBlockUpdateProximity(BlockState oldState, BlockState newState) {
return true;
}
private static double distanceFromDistanceSquared(int squared) {
return squared == Integer.MAX_VALUE ? Double.POSITIVE_INFINITY : Math.sqrt(squared);
}
public int getProximity(Competitor team) {
return this.proximity.get(team);
}
/**
* Get the minimum distance the given team has been from the objective at
* any time during the match (which is +Inf at the start of the match).
* The given metric determines exactly how this is measured.
*/
public double getMinimumDistance(Competitor team) {
return distanceFromDistanceSquared(this.getProximity(team));
}
public void resetProximity(Competitor team) {
Integer oldProximity = proximity.remove(team);
if(oldProximity != null) {
getMatch().callEvent(new GoalProximityChangeEvent(this, team, null, distanceFromDistanceSquared(oldProximity), Double.POSITIVE_INFINITY));
}
}
public void resetProximity() {
for(Competitor team : ImmutableSet.copyOf(proximity.keySet())) {
resetProximity(team);
}
}
public int getProximityFrom(ParticipantState player, Location location) {
if(Double.isInfinite(location.lengthSquared())) return Integer.MAX_VALUE;
ProximityMetric metric = getProximityMetric(player.getParty());
if(metric == null) return Integer.MAX_VALUE;
int minimumDistance = Integer.MAX_VALUE;
for(Location v : getProximityLocations(player)) {
// If either point is at infinity, the distance is infinite
if(Double.isInfinite(v.lengthSquared())) continue;
int dx = location.getBlockX() - v.getBlockX();
int dy = location.getBlockY() - v.getBlockY();
int dz = location.getBlockZ() - v.getBlockZ();
// Note: distances stay squared as long as possible
int distance;
if(metric.horizontal) {
distance = dx*dx + dz*dz;
} else {
distance = dx*dx + dy*dy + dz*dz;
}
if(distance < minimumDistance) {
minimumDistance = distance;
}
}
return minimumDistance;
}
public boolean updateProximity(ParticipantState player, Location location) {
if(isProximityRelevant(player.getParty()) && canPlayerUpdateProximity(player)) {
int oldProximity = proximity.get(player.getParty());
int newProximity = getProximityFrom(player, location);
if(newProximity < oldProximity) {
proximity.put(player.getParty(), newProximity);
getMatch().callEvent(
new GoalProximityChangeEvent(this, player.getParty(), location,
distanceFromDistanceSquared(oldProximity),
distanceFromDistanceSquared(newProximity))
);
return true;
}
}
return false;
}
public boolean shouldShowProximity(@Nullable Competitor team, Party viewer) {
return team != null &&
Config.Scoreboard.showProximity() &&
isProximityRelevant(team) &&
(viewer == team || viewer.isObservingType());
}
public ChatColor renderProximityColor(Competitor team, Party viewer) {
return ChatColor.GRAY;
}
public String renderProximity(@Nullable Competitor team, Party viewer) {
if(!shouldShowProximity(team, viewer)) return "";
String text;
double distance = this.getMinimumDistance(team);
if(distance == Double.POSITIVE_INFINITY) {
text = "\u221e"; // ∞
} else {
text = ChatUtils.tiny(String.format(Locales.DEFAULT_LOCALE, "%.1f", distance));
}
return renderProximityColor(team, viewer) + text;
}
@EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
private void onPlayerMove(CoarsePlayerMoveEvent event) {
MatchPlayer player = getMatch().getParticipant(event.getPlayer());
if(player != null &&
getProximityMetricType(player.getCompetitor()) == ProximityMetric.Type.CLOSEST_PLAYER) {
updateProximity(player.getParticipantState(), event.getBlockTo());
}
}
@EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
private void onPlayerPlaceBlock(ParticipantBlockTransformEvent event) {
if(getProximityMetricType(event.getPlayerState().getParty()) == ProximityMetric.Type.CLOSEST_BLOCK &&
canBlockUpdateProximity(event.getOldState(), event.getNewState())) {
updateProximity(event.getPlayerState(), BlockUtils.center(event.getNewState()));
}
}
@EventHandler(priority = EventPriority.MONITOR)
private void onPlayerKill(MatchPlayerDeathEvent event) {
if(event.getKiller() != null &&
event.isChallengeKill() &&
getProximityMetricType(event.getKiller().getParty()) == ProximityMetric.Type.CLOSEST_KILL) {
updateProximity(event.getKiller(), event.getKiller().getLocation());
}
}
@EventHandler(priority = EventPriority.MONITOR)
private void onTouch(GoalTouchEvent event) {
if(this == event.getGoal() && event.isFirstForCompetitor()) {
resetProximity(event.getCompetitor());
}
}
@EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
private void onComplete(GoalCompleteEvent event) {
if(this == event.getGoal()) {
resetProximity();
}
}
@EventHandler(priority = EventPriority.MONITOR)
private void onCompetitorRemove(CompetitorRemoveEvent event) {
resetProximity(event.getCompetitor());
}
}