225 lines
8.3 KiB
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());
|
|
}
|
|
}
|