Initial Payload Release
This commit is contained in:
parent
f6aa12bcf4
commit
5eb88ae3e0
|
@ -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
|
||||
|
|
|
@ -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());
|
||||
|
|
|
@ -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());
|
||||
}
|
||||
}
|
|
@ -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());
|
||||
}
|
||||
}
|
|
@ -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))),
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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());
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -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
|
||||
);
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue