Refactor control point logic into a controllable goal
This commit is contained in:
parent
068489d9b0
commit
9f73d2a620
|
@ -4,7 +4,7 @@ import tc.oc.commons.core.inject.HybridManifest;
|
|||
import tc.oc.pgm.animation.AnimationManifest;
|
||||
import tc.oc.pgm.broadcast.BroadcastManifest;
|
||||
import tc.oc.pgm.classes.ClassManifest;
|
||||
import tc.oc.pgm.controlpoint.ControlPointManifest;
|
||||
import tc.oc.pgm.control.ControllableGoalManifest;
|
||||
import tc.oc.pgm.core.CoreManifest;
|
||||
import tc.oc.pgm.damage.DamageManifest;
|
||||
import tc.oc.pgm.destroyable.DestroyableManifest;
|
||||
|
@ -65,7 +65,7 @@ public class PGMModulesManifest extends HybridManifest {
|
|||
install(new CoreManifest());
|
||||
install(new DestroyableManifest());
|
||||
install(new WoolManifest());
|
||||
install(new ControlPointManifest());
|
||||
install(new ControllableGoalManifest());
|
||||
install(new PayloadManifest());
|
||||
install(new LaneManifest());
|
||||
install(new BroadcastManifest());
|
||||
|
|
|
@ -1,21 +1,24 @@
|
|||
package tc.oc.pgm.controlpoint;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import javax.annotation.Nullable;
|
||||
package tc.oc.pgm.control;
|
||||
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
import net.md_5.bungee.api.ChatColor;
|
||||
import org.bukkit.event.HandlerList;
|
||||
import org.bukkit.util.Vector;
|
||||
import org.bukkit.Location;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.event.EventHandler;
|
||||
import org.bukkit.event.EventPriority;
|
||||
import org.bukkit.event.Listener;
|
||||
import org.bukkit.event.player.PlayerTeleportEvent;
|
||||
import tc.oc.api.docs.virtual.MatchDoc;
|
||||
import tc.oc.commons.bukkit.event.CoarsePlayerMoveEvent;
|
||||
import tc.oc.commons.core.collection.WeakHashSet;
|
||||
import tc.oc.commons.core.util.Comparables;
|
||||
import tc.oc.commons.core.util.DefaultMapAdapter;
|
||||
import tc.oc.commons.core.util.TimeUtils;
|
||||
import tc.oc.pgm.controlpoint.events.CapturingTeamChangeEvent;
|
||||
import tc.oc.pgm.controlpoint.events.CapturingTimeChangeEvent;
|
||||
import tc.oc.pgm.controlpoint.events.ControllerChangeEvent;
|
||||
import tc.oc.pgm.control.events.ControllableOwnerChangeEvent;
|
||||
import tc.oc.pgm.control.events.ControllableTeamChangeEvent;
|
||||
import tc.oc.pgm.control.events.ControllableTimeChangeEvent;
|
||||
import tc.oc.pgm.events.ListenerScope;
|
||||
import tc.oc.pgm.filters.Filter;
|
||||
import tc.oc.pgm.goals.IncrementalGoal;
|
||||
import tc.oc.pgm.goals.SimpleGoal;
|
||||
import tc.oc.pgm.goals.events.GoalCompleteEvent;
|
||||
|
@ -23,147 +26,133 @@ import tc.oc.pgm.goals.events.GoalStatusChangeEvent;
|
|||
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.Party;
|
||||
import tc.oc.pgm.regions.Region;
|
||||
import tc.oc.pgm.score.ScoreMatchModule;
|
||||
import tc.oc.pgm.spawns.events.ParticipantDespawnEvent;
|
||||
import tc.oc.pgm.teams.TeamMatchModule;
|
||||
import tc.oc.pgm.utils.MatchPlayers;
|
||||
import tc.oc.pgm.utils.Strings;
|
||||
|
||||
public class ControlPoint extends SimpleGoal<ControlPointDefinition> implements IncrementalGoal<ControlPointDefinition> {
|
||||
import javax.annotation.Nullable;
|
||||
import java.time.Duration;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
|
||||
public static final ChatColor COLOR_NEUTRAL_TEAM = ChatColor.WHITE;
|
||||
@ListenerScope(MatchScope.LOADED)
|
||||
public abstract class ControllableGoal<T extends ControllableGoalDefinition> extends SimpleGoal<T> implements IncrementalGoal<T>, Listener {
|
||||
|
||||
public static final String SYMBOL_CP_INCOMPLETE = "\u29be"; // ⦾
|
||||
public static final String SYMBOL_CP_COMPLETE = "\u29bf"; // ⦿
|
||||
/**
|
||||
* Set to false if the goal is defined as permanent.
|
||||
*/
|
||||
protected boolean capturable;
|
||||
|
||||
protected final ControlPointPlayerTracker playerTracker;
|
||||
protected final ControlPointBlockDisplay blockDisplay;
|
||||
/**
|
||||
* The team that currently owns the goal. The goal is completed for this team.
|
||||
* If this is null then the goal is unowned, either because it is in the
|
||||
* neutral state, or because it has no initial owner and has not yet been captured.
|
||||
*/
|
||||
protected Competitor owner;
|
||||
|
||||
protected final Vector centerPoint;
|
||||
/**
|
||||
* The team that will own the goal if the current capture is successful.
|
||||
* If this is null then either the goal is not being captured or it is
|
||||
* being "uncaptured" toward the neutral state.
|
||||
*/
|
||||
protected Competitor capturer;
|
||||
|
||||
// This is set false after the first state change if definition.permanent == true
|
||||
protected boolean capturable = true;
|
||||
/**
|
||||
* Time accumulated towards the next owner change. When this passes the capture time,
|
||||
* it is reset to zero and the owner changes to the capturer (which may be null,
|
||||
* if changing to the neutral state). When this is zero, the capturer is null.
|
||||
*/
|
||||
protected Duration progress;
|
||||
|
||||
// The team that currently owns the point. The goal is completed for this team.
|
||||
// If this is null then the point is unowned, either because it is in the
|
||||
// neutral state, or because it has no initial owner and has not yet been captured.
|
||||
protected Competitor owner = null;
|
||||
/**
|
||||
* Keeps track of players that are nearby the controllable goal.
|
||||
*/
|
||||
protected Set<MatchPlayer> players;
|
||||
|
||||
// The team that will own the CP if the current capture is successful.
|
||||
// If this is null then either the point is not being captured or it is
|
||||
// being "uncaptured" toward the neutral state.
|
||||
protected Competitor capturer = null;
|
||||
/**
|
||||
* String symbols to render the completion of the goal in the sidebar.
|
||||
* Incomplete symbol is at index 0, complete symbol is at index 1.
|
||||
*/
|
||||
protected String[] symbols;
|
||||
|
||||
// Time accumulated towards the next owner change. When this passes timeToCapture,
|
||||
// it is reset to zero and the owner changes to the capturer (which may be null,
|
||||
// if changing to the neutral state). When this is zero, the capturer is null.
|
||||
protected Duration progress = Duration.ZERO;
|
||||
|
||||
public ControlPoint(Match match, ControlPointDefinition definition) {
|
||||
public ControllableGoal(T definition, Match match, String... symbols) {
|
||||
super(definition, match);
|
||||
|
||||
if(this.definition.getInitialOwner() != null) {
|
||||
this.owner = match.needMatchModule(TeamMatchModule.class).team(this.definition.getInitialOwner());
|
||||
this.capturable = true;
|
||||
this.owner = definition.initialOwner().map(owner -> match.needMatchModule(TeamMatchModule.class).team(owner)).orElse(null);
|
||||
this.capturer = null;
|
||||
this.progress = Duration.ZERO;
|
||||
this.players = new WeakHashSet<>();
|
||||
this.symbols = new String[] {"\u29be", "\u29bf"};
|
||||
for(int i = 0; symbols.length == 2 && i < 2; i++) {
|
||||
if(symbols[i] != null) {
|
||||
this.symbols[i] = symbols[i];
|
||||
}
|
||||
}
|
||||
|
||||
this.centerPoint = this.getCaptureRegion().getBounds().center();
|
||||
|
||||
this.playerTracker = new ControlPointPlayerTracker(match, this.getCaptureRegion());
|
||||
|
||||
this.blockDisplay = new ControlPointBlockDisplay(match, this);
|
||||
}
|
||||
|
||||
public void registerEvents() {
|
||||
this.match.registerEvents(this.playerTracker);
|
||||
this.match.registerEvents(this.blockDisplay);
|
||||
|
||||
this.blockDisplay.render();
|
||||
}
|
||||
|
||||
public void unregisterEvents() {
|
||||
HandlerList.unregisterAll(this.blockDisplay);
|
||||
HandlerList.unregisterAll(this.playerTracker);
|
||||
}
|
||||
|
||||
public ControlPointBlockDisplay getBlockDisplay() {
|
||||
return blockDisplay;
|
||||
}
|
||||
|
||||
public ControlPointPlayerTracker getPlayerTracker() {
|
||||
return playerTracker;
|
||||
}
|
||||
|
||||
public Region getCaptureRegion() {
|
||||
return definition.getCaptureRegion();
|
||||
}
|
||||
|
||||
public Duration getTimeToCapture() {
|
||||
return definition.getTimeToCapture();
|
||||
}
|
||||
|
||||
/**
|
||||
* Point that can be used as the location of the ControlPoint
|
||||
* The team that owns (is receiving points from) this goal,
|
||||
* or null if the goal is unowned.
|
||||
*/
|
||||
public Vector getCenterPoint() {
|
||||
return centerPoint.clone();
|
||||
public Competitor owner() {
|
||||
return owner;
|
||||
}
|
||||
|
||||
/**
|
||||
* The team that owns (is receiving points from) this ControlPoint,
|
||||
* or null if the ControlPoint is unowned.
|
||||
* The team that is "capturing" the goal. This is the team
|
||||
* that the current progress counts towards. The progress
|
||||
* goes up whenever this team has the most players on the goal,
|
||||
* and goes down when any other team has the most players on the goal.
|
||||
* If progress reaches captureTime, this team will take ownership of the goal,
|
||||
* if they don't own it already. When progress goes below zero, the capturing team
|
||||
* changes to the team with the most players on the goal, and the goal becomes unowned.
|
||||
*/
|
||||
public Competitor getOwner() {
|
||||
return this.owner;
|
||||
public Competitor capturer() {
|
||||
return capturer;
|
||||
}
|
||||
|
||||
/**
|
||||
* The team that is "capturing" the ControlPoint. This is the team
|
||||
* that the current capturingTime counts towards. The capturingTime
|
||||
* goes up whenever this team has the most players on the point,
|
||||
* and goes down when any other team has the most players on the point.
|
||||
* If capturingTime reaches timeToCapture, this team will take
|
||||
* ownership of the point, if they don't own it already. When capturingTime
|
||||
* goes below zero, the capturingTeam changes to the team with the most
|
||||
* players on the point, and the point becomes unowned.
|
||||
* The partial owner of the goal is defined in three scenarios:
|
||||
* 1. If the goal is owned and has a neutral state, the partial owner is the owner of the goal.
|
||||
* 2. If the goal is in contest, the partial owner is the team that is currently capturing the goal.
|
||||
* 3. If the goal is un-owned and not in contest, the progressingTeam is null.
|
||||
*/
|
||||
public Competitor getCapturer() {
|
||||
return this.capturer;
|
||||
public Competitor partialOwner() {
|
||||
return definition.neutralState() && owner != null ? owner() : capturer();
|
||||
}
|
||||
|
||||
/**
|
||||
* The partial owner of the ControlPoint. The "partial owner" is defined in
|
||||
* three scenarios. If the ControlPoint is owned and has a neutral state, the
|
||||
* partial owner is the owner of the ControlPoint. If the ControlPoint is in
|
||||
* contest, the partial owner is the team that is currently capturing the
|
||||
* ControlPoint. Lastly, if the ControlPoint is un-owned and not in contest,
|
||||
* the progressingTeam is null.
|
||||
*
|
||||
* @return The team that should be displayed as having partial ownership of
|
||||
* the point, if any.
|
||||
*/
|
||||
public Competitor getPartialOwner() {
|
||||
return this.definition.hasNeutralState() && this.getOwner() != null ? this.getOwner() : this.getCapturer();
|
||||
}
|
||||
|
||||
/**
|
||||
* Progress towards "capturing" the ControlPoint for the current capturingTeam
|
||||
* Progress towards "capturing" the goal for the current capturing team.
|
||||
*/
|
||||
public Duration getProgress() {
|
||||
return this.progress;
|
||||
return progress;
|
||||
}
|
||||
|
||||
/**
|
||||
* Progress toward "capturing" the ControlPoint for the current capturingTeam,
|
||||
* as a real number from 0 to 1.
|
||||
*/
|
||||
@Override
|
||||
public double getCompletion() {
|
||||
return (double) this.progress.toMillis() / (double) this.definition.getTimeToCapture().toMillis();
|
||||
return (double) progress.toMillis() / (double) definition.captureTime().toMillis();
|
||||
}
|
||||
|
||||
public double getCompletion(Competitor team) {
|
||||
if(Objects.equals(owner, team)) {
|
||||
return 1 - getCompletion();
|
||||
} else if(Objects.equals(capturer, team)) {
|
||||
return getCompletion();
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String renderCompletion() {
|
||||
return Strings.progressPercentage(this.getCompletion());
|
||||
return Strings.progressPercentage(getCompletion());
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -173,112 +162,84 @@ public class ControlPoint extends SimpleGoal<ControlPointDefinition> implements
|
|||
|
||||
@Override
|
||||
public ChatColor renderSidebarStatusColor(@Nullable Competitor competitor, Party viewer) {
|
||||
return this.capturer == null ? COLOR_NEUTRAL_TEAM : this.capturer.getColor();
|
||||
return capturer != null ? capturer.getColor() : ChatColor.WHITE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String renderSidebarStatusText(@Nullable Competitor competitor, Party viewer) {
|
||||
if(Duration.ZERO.equals(this.progress)) {
|
||||
return this.owner == null ? SYMBOL_CP_INCOMPLETE : SYMBOL_CP_COMPLETE;
|
||||
if(Duration.ZERO.equals(progress)) {
|
||||
return symbols[owner != null ? 1 : 0];
|
||||
} else {
|
||||
return this.renderCompletion();
|
||||
return renderCompletion();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public ChatColor renderSidebarLabelColor(@Nullable Competitor competitor, Party viewer) {
|
||||
return this.owner == null ? COLOR_NEUTRAL_TEAM : this.owner.getColor();
|
||||
}
|
||||
|
||||
/**
|
||||
* Ownership of the ControlPoint for a specific team given as a real number from
|
||||
* 0 to 1.
|
||||
*/
|
||||
public double getCompletion(Competitor team) {
|
||||
if (this.getOwner() == team) {
|
||||
return 1 - this.getCompletion();
|
||||
} else if (this.getCapturer() == team) {
|
||||
return this.getCompletion();
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
return owner != null ? owner.getColor() : ChatColor.WHITE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean getShowProgress() {
|
||||
return this.definition.getShowProgress();
|
||||
return definition.showProgress();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canComplete(Competitor team) {
|
||||
return this.canCapture(team);
|
||||
return canCapture(team);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isCompleted() {
|
||||
return this.owner != null;
|
||||
return owner != null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isCompleted(Competitor team) {
|
||||
return this.owner != null && this.owner == team;
|
||||
return isCompleted() && owner.equals(team);
|
||||
}
|
||||
|
||||
private boolean canCapture(Competitor team) {
|
||||
return this.definition.getCaptureFilter() == null ||
|
||||
this.definition.getCaptureFilter().query(team).isAllowed();
|
||||
Filter filter = definition.captureFilter();
|
||||
return filter == null || filter.query(team).isAllowed();
|
||||
}
|
||||
|
||||
private boolean canDominate(MatchPlayer player) {
|
||||
return this.definition.getPlayerFilter() == null ||
|
||||
this.definition.getPlayerFilter().query(player).isAllowed();
|
||||
Filter filter = definition.defendFilter();
|
||||
return filter == null || filter.query(player).isAllowed();
|
||||
}
|
||||
|
||||
private Duration calculateDominateTime(int lead, Duration duration) {
|
||||
// Don't scale time if only one player is present, don't zero duration if multiplier is zero
|
||||
return TimeUtils.multiply(duration, 1 + (lead - 1) * definition.getTimeMultiplier());
|
||||
return TimeUtils.multiply(duration, 1 + (lead - 1) * definition.multiplierTime());
|
||||
}
|
||||
|
||||
public void tick(Duration duration) {
|
||||
this.tickCapture(duration);
|
||||
this.tickScore(duration);
|
||||
tickCapture(duration);
|
||||
tickScore(duration);
|
||||
}
|
||||
|
||||
/**
|
||||
* Do a scoring cycle on this ControlPoint over the given duration.
|
||||
*/
|
||||
protected void tickScore(Duration duration) {
|
||||
if(this.getOwner() != null && this.getDefinition().affectsScore()) {
|
||||
ScoreMatchModule scoreMatchModule = this.getMatch().getMatchModule(ScoreMatchModule.class);
|
||||
if(scoreMatchModule != null) {
|
||||
float seconds = this.getMatch().getLength().getSeconds();
|
||||
float initial = this.getDefinition().getPointsPerSecond();
|
||||
float growth = this.getDefinition().getPointsGrowth();
|
||||
if(definition.affectsScore() && isCompleted()) {
|
||||
match.module(ScoreMatchModule.class).ifPresent(scores -> {
|
||||
float seconds = match.getLength().getSeconds();
|
||||
float initial = definition.pointsPerSecond();
|
||||
float growth = definition.pointsGrowth();
|
||||
float rate = (float) (initial * Math.pow(2, seconds / growth));
|
||||
scoreMatchModule.incrementScore(this.getOwner(), rate * duration.toMillis() / 1000d);
|
||||
}
|
||||
scores.incrementScore(owner, rate * duration.toMillis() / 1000d);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Do a capturing cycle on this ControlPoint over the given duration.
|
||||
*/
|
||||
protected void tickCapture(Duration duration) {
|
||||
Map<Competitor, Integer> playerCounts = new DefaultMapAdapter<>(new HashMap<>(), 0);
|
||||
|
||||
// The teams with the most and second-most capturing players on the point, respectively
|
||||
Competitor leader = null, runnerUp = null;
|
||||
|
||||
// The total number of players on the point who are allowed to dominate and not on the leading team
|
||||
int defenderCount = 0;
|
||||
|
||||
for(MatchPlayer player : this.playerTracker.getPlayersOnPoint()) {
|
||||
for(MatchPlayer player : ImmutableSet.copyOf(players)) {
|
||||
Competitor team = player.getCompetitor();
|
||||
if(this.canDominate(player)) {
|
||||
if(canDominate(player)) {
|
||||
defenderCount++;
|
||||
int playerCount = playerCounts.get(team) + 1;
|
||||
playerCounts.put(team, playerCount);
|
||||
|
||||
if(team != leader) {
|
||||
if(leader == null || playerCount > playerCounts.get(leader)) {
|
||||
runnerUp = leader;
|
||||
|
@ -289,23 +250,19 @@ public class ControlPoint extends SimpleGoal<ControlPointDefinition> implements
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
int lead = 0;
|
||||
if(leader != null) {
|
||||
lead = playerCounts.get(leader);
|
||||
defenderCount -= lead;
|
||||
|
||||
switch(this.definition.getCaptureCondition()) {
|
||||
switch(definition.captureCondition()) {
|
||||
case EXCLUSIVE:
|
||||
if(defenderCount > 0) {
|
||||
lead = 0;
|
||||
}
|
||||
break;
|
||||
|
||||
case MAJORITY:
|
||||
lead = Math.max(0, lead - defenderCount);
|
||||
break;
|
||||
|
||||
case LEAD:
|
||||
if(runnerUp != null) {
|
||||
lead -= playerCounts.get(runnerUp);
|
||||
|
@ -313,44 +270,42 @@ public class ControlPoint extends SimpleGoal<ControlPointDefinition> implements
|
|||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if(lead > 0) {
|
||||
this.dominateAndFireEvents(leader, calculateDominateTime(lead, duration));
|
||||
dominateAndFireEvents(leader, calculateDominateTime(lead, duration));
|
||||
} else {
|
||||
this.dominateAndFireEvents(null, duration);
|
||||
dominateAndFireEvents(null, duration);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Do a cycle of domination on this ControlPoint for the given team over the given duration. The team can be null,
|
||||
* which means no team is dominating the point, which can cause the state to change in some configurations.
|
||||
* Cycle of domination on this goal for the given team over the duration.
|
||||
* The team can be null, which means no team is dominating the goal,
|
||||
* which can cause the state to change in some configurations.
|
||||
*/
|
||||
private void dominateAndFireEvents(@Nullable Competitor dominator, Duration duration) {
|
||||
final Duration oldProgress = progress;
|
||||
final Competitor oldCapturer = capturer;
|
||||
final Competitor oldOwner = owner;
|
||||
|
||||
final Competitor oldCapturer = capturer, oldOwner = owner;
|
||||
dominate(dominator, duration);
|
||||
|
||||
if(!Objects.equals(oldCapturer, capturer) || !oldProgress.equals(progress)) {
|
||||
match.callEvent(new CapturingTimeChangeEvent(match, this));
|
||||
displayProgress(owner, capturer, getCompletion());
|
||||
match.callEvent(new ControllableTimeChangeEvent(match, this));
|
||||
match.callEvent(new GoalStatusChangeEvent(this));
|
||||
}
|
||||
|
||||
if(!Objects.equals(oldCapturer, capturer)) {
|
||||
match.callEvent(new CapturingTeamChangeEvent(match, this, oldCapturer, capturer));
|
||||
match.callEvent(new ControllableTeamChangeEvent(match, this, oldCapturer, capturer));
|
||||
}
|
||||
|
||||
if(!Objects.equals(oldOwner, owner)) {
|
||||
match.callEvent(new ControllerChangeEvent(match, this, oldOwner, owner));
|
||||
displaySet(owner);
|
||||
match.callEvent(new ControllableOwnerChangeEvent(match, this, oldOwner, owner));
|
||||
match.callEvent(new GoalCompleteEvent(this, owner != null, c -> c.equals(oldOwner), c -> c.equals(owner)));
|
||||
|
||||
ScoreMatchModule smm = this.getMatch().getMatchModule(ScoreMatchModule.class);
|
||||
if (smm != null) {
|
||||
if (oldOwner != null) smm.incrementScore(oldOwner, getDefinition().getPointsOwned() * -1);
|
||||
if (owner != null) smm.incrementScore(owner, getDefinition().getPointsOwned());
|
||||
}
|
||||
match.module(ScoreMatchModule.class).ifPresent(scores -> {
|
||||
if(oldOwner != null) {
|
||||
scores.incrementScore(owner, definition.pointsOwned() * -1);
|
||||
}
|
||||
if(owner != null) {
|
||||
scores.incrementScore(owner, definition.pointsOwned());
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -379,10 +334,9 @@ public class ControlPoint extends SimpleGoal<ControlPointDefinition> implements
|
|||
if(!capturable || Comparables.lessOrEqual(duration, Duration.ZERO)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if(owner != null && definition.hasNeutralState()) {
|
||||
if(owner != null && definition.neutralState()) {
|
||||
// Point is owned and has a neutral state
|
||||
if(Objects.equals(dominator, owner)) {
|
||||
if(Objects.equals(owner, dominator)) {
|
||||
// Owner is recovering the point
|
||||
recover(duration, dominator);
|
||||
} else if(dominator != null) {
|
||||
|
@ -394,7 +348,7 @@ public class ControlPoint extends SimpleGoal<ControlPointDefinition> implements
|
|||
}
|
||||
} else if(capturer != null) {
|
||||
// Point is partly captured by someone
|
||||
if(Objects.equals(dominator, capturer)) {
|
||||
if(Objects.equals(capturer, dominator)) {
|
||||
// Capturer is making progress
|
||||
capture(duration);
|
||||
} else if(dominator != null) {
|
||||
|
@ -404,37 +358,15 @@ public class ControlPoint extends SimpleGoal<ControlPointDefinition> implements
|
|||
// Point is decaying towards owner or neutral
|
||||
decay(duration);
|
||||
}
|
||||
} else if(dominator != null && !Objects.equals(dominator, owner) && canCapture(dominator)) {
|
||||
} else if(dominator != null && !Objects.equals(owner, dominator) && canCapture(dominator)) {
|
||||
// Point is not being captured and there is a dominant team that is not the owner, so they start capturing
|
||||
capturer = dominator;
|
||||
dominate(dominator, duration);
|
||||
}
|
||||
}
|
||||
|
||||
private @Nullable Duration addCaptureTime(final Duration duration) {
|
||||
progress = progress.plus(duration);
|
||||
if(Comparables.lessThan(progress, definition.getTimeToCapture())) {
|
||||
return null;
|
||||
} else {
|
||||
final Duration remainder = progress.minus(definition.getTimeToCapture());
|
||||
progress = Duration.ZERO;
|
||||
return remainder;
|
||||
}
|
||||
}
|
||||
|
||||
private @Nullable Duration subtractCaptureTime(final Duration duration) {
|
||||
if(Comparables.greaterThan(progress, duration)) {
|
||||
progress = progress.minus(duration);
|
||||
return null;
|
||||
} else {
|
||||
final Duration remainder = duration.minus(progress);
|
||||
progress = Duration.ZERO;
|
||||
return remainder;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Point is owned, and a non-owner is pushing it towards neutral
|
||||
* The goal is owned, and a non-owner is pushing it towards neutral.
|
||||
*/
|
||||
private void uncapture(Duration duration, Competitor dominator) {
|
||||
duration = addCaptureTime(duration);
|
||||
|
@ -446,14 +378,14 @@ public class ControlPoint extends SimpleGoal<ControlPointDefinition> implements
|
|||
}
|
||||
|
||||
/**
|
||||
* Point is either owned or neutral, and someone is pushing it towards themselves
|
||||
* The goal is either owned or neutral, and someone is pushing it towards themselves.
|
||||
*/
|
||||
private void capture(Duration duration) {
|
||||
duration = addCaptureTime(duration);
|
||||
if(duration != null) {
|
||||
owner = capturer;
|
||||
capturer = null;
|
||||
if(definition.isPermanent()) {
|
||||
if(definition.permanent()) {
|
||||
// The objective is permanent, so the first capture disables it
|
||||
capturable = false;
|
||||
}
|
||||
|
@ -461,7 +393,7 @@ public class ControlPoint extends SimpleGoal<ControlPointDefinition> implements
|
|||
}
|
||||
|
||||
/**
|
||||
* Point is being pulled back towards its current state
|
||||
* The goal is being pulled back towards its current state.
|
||||
*/
|
||||
private void recover(Duration duration, Competitor dominator) {
|
||||
duration = TimeUtils.multiply(duration, definition.recoveryRate());
|
||||
|
@ -476,7 +408,7 @@ public class ControlPoint extends SimpleGoal<ControlPointDefinition> implements
|
|||
}
|
||||
|
||||
/**
|
||||
* Point is decaying back towards its current state
|
||||
* The goal is decaying back towards its current state.
|
||||
*/
|
||||
private void decay(Duration duration) {
|
||||
duration = TimeUtils.multiply(duration, definition.decayRate());
|
||||
|
@ -486,6 +418,90 @@ public class ControlPoint extends SimpleGoal<ControlPointDefinition> implements
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Increase the base amount of capture time by a certain amount.
|
||||
*/
|
||||
private @Nullable Duration addCaptureTime(final Duration duration) {
|
||||
progress = progress.plus(duration);
|
||||
if(Comparables.lessThan(progress, definition.captureTime())) {
|
||||
return null;
|
||||
} else {
|
||||
final Duration remainder = progress.minus(definition.captureTime());
|
||||
progress = Duration.ZERO;
|
||||
return remainder;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Decrease the base amount of capture time by a certain amount.
|
||||
*/
|
||||
private @Nullable Duration subtractCaptureTime(final Duration duration) {
|
||||
if(Comparables.greaterThan(progress, duration)) {
|
||||
progress = progress.minus(duration);
|
||||
return null;
|
||||
} else {
|
||||
final Duration remainder = duration.minus(progress);
|
||||
progress = Duration.ZERO;
|
||||
return remainder;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get an approximate region of where the capture/control region of the goal is.
|
||||
*/
|
||||
public abstract Region region();
|
||||
|
||||
/**
|
||||
* Reset and show progress of the controllable goal in the physical world for the first time.
|
||||
*/
|
||||
public void display() {
|
||||
displaySet(owner);
|
||||
displayProgress(owner, capturer, getCompletion());
|
||||
}
|
||||
|
||||
/**
|
||||
* Display progress of the controllable goal in the physical world.
|
||||
*/
|
||||
protected abstract void displayProgress(Competitor controlling, Competitor capturing, double progress);
|
||||
|
||||
/**
|
||||
* Reset progress of the controllable goal in the physical world to a new owning team.
|
||||
*/
|
||||
protected abstract void displaySet(Competitor owner);
|
||||
|
||||
/**
|
||||
* Update the tracked players that are on the controllable goal
|
||||
*/
|
||||
protected void updatePlayers(Player bukkit, Location to) {
|
||||
MatchPlayer player = match.getPlayer(bukkit);
|
||||
if(!MatchPlayers.canInteract(player)) return;
|
||||
if(!player.getBukkit().isDead() && tracking(player, to)) {
|
||||
players.add(player);
|
||||
} else {
|
||||
players.remove(player);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Should the controllable goal track the given player at the given location.
|
||||
*/
|
||||
protected abstract boolean tracking(MatchPlayer player, Location location);
|
||||
|
||||
@EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
|
||||
public void onPlayerMove(CoarsePlayerMoveEvent event) {
|
||||
updatePlayers(event.getPlayer(), event.getTo());
|
||||
}
|
||||
|
||||
@EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
|
||||
public void onPlayerTeleport(PlayerTeleportEvent event) {
|
||||
updatePlayers(event.getPlayer(), event.getTo());
|
||||
}
|
||||
|
||||
@EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
|
||||
public void onPlayerDespawn(ParticipantDespawnEvent event) {
|
||||
players.remove(event.getPlayer());
|
||||
}
|
||||
|
||||
@Override
|
||||
public MatchDoc.IncrementalGoal getDocument() {
|
||||
return new Document();
|
||||
|
@ -497,4 +513,5 @@ public class ControlPoint extends SimpleGoal<ControlPointDefinition> implements
|
|||
return getCompletion();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -1,31 +1,35 @@
|
|||
package tc.oc.pgm.controlpoint;
|
||||
package tc.oc.pgm.control;
|
||||
|
||||
import net.md_5.bungee.api.ChatColor;
|
||||
import org.bukkit.event.EventHandler;
|
||||
import org.bukkit.event.EventPriority;
|
||||
import org.bukkit.event.Listener;
|
||||
import tc.oc.commons.core.chat.Component;
|
||||
import tc.oc.pgm.controlpoint.events.ControllerChangeEvent;
|
||||
import tc.oc.pgm.control.events.ControllableOwnerChangeEvent;
|
||||
import tc.oc.pgm.events.ListenerScope;
|
||||
import tc.oc.pgm.match.Competitor;
|
||||
import tc.oc.pgm.match.Match;
|
||||
import tc.oc.pgm.match.MatchScope;
|
||||
|
||||
/**
|
||||
* Announces changes of state and ownership of any controllable goal.
|
||||
*/
|
||||
@ListenerScope(MatchScope.LOADED)
|
||||
public class ControlPointAnnouncer implements Listener {
|
||||
public class ControllableGoalAnnouncer implements Listener {
|
||||
|
||||
private final Match match;
|
||||
|
||||
public ControlPointAnnouncer(Match match) {
|
||||
public ControllableGoalAnnouncer(Match match) {
|
||||
this.match = match;
|
||||
}
|
||||
|
||||
@EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
|
||||
public void onOwnerChange(ControllerChangeEvent event) {
|
||||
if(event.getControlPoint().isVisible()) {
|
||||
final Component point = new Component(event.getControlPoint().getComponentName(), ChatColor.WHITE);
|
||||
public void onOwnerChange(ControllableOwnerChangeEvent event) {
|
||||
if(event.controllable().isVisible()) {
|
||||
final Component point = new Component(event.controllable().getComponentName(), ChatColor.WHITE);
|
||||
final Component message = new Component(ChatColor.GRAY);
|
||||
final Competitor before = event.getOldController();
|
||||
final Competitor after = event.getNewController();
|
||||
final Competitor before = event.oldController();
|
||||
final Competitor after = event.newController();
|
||||
if(after == null) {
|
||||
message.translate("objective.lose",
|
||||
before.getComponentName(),
|
||||
|
@ -43,4 +47,5 @@ public class ControlPointAnnouncer implements Listener {
|
|||
match.sendMessage(message);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,97 @@
|
|||
package tc.oc.pgm.control;
|
||||
|
||||
import tc.oc.pgm.features.GamemodeFeature;
|
||||
import tc.oc.pgm.filters.Filter;
|
||||
import tc.oc.pgm.goals.GoalDefinition;
|
||||
import tc.oc.pgm.teams.TeamFactory;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.util.Optional;
|
||||
|
||||
public interface ControllableGoalDefinition extends GoalDefinition, GamemodeFeature {
|
||||
|
||||
/**
|
||||
* Which players are allowed to control the goal.
|
||||
*/
|
||||
Filter captureFilter();
|
||||
|
||||
/**
|
||||
* Which players are allowed to defend (revert progress) on the goal.
|
||||
*/
|
||||
Filter defendFilter();
|
||||
|
||||
/**
|
||||
* Base time for the goal to transition between states.
|
||||
*/
|
||||
Duration captureTime();
|
||||
|
||||
/**
|
||||
* Multiplier applied to capture time based on the number of players dominating the goal.
|
||||
*/
|
||||
double multiplierTime();
|
||||
|
||||
/**
|
||||
* Relative rate at which progress reverts while nobody is dominating the goal.
|
||||
*/
|
||||
double decayRate();
|
||||
|
||||
/**
|
||||
* Relative rate at which progress reverts from players dominating the goal.
|
||||
*/
|
||||
double recoveryRate();
|
||||
|
||||
/**
|
||||
* The team that initially controls the goal before the match starts.
|
||||
*/
|
||||
Optional<TeamFactory> initialOwner();
|
||||
|
||||
/**
|
||||
* Condition for when the goal transitions from one controller to another.
|
||||
*/
|
||||
CaptureCondition captureCondition();
|
||||
|
||||
enum CaptureCondition {
|
||||
EXCLUSIVE, // Team owns all players on the point
|
||||
MAJORITY, // Team owns more than half the players on the point
|
||||
LEAD; // Team owns more players on the point than any other single team
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether the goal transitions to neural control before a team control.
|
||||
*/
|
||||
boolean neutralState();
|
||||
|
||||
/**
|
||||
* Whether goal transitions are immutable and cannot be reverted.
|
||||
*/
|
||||
boolean permanent();
|
||||
|
||||
/**
|
||||
* Whether active ownership of the goal gives points to the controlling team.
|
||||
*/
|
||||
default boolean affectsScore() {
|
||||
return pointsPerSecond() != 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Amount of points given to a team once they control the goal.
|
||||
* Those points are revoked if they loose ownership.
|
||||
*/
|
||||
float pointsOwned();
|
||||
|
||||
/**
|
||||
* Amount of points given to the controlling team every second.
|
||||
*/
|
||||
float pointsPerSecond();
|
||||
|
||||
/**
|
||||
* After this many seconds pass in the match, the points per second will double exponentially.
|
||||
*/
|
||||
float pointsGrowth();
|
||||
|
||||
/**
|
||||
* Whether progress of controlling the goal is published and visible.
|
||||
*/
|
||||
boolean showProgress();
|
||||
|
||||
}
|
|
@ -0,0 +1,145 @@
|
|||
package tc.oc.pgm.control;
|
||||
|
||||
import tc.oc.api.docs.virtual.MapDoc;
|
||||
import tc.oc.pgm.filters.Filter;
|
||||
import tc.oc.pgm.goals.GoalDefinitionImpl;
|
||||
import tc.oc.pgm.match.Match;
|
||||
import tc.oc.pgm.teams.TeamFactory;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
import java.time.Duration;
|
||||
import java.util.Optional;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
public abstract class ControllableGoalDefinitionImpl extends GoalDefinitionImpl implements ControllableGoalDefinition {
|
||||
|
||||
private final Filter captureFilter;
|
||||
private final Filter defendFilter;
|
||||
private final Duration captureTime;
|
||||
private final double multiplierTime;
|
||||
private final double recoveryRate;
|
||||
private final double decayRate;
|
||||
private final Optional<TeamFactory> initialOwner;
|
||||
private final CaptureCondition captureCondition;
|
||||
private final boolean neutralState;
|
||||
private final boolean permanent;
|
||||
private final float pointsOwned;
|
||||
private final float pointsPerSecond;
|
||||
private final float pointsGrowth;
|
||||
private final boolean showProgress;
|
||||
|
||||
public ControllableGoalDefinitionImpl(String name,
|
||||
@Nullable Boolean required,
|
||||
boolean visible,
|
||||
Filter captureFilter,
|
||||
Filter defendFilter,
|
||||
Duration captureTime,
|
||||
double multiplierTime,
|
||||
double recoveryRate,
|
||||
double decayRate,
|
||||
Optional<TeamFactory> initialOwner,
|
||||
CaptureCondition captureCondition,
|
||||
boolean neutralState,
|
||||
boolean permanent,
|
||||
float pointsOwned,
|
||||
float pointsPerSecond,
|
||||
float pointsGrowth,
|
||||
boolean showProgress) {
|
||||
super(name, required, visible);
|
||||
this.captureFilter = captureFilter;
|
||||
this.defendFilter = defendFilter;
|
||||
this.captureTime = captureTime;
|
||||
this.multiplierTime = multiplierTime;
|
||||
this.recoveryRate = recoveryRate;
|
||||
this.decayRate = decayRate;
|
||||
this.initialOwner = initialOwner;
|
||||
this.captureCondition = captureCondition;
|
||||
this.neutralState = neutralState;
|
||||
this.permanent = permanent;
|
||||
this.pointsOwned = pointsOwned;
|
||||
this.pointsPerSecond = pointsPerSecond;
|
||||
this.pointsGrowth = pointsGrowth;
|
||||
this.showProgress = showProgress;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Stream<MapDoc.Gamemode> gamemodes() {
|
||||
return Stream.of(MapDoc.Gamemode.koth);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isShared() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Filter captureFilter() {
|
||||
return captureFilter;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Filter defendFilter() {
|
||||
return defendFilter;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Duration captureTime() {
|
||||
return captureTime;
|
||||
}
|
||||
|
||||
@Override
|
||||
public double multiplierTime() {
|
||||
return multiplierTime;
|
||||
}
|
||||
|
||||
@Override
|
||||
public double decayRate() {
|
||||
return decayRate;
|
||||
}
|
||||
|
||||
@Override
|
||||
public double recoveryRate() {
|
||||
return recoveryRate;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<TeamFactory> initialOwner() {
|
||||
return initialOwner;
|
||||
}
|
||||
|
||||
@Override
|
||||
public CaptureCondition captureCondition() {
|
||||
return captureCondition;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean neutralState() {
|
||||
return neutralState;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean permanent() {
|
||||
return permanent;
|
||||
}
|
||||
|
||||
@Override
|
||||
public float pointsOwned() {
|
||||
return pointsOwned;
|
||||
}
|
||||
|
||||
@Override
|
||||
public float pointsPerSecond() {
|
||||
return pointsPerSecond;
|
||||
}
|
||||
|
||||
@Override
|
||||
public float pointsGrowth() {
|
||||
return pointsGrowth;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean showProgress() {
|
||||
return showProgress;
|
||||
}
|
||||
|
||||
}
|
|
@ -1,18 +1,22 @@
|
|||
package tc.oc.pgm.controlpoint;
|
||||
package tc.oc.pgm.control;
|
||||
|
||||
import tc.oc.commons.core.inject.HybridManifest;
|
||||
import tc.oc.pgm.control.point.ControlPointDefinition;
|
||||
import tc.oc.pgm.control.point.ControlPointParser;
|
||||
import tc.oc.pgm.control.point.ControlPointRootNodeFinder;
|
||||
import tc.oc.pgm.features.FeatureBinder;
|
||||
import tc.oc.pgm.map.inject.MapBinders;
|
||||
import tc.oc.pgm.map.inject.MapScoped;
|
||||
import tc.oc.pgm.match.inject.MatchModuleFixtureManifest;
|
||||
|
||||
public class ControlPointManifest extends HybridManifest implements MapBinders {
|
||||
public class ControllableGoalManifest extends HybridManifest implements MapBinders {
|
||||
@Override
|
||||
protected void configure() {
|
||||
bind(ControlPointParser.class).in(MapScoped.class);
|
||||
install(new MatchModuleFixtureManifest<ControllableGoalMatchModule>(){});
|
||||
|
||||
final FeatureBinder<ControlPointDefinition> cp = new FeatureBinder<>(binder(), ControlPointDefinition.class);
|
||||
cp.bindDefinitionParser().to(ControlPointParser.class);
|
||||
cp.installMatchModule(ControlPointMatchModule.class);
|
||||
cp.installRootParser(new ControlPointRootNodeFinder());
|
||||
}
|
||||
}
|
|
@ -0,0 +1,54 @@
|
|||
package tc.oc.pgm.control;
|
||||
|
||||
import tc.oc.pgm.match.Match;
|
||||
import tc.oc.pgm.match.MatchModule;
|
||||
import tc.oc.pgm.match.Repeatable;
|
||||
import tc.oc.time.Time;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import java.time.Duration;
|
||||
import java.util.List;
|
||||
|
||||
import static tc.oc.commons.core.stream.Collectors.toImmutableList;
|
||||
|
||||
public class ControllableGoalMatchModule extends MatchModule {
|
||||
|
||||
private static final long MILLIS = 100;
|
||||
private static final Duration INTERVAL = Duration.ofMillis(100);
|
||||
|
||||
private final List<ControllableGoal> controllables;
|
||||
private final ControllableGoalAnnouncer announcer;
|
||||
|
||||
@Inject private ControllableGoalMatchModule(Match match) {
|
||||
this.announcer = new ControllableGoalAnnouncer(match);
|
||||
this.controllables = match.featureDefinitions()
|
||||
.all(ControllableGoalDefinition.class)
|
||||
.map(cp -> (ControllableGoal) cp.getGoal(match))
|
||||
.collect(toImmutableList());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void load() {
|
||||
match.registerEvents(announcer);
|
||||
for(ControllableGoal controllable : controllables) {
|
||||
match.registerEventsAndRepeatables(controllable);
|
||||
controllable.display();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void unload() {
|
||||
for(ControllableGoal controllable : controllables) {
|
||||
match.unregisterEventsAndRepeatables(controllable);
|
||||
}
|
||||
match.unregisterEvents(announcer);
|
||||
}
|
||||
|
||||
@Repeatable(interval = @Time(milliseconds = MILLIS))
|
||||
public void tick() {
|
||||
for(ControllableGoal controllable : controllables) {
|
||||
controllable.tick(INTERVAL);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,26 @@
|
|||
package tc.oc.pgm.control.events;
|
||||
|
||||
import tc.oc.pgm.control.ControllableGoal;
|
||||
import tc.oc.pgm.events.MatchEvent;
|
||||
import tc.oc.pgm.match.Match;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
public abstract class ControllableGoalEvent extends MatchEvent {
|
||||
|
||||
protected final ControllableGoal controllable;
|
||||
|
||||
public ControllableGoalEvent(Match match, ControllableGoal controllable) {
|
||||
super(match);
|
||||
this.controllable = controllable;
|
||||
}
|
||||
|
||||
public ControllableGoal controllable() {
|
||||
return controllable;
|
||||
}
|
||||
|
||||
public <T extends ControllableGoal> Optional<ControllableGoal> controllable(Class<T> controllableClass) {
|
||||
return Optional.ofNullable(controllableClass.isInstance(controllable) ? controllable : null);
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,38 @@
|
|||
package tc.oc.pgm.control.events;
|
||||
|
||||
import org.bukkit.event.HandlerList;
|
||||
import tc.oc.pgm.control.ControllableGoal;
|
||||
import tc.oc.pgm.match.Competitor;
|
||||
import tc.oc.pgm.match.Match;
|
||||
|
||||
public class ControllableOwnerChangeEvent extends ControllableGoalEvent {
|
||||
|
||||
private final Competitor oldController;
|
||||
private final Competitor newController;
|
||||
|
||||
public ControllableOwnerChangeEvent(Match match, ControllableGoal controllable, Competitor oldController, Competitor newController) {
|
||||
super(match, controllable);
|
||||
this.oldController = oldController;
|
||||
this.newController = newController;
|
||||
}
|
||||
|
||||
public Competitor oldController() {
|
||||
return oldController;
|
||||
}
|
||||
|
||||
public Competitor newController() {
|
||||
return newController;
|
||||
}
|
||||
|
||||
private static final HandlerList handlers = new HandlerList();
|
||||
|
||||
@Override
|
||||
public HandlerList getHandlers() {
|
||||
return handlers;
|
||||
}
|
||||
|
||||
public static HandlerList getHandlerList() {
|
||||
return handlers;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,38 @@
|
|||
package tc.oc.pgm.control.events;
|
||||
|
||||
import org.bukkit.event.HandlerList;
|
||||
import tc.oc.pgm.control.ControllableGoal;
|
||||
import tc.oc.pgm.match.Competitor;
|
||||
import tc.oc.pgm.match.Match;
|
||||
|
||||
public class ControllableTeamChangeEvent extends ControllableGoalEvent {
|
||||
|
||||
private final Competitor oldTeam;
|
||||
private final Competitor newTeam;
|
||||
|
||||
public ControllableTeamChangeEvent(Match match, ControllableGoal controllable, Competitor oldTeam, Competitor newTeam) {
|
||||
super(match, controllable);
|
||||
this.oldTeam = oldTeam;
|
||||
this.newTeam = newTeam;
|
||||
}
|
||||
|
||||
public Competitor oldTeam() {
|
||||
return oldTeam;
|
||||
}
|
||||
|
||||
public Competitor newTeam() {
|
||||
return newTeam;
|
||||
}
|
||||
|
||||
private static final HandlerList handlers = new HandlerList();
|
||||
|
||||
@Override
|
||||
public HandlerList getHandlers() {
|
||||
return handlers;
|
||||
}
|
||||
|
||||
public static HandlerList getHandlerList() {
|
||||
return handlers;
|
||||
}
|
||||
|
||||
}
|
|
@ -1,16 +1,17 @@
|
|||
package tc.oc.pgm.controlpoint.events;
|
||||
package tc.oc.pgm.control.events;
|
||||
|
||||
import org.bukkit.event.HandlerList;
|
||||
import tc.oc.pgm.control.ControllableGoal;
|
||||
import tc.oc.pgm.match.Match;
|
||||
import tc.oc.pgm.controlpoint.ControlPoint;
|
||||
|
||||
public class CapturingTimeChangeEvent extends ControlPointEvent {
|
||||
private static final HandlerList handlers = new HandlerList();
|
||||
public class ControllableTimeChangeEvent extends ControllableGoalEvent {
|
||||
|
||||
public CapturingTimeChangeEvent(Match match, ControlPoint point) {
|
||||
super(match, point);
|
||||
public ControllableTimeChangeEvent(Match match, ControllableGoal controllable) {
|
||||
super(match, controllable);
|
||||
}
|
||||
|
||||
private static final HandlerList handlers = new HandlerList();
|
||||
|
||||
@Override
|
||||
public HandlerList getHandlers() {
|
||||
return handlers;
|
||||
|
@ -19,4 +20,5 @@ public class CapturingTimeChangeEvent extends ControlPointEvent {
|
|||
public static HandlerList getHandlerList() {
|
||||
return handlers;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,107 @@
|
|||
package tc.oc.pgm.control.point;
|
||||
|
||||
import org.bukkit.Location;
|
||||
import org.bukkit.block.Block;
|
||||
import org.bukkit.util.BlockVector;
|
||||
import org.bukkit.util.Vector;
|
||||
import tc.oc.commons.bukkit.util.BlockUtils;
|
||||
import tc.oc.commons.bukkit.util.BukkitUtils;
|
||||
import tc.oc.pgm.control.ControllableGoal;
|
||||
import tc.oc.pgm.filters.Filter;
|
||||
import tc.oc.pgm.filters.operator.AllFilter;
|
||||
import tc.oc.pgm.filters.operator.InverseFilter;
|
||||
import tc.oc.pgm.filters.query.BlockQuery;
|
||||
import tc.oc.pgm.match.Competitor;
|
||||
import tc.oc.pgm.match.Match;
|
||||
import tc.oc.pgm.match.MatchPlayer;
|
||||
import tc.oc.pgm.regions.FiniteBlockRegion;
|
||||
import tc.oc.pgm.regions.Region;
|
||||
import tc.oc.pgm.regions.SectorRegion;
|
||||
import tc.oc.pgm.renewable.BlockImage;
|
||||
|
||||
public class ControlPoint extends ControllableGoal<ControlPointDefinition> {
|
||||
|
||||
protected final FiniteBlockRegion progressDisplayRegion;
|
||||
protected final BlockImage progressDisplayImage;
|
||||
protected final FiniteBlockRegion controllerDisplayRegion;
|
||||
protected final BlockImage controllerDisplayImage;
|
||||
|
||||
public ControlPoint(Match match, ControlPointDefinition definition) {
|
||||
super(definition, match);
|
||||
Filter visualMaterials = definition.visualMaterials();
|
||||
Region progressDisplayRegion = definition.progressDisplayRegion();
|
||||
Region controllerDisplayRegion = definition.controllerDisplayRegion();
|
||||
final FiniteBlockRegion.Factory regionFactory = new FiniteBlockRegion.Factory(match.getMapInfo().proto);
|
||||
if(progressDisplayRegion == null) {
|
||||
this.progressDisplayRegion = null;
|
||||
this.progressDisplayImage = null;
|
||||
} else {
|
||||
this.progressDisplayRegion = regionFactory.fromWorld(progressDisplayRegion, match.getWorld(), visualMaterials);
|
||||
this.progressDisplayImage = new BlockImage(match.getWorld(), this.progressDisplayRegion.getBounds());
|
||||
this.progressDisplayImage.save();
|
||||
}
|
||||
if(controllerDisplayRegion == null) {
|
||||
this.controllerDisplayRegion = null;
|
||||
this.controllerDisplayImage = null;
|
||||
} else {
|
||||
this.controllerDisplayRegion = regionFactory.fromWorld(controllerDisplayRegion, match.getWorld(), this.progressDisplayRegion == null ? visualMaterials : AllFilter.of(visualMaterials, new InverseFilter(progressDisplayRegion)));
|
||||
this.controllerDisplayImage = new BlockImage(match.getWorld(), this.controllerDisplayRegion.getBounds());
|
||||
this.controllerDisplayImage.save();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Region region() {
|
||||
return definition.captureRegion();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean tracking(MatchPlayer player, Location location) {
|
||||
return region().contains(location);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void displayProgress(Competitor controlling, Competitor capturing, double progress) {
|
||||
if(progressDisplayRegion != null) {
|
||||
Vector center = progressDisplayRegion.getBounds().center();
|
||||
// capturingProgress can be zero, but it can never be one, so invert it to avoid
|
||||
// a zero-area SectorRegion that can cause glitchy rendering
|
||||
SectorRegion sectorRegion = new SectorRegion(center.getX(), center.getZ(), 0, (1 - progress) * 2 * Math.PI);
|
||||
for(BlockVector pos : progressDisplayRegion.getBlockVectors()) {
|
||||
if(sectorRegion.contains(pos)) {
|
||||
setBlock(pos, controlling);
|
||||
} else {
|
||||
setBlock(pos, capturing);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void displaySet(Competitor owner) {
|
||||
if(controllerDisplayRegion != null) {
|
||||
if(owner == null) {
|
||||
for(BlockVector block : controllerDisplayRegion.getBlockVectors()) {
|
||||
controllerDisplayImage.restore(block);
|
||||
}
|
||||
} else {
|
||||
byte blockData = BukkitUtils.chatColorToDyeColor(owner.getColor()).getWoolData();
|
||||
for(BlockVector pos : controllerDisplayRegion.getBlockVectors()) {
|
||||
BlockUtils.blockAt(match.getWorld(), pos).setData(blockData);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void setBlock(BlockVector pos, Competitor team) {
|
||||
final Block block = BlockUtils.blockAt(match.getWorld(), pos);
|
||||
if(definition.visualMaterials().query(new BlockQuery(block)).isAllowed()) {
|
||||
if(team != null) {
|
||||
block.setData(BukkitUtils.chatColorToDyeColor(team.getColor()).getWoolData());
|
||||
} else {
|
||||
progressDisplayImage.restore(pos);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,84 @@
|
|||
package tc.oc.pgm.control.point;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.util.Optional;
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
import tc.oc.pgm.control.ControllableGoalDefinition;
|
||||
import tc.oc.pgm.control.ControllableGoalDefinitionImpl;
|
||||
import tc.oc.pgm.features.Feature;
|
||||
import tc.oc.pgm.features.FeatureInfo;
|
||||
import tc.oc.pgm.filters.Filter;
|
||||
import tc.oc.pgm.match.Match;
|
||||
import tc.oc.pgm.module.ModuleLoadException;
|
||||
import tc.oc.pgm.regions.Region;
|
||||
import tc.oc.pgm.teams.TeamFactory;
|
||||
|
||||
@FeatureInfo(name = "control-point",
|
||||
plural = {"control-points", "hills"},
|
||||
singular = {"control-point", "hill"})
|
||||
public interface ControlPointDefinition extends ControllableGoalDefinition {
|
||||
|
||||
@Override
|
||||
ControlPoint getGoal(Match match);
|
||||
|
||||
Region captureRegion();
|
||||
|
||||
Region progressDisplayRegion();
|
||||
|
||||
Region controllerDisplayRegion();
|
||||
|
||||
Filter visualMaterials();
|
||||
|
||||
}
|
||||
|
||||
class ControlPointDefinitionImpl extends ControllableGoalDefinitionImpl implements ControlPointDefinition {
|
||||
|
||||
private final Region captureRegion;
|
||||
private final Region progressDisplayRegion;
|
||||
private final Region controllerDisplayRegion;
|
||||
private final Filter visualMaterials;
|
||||
|
||||
public ControlPointDefinitionImpl(String name, @Nullable Boolean required, boolean visible, Filter captureFilter, Filter defendFilter, Duration captureTime, double multiplierTime, double recoveryRate, double decayRate, Optional<TeamFactory> initialOwner, CaptureCondition captureCondition, boolean neutralState, boolean permanent, float pointsOwned, float pointsPerSecond, float pointsGrowth, boolean showProgress,
|
||||
Region captureRegion,
|
||||
Region progressDisplayRegion,
|
||||
Region controllerDisplayRegion,
|
||||
Filter visualMaterials) {
|
||||
super(name, required, visible, captureFilter, defendFilter, captureTime, multiplierTime, recoveryRate, decayRate, initialOwner, captureCondition, neutralState, permanent, pointsOwned, pointsPerSecond, pointsGrowth, showProgress);
|
||||
this.captureRegion = captureRegion;
|
||||
this.progressDisplayRegion = progressDisplayRegion;
|
||||
this.controllerDisplayRegion = controllerDisplayRegion;
|
||||
this.visualMaterials = visualMaterials;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Region captureRegion() {
|
||||
return captureRegion;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Region progressDisplayRegion() {
|
||||
return progressDisplayRegion;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Region controllerDisplayRegion() {
|
||||
return controllerDisplayRegion;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Filter visualMaterials() {
|
||||
return visualMaterials;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ControlPoint getGoal(Match match) {
|
||||
return (ControlPoint) super.getGoal(match);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Feature<?> createFeature(Match match) throws ModuleLoadException {
|
||||
return new ControlPoint(match, this);
|
||||
}
|
||||
|
||||
}
|
|
@ -1,12 +1,12 @@
|
|||
package tc.oc.pgm.controlpoint;
|
||||
package tc.oc.pgm.control.point;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.util.Optional;
|
||||
import java.util.stream.Stream;
|
||||
import javax.inject.Inject;
|
||||
|
||||
import com.google.common.collect.Range;
|
||||
import org.bukkit.Material;
|
||||
import org.bukkit.util.Vector;
|
||||
import org.jdom2.Element;
|
||||
import tc.oc.pgm.features.FeatureDefinitionParser;
|
||||
import tc.oc.pgm.features.FeatureParser;
|
||||
|
@ -31,7 +31,6 @@ public final class ControlPointParser implements FeatureDefinitionParser<Control
|
|||
Material.STAINED_GLASS,
|
||||
Material.STAINED_GLASS_PANE)
|
||||
.map(material -> MaterialFilter.of(MaterialPattern.accepting(material))));
|
||||
|
||||
private final RegionParser regionParser;
|
||||
private final FilterParser filterParser;
|
||||
private final FeatureParser<TeamFactory> teamParser;
|
||||
|
@ -45,7 +44,6 @@ public final class ControlPointParser implements FeatureDefinitionParser<Control
|
|||
@Override
|
||||
public ControlPointDefinition parseElement(Element elControlPoint) throws InvalidXMLException {
|
||||
final boolean koth = "hill".equals(elControlPoint.getName());
|
||||
|
||||
Region captureRegion = regionParser.property(elControlPoint, "capture-region")
|
||||
.alias("capture")
|
||||
.union();
|
||||
|
@ -59,24 +57,18 @@ public final class ControlPointParser implements FeatureDefinitionParser<Control
|
|||
.optionalUnion(null);
|
||||
Filter captureFilter = filterParser.property(elControlPoint, "capture-filter").optional(null);
|
||||
Filter playerFilter = filterParser.property(elControlPoint, "player-filter").optional(null);
|
||||
|
||||
Filter visualMaterials = filterParser.property(elControlPoint, "visual-materials")
|
||||
.optionalMulti()
|
||||
.<Filter>map(AnyFilter::new)
|
||||
.orElse(VISUAL_MATERIALS);
|
||||
|
||||
String name = elControlPoint.getAttributeValue("name", "Hill");
|
||||
TeamFactory initialOwner = teamParser.property(elControlPoint, "initial-owner").optional(null);
|
||||
Vector capturableDisplayBeacon = XMLUtils.parseVector(elControlPoint.getAttribute("beacon"));
|
||||
Duration timeToCapture = XMLUtils.parseDuration(elControlPoint.getAttribute("capture-time"), Duration.ofSeconds(30));
|
||||
|
||||
double timeMultiplier = XMLUtils.parseNumber(elControlPoint.getAttribute("time-multiplier"), Double.class, koth ? 0.1D : 0D);
|
||||
|
||||
final double recoveryRate, decayRate;
|
||||
final Node attrIncremental = Node.fromAttr(elControlPoint, "incremental");
|
||||
final Node attrRecovery = Node.fromAttr(elControlPoint, "recovery");
|
||||
final Node attrDecay = Node.fromAttr(elControlPoint, "decay");
|
||||
|
||||
if(attrIncremental == null) {
|
||||
recoveryRate = XMLUtils.parseNumber(attrRecovery, Double.class, Range.atLeast(0D), koth ? 1D : Double.POSITIVE_INFINITY);
|
||||
decayRate = XMLUtils.parseNumber(attrDecay, Double.class, Range.atLeast(0D), koth ? 0D : Double.POSITIVE_INFINITY);
|
||||
|
@ -88,7 +80,6 @@ public final class ControlPointParser implements FeatureDefinitionParser<Control
|
|||
recoveryRate = incremental ? 1D : Double.POSITIVE_INFINITY;
|
||||
decayRate = incremental ? 0D : Double.POSITIVE_INFINITY;
|
||||
}
|
||||
|
||||
boolean neutralState = XMLUtils.parseBoolean(elControlPoint.getAttribute("neutral-state"), koth);
|
||||
boolean permanent = XMLUtils.parseBoolean(elControlPoint.getAttribute("permanent"), false);
|
||||
float pointsOwned = XMLUtils.parseNumber(elControlPoint.getAttribute("owner-points"), Float.class, 0f);
|
||||
|
@ -97,20 +88,18 @@ public final class ControlPointParser implements FeatureDefinitionParser<Control
|
|||
boolean showProgress = XMLUtils.parseBoolean(elControlPoint.getAttribute("show-progress"), koth);
|
||||
boolean visible = XMLUtils.parseBoolean(elControlPoint.getAttribute("show"), true);
|
||||
Boolean required = XMLUtils.parseBoolean(elControlPoint.getAttribute("required"), null);
|
||||
|
||||
ControlPointDefinition.CaptureCondition captureCondition =
|
||||
XMLUtils.parseEnum(Node.fromAttr(elControlPoint, "capture-rule"),
|
||||
ControlPointDefinition.CaptureCondition.class,
|
||||
"capture rule",
|
||||
ControlPointDefinition.CaptureCondition.EXCLUSIVE);
|
||||
|
||||
return new ControlPointDefinitionImpl(
|
||||
name, required, visible,
|
||||
captureRegion, captureFilter, playerFilter,
|
||||
progressDisplayRegion, ownerDisplayRegion, visualMaterials,
|
||||
capturableDisplayBeacon == null ? null : capturableDisplayBeacon.toBlockVector(),
|
||||
timeToCapture, timeMultiplier, recoveryRate, decayRate, initialOwner, captureCondition,
|
||||
neutralState, permanent, pointsOwned, pointsPerSecond, pointsGrowth, showProgress
|
||||
name, required, visible, captureFilter, playerFilter,
|
||||
timeToCapture, timeMultiplier, recoveryRate, decayRate,
|
||||
Optional.ofNullable(initialOwner), captureCondition, neutralState, permanent,
|
||||
pointsOwned, pointsPerSecond, pointsGrowth, showProgress,
|
||||
captureRegion, progressDisplayRegion, ownerDisplayRegion, visualMaterials
|
||||
);
|
||||
}
|
||||
|
||||
}
|
|
@ -1,15 +1,15 @@
|
|||
package tc.oc.pgm.controlpoint;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.stream.Stream;
|
||||
package tc.oc.pgm.control.point;
|
||||
|
||||
import org.jdom2.Element;
|
||||
import tc.oc.pgm.utils.XMLUtils;
|
||||
import tc.oc.pgm.xml.Node;
|
||||
import tc.oc.pgm.xml.finder.NodeFinder;
|
||||
|
||||
class ControlPointRootNodeFinder implements NodeFinder {
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
public class ControlPointRootNodeFinder implements NodeFinder {
|
||||
@Override
|
||||
public Stream<Node> findNodes(Element parent, String name) {
|
||||
final List<Element> elements = new ArrayList<>();
|
|
@ -1,152 +0,0 @@
|
|||
package tc.oc.pgm.controlpoint;
|
||||
|
||||
import org.bukkit.block.Block;
|
||||
import org.bukkit.event.EventHandler;
|
||||
import org.bukkit.event.EventPriority;
|
||||
import org.bukkit.event.Listener;
|
||||
import org.bukkit.util.BlockVector;
|
||||
import org.bukkit.util.Vector;
|
||||
import tc.oc.commons.bukkit.util.BlockUtils;
|
||||
import tc.oc.commons.bukkit.util.BukkitUtils;
|
||||
import tc.oc.pgm.events.ListenerScope;
|
||||
import tc.oc.pgm.filters.operator.AllFilter;
|
||||
import tc.oc.pgm.filters.operator.InverseFilter;
|
||||
import tc.oc.pgm.match.Competitor;
|
||||
import tc.oc.pgm.match.Match;
|
||||
import tc.oc.pgm.filters.query.BlockQuery;
|
||||
import tc.oc.pgm.controlpoint.events.CapturingTimeChangeEvent;
|
||||
import tc.oc.pgm.controlpoint.events.ControllerChangeEvent;
|
||||
import tc.oc.pgm.filters.Filter;
|
||||
import tc.oc.pgm.match.MatchScope;
|
||||
import tc.oc.pgm.regions.FiniteBlockRegion;
|
||||
import tc.oc.pgm.regions.Region;
|
||||
import tc.oc.pgm.regions.SectorRegion;
|
||||
import tc.oc.pgm.renewable.BlockImage;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* Displays the status of a ControlPoint by coloring blocks in specified regions
|
||||
*/
|
||||
@ListenerScope(MatchScope.LOADED)
|
||||
public class ControlPointBlockDisplay implements Listener {
|
||||
protected final Match match;
|
||||
protected final ControlPoint controlPoint;
|
||||
|
||||
protected final FiniteBlockRegion progressDisplayRegion;
|
||||
protected final BlockImage progressDisplayImage;
|
||||
protected final FiniteBlockRegion controllerDisplayRegion;
|
||||
protected final BlockImage controllerDisplayImage;
|
||||
|
||||
protected Competitor controllingTeam;
|
||||
|
||||
public ControlPointBlockDisplay(Match match, ControlPoint controlPoint) {
|
||||
this.match = match;
|
||||
this.controlPoint = controlPoint;
|
||||
|
||||
Filter visualMaterials = controlPoint.getDefinition().getVisualMaterials();
|
||||
Region progressDisplayRegion = controlPoint.getDefinition().getProgressDisplayRegion();
|
||||
Region controllerDisplayRegion = controlPoint.getDefinition().getControllerDisplayRegion();
|
||||
|
||||
final FiniteBlockRegion.Factory regionFactory = new FiniteBlockRegion.Factory(match.getMapInfo().proto);
|
||||
|
||||
if(progressDisplayRegion == null) {
|
||||
this.progressDisplayRegion = null;
|
||||
this.progressDisplayImage = null;
|
||||
} else {
|
||||
this.progressDisplayRegion = regionFactory.fromWorld(progressDisplayRegion,
|
||||
match.getWorld(),
|
||||
visualMaterials);
|
||||
this.progressDisplayImage = new BlockImage(match.getWorld(), this.progressDisplayRegion.getBounds());
|
||||
this.progressDisplayImage.save();
|
||||
}
|
||||
|
||||
if(controllerDisplayRegion == null) {
|
||||
this.controllerDisplayRegion = null;
|
||||
this.controllerDisplayImage = null;
|
||||
} else {
|
||||
// Ensure the controller and progress display regions do not overlap. The progress display has priority.
|
||||
this.controllerDisplayRegion = regionFactory.fromWorld(
|
||||
controllerDisplayRegion,
|
||||
match.getWorld(),
|
||||
this.progressDisplayRegion == null ? visualMaterials
|
||||
: AllFilter.of(visualMaterials, new InverseFilter(progressDisplayRegion))
|
||||
);
|
||||
|
||||
this.controllerDisplayImage = new BlockImage(match.getWorld(), this.controllerDisplayRegion.getBounds());
|
||||
this.controllerDisplayImage.save();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Change the controller display to the given team's color, or reset the display if team is null
|
||||
*/
|
||||
@SuppressWarnings("deprecation")
|
||||
public void setController(Competitor controllingTeam) {
|
||||
if(!Objects.equals(this.controllingTeam, controllingTeam) && this.controllerDisplayRegion != null) {
|
||||
if(controllingTeam == null) {
|
||||
for(BlockVector block : this.controllerDisplayRegion.getBlockVectors()) {
|
||||
this.controllerDisplayImage.restore(block);
|
||||
}
|
||||
} else {
|
||||
byte blockData = BukkitUtils.chatColorToDyeColor(controllingTeam.getColor()).getWoolData();
|
||||
for(BlockVector pos : this.controllerDisplayRegion.getBlockVectors()) {
|
||||
BlockUtils.blockAt(match.getWorld(), pos).setData(blockData);
|
||||
}
|
||||
}
|
||||
this.controllingTeam = controllingTeam;
|
||||
}
|
||||
}
|
||||
|
||||
private void setBlock(BlockVector pos, Competitor team) {
|
||||
final Block block = BlockUtils.blockAt(match.getWorld(), pos);
|
||||
if(this.controlPoint.getDefinition().getVisualMaterials().query(new BlockQuery(block)).isAllowed()) {
|
||||
if(team != null) {
|
||||
block.setData(BukkitUtils.chatColorToDyeColor(team.getColor()).getWoolData());
|
||||
} else {
|
||||
this.progressDisplayImage.restore(pos);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected void setProgress(Competitor controllingTeam, Competitor capturingTeam, double capturingProgress) {
|
||||
if(this.progressDisplayRegion != null) {
|
||||
Vector center = this.progressDisplayRegion.getBounds().center();
|
||||
|
||||
// capturingProgress can be zero, but it can never be one, so invert it to avoid
|
||||
// a zero-area SectorRegion that can cause glitchy rendering
|
||||
SectorRegion sectorRegion = new SectorRegion(center.getX(), center.getZ(), 0, (1 - capturingProgress) * 2 * Math.PI);
|
||||
|
||||
for(BlockVector pos : this.progressDisplayRegion.getBlockVectors()) {
|
||||
if(sectorRegion.contains(pos)) {
|
||||
this.setBlock(pos, controllingTeam);
|
||||
} else {
|
||||
this.setBlock(pos, capturingTeam);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void render() {
|
||||
this.setController(this.controlPoint.getOwner());
|
||||
this.setProgress(this.controlPoint.getOwner(),
|
||||
this.controlPoint.getCapturer(),
|
||||
this.controlPoint.getCompletion());
|
||||
}
|
||||
|
||||
@EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
|
||||
public void onTimeChange(CapturingTimeChangeEvent event) {
|
||||
if(this.controlPoint == event.getControlPoint()) {
|
||||
this.setProgress(event.getControlPoint().getOwner(),
|
||||
event.getControlPoint().getCapturer(),
|
||||
event.getControlPoint().getCompletion());
|
||||
}
|
||||
}
|
||||
|
||||
@EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
|
||||
public void onControllerChange(ControllerChangeEvent event) {
|
||||
if(this.controlPoint == event.getControlPoint()) {
|
||||
this.setController(event.getNewController());
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,316 +0,0 @@
|
|||
package tc.oc.pgm.controlpoint;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.util.stream.Stream;
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
import org.bukkit.util.BlockVector;
|
||||
import tc.oc.api.docs.virtual.MapDoc;
|
||||
import tc.oc.pgm.features.FeatureInfo;
|
||||
import tc.oc.pgm.features.GamemodeFeature;
|
||||
import tc.oc.pgm.filters.Filter;
|
||||
import tc.oc.pgm.goals.GoalDefinition;
|
||||
import tc.oc.pgm.goals.GoalDefinitionImpl;
|
||||
import tc.oc.pgm.match.Match;
|
||||
import tc.oc.pgm.regions.Region;
|
||||
import tc.oc.pgm.teams.TeamFactory;
|
||||
|
||||
@FeatureInfo(name = "control-point",
|
||||
plural = {"control-points", "hills"},
|
||||
singular = {"control-point", "hill"})
|
||||
public interface ControlPointDefinition extends GoalDefinition, GamemodeFeature {
|
||||
|
||||
@Override ControlPoint getGoal(Match match);
|
||||
|
||||
Region getCaptureRegion();
|
||||
|
||||
Filter getCaptureFilter();
|
||||
|
||||
Filter getPlayerFilter();
|
||||
|
||||
Region getProgressDisplayRegion();
|
||||
|
||||
Region getControllerDisplayRegion();
|
||||
|
||||
Filter getVisualMaterials();
|
||||
|
||||
BlockVector getCapturableDisplayBeacon();
|
||||
|
||||
Duration getTimeToCapture();
|
||||
|
||||
double decayRate();
|
||||
|
||||
double recoveryRate();
|
||||
|
||||
double getTimeMultiplier();
|
||||
|
||||
TeamFactory getInitialOwner();
|
||||
|
||||
CaptureCondition getCaptureCondition();
|
||||
|
||||
boolean hasNeutralState();
|
||||
|
||||
boolean isPermanent();
|
||||
|
||||
boolean affectsScore();
|
||||
|
||||
float getPointsOwned();
|
||||
|
||||
float getPointsPerSecond();
|
||||
|
||||
float getPointsGrowth();
|
||||
|
||||
boolean getShowProgress();
|
||||
|
||||
// Conditions required for a team to capture:
|
||||
enum CaptureCondition {
|
||||
EXCLUSIVE, // Team owns all players on the point
|
||||
MAJORITY, // Team owns more than half the players on the point
|
||||
LEAD // Team owns more players on the point than any other single team
|
||||
}
|
||||
}
|
||||
|
||||
class ControlPointDefinitionImpl extends GoalDefinitionImpl implements ControlPointDefinition {
|
||||
// Players in this region are considered "on" the point
|
||||
private final Region captureRegion;
|
||||
|
||||
// Which players can capture the point
|
||||
private final Filter captureFilter;
|
||||
|
||||
// Which players can prevent other teams from capturing the point
|
||||
private final Filter playerFilter;
|
||||
|
||||
// Blocks in this region are used to show capturing progress
|
||||
private final Region progressDisplayRegion;
|
||||
|
||||
// Blocks in this region are used to show the team that owns the point
|
||||
private final Region ownerDisplayRegion;
|
||||
|
||||
// Block types used for the regions above (currently fixed to wool and stained clay)
|
||||
private final Filter visualMaterials;
|
||||
|
||||
// Location of a beacon used to indicate to players that they can capture this point
|
||||
private final BlockVector capturableDisplayBeacon;
|
||||
|
||||
// Base time for the point to transition between states
|
||||
private final Duration timeToCapture;
|
||||
|
||||
// Capture time multiplier for increasing or decreasing capture time based on the number of players on the point
|
||||
private final double timeMultiplier;
|
||||
|
||||
// Relative rate at which progress reverts from players dominating the point
|
||||
private final double recoveryRate;
|
||||
|
||||
// Relative rate at which progress reverts while nobody is dominating the point
|
||||
private final double decayRate;
|
||||
|
||||
// The team that owns the point when the match starts, null for no owner (neutral state)
|
||||
private final TeamFactory initialOwner;
|
||||
private final CaptureCondition captureCondition;
|
||||
|
||||
// true: point must transition through unowned state to change owners
|
||||
// false: point transitions directly from one owner to the next
|
||||
// NOTE: points always start in an unowned state, regardless of this value
|
||||
private final boolean neutralState;
|
||||
|
||||
// If true, the point can only be captured once in the match
|
||||
private final boolean permanent;
|
||||
|
||||
// Amount of points given to the team that owns the point, they are removed when the team looses the point
|
||||
private final float pointsOwned;
|
||||
|
||||
// Rate that the owner's score increases, or 0 if the CP does not affect score
|
||||
private final float pointsPerSecond;
|
||||
|
||||
// If this is less than +inf, the effective pointsPerSecond will increase over time
|
||||
// at an exponential rate, such that it doubles every time this many seconds elapses.
|
||||
private final float pointsGrowth;
|
||||
|
||||
// If true, capturing progress is displayed on the scoreboard
|
||||
private final boolean showProgress;
|
||||
|
||||
public ControlPointDefinitionImpl(String name,
|
||||
@Nullable Boolean required,
|
||||
boolean visible,
|
||||
Region captureRegion,
|
||||
Filter captureFilter,
|
||||
Filter playerFilter,
|
||||
Region progressDisplayRegion,
|
||||
Region ownerDisplayRegion,
|
||||
Filter visualMaterials,
|
||||
BlockVector capturableDisplayBeacon,
|
||||
Duration timeToCapture,
|
||||
double timeMultiplier,
|
||||
double recoveryRate,
|
||||
double decayRate,
|
||||
TeamFactory initialOwner,
|
||||
CaptureCondition captureCondition,
|
||||
boolean neutralState,
|
||||
boolean permanent,
|
||||
float pointsOwned,
|
||||
float pointsPerSecond,
|
||||
float pointsGrowth,
|
||||
boolean progress) {
|
||||
|
||||
super(name, required, visible);
|
||||
this.captureRegion = captureRegion;
|
||||
this.captureFilter = captureFilter;
|
||||
this.playerFilter = playerFilter;
|
||||
this.progressDisplayRegion = progressDisplayRegion;
|
||||
this.ownerDisplayRegion = ownerDisplayRegion;
|
||||
this.visualMaterials = visualMaterials;
|
||||
this.capturableDisplayBeacon = capturableDisplayBeacon;
|
||||
this.timeToCapture = timeToCapture;
|
||||
this.timeMultiplier = timeMultiplier;
|
||||
this.recoveryRate = recoveryRate;
|
||||
this.decayRate = decayRate;
|
||||
this.initialOwner = initialOwner;
|
||||
this.captureCondition = captureCondition;
|
||||
this.neutralState = neutralState;
|
||||
this.permanent = permanent;
|
||||
this.pointsOwned = pointsOwned;
|
||||
this.pointsPerSecond = pointsPerSecond;
|
||||
this.pointsGrowth = pointsGrowth;
|
||||
this.showProgress = progress;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "ControlPointDefinition {name=" + this.getName() +
|
||||
" timeToCapture=" + this.getTimeToCapture() +
|
||||
" timeMultiplier=" + this.getTimeMultiplier() +
|
||||
" initialOwner=" + this.getInitialOwner() +
|
||||
" captureCondition=" + this.getCaptureCondition() +
|
||||
" neutralState=" + this.hasNeutralState() +
|
||||
" permanent=" + this.isPermanent() +
|
||||
" captureRegion=" + this.getCaptureRegion() +
|
||||
" captureFilter=" + this.getCaptureFilter() +
|
||||
" playerFilter=" + this.getPlayerFilter() +
|
||||
" progressDisplay=" + this.getProgressDisplayRegion() +
|
||||
" ownerDisplay=" + this.getControllerDisplayRegion() +
|
||||
" beacon=" + this.getCapturableDisplayBeacon() +
|
||||
" visible=" + this.isVisible();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Stream<MapDoc.Gamemode> gamemodes() {
|
||||
return Stream.of(MapDoc.Gamemode.koth);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ControlPoint getGoal(Match match) {
|
||||
return (ControlPoint) super.getGoal(match);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ControlPoint createFeature(Match match) {
|
||||
final ControlPoint cp = new ControlPoint(match, this);
|
||||
cp.registerEvents();
|
||||
return cp;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isShared() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Region getCaptureRegion() {
|
||||
return this.captureRegion;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Filter getCaptureFilter() {
|
||||
return this.captureFilter;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Filter getPlayerFilter() {
|
||||
return this.playerFilter;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Region getProgressDisplayRegion() {
|
||||
return this.progressDisplayRegion;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Region getControllerDisplayRegion() {
|
||||
return this.ownerDisplayRegion;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Filter getVisualMaterials() {
|
||||
return this.visualMaterials;
|
||||
}
|
||||
|
||||
@Override
|
||||
public BlockVector getCapturableDisplayBeacon() {
|
||||
return this.capturableDisplayBeacon;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Duration getTimeToCapture() {
|
||||
return this.timeToCapture;
|
||||
}
|
||||
|
||||
@Override
|
||||
public double getTimeMultiplier() {
|
||||
return this.timeMultiplier;
|
||||
}
|
||||
|
||||
@Override
|
||||
public double recoveryRate() {
|
||||
return this.recoveryRate;
|
||||
}
|
||||
|
||||
@Override
|
||||
public double decayRate() {
|
||||
return this.decayRate;
|
||||
}
|
||||
|
||||
@Override
|
||||
public TeamFactory getInitialOwner() {
|
||||
return this.initialOwner;
|
||||
}
|
||||
|
||||
@Override
|
||||
public CaptureCondition getCaptureCondition() {
|
||||
return this.captureCondition;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasNeutralState() {
|
||||
return this.neutralState;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isPermanent() {
|
||||
return this.permanent;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean affectsScore() {
|
||||
return this.pointsPerSecond > 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public float getPointsOwned() {
|
||||
return this.pointsOwned;
|
||||
}
|
||||
|
||||
@Override
|
||||
public float getPointsPerSecond() {
|
||||
return this.pointsPerSecond;
|
||||
}
|
||||
|
||||
@Override
|
||||
public float getPointsGrowth() {
|
||||
return this.pointsGrowth;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean getShowProgress() {
|
||||
return this.showProgress;
|
||||
}
|
||||
}
|
|
@ -1,53 +0,0 @@
|
|||
package tc.oc.pgm.controlpoint;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.util.List;
|
||||
import javax.inject.Inject;
|
||||
|
||||
import org.bukkit.event.HandlerList;
|
||||
import tc.oc.pgm.match.Match;
|
||||
import tc.oc.pgm.match.MatchModule;
|
||||
import tc.oc.pgm.match.Repeatable;
|
||||
import tc.oc.time.Time;
|
||||
|
||||
import static tc.oc.commons.core.stream.Collectors.toImmutableList;
|
||||
|
||||
public class ControlPointMatchModule extends MatchModule {
|
||||
|
||||
private static final long MILLIS = 100;
|
||||
private static final Duration INTERVAL = Duration.ofMillis(MILLIS); // milliseconds, two ticks
|
||||
|
||||
private final List<ControlPoint> controlPoints;
|
||||
private final ControlPointAnnouncer announcer;
|
||||
|
||||
@Inject private ControlPointMatchModule(Match match) {
|
||||
this.announcer = new ControlPointAnnouncer(match);
|
||||
this.controlPoints = match.featureDefinitions()
|
||||
.all(ControlPointDefinition.class)
|
||||
.map(cp -> cp.getGoal(match))
|
||||
.collect(toImmutableList());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void load() {
|
||||
super.load();
|
||||
getMatch().registerEvents(this.announcer);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void unload() {
|
||||
for(ControlPoint controlPoint : this.controlPoints) {
|
||||
controlPoint.unregisterEvents();
|
||||
}
|
||||
HandlerList.unregisterAll(this.announcer);
|
||||
super.unload();
|
||||
}
|
||||
|
||||
@Repeatable(interval = @Time(milliseconds = MILLIS))
|
||||
public void tick() {
|
||||
for(ControlPoint controlPoint : controlPoints) {
|
||||
controlPoint.tick(INTERVAL);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -1,92 +0,0 @@
|
|||
package tc.oc.pgm.controlpoint;
|
||||
|
||||
import com.google.common.collect.Sets;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.event.EventHandler;
|
||||
import org.bukkit.event.EventPriority;
|
||||
import org.bukkit.event.Listener;
|
||||
import org.bukkit.event.player.PlayerTeleportEvent;
|
||||
import org.bukkit.util.Vector;
|
||||
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.spawns.events.ParticipantDespawnEvent;
|
||||
import tc.oc.commons.bukkit.event.CoarsePlayerMoveEvent;
|
||||
import tc.oc.pgm.regions.Region;
|
||||
import tc.oc.commons.core.util.DefaultMapAdapter;
|
||||
import tc.oc.pgm.utils.MatchPlayers;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* Tracks which players are on a control point and answers some queries about them
|
||||
*/
|
||||
@ListenerScope(MatchScope.LOADED)
|
||||
public class ControlPointPlayerTracker implements Listener {
|
||||
protected final Match match;
|
||||
protected final Region captureRegion;
|
||||
protected final Set<MatchPlayer> playersOnPoint = Sets.newHashSet();
|
||||
|
||||
public ControlPointPlayerTracker(Match match, Region captureRegion) {
|
||||
this.match = match;
|
||||
this.captureRegion = captureRegion;
|
||||
}
|
||||
|
||||
public Set<MatchPlayer> getPlayersOnPoint() {
|
||||
return this.playersOnPoint;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the number of players that each team in the match has on the point
|
||||
*/
|
||||
public Map<Competitor, Integer> getPlayerCountsByTeam() {
|
||||
// calculate how many players from each team are on the hill
|
||||
Map<Competitor, Integer> counts = new DefaultMapAdapter<>(new HashMap<>(), 0);
|
||||
for(MatchPlayer player : this.getPlayersOnPoint()) {
|
||||
Competitor team = player.getCompetitor();
|
||||
counts.put(team, counts.get(team) + 1);
|
||||
}
|
||||
return counts;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the number of players that each team in the match has on the point, sorted from most to least
|
||||
*/
|
||||
public List<Map.Entry<Competitor, Integer>> getSortedPlayerCountsByTeam() {
|
||||
// reverse natural ordering of value
|
||||
return new ArrayList<>(this.getPlayerCountsByTeam().entrySet()).stream().sorted((o1, o2) -> Integer.compare(o2.getValue(), o1.getValue())).collect(Collectors.toList());
|
||||
}
|
||||
|
||||
@EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
|
||||
public void onPlayerMove(final CoarsePlayerMoveEvent event) {
|
||||
this.handlePlayerMove(event.getPlayer(), event.getTo().toVector());
|
||||
}
|
||||
|
||||
@EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
|
||||
public void onPlayerTeleport(final PlayerTeleportEvent event) {
|
||||
this.handlePlayerMove(event.getPlayer(), event.getTo().toVector());
|
||||
}
|
||||
|
||||
private void handlePlayerMove(Player bukkit, Vector to) {
|
||||
MatchPlayer player = this.match.getPlayer(bukkit);
|
||||
if(!MatchPlayers.canInteract(player)) return;
|
||||
|
||||
if(!player.getBukkit().isDead() && this.captureRegion.contains(to.toBlockVector())) {
|
||||
this.playersOnPoint.add(player);
|
||||
} else {
|
||||
this.playersOnPoint.remove(player);
|
||||
}
|
||||
}
|
||||
|
||||
@EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
|
||||
public void onPlayerDespawn(final ParticipantDespawnEvent event) {
|
||||
playersOnPoint.remove(event.getPlayer());
|
||||
}
|
||||
}
|
|
@ -1,38 +0,0 @@
|
|||
package tc.oc.pgm.controlpoint.events;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
import org.bukkit.event.HandlerList;
|
||||
|
||||
import tc.oc.pgm.match.Competitor;
|
||||
import tc.oc.pgm.match.Match;
|
||||
import tc.oc.pgm.controlpoint.ControlPoint;
|
||||
|
||||
public class CapturingTeamChangeEvent extends ControlPointEvent {
|
||||
private static final HandlerList handlers = new HandlerList();
|
||||
@Nullable private final Competitor oldTeam;
|
||||
@Nullable private final Competitor newTeam;
|
||||
|
||||
public CapturingTeamChangeEvent(Match match, ControlPoint hill, Competitor oldTeam, Competitor newTeam) {
|
||||
super(match, hill);
|
||||
this.oldTeam = oldTeam;
|
||||
this.newTeam = newTeam;
|
||||
}
|
||||
|
||||
public @Nullable Competitor getOldTeam() {
|
||||
return this.oldTeam;
|
||||
}
|
||||
|
||||
public @Nullable Competitor getNewTeam() {
|
||||
return this.newTeam;
|
||||
}
|
||||
|
||||
@Override
|
||||
public HandlerList getHandlers() {
|
||||
return handlers;
|
||||
}
|
||||
|
||||
public static HandlerList getHandlerList() {
|
||||
return handlers;
|
||||
}
|
||||
}
|
|
@ -1,18 +0,0 @@
|
|||
package tc.oc.pgm.controlpoint.events;
|
||||
|
||||
import tc.oc.pgm.match.Match;
|
||||
import tc.oc.pgm.controlpoint.ControlPoint;
|
||||
import tc.oc.pgm.events.MatchEvent;
|
||||
|
||||
public abstract class ControlPointEvent extends MatchEvent {
|
||||
protected final ControlPoint controlPoint;
|
||||
|
||||
public ControlPointEvent(Match match, ControlPoint controlPoint) {
|
||||
super(match);
|
||||
this.controlPoint = controlPoint;
|
||||
}
|
||||
|
||||
public ControlPoint getControlPoint() {
|
||||
return this.controlPoint;
|
||||
}
|
||||
}
|
|
@ -1,40 +0,0 @@
|
|||
package tc.oc.pgm.controlpoint.events;
|
||||
|
||||
import org.bukkit.event.HandlerList;
|
||||
|
||||
import tc.oc.pgm.match.Competitor;
|
||||
import tc.oc.pgm.match.Match;
|
||||
import tc.oc.pgm.controlpoint.ControlPoint;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
public class ControllerChangeEvent extends ControlPointEvent {
|
||||
private static final HandlerList handlers = new HandlerList();
|
||||
@Nullable private final Competitor oldController;
|
||||
@Nullable private final Competitor newController;
|
||||
|
||||
public ControllerChangeEvent(Match match, ControlPoint hill, Competitor oldController, Competitor newController) {
|
||||
super(match, hill);
|
||||
this.oldController = oldController;
|
||||
this.newController = newController;
|
||||
}
|
||||
|
||||
@Override
|
||||
public HandlerList getHandlers() {
|
||||
return handlers;
|
||||
}
|
||||
|
||||
public static HandlerList getHandlerList() {
|
||||
return handlers;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public Competitor getNewController() {
|
||||
return newController;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public Competitor getOldController() {
|
||||
return oldController;
|
||||
}
|
||||
}
|
|
@ -12,7 +12,7 @@ import org.bukkit.event.Listener;
|
|||
import org.bukkit.geometry.Cuboid;
|
||||
import tc.oc.commons.bukkit.util.BlockUtils;
|
||||
import tc.oc.commons.bukkit.util.BukkitUtils;
|
||||
import tc.oc.pgm.controlpoint.events.ControllerChangeEvent;
|
||||
import tc.oc.pgm.control.events.ControllableOwnerChangeEvent;
|
||||
import tc.oc.pgm.core.CoreLeakEvent;
|
||||
import tc.oc.pgm.destroyable.DestroyableDestroyedEvent;
|
||||
import tc.oc.pgm.flag.event.FlagCaptureEvent;
|
||||
|
@ -78,11 +78,11 @@ public class ObjectivesFireworkListener implements Listener {
|
|||
}
|
||||
|
||||
@EventHandler(priority = EventPriority.MONITOR)
|
||||
public void onHillCapture(final ControllerChangeEvent event) {
|
||||
if(FireworksConfig.Goals.enabled() && event.getControlPoint().isVisible() && event.getNewController() != null) {
|
||||
public void onHillCapture(final ControllableOwnerChangeEvent event) {
|
||||
if(FireworksConfig.Goals.enabled() && event.controllable().isVisible() && event.newController() != null) {
|
||||
this.spawnFireworkDisplay(event.getMatch().getWorld(),
|
||||
event.getControlPoint().getCaptureRegion(),
|
||||
BukkitUtils.colorOf(event.getNewController().getColor()),
|
||||
event.controllable().region(),
|
||||
BukkitUtils.colorOf(event.newController().getColor()),
|
||||
8, 1, 2);
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue