Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add decay-time, recovery-time, and owned-decay-time #864

Merged
merged 3 commits into from
May 19, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
123 changes: 74 additions & 49 deletions core/src/main/java/tc/oc/pgm/controlpoint/ControlPoint.java
Original file line number Diff line number Diff line change
Expand Up @@ -403,37 +403,39 @@ private void dominateAndFireEvents(@Nullable Competitor dominantTeam, Duration d
*
* <p>If there is no neutral state, then the point is always either being captured by a specific
* team, or not being captured at all.
*
* <p>If incremental capturing is disabled, then capturingTimeMillis is reset to zero whenever it
* stops increasing.
*/
private void dominate(Competitor dominantTeam, Duration dominantTime) {
if (!this.capturable || !TimeUtils.isLongerThan(dominantTime, Duration.ZERO)) {
return;
}

ControlPointDefinition definition = this.getDefinition();

if (this.controllingTeam != null && definition.hasNeutralState()) {
// Point is owned and must go through the neutral state before another team can capture it
if (dominantTeam == this.controllingTeam) {
this.regressCapture(dominantTeam, dominantTime);
// owner is recovering the point
recover(dominantTeam, dominantTime);
} else if (dominantTeam != null) {
this.progressUncapture(dominantTeam, dominantTime);
} else if (!definition.isIncrementalCapture()) {
// No team is dominant and point is not incremental, so reset the time
this.capturingTime = Duration.ZERO;
// non-owner is uncapturing the point
uncapture(dominantTeam, dominantTime);
} else if (definition.getOwnedDecayRate() > 0) {
// nobody on point so decay to neutral state
ownedDecay(dominantTime);
} else {
// nobody on point, so "decay" to fully captured
decay(dominantTime);
}
} else if (this.capturingTeam != null) {
// Point is being captured by a specific team
if (dominantTeam == this.capturingTeam) {
this.progressCapture(dominantTeam, dominantTime);
// capturing team is making progress
capture(dominantTime);
} else if (dominantTeam != null) {
this.regressCapture(dominantTeam, dominantTime);
} else if (!definition.isIncrementalCapture()) {
// No team is dominant and point is not incremental, so reset time and clear capturing team
this.capturingTime = Duration.ZERO;
this.capturingTeam = null;
// non-capturing team is dominate, so regress capturing team's progress
recover(dominantTeam, dominantTime);
} else {
// No team is dominating so decay
decay(dominantTime);
}
} else if (dominantTeam != null
&& dominantTeam != this.controllingTeam
Expand All @@ -445,24 +447,30 @@ private void dominate(Competitor dominantTeam, Duration dominantTime) {
}
}

/** Progress toward the neutral state */
private void progressUncapture(Competitor dominantTeam, Duration dominantTime) {
this.capturingTime = this.capturingTime.plus(dominantTime);

if (!TimeUtils.isShorterThan(this.capturingTime, this.definition.getTimeToCapture())) {
// If uncapture is complete, recurse with the dominant team's remaining time
dominantTime = this.capturingTime.minus(this.definition.getTimeToCapture());
private @Nullable Duration addCaptureTime(final Duration duration) {
this.capturingTime = this.capturingTime.plus(duration);
if (!TimeUtils.isLongerThan(definition.getTimeToCapture(), this.capturingTime)) {
final Duration remainder = this.capturingTime.minus(definition.getTimeToCapture());
this.capturingTime = Duration.ZERO;
this.controllingTeam = null;
this.dominate(dominantTeam, dominantTime);
return remainder;
}
return null;
}

/** Progress toward a new controller */
private void progressCapture(Competitor dominantTeam, Duration dominantTime) {
this.capturingTime = this.capturingTime.plus(dominantTime);
if (!TimeUtils.isShorterThan(this.capturingTime, this.definition.getTimeToCapture())) {
this.capturingTime = Duration.ZERO;
private @Nullable Duration subtractCaptureTime(final Duration duration) {
if (TimeUtils.isLongerThan(this.capturingTime, duration)) {
this.capturingTime = this.capturingTime.minus(duration);
return null;
} else {
final Duration remainder = duration.minus(this.capturingTime);
this.capturingTime = duration.ZERO;
return remainder;
}
}
// Progress to a new owner
private void capture(Duration dominantTime) {
dominantTime = addCaptureTime(dominantTime);
if (dominantTime != null) { // Point is captured
this.controllingTeam = this.capturingTeam;
this.capturingTeam = null;
if (this.getDefinition().isPermanent()) {
Expand All @@ -471,31 +479,48 @@ private void progressCapture(Competitor dominantTeam, Duration dominantTime) {
}
}
}

/** Regress toward the current state */
private void regressCapture(Competitor dominantTeam, Duration dominantTime) {
boolean crossZero = false;
if (definition.isIncrementalCapture()) {
// For incremental points, decrease the capture time
if (TimeUtils.isLongerThan(this.capturingTime, dominantTime)) {
this.capturingTime = this.capturingTime.minus(dominantTime);
} else {
dominantTime = dominantTime.minus(this.capturingTime);
this.capturingTime = Duration.ZERO;
crossZero = true;
}
} else {
// For non-incremental points, reset capture time to zero
this.capturingTime = Duration.ZERO;
crossZero = true;
// Progress towards the neutral state
private void uncapture(Competitor dominantTeam, Duration dominantTime) {
dominantTime = addCaptureTime(dominantTime);
if (dominantTime != null) {
this.controllingTeam = null;
this.dominate(dominantTeam, dominantTime);
}

if (crossZero) {
}
// Point being pulled back to current state (There is a lead on the point)
private void recover(Competitor dominantTeam, Duration dominantTime) {
dominantTime =
subtractCaptureTime(
Duration.ofMillis((long) (definition.getRecoveryRate() * dominantTime.toMillis())));
if (dominantTeam != null) {
this.capturingTeam = null;
if (dominantTeam != this.controllingTeam) {
// If the dominant team is not the controller, recurse with the remaining time
this.dominate(dominantTeam, dominantTime);
this.dominate(
dominantTeam,
Duration.ofMillis(
(long) ((1.0 / definition.getRecoveryRate()) * dominantTime.toMillis())));
}
}
}
// Point is being decayed back to its current state (No lead on point)
private void decay(Duration dominantTime) {
dominantTime =
subtractCaptureTime(
Duration.ofMillis((long) (definition.getDecayRate() * dominantTime.toMillis())));
if (dominantTime != null) {
this.capturingTeam = null;
}
}

// Point is being decayed back to neutral (No lead on point)
private void ownedDecay(Duration dominantTime) {
dominantTime =
addCaptureTime(
Duration.ofMillis((long) (definition.getOwnedDecayRate() * dominantTime.toMillis())));
if (dominantTime != null) {
this.controllingTeam = null;
this.capturingTeam = null;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,15 @@ public class ControlPointDefinition extends GoalDefinition {
// Base time for the point to transition between states
private final Duration timeToCapture;

// Time it takes for a point to decay while unowned. (Time is accurate when near 100% capture)
private final double decayRate;

// Time it takes for a point to recover to captured state. (Accurate when almost uncaptured)
private final double recoveryRate;

// Time it takes for a point to transition to neutral state.
private final double ownedDecayRate;

// Capture time multiplier for increasing or decreasing capture time based on the number of
// players on the point
private final float timeMultiplier;
Expand All @@ -54,10 +63,6 @@ public enum CaptureCondition {

private final CaptureCondition captureCondition;

// true: progress is retained if capturing is interrupted
// false: progress resets to zero if capturing is interrupted
private final boolean incrementalCapture;

// 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
Expand Down Expand Up @@ -92,10 +97,12 @@ public ControlPointDefinition(
Filter visualMaterials,
BlockVector capturableDisplayBeacon,
Duration timeToCapture,
double decayRate,
double recoveryRate,
double ownedDecayRate,
float timeMultiplier,
@Nullable TeamFactory initialOwner,
CaptureCondition captureCondition,
boolean incrementalCapture,
boolean neutralState,
boolean permanent,
float pointsPerSecond,
Expand All @@ -112,10 +119,12 @@ public ControlPointDefinition(
this.visualMaterials = visualMaterials;
this.capturableDisplayBeacon = capturableDisplayBeacon;
this.timeToCapture = timeToCapture;
this.decayRate = decayRate;
this.recoveryRate = recoveryRate;
this.ownedDecayRate = ownedDecayRate;
this.timeMultiplier = timeMultiplier;
this.initialOwner = initialOwner;
this.captureCondition = captureCondition;
this.incrementalCapture = incrementalCapture;
this.neutralState = neutralState;
this.permanent = permanent;
this.pointsPerSecond = pointsPerSecond;
Expand All @@ -132,14 +141,18 @@ public String toString() {
+ this.getId()
+ " timeToCapture="
+ this.getTimeToCapture()
+ " decayRate="
+ this.getDecayRate()
+ " recoveryRate="
+ this.getRecoveryRate()
+ " ownedDecayRate="
+ this.getOwnedDecayRate()
+ " timeMultiplier="
+ this.getTimeMultiplier()
+ " initialOwner="
+ this.getInitialOwner()
+ " captureCondition="
+ this.getCaptureCondition()
+ " incrementalCapture="
+ this.isIncrementalCapture()
+ " neutralState="
+ this.hasNeutralState()
+ " permanent="
Expand Down Expand Up @@ -192,6 +205,18 @@ public Duration getTimeToCapture() {
return this.timeToCapture;
}

public double getDecayRate() {
return this.decayRate;
}

public double getRecoveryRate() {
return this.recoveryRate;
}

public double getOwnedDecayRate() {
return this.ownedDecayRate;
}

public float getTimeMultiplier() {
return this.timeMultiplier;
}
Expand All @@ -205,10 +230,6 @@ public CaptureCondition getCaptureCondition() {
return this.captureCondition;
}

public boolean isIncrementalCapture() {
return this.incrementalCapture;
}

public boolean hasNeutralState() {
return this.neutralState;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -77,13 +77,38 @@ public static ControlPointDefinition parseControlPoint(
Duration timeToCapture =
XMLUtils.parseDuration(elControlPoint.getAttribute("capture-time"), Duration.ofSeconds(30));

final double decayRate, recoveryRate, ownedDecayRate;
final Node attrIncremental = Node.fromAttr(elControlPoint, "incremental");
final Node attrDecay = Node.fromAttr(elControlPoint, "decay-rate");
final Node attrRecovery = Node.fromAttr(elControlPoint, "recovery-rate");
final Node attrOwnedDecay = Node.fromAttr(elControlPoint, "owned-decay-rate");
if (attrIncremental == null) {
recoveryRate =
XMLUtils.parseNumber(attrRecovery, Double.class, koth ? 1D : Double.POSITIVE_INFINITY);
decayRate =
XMLUtils.parseNumber(attrDecay, Double.class, koth ? 0.0 : Double.POSITIVE_INFINITY);
ownedDecayRate = XMLUtils.parseNumber(attrOwnedDecay, Double.class, 0.0);
} else {
if (attrDecay != null || attrRecovery != null || attrOwnedDecay != null)
throw new InvalidXMLException(
"Cannot combine this attribute with incremental",
attrDecay != null ? attrDecay : attrRecovery != null ? attrRecovery : attrOwnedDecay);

final boolean incremental = XMLUtils.parseBoolean(attrIncremental, koth);
recoveryRate = incremental ? 1.0 : Double.POSITIVE_INFINITY;
decayRate = incremental ? 0.0 : Double.POSITIVE_INFINITY;
ownedDecayRate = 0.0;
}

float timeMultiplier =
XMLUtils.parseNumber(
elControlPoint.getAttribute("time-multiplier"), Float.class, koth ? 0.1f : 0f);
boolean incrementalCapture =
XMLUtils.parseBoolean(elControlPoint.getAttribute("incremental"), koth);
boolean neutralState =
XMLUtils.parseBoolean(elControlPoint.getAttribute("neutral-state"), koth);

if (neutralState == false && ownedDecayRate > 0) {
throw new InvalidXMLException("This attribute requires a neutral state.", attrOwnedDecay);
}
boolean permanent = XMLUtils.parseBoolean(elControlPoint.getAttribute("permanent"), false);
float pointsPerSecond =
XMLUtils.parseNumber(elControlPoint.getAttribute("points"), Float.class, 1f);
Expand Down Expand Up @@ -117,10 +142,12 @@ public static ControlPointDefinition parseControlPoint(
visualMaterials,
capturableDisplayBeacon == null ? null : capturableDisplayBeacon.toBlockVector(),
timeToCapture,
decayRate,
recoveryRate,
ownedDecayRate,
timeMultiplier,
initialOwner,
captureCondition,
incrementalCapture,
neutralState,
permanent,
pointsPerSecond,
Expand Down