Initial Payload Release

This commit is contained in:
ShinyDialga 2017-12-27 14:31:17 -06:00
parent f6aa12bcf4
commit 5eb88ae3e0
17 changed files with 1854 additions and 7 deletions

View File

@ -239,6 +239,10 @@ match.flag.willRespawn = {0} will respawn in {1} seconds
# {1} flag preventing capture
match.flag.captureDenied.byFlag = {0} will be captured when {1} is dropped
# {0} team reaching a checkpoint
# {1} the checkpoint number reached
match.payload.checkpoint = {0} reached Checkpoint {1}
# {0} = the player
# {1} = singular / plural substitution
# {2} = the team name

View File

@ -19,6 +19,7 @@ import tc.oc.pgm.legacy.LegacyManifest;
import tc.oc.pgm.loot.LootManifest;
import tc.oc.pgm.menu.MenuManifest;
import tc.oc.pgm.modes.ObjectiveModeManifest;
import tc.oc.pgm.payload.PayloadManifest;
import tc.oc.pgm.physics.PlayerPhysicsManifest;
import tc.oc.pgm.picker.PickerManifest;
import tc.oc.pgm.playerstats.StatsManifest;
@ -65,6 +66,7 @@ public class PGMModulesManifest extends HybridManifest {
install(new DestroyableManifest());
install(new WoolManifest());
install(new ControlPointManifest());
install(new PayloadManifest());
install(new LaneManifest());
install(new BroadcastManifest());
install(new StatsManifest());

View File

@ -0,0 +1,45 @@
package tc.oc.pgm.filters.matcher.match;
import com.google.common.collect.Range;
import tc.oc.pgm.filters.matcher.TypedFilter;
import tc.oc.pgm.filters.query.IMatchQuery;
import tc.oc.pgm.flag.FlagDefinition;
import tc.oc.pgm.flag.Post;
import tc.oc.pgm.flag.state.State;
import tc.oc.pgm.payload.Payload;
import tc.oc.pgm.payload.PayloadDefinition;
import java.util.Optional;
public class PayloadEnemyCheckpointFilter extends TypedFilter.Impl<IMatchQuery> {
private final @Inspect(brief=true) PayloadDefinition payload;
private final @Inspect(brief=true) Range<Integer> checkpointRange;
public PayloadEnemyCheckpointFilter(PayloadDefinition payload, Range<Integer> checkpointRange) {
this.payload = payload;
this.checkpointRange = checkpointRange;
}
@Override
public String inspectType() {
return "PayloadCheckpoint";
}
@Override
public String toString() {
return inspect();
}
@Override
public boolean isDynamic() {
return true;
}
@Override
public boolean matches(IMatchQuery query) {
Payload s = query.feature(payload);
return this.checkpointRange.contains(s.getCheckpointCount());
}
}

View File

@ -0,0 +1,40 @@
package tc.oc.pgm.filters.matcher.match;
import com.google.common.collect.Range;
import tc.oc.pgm.filters.matcher.TypedFilter;
import tc.oc.pgm.filters.query.IMatchQuery;
import tc.oc.pgm.payload.Payload;
import tc.oc.pgm.payload.PayloadDefinition;
public class PayloadFriendlyCheckpointFilter extends TypedFilter.Impl<IMatchQuery> {
private final @Inspect(brief=true) PayloadDefinition payload;
private final @Inspect(brief=true) Range<Integer> checkpointRange;
public PayloadFriendlyCheckpointFilter(PayloadDefinition payload, Range<Integer> checkpointRange) {
this.payload = payload;
this.checkpointRange = checkpointRange;
}
@Override
public String inspectType() {
return "PayloadFriendlyCheckpoint";
}
@Override
public String toString() {
return inspect();
}
@Override
public boolean isDynamic() {
return true;
}
@Override
public boolean matches(IMatchQuery query) {
Payload s = query.feature(payload);
return this.checkpointRange.contains(s.getCheckpointCount());
}
}

View File

@ -35,13 +35,7 @@ import tc.oc.pgm.filters.matcher.damage.RelationFilter;
import tc.oc.pgm.filters.matcher.damage.VictimFilter;
import tc.oc.pgm.filters.matcher.entity.EntityTypeFilter;
import tc.oc.pgm.filters.matcher.entity.SpawnReasonFilter;
import tc.oc.pgm.filters.matcher.match.FlagStateFilter;
import tc.oc.pgm.filters.matcher.match.LegacyRandomFilter;
import tc.oc.pgm.filters.matcher.match.MatchMutationFilter;
import tc.oc.pgm.filters.matcher.match.MatchStateFilter;
import tc.oc.pgm.filters.matcher.match.MonostableFilter;
import tc.oc.pgm.filters.matcher.match.PlayerCountFilter;
import tc.oc.pgm.filters.matcher.match.RandomFilter;
import tc.oc.pgm.filters.matcher.match.*;
import tc.oc.pgm.filters.matcher.party.CompetitorFilter;
import tc.oc.pgm.filters.matcher.party.GoalFilter;
import tc.oc.pgm.filters.matcher.party.RankFilter;
@ -79,6 +73,7 @@ import tc.oc.pgm.map.ProtoVersions;
import tc.oc.pgm.match.MatchState;
import tc.oc.pgm.match.PlayerRelation;
import tc.oc.pgm.mutation.Mutation;
import tc.oc.pgm.payload.PayloadDefinition;
import tc.oc.pgm.teams.TeamFactory;
import tc.oc.pgm.utils.MethodParser;
import tc.oc.pgm.utils.XMLUtils;
@ -349,6 +344,12 @@ public class FilterDefinitionParser extends MagicMethodFeatureParser<Filter> imp
return parseExplicitTeam(el, new ScoreFilter(XMLUtils.parseNumericRange(new Node(el), Integer.class)));
}
@MethodParser("payload-checkpoint")
public PayloadEnemyCheckpointFilter parsePayloadCheckpoint(Element el) throws InvalidXMLException {
return new PayloadEnemyCheckpointFilter(features.reference(Node.fromAttr(el, "payload-id"), PayloadDefinition.class),
XMLUtils.parseNumericRange(new Node(el), Integer.class));
}
protected FlagStateFilter parseFlagState(Element el, Class<? extends State> state) throws InvalidXMLException {
return new FlagStateFilter(features.reference(new Node(el), FlagDefinition.class),
Node.tryAttr(el, "post").map(rethrowFunction(attr -> features.reference(attr, Post.class))),

View File

@ -0,0 +1,937 @@
package tc.oc.pgm.payload;
import net.md_5.bungee.api.ChatColor;
import org.bukkit.*;
import org.bukkit.block.BlockFace;
import org.bukkit.entity.ArmorStand;
import org.bukkit.entity.Minecart;
import org.bukkit.event.HandlerList;
import org.bukkit.material.MaterialData;
import org.bukkit.material.Rails;
import org.bukkit.util.Vector;
import tc.oc.api.docs.virtual.MatchDoc;
import tc.oc.commons.bukkit.util.BukkitUtils;
import tc.oc.commons.bukkit.util.NMSHacks;
import tc.oc.commons.core.chat.Component;
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.goals.OwnedGoal;
import tc.oc.pgm.goals.SimpleGoal;
import tc.oc.pgm.goals.events.GoalCompleteEvent;
import tc.oc.pgm.goals.events.GoalStatusChangeEvent;
import tc.oc.pgm.match.*;
import tc.oc.pgm.payload.events.CapturingTeamChangeEvent;
import tc.oc.pgm.payload.events.CapturingTimeChangeEvent;
import tc.oc.pgm.payload.events.ControllerChangeEvent;
import tc.oc.pgm.score.ScoreMatchModule;
import tc.oc.pgm.teams.TeamMatchModule;
import tc.oc.pgm.utils.Strings;
import javax.annotation.Nullable;
import java.time.Duration;
import java.util.*;
public class Payload extends OwnedGoal<PayloadDefinition> {
public static final ChatColor COLOR_NEUTRAL_TEAM = ChatColor.WHITE;
public static final String SYMBOL_CP_INCOMPLETE = "\u29be"; //
public static final String SYMBOL_CP_COMPLETE = "\u279f"; //
protected final PayloadPlayerTracker playerTracker;
private Location payloadLocation;
private int railSize = 0;
protected Minecart payloadEntity;
protected ArmorStand labelEntity;
private Path headPath;
private Path tailPath;
private Path currentPath;
private Set<Path> friendlyReachedCheckpoints = new HashSet<>();
private Set<Path> enemyReachedCheckpoints = new HashSet<>();
// This is set false after the first state change if definition.permanent == true
protected boolean capturable = true;
// 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 currentOwner and has not yet been captured.
protected Competitor currentOwner = null;
// 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;
// Time accumulated towards the next currentOwner change. When this passes timeToCapture,
// it is reset to zero and the currentOwner 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 Payload(Match match, PayloadDefinition definition) {
super(definition, match);
if(this.definition.getInitialOwner() != null) {
this.currentOwner = match.needMatchModule(TeamMatchModule.class).team(this.definition.getInitialOwner());
}
this.createPayload();
this.playerTracker = new PayloadPlayerTracker(match, this.payloadLocation, this.definition.getRadius(), this.definition.getHeight(), this.payloadEntity);
}
public void registerEvents() {
this.match.registerEvents(this.playerTracker);
}
public void unregisterEvents() {
HandlerList.unregisterAll(this.playerTracker);
}
public PayloadPlayerTracker getPlayerTracker() {
return playerTracker;
}
public Vector getStartingLocation() {
return definition.getStartingLocation();
}
public Vector getSpawnLocation() {
return definition.getSpawnLocation();
}
public float getYaw() {
return definition.getYaw();
}
public Duration getTimeToCapture() {
return definition.getTimeToCapture();
}
/**
* The team that owns (is receiving points from) this Payload,
* or null if the Payload is unowned.
*/
public Competitor getCurrentOwner() {
return this.currentOwner;
}
/**
* The team that is "capturing" the Payload. 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.
*/
public Competitor getCapturer() {
return this.capturer;
}
/**
* The partial currentOwner of the Payload. The "partial currentOwner" is defined in
* three scenarios. If the Payload is owned and has a neutral state, the
* partial currentOwner is the currentOwner of the Payload. If the Payload is in
* contest, the partial currentOwner is the team that is currently capturing the
* Payload. Lastly, if the Payload 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.getCurrentOwner() != null ? this.getCurrentOwner() : this.getCapturer();
}
/**
* Progress towards "capturing" the Payload for the current capturingTeam
*/
public Duration getProgress() {
return this.progress;
}
/**
* Progress toward "capturing" the Payload for the current capturingTeam,
* as a real number from 0 to 1.
*/
public double getCompletion() {
return (double) this.progress.toMillis() / (double) this.definition.getTimeToCapture().toMillis();
}
public String renderCompletion() {
return Strings.progressPercentage(this.getCompletion());
}
public @Nullable String renderPreciseCompletion() {
return null;
}
@Override
public ChatColor renderSidebarStatusColor(@Nullable Competitor competitor, Party viewer) {
return this.capturer == null ? COLOR_NEUTRAL_TEAM : this.capturer.getColor();
}
@Override
public String renderSidebarStatusText(@Nullable Competitor competitor, Party viewer) {
if(Duration.ZERO.equals(this.progress)) {
return this.currentOwner == null ? SYMBOL_CP_INCOMPLETE : SYMBOL_CP_COMPLETE;
} else {
return this.renderCompletion();
}
}
@Override
public ChatColor renderSidebarLabelColor(@Nullable Competitor competitor, Party viewer) {
return this.currentOwner == null ? COLOR_NEUTRAL_TEAM : this.currentOwner.getColor();
}
/**
* Ownership of the Payload for a specific team given as a real number from
* 0 to 1.
*/
public double getCompletion(Competitor team) {
if (this.getCurrentOwner() == team) {
return 1 - this.getCompletion();
} else if (this.getCapturer() == team) {
return this.getCompletion();
} else {
return 0;
}
}
public boolean getShowProgress() {
return this.definition.getShowProgress();
}
@Override
public boolean canComplete(Competitor team) {
return this.canCapture(team);
}
@Override
public boolean isCompleted() {
return this.currentOwner != null;
}
@Override
public boolean isCompleted(Competitor team) {
return this.currentOwner != null && this.currentOwner == team;
}
private boolean canCapture(Competitor team) {
return this.definition.getCaptureFilter() == null ||
this.definition.getCaptureFilter().query(team).isAllowed();
}
private boolean canDominate(MatchPlayer player) {
return this.definition.getPlayerFilter() == null ||
this.definition.getPlayerFilter().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());
}
public int getCheckpointCount() {
return this.enemyReachedCheckpoints.size();
}
public void tick(Duration duration) {
this.tickCapture(duration);
this.tickDisplay();
this.tickMove();
}
private void tickMove() {
if (!this.capturable) {
return;
}
if (this.currentOwner == null) {
return;
}
double speed = isInEnemyControl() ? this.definition.getEnemySpeed() : this.definition.getFriendlySpeed();
if (!isInEnemyControl() && this.currentPath.hasNext() && this.currentPath.next().isCheckpoint()) {
return;
}
Path finalPath = isInEnemyControl() ? this.tailPath : this.headPath;
Location finalLocation = finalPath.getLocation();
float points = isInEnemyControl() ? this.getDefinition().getPoints() : this.getDefinition().getFriendlyPoints();
if (this.payloadLocation.getX() == finalLocation.getX() &&
this.payloadLocation.getZ() == finalLocation.getZ()) {
if (points > 0) {
this.capturable = false;
this.match.callEvent(new GoalCompleteEvent(this, this.currentOwner != null, c -> false, c -> c.equals(this.currentOwner)));
ScoreMatchModule smm = this.getMatch().getMatchModule(ScoreMatchModule.class);
if (smm != null) {
if (this.currentOwner != null) smm.incrementScore(this.currentOwner, points);
}
return;
}
}
speed = Math.abs(speed);
move(speed/10.0);
this.payloadEntity.teleport(payloadLocation);
this.playerTracker.setLocation(payloadLocation);
Location labelLocation = this.payloadEntity.getLocation().clone();
labelLocation.setY(labelLocation.getY() - 0.2);
this.labelEntity.teleport(labelLocation);
}
private boolean isInEnemyControl() {
return !super.getOwner().equals(this.getCurrentOwner());
}
private void move(double distance) {
boolean hasNext = isInEnemyControl() ? currentPath.hasNext() : currentPath.hasPrevious();
if (!hasNext) { // Path is over
this.payloadLocation.setPosition(currentPath.getLocation().position());
return;
}
if (currentPath.isCheckpoint()) {
if (false) { //TODO: Friendly checkpoints
if (isInEnemyControl() && this.enemyReachedCheckpoints.add(currentPath)) {
friendlyReachedCheckpoints.remove(currentPath);
} else if (!isInEnemyControl() && this.friendlyReachedCheckpoints.add(currentPath)) {
enemyReachedCheckpoints.remove(currentPath);
}
} else if (isInEnemyControl() && this.enemyReachedCheckpoints.add(currentPath)) {
final Component message = new Component(ChatColor.GRAY);
message.translate("match.payload.checkpoint",
this.getCurrentOwner().getComponentName(),
Math.abs(getCheckpointCount()));
match.sendMessage(message);
}
}
Path nextPath = isInEnemyControl() ? currentPath.next() : currentPath.previous();
Vector direction = nextPath.getLocation().position().minus(payloadLocation.position()).mutableCopy();
double len = direction.length(),
extraLen = distance - len;
// If there's extra distance, skip calculations, otherwise, move payload proportionally
if (extraLen > 0) {
this.currentPath = nextPath;
move(extraLen);
} else this.payloadLocation.position().add(direction.multiply(distance / len));
}
private static final double TAU = Math.PI * 2;
private int PARTICLE_BASE_COUNT = (int) Math.max(this.definition.getRadius() * Math.PI, 5);
private int PARTICLE_SUB_COUNT = 3;
private double ANGLE_PER_STEP = TAU / PARTICLE_BASE_COUNT;
private double ANGLE_PER_SUB_STEP = 0.75 * ANGLE_PER_STEP / PARTICLE_SUB_COUNT;
private double MIN_HEIGHT_OFFSET = 0.5; // Starting height of the bottom most particle
private double HEIGHT_OFFSET = 0.75; // Vertical offset of each particle
private void tickDisplay() {
Color controllingColor = currentOwner != null ? currentOwner.getFullColor() : BukkitUtils.colorOf(COLOR_NEUTRAL_TEAM);
Color capturingColor = capturer != null ? capturer.getFullColor() : BukkitUtils.colorOf(COLOR_NEUTRAL_TEAM);
double completionAngle = (1 - getCompletion()) * TAU;
for(int i = 0; i < PARTICLE_BASE_COUNT; i++) {
double angleBasePoint = i * ANGLE_PER_STEP;
for (int j = 0; j < PARTICLE_SUB_COUNT; j++) {
double angle = angleBasePoint + ANGLE_PER_SUB_STEP * j;
Color fullColor = (angle <= completionAngle) ? controllingColor : capturingColor;
Location base = this.payloadLocation.clone().add(new Vector(
this.definition.getRadius() * Math.cos(angle),
j * HEIGHT_OFFSET + MIN_HEIGHT_OFFSET,
this.definition.getRadius() * Math.sin(angle)));
match.getWorld().spawnParticle(
Particle.REDSTONE,
base,
0,
rgbToParticle(fullColor.getRed()),
rgbToParticle(fullColor.getGreen()),
rgbToParticle(fullColor.getBlue()),
1
);
}
}
}
private double rgbToParticle(int rgb) {
return Math.max(0.001, rgb / 255.0);
}
/**
* Do a capturing cycle on this Payload 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;
List<MatchPlayer> removePlayers = new ArrayList<>();
for (MatchPlayer player : this.playerTracker.getPlayersOnPoint()) {
if (!this.playerTracker.isOnPoint(player, player.getLocation().toVector())) {
removePlayers.add(player);
continue;
}
Competitor team = player.getCompetitor();
if(this.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;
leader = team;
} else if(team != runnerUp && (runnerUp == null || playerCount > playerCounts.get(runnerUp))) {
runnerUp = team;
}
}
}
}
for (MatchPlayer player : removePlayers) {
this.playerTracker.removePlayerOnPoint(player);
}
int lead = 0;
if(leader != null) {
lead = playerCounts.get(leader);
defenderCount -= lead;
switch(this.definition.getCaptureCondition()) {
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);
}
break;
}
}
if(lead > 0) {
this.dominateAndFireEvents(leader, calculateDominateTime(lead, duration));
} else {
this.dominateAndFireEvents(null, duration);
}
}
/**
* Do a cycle of domination on this Payload 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.
*/
private void dominateAndFireEvents(@Nullable Competitor dominator, Duration duration) {
final Duration oldProgress = progress;
final Competitor oldCapturer = capturer;
final Competitor oldOwner = currentOwner;
dominate(dominator, duration);
if(!Objects.equals(oldCapturer, capturer) || !oldProgress.equals(progress)) {
match.callEvent(new CapturingTimeChangeEvent(match, this));
match.callEvent(new GoalStatusChangeEvent(this));
}
if(!Objects.equals(oldCapturer, capturer)) {
match.callEvent(new CapturingTeamChangeEvent(match, this, oldCapturer, capturer));
}
if(!Objects.equals(oldOwner, currentOwner)) {
match.callEvent(new ControllerChangeEvent(match, this, oldOwner, currentOwner));
}
}
/**
* If there is a neutral state, then the point cannot be owned and captured
* at the same time. This means that at least one of controllingTeam or capturingTeam
* must be null at any particular time.
*
* If controllingTeam is non-null, the point is owned, and it must be "uncaptured"
* before any other team can capture it. In this state, capturingTeam is null,
* the controlling team will decrease capturingTimeMillis, and all other teams will
* increase it.
*
* If controllingTeam is null, then the point is in the neutral state. If capturingTeam
* is also null, then the point is not being captured, and capturingTimeMillis is
* zero. If capturingTeam is non-null, then that is the only team that will increase
* capturingTimeMillis. All other teams will decrease it.
*
* If there is no neutral state, then the point is always either being captured
* by a specific team, or not being captured at all.
*
* If incremental capturing is disabled, then capturingTimeMillis is reset to
* zero whenever it stops increasing.
*/
private void dominate(@Nullable Competitor dominator, Duration duration) {
if(!capturable || Comparables.lessOrEqual(duration, Duration.ZERO)) {
return;
}
if(currentOwner != null && definition.hasNeutralState()) {
// Point is owned and has a neutral state
if(Objects.equals(dominator, currentOwner)) {
// Owner is recovering the point
recover(duration, dominator);
} else if(dominator != null) {
// Non-currentOwner is uncapturing the point
uncapture(duration, dominator);
} else if (!this.playerTracker.hasPlayersOnPoint(currentOwner) && this.definition.emptyDecayRate() > 0) {
//Nobody is on point and empty decay is enabled
emptyDecay(duration);
} else {
// Point is decaying towards the currentOwner
decay(duration);
}
} else if(capturer != null) {
// Point is partly captured by someone
if(Objects.equals(dominator, capturer)) {
// Capturer is making progress
capture(duration);
} else if(dominator != null) {
// Non-capturer is reversing progress
recover(duration, dominator);
} else {
// Point is decaying towards currentOwner or neutral
decay(duration);
}
} else if(dominator != null && !Objects.equals(dominator, currentOwner) && canCapture(dominator)) {
// Point is not being captured and there is a dominant team that is not the currentOwner, 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-currentOwner is pushing it towards neutral
*/
private void uncapture(Duration duration, Competitor dominator) {
duration = addCaptureTime(duration);
if(duration != null) {
// If uncapture is complete, recurse with the dominant team's remaining time
currentOwner = null;
dominate(dominator, duration);
byte blockData = BukkitUtils.chatColorToDyeColor(COLOR_NEUTRAL_TEAM).getWoolData();
MaterialData payloadBlock = this.payloadEntity.getDisplayBlock();
payloadBlock.setData(blockData);
this.payloadEntity.setDisplayBlock(payloadBlock);
this.labelEntity.setCustomName(this.getColoredName());
}
}
/**
* Point is owned, and a non-currentOwner is pushing it towards neutral
*/
private void emptyDecay(Duration duration) {
duration = TimeUtils.multiply(duration, 1.0/definition.emptyDecayRate());
duration = addCaptureTime(duration);
if(duration != null) {
// If uncapture is complete, recurse with the dominant team's remaining time
currentOwner = null;
byte blockData = BukkitUtils.chatColorToDyeColor(COLOR_NEUTRAL_TEAM).getWoolData();
MaterialData payloadBlock = this.payloadEntity.getDisplayBlock();
payloadBlock.setData(blockData);
this.payloadEntity.setDisplayBlock(payloadBlock);
this.labelEntity.setCustomName(this.getColoredName());
}
}
/**
* Point is either owned or neutral, and someone is pushing it towards themselves
*/
private void capture(Duration duration) {
duration = addCaptureTime(duration);
if(duration != null) {
currentOwner = capturer;
capturer = null;
byte blockData = BukkitUtils.chatColorToDyeColor(currentOwner.getColor()).getWoolData();
MaterialData payloadBlock = this.payloadEntity.getDisplayBlock();
payloadBlock.setData(blockData);
this.payloadEntity.setDisplayBlock(payloadBlock);
this.labelEntity.setCustomName(this.getColoredName());
}
}
/**
* Point is being pulled back towards its current state
*/
private void recover(Duration duration, Competitor dominator) {
duration = TimeUtils.multiply(duration, definition.recoveryRate());
duration = subtractCaptureTime(duration);
if(duration != null) {
capturer = null;
if(!Objects.equals(dominator, currentOwner)) {
// If the dominant team is not the controller, recurse with the remaining time
dominate(dominator, TimeUtils.multiply(duration, 1D / definition.recoveryRate()));
}
}
}
/**
* Point is decaying back towards its current state
*/
private void decay(Duration duration) {
duration = TimeUtils.multiply(duration, definition.decayRate());
duration = subtractCaptureTime(duration);
if(duration != null) {
capturer = null;
}
}
protected void createPayload() {
this.makePath();
this.summonMinecart();
}
protected void summonMinecart() {
Location location = this.getSpawnLocation().toLocation(getMatch().getWorld());
this.payloadLocation = location;
//Set the floor to gold
Location below = location.clone();
below.setY(location.getY() - 1);
below.getBlock().setType(Material.GOLD_BLOCK);
//Spawn the Payload entity
Location spawn = location.clone();
spawn.setYaw(this.getYaw());
this.payloadEntity = location.getWorld().spawn(spawn, Minecart.class);
ChatColor color = currentOwner != null ? currentOwner.getColor() : COLOR_NEUTRAL_TEAM;
byte blockData = BukkitUtils.chatColorToDyeColor(color).getWoolData();
MaterialData payloadBlock = new MaterialData(Material.STAINED_CLAY);
payloadBlock.setData(blockData);
this.payloadEntity.setDisplayBlock(payloadBlock);
this.payloadEntity.setInvulnerable(true);
this.payloadEntity.setGravity(false);
this.payloadEntity.setMaxSpeed(0);
this.payloadEntity.setSlowWhenEmpty(true);
//Summon a label for it
this.labelEntity = this.payloadLocation.getWorld().spawn(this.payloadLocation.clone().add(0, 0.2, 0), ArmorStand.class);
this.labelEntity.setVisible(false);
this.labelEntity.setGravity(false);
this.labelEntity.setRemoveWhenFarAway(false);
this.labelEntity.setArms(false);
this.labelEntity.setBasePlate(false);
this.labelEntity.setCustomName(this.getColoredName());
this.labelEntity.setCustomNameVisible(true);
this.labelEntity.setInvulnerable(true);
NMSHacks.enableArmorSlots(this.labelEntity, false);
}
class Path {
private int index;
private Location location;
private Path previousPath;
private Path nextPath;
private boolean checkpoint;
Path(Location location, Path previousPath, Path nextPath) {
this(0, location, previousPath, nextPath, false);
}
Path(int index, Location location, Path previousPath, Path nextPath) {
this(index, location, previousPath, nextPath, false);
}
Path(Location location, Path previousPath, Path nextPath, boolean checkpoint) {
this(0, location, previousPath, nextPath, checkpoint);
}
Path(int index, Location location, Path previousPath, Path nextPath, boolean checkpoint) {
this.index = index;
this.location = location;
this.previousPath = previousPath;
this.nextPath = nextPath;
this.checkpoint = checkpoint;
}
public int getIndex() {
return index;
}
public Location getLocation() {
return location;
}
public boolean hasPrevious() {
return previous() != null;
}
public Path previous() {
return previousPath;
}
public void setPrevious(Path previousPath) {
this.previousPath = previousPath;
}
public boolean hasNext() {
return next() != null;
}
public Path next() {
return nextPath;
}
public void setNext(Path nextPath) {
this.nextPath = nextPath;
}
public boolean isCheckpoint() {
return checkpoint;
}
}
protected void makePath() {
Location location = this.getStartingLocation().toLocation(getMatch().getWorld());
//Payload must start on a rail
if (!isRails(location.getBlock().getType())) {
return;
}
Rails startingRails = (Rails) location.getBlock().getState().getMaterialData();
if (startingRails.isCurve() || startingRails.isOnSlope()) {
return;
}
BlockFace direction = startingRails.getDirection();
List<Double> differingX = new ArrayList<>();
List<Double> differingY = new ArrayList<>();
List<Double> differingZ = new ArrayList<>();
differingY.add(0.0);
differingY.add(1.0);
differingY.add(-1.0);
headPath = new Path(this.railSize, location, null, null);
this.railSize++;
Path previousPath = headPath;
Path neighborRail = getNewNeighborRail(previousPath, direction, differingX, differingY, differingZ);
while (neighborRail != null) {
previousPath.setNext(neighborRail);
previousPath = neighborRail;
differingX.clear();
differingZ.clear();
if (previousPath.getLocation().getBlock().getState().getMaterialData() instanceof Rails) {
direction = ((Rails)previousPath.getLocation().getBlock().getState().getMaterialData()).getDirection();
} else {
direction = null;
}
neighborRail = getNewNeighborRail(previousPath, direction, differingX, differingY, differingZ);
}
tailPath = previousPath;
Path currentPath = headPath;
Path lastPath = null;
headPath = null;
boolean moreRails = currentPath.hasNext();
while (moreRails) {
Path nextPath = currentPath.next();
Location newLocation = currentPath.getLocation().toVector().midpoint(nextPath.getLocation().toVector()).toLocation(getMatch().getWorld());
newLocation.setY(Math.max(currentPath.getLocation().getY(), nextPath.getLocation().getY()));
Path newPath;
if (headPath == null) {
Location headLocation = newLocation.clone().add(currentPath.getLocation().subtract(nextPath.getLocation()));
headPath = new Path(this.railSize, headLocation, null, null);
this.railSize++;
newPath = new Path(this.railSize, newLocation, headPath, null);
this.railSize++;
headPath.setNext(newPath);
lastPath = newPath;
this.currentPath = headPath;
} else {
newPath = new Path(this.railSize, newLocation, lastPath, null, currentPath.isCheckpoint());
this.railSize++;
lastPath.setNext(newPath);
lastPath = newPath;
tailPath = lastPath;
}
if (this.getSpawnLocation().getX() == currentPath.getLocation().getX() &&
this.getSpawnLocation().getY() == currentPath.getLocation().getY() &&
this.getSpawnLocation().getZ() == currentPath.getLocation().getZ()) {
this.currentPath = newPath;
}
currentPath = nextPath;
moreRails = currentPath.hasNext();
}
Path tail = tailPath;
Path beforeTail = tail.previous();
Location newLocation = tail.getLocation().getLocation().toVector().midpoint(beforeTail.getLocation().toVector()).toLocation(getMatch().getWorld());
newLocation.setY(Math.max(tail.getLocation().getY(), beforeTail.getLocation().getY()));
Location tailLocation = newLocation.clone().add(currentPath.getLocation().subtract(beforeTail.getLocation()));
tailPath = new Path(tailLocation, tail, null);
tail.setNext(tailPath);
}
public boolean isRails(Material material) {
return material.equals(Material.RAILS);
}
public boolean isCheckpoint(Material material) {
return this.definition.getCheckpointMaterial() != null ?
material.equals(this.definition.getCheckpointMaterial().getMaterial()) :
material.equals(Material.ACTIVATOR_RAIL) ||
material.equals(Material.DETECTOR_RAIL) ||
material.equals(Material.POWERED_RAIL);
}
public Path getNewNeighborRail(Path path, BlockFace direction, List<Double> differingX, List<Double> differingY, List<Double> differingZ) {
Location previousLocation = null;
if (path.previous() != null) {
previousLocation = path.previous().getLocation();
}
Location location = path.getLocation();
if (direction == null) {
differingX.add(-1.0);
differingX.add(0.0);
differingX.add(1.0);
differingZ.add(-1.0);
differingZ.add(0.0);
differingZ.add(1.0);
} else if (direction.equals(BlockFace.SOUTH) || direction.equals(BlockFace.NORTH)) {
differingZ.add(-1.0);
differingZ.add(1.0);
differingX.add(0.0);
} else if (direction.equals(BlockFace.EAST) || direction.equals(BlockFace.WEST)) {
differingX.add(-1.0);
differingX.add(1.0);
differingZ.add(0.0);
} else {
Location side = location.clone();
side.setZ(side.getZ() + (direction.equals(BlockFace.NORTH_WEST) || direction.equals(BlockFace.NORTH_EAST) ? 1 : -1));
if (side.getX() == previousLocation.getX() && side.getZ() == previousLocation.getZ()) {
differingX.add(direction.equals(BlockFace.SOUTH_WEST) || direction.equals(BlockFace.NORTH_WEST) ? 1.0 : -1.0);
differingZ.add(0.0);
} else {
differingX.add(0.0);
differingZ.add(direction.equals(BlockFace.NORTH_WEST) || direction.equals(BlockFace.NORTH_EAST) ? 1.0 : -1.0);
}
}
Location newLocation = location.clone();
for (double x : differingX) {
for (double y : differingY) {
for (double z : differingZ) {
newLocation.add(x, y, z);
boolean isCheckpoint = isCheckpoint(newLocation.getBlock().getType());
if (isRails(newLocation.getBlock().getType()) || isCheckpoint) {
Path currentPath = path;
if (currentPath.equals(headPath)) {
return new Path(newLocation, path, null, isCheckpoint);
}
boolean alreadyExists = false;
while (currentPath.hasPrevious()) {
if (currentPath.getLocation().getX() == newLocation.getX() &&
currentPath.getLocation().getY() == newLocation.getY() &&
currentPath.getLocation().getZ() == newLocation.getZ()) {
alreadyExists = true;
break;
}
currentPath = currentPath.previous();
}
if (!alreadyExists) {
return new Path(newLocation, path, null, isCheckpoint);
}
}
newLocation.subtract(x, y, z);
}
}
}
return null;
}
@Override
public MatchDoc.OwnedGoal getDocument() {
return new Document();
}
class Document extends SimpleGoal.Document implements MatchDoc.OwnedGoal {
@Override
public @Nullable String owner_id() {
return getOwner() == null ? null : getOwner().slug();
}
@Override
public @Nullable String owner_name() {
return getOwner() == null ? null : getOwner().getName();
}
}
}

View File

@ -0,0 +1,46 @@
package tc.oc.pgm.payload;
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.events.ListenerScope;
import tc.oc.pgm.match.Competitor;
import tc.oc.pgm.match.Match;
import tc.oc.pgm.match.MatchScope;
import tc.oc.pgm.payload.events.ControllerChangeEvent;
@ListenerScope(MatchScope.LOADED)
public class PayloadAnnouncer implements Listener {
private final Match match;
public PayloadAnnouncer(Match match) {
this.match = match;
}
@EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
public void onOwnerChange(ControllerChangeEvent event) {
if(event.getPayload().isVisible()) {
final Component point = new Component(event.getPayload().getComponentName(), ChatColor.WHITE);
final Component message = new Component(ChatColor.GRAY);
final Competitor before = event.getOldController();
final Competitor after = event.getNewController();
if(after == null) {
message.translate("objective.lose",
before.getComponentName(),
point);
} else if(before == null) {
message.translate("objective.capture",
after.getComponentName(),
point);
} else {
message.translate("objective.take",
after.getComponentName(),
point,
before.getComponentName());
}
match.sendMessage(message);
}
}
}

View File

@ -0,0 +1,333 @@
package tc.oc.pgm.payload;
import org.bukkit.util.Vector;
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.goals.OwnableGoalDefinition;
import tc.oc.pgm.goals.OwnableGoalDefinitionImpl;
import tc.oc.pgm.match.Match;
import tc.oc.pgm.regions.Region;
import tc.oc.pgm.teams.TeamFactory;
import tc.oc.pgm.utils.MaterialPattern;
import javax.annotation.Nullable;
import java.time.Duration;
import java.util.Optional;
import java.util.stream.Stream;
@FeatureInfo(name = "payload",
plural = {"payloads"},
singular = {"payload"})
public interface PayloadDefinition extends OwnableGoalDefinition<Payload>, GamemodeFeature {
@Override
Payload getGoal(Match match);
Vector getStartingLocation();
Vector getSpawnLocation();
float getYaw();
Filter getCaptureFilter();
Filter getPlayerFilter();
Duration getTimeToCapture();
double decayRate();
double emptyDecayRate();
double recoveryRate();
double getTimeMultiplier();
TeamFactory getInitialOwner();
CaptureCondition getCaptureCondition();
boolean hasNeutralState();
float getRadius();
float getHeight();
MaterialPattern getCheckpointMaterial();
float getFriendlySpeed();
float getEnemySpeed();
float getPoints();
float getFriendlyPoints();
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 PayloadDefinitionImpl extends OwnableGoalDefinitionImpl<Payload> implements PayloadDefinition {
//Where the rail starts
private final Vector startingLocation;
//Where the payload starts
private final Vector spawnLocation;
//The direction the Payload spawns
private final float yaw;
// Which players can capture the point
private final Filter captureFilter;
// Which players can prevent other teams from capturing the point
private final Filter playerFilter;
// 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;
// Relative rate at which progress reverts while nobody is standing on the point
private final double emptyDecayRate;
// The team that owns the point when the match starts, null for no currentOwner (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 currentOwner to the next
// NOTE: points always start in an unowned state, regardless of this value
private final boolean neutralState;
//The radius of the control point of the payload
private final float radius;
//The height of the control point of the payload
private final float height;
//The material of the checkpoint blocks
private final MaterialPattern checkpointMaterial;
//The speed of the payload when under control of the owning team
private final float friendlySpeed;
//The speed of the payload when not under control of the owning team
private final float enemySpeed;
// Amount of points given to the team that captures the payload
private final float points;
// Amount of points given to the team that owns and captures the payload at the initial location
private final float friendlyPoints;
// If true, capturing progress is displayed on the scoreboard
private final boolean showProgress;
public PayloadDefinitionImpl(String name,
@Nullable Boolean required,
boolean visible,
Vector location,
Vector spawnLocation,
float yaw,
Filter captureFilter,
Filter playerFilter,
Duration timeToCapture,
double timeMultiplier,
double recoveryRate,
double decayRate,
double emptyDecayRate,
TeamFactory initialOwner,
TeamFactory owner,
CaptureCondition captureCondition,
boolean neutralState,
float radius,
float height,
MaterialPattern checkpointMaterial,
float friendlySpeed,
float enemySpeed,
float points,
float friendlyPoints,
boolean progress) {
super(name, required, visible, Optional.of(owner));
this.startingLocation = location;
this.spawnLocation = spawnLocation;
this.yaw = yaw;
this.captureFilter = captureFilter;
this.playerFilter = playerFilter;
this.timeToCapture = timeToCapture;
this.timeMultiplier = timeMultiplier;
this.recoveryRate = recoveryRate;
this.decayRate = decayRate;
this.emptyDecayRate = emptyDecayRate;
this.initialOwner = initialOwner;
this.captureCondition = captureCondition;
this.neutralState = neutralState;
this.radius = radius;
this.height = height;
this.checkpointMaterial = checkpointMaterial;
this.friendlySpeed = friendlySpeed;
this.enemySpeed = enemySpeed;
this.points = points;
this.friendlyPoints = friendlyPoints;
this.showProgress = progress;
}
@Override
public String toString() {
return "PayloadDefinition {name=" + this.getName() +
" timeToCapture=" + this.getTimeToCapture() +
" timeMultiplier=" + this.getTimeMultiplier() +
" initialOwner=" + this.getInitialOwner() +
" captureCondition=" + this.getCaptureCondition() +
" neutralState=" + this.hasNeutralState() +
" captureFilter=" + this.getCaptureFilter() +
" playerFilter=" + this.getPlayerFilter() +
" visible=" + this.isVisible();
}
@Override
public Stream<MapDoc.Gamemode> gamemodes() {
return Stream.of(MapDoc.Gamemode.koth);
}
@Override
public Payload getGoal(Match match) {
return (Payload) super.getGoal(match);
}
@Override
public Payload createFeature(Match match) {
final Payload cp = new Payload(match, this);
cp.registerEvents();
return cp;
}
@Override
public boolean isShared() {
return true;
}
@Override
public float getYaw() {
return this.yaw;
}
@Override
public Vector getStartingLocation() {
return this.startingLocation;
}
@Override
public Vector getSpawnLocation() {
return this.spawnLocation;
}
@Override
public Filter getCaptureFilter() {
return this.captureFilter;
}
@Override
public Filter getPlayerFilter() {
return this.playerFilter;
}
@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 double emptyDecayRate() {
return this.emptyDecayRate;
}
@Override
public TeamFactory getInitialOwner() {
return this.initialOwner;
}
@Override
public CaptureCondition getCaptureCondition() {
return this.captureCondition;
}
@Override
public boolean hasNeutralState() {
return this.neutralState;
}
@Override
public float getRadius() {
return this.radius;
}
@Override
public float getHeight() {
return this.height;
}
@Override
public MaterialPattern getCheckpointMaterial() {
return checkpointMaterial;
}
@Override
public float getFriendlySpeed() {
return this.friendlySpeed;
}
@Override
public float getEnemySpeed() {
return this.enemySpeed;
}
@Override
public float getFriendlyPoints() {
return this.friendlyPoints;
}
@Override
public float getPoints() {
return this.points;
}
@Override
public boolean getShowProgress() {
return this.showProgress;
}
}

View File

@ -0,0 +1,19 @@
package tc.oc.pgm.payload;
import tc.oc.commons.core.inject.HybridManifest;
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.MatchBinders;
public class PayloadManifest extends HybridManifest implements MapBinders, MatchBinders {
@Override
protected void configure() {
bind(PayloadParser.class).in(MapScoped.class);
final FeatureBinder<PayloadDefinition> cp = new FeatureBinder<>(binder(), PayloadDefinition.class);
cp.bindDefinitionParser().to(PayloadParser.class);
cp.installMatchModule(PayloadMatchModule.class);
cp.installRootParser(new PayloadRootNodeFinder());
}
}

View File

@ -0,0 +1,54 @@
package tc.oc.pgm.payload;
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 javax.inject.Inject;
import java.time.Duration;
import java.util.List;
import static tc.oc.commons.core.stream.Collectors.toImmutableList;
public class PayloadMatchModule extends MatchModule {
private static final long MILLIS = 100;
private static final Duration INTERVAL = Duration.ofMillis(MILLIS); // milliseconds, two ticks
private final List<Payload> payloads;
private final PayloadAnnouncer announcer;
@Inject
private PayloadMatchModule(Match match) {
this.announcer = new PayloadAnnouncer(match);
this.payloads = match.featureDefinitions()
.all(PayloadDefinition.class)
.map(cp -> cp.getGoal(match))
.collect(toImmutableList());
}
@Override
public void load() {
super.load();
getMatch().registerEvents(this.announcer);
}
@Override
public void unload() {
for(Payload payload : this.payloads) {
payload.unregisterEvents();
}
HandlerList.unregisterAll(this.announcer);
super.unload();
}
@Repeatable(interval = @Time(milliseconds = MILLIS))
public void tick() {
for(Payload payload : payloads) {
payload.tick(INTERVAL);
}
}
}

View File

@ -0,0 +1,97 @@
package tc.oc.pgm.payload;
import com.google.common.collect.Range;
import org.bukkit.util.ImVector;
import org.bukkit.util.Vector;
import org.jdom2.Element;
import tc.oc.pgm.features.FeatureDefinitionParser;
import tc.oc.pgm.features.FeatureParser;
import tc.oc.pgm.filters.Filter;
import tc.oc.pgm.filters.parser.FilterParser;
import tc.oc.pgm.goals.ProximityMetric;
import tc.oc.pgm.regions.Region;
import tc.oc.pgm.regions.RegionParser;
import tc.oc.pgm.teams.TeamFactory;
import tc.oc.pgm.utils.MaterialPattern;
import tc.oc.pgm.utils.XMLUtils;
import tc.oc.pgm.xml.InvalidXMLException;
import tc.oc.pgm.xml.Node;
import tc.oc.pgm.xml.property.PropertyBuilderFactory;
import javax.inject.Inject;
import java.time.Duration;
public final class PayloadParser implements FeatureDefinitionParser<PayloadDefinition> {
private final FilterParser filterParser;
private final FeatureParser<TeamFactory> teamParser;
private final PropertyBuilderFactory<ImVector, ?> vectors;
@Inject
private PayloadParser(FilterParser filterParser, FeatureParser<TeamFactory> teamParser, PropertyBuilderFactory<ImVector, ?> vectors) {
this.filterParser = filterParser;
this.teamParser = teamParser;
this.vectors = vectors;
}
@Override
public PayloadDefinition parseElement(Element elPayload) throws InvalidXMLException {
final Vector location = vectors.property(elPayload, "location").required();
final Vector spawnLocation = vectors.property(elPayload, "spawn-location").optional((ImVector)location);
final float yaw = XMLUtils.parseNumber(elPayload.getAttribute("yaw"), Float.class, (Float) null);
Filter captureFilter = filterParser.property(elPayload, "capture-filter").optional(null);
Filter playerFilter = filterParser.property(elPayload, "player-filter").optional(null);
String name = elPayload.getAttributeValue("name", "Payload");
TeamFactory initialOwner = teamParser.property(elPayload, "initial-owner").optional(null);
TeamFactory owner = teamParser.property(elPayload, "owner").required();
Duration timeToCapture = XMLUtils.parseDuration(elPayload.getAttribute("capture-time"), Duration.ofSeconds(30));
double timeMultiplier = XMLUtils.parseNumber(elPayload.getAttribute("time-multiplier"), Double.class,0D);
final double recoveryRate, decayRate;
final Node attrIncremental = Node.fromAttr(elPayload, "incremental");
final Node attrRecovery = Node.fromAttr(elPayload, "recovery");
final Node attrDecay = Node.fromAttr(elPayload, "decay");
double emptyDecayRate = XMLUtils.parseNumber(elPayload.getAttribute("empty-decay"), Double.class, 0D);
if(attrIncremental == null) {
recoveryRate = XMLUtils.parseNumber(attrRecovery, Double.class, Range.atLeast(0D), 1D);
decayRate = XMLUtils.parseNumber(attrDecay, Double.class, Range.atLeast(0D), 0D);
} else {
if(attrRecovery != null || attrDecay != null) {
throw new InvalidXMLException("Cannot combine this attribute with 'incremental'", attrRecovery != null ? attrRecovery : attrDecay);
}
final boolean incremental = XMLUtils.parseBoolean(attrIncremental, true);
recoveryRate = incremental ? 1D : Double.POSITIVE_INFINITY;
decayRate = incremental ? 0D : Double.POSITIVE_INFINITY;
}
boolean neutralState = XMLUtils.parseBoolean(elPayload.getAttribute("neutral-state"), true);
float radius = XMLUtils.parseNumber(elPayload.getAttribute("radius"), Float.class, 5f);
float height = XMLUtils.parseNumber(elPayload.getAttribute("height"), Float.class, 3f);
MaterialPattern checkpointMaterial = XMLUtils.parseMaterialPattern(Node.fromAttr(elPayload, "checkpoint-material"));
float friendlySpeed = XMLUtils.parseNumber(elPayload.getAttribute("friendly-speed"), Float.class, 0f);
float enemySpeed = XMLUtils.parseNumber(elPayload.getAttribute("enemy-speed"), Float.class, 1f);
float points = XMLUtils.parseNumber(elPayload.getAttribute("points"), Float.class, 1f);
float friendlyPoints = XMLUtils.parseNumber(elPayload.getAttribute("friendly-points"), Float.class, 0f);
boolean showProgress = XMLUtils.parseBoolean(elPayload.getAttribute("show-progress"), true);
boolean visible = XMLUtils.parseBoolean(elPayload.getAttribute("show"), true);
Boolean required = XMLUtils.parseBoolean(elPayload.getAttribute("required"), null);
PayloadDefinition.CaptureCondition captureCondition =
XMLUtils.parseEnum(Node.fromAttr(elPayload, "capture-rule"),
PayloadDefinition.CaptureCondition.class,
"capture rule",
PayloadDefinition.CaptureCondition.EXCLUSIVE);
return new PayloadDefinitionImpl(
name, required, visible,
location, spawnLocation, yaw, captureFilter, playerFilter,
timeToCapture, timeMultiplier, recoveryRate, decayRate, emptyDecayRate, initialOwner, owner, captureCondition,
neutralState, radius, height, checkpointMaterial, friendlySpeed, enemySpeed, points, friendlyPoints, showProgress
);
}
}

View File

@ -0,0 +1,132 @@
package tc.oc.pgm.payload;
import com.google.common.collect.Sets;
import org.bukkit.Location;
import org.bukkit.entity.Minecart;
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.PlayerInteractEntityEvent;
import org.bukkit.event.player.PlayerMoveEvent;
import org.bukkit.event.player.PlayerTeleportEvent;
import org.bukkit.util.Vector;
import tc.oc.commons.bukkit.event.CoarsePlayerMoveEvent;
import tc.oc.commons.core.util.DefaultMapAdapter;
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.pgm.utils.MatchPlayers;
import java.util.*;
import java.util.stream.Collectors;
/**
* Tracks which players are by a payload and answers some queries about them
*/
@ListenerScope(MatchScope.LOADED)
public class PayloadPlayerTracker implements Listener {
protected final Match match;
protected Location location;
protected final double radius;
protected final double height;
protected final Minecart payload;
protected final Set<MatchPlayer> playersOnPoint = Sets.newHashSet();
public PayloadPlayerTracker(Match match, Location location, double radius, double height, Minecart payload) {
this.match = match;
this.location = location;
this.radius = radius;
this.height = height;
this.payload = payload;
}
public void setLocation(Location location) {
this.location = location;
}
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 by the payload
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 PlayerMoveEvent 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(isOnPoint(player, to)) { //Determine if they are in the height
this.playersOnPoint.add(player); //TODO!!! Since the point is moving, if the player stands still, the payload will keep moving no matter what.
} else {
this.playersOnPoint.remove(player);
}
}
@EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
public void onPlayerDespawn(final ParticipantDespawnEvent event) {
playersOnPoint.remove(event.getPlayer());
}
@EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
public void onPlayerDespawn(final PlayerInteractEntityEvent event) {
if (event.getRightClicked().equals(payload)) {
event.setCancelled(true);
}
}
public void removePlayerOnPoint(MatchPlayer player) {
this.playersOnPoint.remove(player);
}
public boolean isOnPoint(MatchPlayer player, Vector location) {
Vector payloadLocation = this.location.toVector();
return !player.getBukkit().isDead() &&
Math.sqrt(Math.pow(location.getX() - payloadLocation.getX(), 2) + Math.pow(location.getZ() - payloadLocation.getBlockZ(), 2)) <= this.radius && //Determine if they are in radius
Math.abs(location.getY() - payloadLocation.getY()) <= this.height;
}
public boolean hasPlayersOnPoint(Competitor competitor) {
if (competitor == null) {
return false;
}
for (MatchPlayer player : playersOnPoint) {
if (player.getCompetitor() != null && player.getCompetitor().equals(competitor)) {
return true;
}
}
return false;
}
}

View File

@ -0,0 +1,19 @@
package tc.oc.pgm.payload;
import org.jdom2.Element;
import tc.oc.pgm.utils.XMLUtils;
import tc.oc.pgm.xml.Node;
import tc.oc.pgm.xml.finder.NodeFinder;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Stream;
class PayloadRootNodeFinder implements NodeFinder {
@Override
public Stream<Node> findNodes(Element parent, String name) {
final List<Element> elements = new ArrayList<>();
elements.addAll(XMLUtils.flattenElements(parent, "payloads", "payload"));
return elements.stream().map(Node::of);
}
}

View File

@ -0,0 +1,39 @@
package tc.oc.pgm.payload.events;
import org.bukkit.event.HandlerList;
import tc.oc.pgm.match.Competitor;
import tc.oc.pgm.match.Match;
import tc.oc.pgm.payload.Payload;
import javax.annotation.Nullable;
public class CapturingTeamChangeEvent extends PayloadPointEvent {
private static final HandlerList handlers = new HandlerList();
@Nullable private final Competitor oldTeam;
@Nullable private final Competitor newTeam;
public CapturingTeamChangeEvent(Match match, Payload payload, Competitor oldTeam, Competitor newTeam) {
super(match, payload);
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;
}
}

View File

@ -0,0 +1,22 @@
package tc.oc.pgm.payload.events;
import org.bukkit.event.HandlerList;
import tc.oc.pgm.match.Match;
import tc.oc.pgm.payload.Payload;
public class CapturingTimeChangeEvent extends PayloadPointEvent {
private static final HandlerList handlers = new HandlerList();
public CapturingTimeChangeEvent(Match match, Payload point) {
super(match, point);
}
@Override
public HandlerList getHandlers() {
return handlers;
}
public static HandlerList getHandlerList() {
return handlers;
}
}

View File

@ -0,0 +1,39 @@
package tc.oc.pgm.payload.events;
import org.bukkit.event.HandlerList;
import tc.oc.pgm.match.Competitor;
import tc.oc.pgm.match.Match;
import tc.oc.pgm.payload.Payload;
import javax.annotation.Nullable;
public class ControllerChangeEvent extends PayloadPointEvent {
private static final HandlerList handlers = new HandlerList();
@Nullable private final Competitor oldController;
@Nullable private final Competitor newController;
public ControllerChangeEvent(Match match, Payload payload, Competitor oldController, Competitor newController) {
super(match, payload);
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;
}
}

View File

@ -0,0 +1,18 @@
package tc.oc.pgm.payload.events;
import tc.oc.pgm.events.MatchEvent;
import tc.oc.pgm.match.Match;
import tc.oc.pgm.payload.Payload;
public abstract class PayloadPointEvent extends MatchEvent {
protected final Payload payload;
public PayloadPointEvent(Match match, Payload payload) {
super(match);
this.payload = payload;
}
public Payload getPayload() {
return this.payload;
}
}