package tc.oc.pgm.flag; import static com.google.common.base.Preconditions.checkArgument; import com.google.common.collect.ImmutableList; import java.time.Duration; import java.util.Random; import javax.annotation.Nullable; import org.bukkit.ChatColor; import org.bukkit.Location; import tc.oc.pgm.api.feature.FeatureInfo; import tc.oc.pgm.api.feature.FeatureReference; import tc.oc.pgm.api.filter.Filter; import tc.oc.pgm.features.SelfIdentifyingFeatureDefinition; import tc.oc.pgm.points.AngleProvider; import tc.oc.pgm.points.PointProvider; import tc.oc.pgm.points.PointProviderLocation; import tc.oc.pgm.teams.TeamFactory; @FeatureInfo(name = "post") public class Post extends SelfIdentifyingFeatureDefinition { public static final Duration DEFAULT_RETURN_TIME = Duration.ofSeconds(30); public static final double DEFAULT_RESPAWN_SPEED = 8; private static final int MAX_SPAWN_ATTEMPTS = 100; private final @Nullable FeatureReference<TeamFactory> owner; // Team that owns the post, affects various things private final Duration recoverTime; // Time between a flag dropping and being recovered, can be infinite private final @Nullable Duration respawnTime; // Fixed time between a flag being recovered and respawning at the post private final @Nullable Double respawnSpeed; // Makes respawn time proportional to distance, flag "moves" back at this m/s private final ImmutableList<PointProvider> returnPoints; // Spawn points for the flag private final boolean sequential; // Search for spawn points sequentially, see equivalent field in SpawnInfo private final boolean permanent; // Flag enters Completed state when at this post private final double pointsPerSecond; // Points awarded while any flag is at this post private final Filter pickupFilter; // Filter players who can pickup a flag at this post private final @Nullable String postName; // The name of the post to be shown in chat when the flag is respawning private boolean specifiedPost = false; public Post( @Nullable String id, @Nullable String name, @Nullable FeatureReference<TeamFactory> owner, Duration recoverTime, @Nullable Duration respawnTime, @Nullable Double respawnSpeed, ImmutableList<PointProvider> returnPoints, boolean sequential, boolean permanent, double pointsPerSecond, Filter pickupFilter) { super(id); checkArgument(respawnTime == null || respawnSpeed == null); if (respawnSpeed != null) checkArgument(respawnSpeed > 0); this.owner = owner; this.recoverTime = recoverTime; this.respawnTime = respawnTime; this.respawnSpeed = respawnSpeed; this.returnPoints = returnPoints; this.sequential = sequential; this.permanent = permanent; this.pointsPerSecond = pointsPerSecond; this.pickupFilter = pickupFilter; this.postName = name; } public @Nullable String getPostName() { return this.postName; } public @Nullable TeamFactory getOwner() { return this.owner == null ? null : this.owner.get(); } public ChatColor getColor() { return this.owner == null ? ChatColor.WHITE : this.owner.get().getDefaultColor(); } public Duration getRecoverTime() { return this.recoverTime; } public Duration getRespawnTime(double distance) { if (respawnTime != null) { return respawnTime; } else if (respawnSpeed != null) { return Duration.ofSeconds(Math.round(distance / respawnSpeed)); } else { return Duration.ZERO; } } public ImmutableList<PointProvider> getReturnPoints() { return this.returnPoints; } public boolean isSequential() { return this.sequential; } public Boolean isPermanent() { return this.permanent; } public double getPointsPerSecond() { return this.pointsPerSecond; } public Filter getPickupFilter() { return this.pickupFilter; } public Location getReturnPoint(Flag flag, AngleProvider yawProvider) { Location location = getReturnPoint(flag); if (location instanceof PointProviderLocation && !((PointProviderLocation) location).hasYaw()) { location.setYaw(yawProvider.getAngle(location.toVector())); } return location; } public boolean isSpecifiedPost() { return this.specifiedPost; } public void setSpecifiedPost(boolean value) { this.specifiedPost = value; } private Location getReturnPoint(Flag flag) { if (this.sequential) { for (PointProvider provider : this.returnPoints) { for (int i = 0; i < MAX_SPAWN_ATTEMPTS; i++) { Location loc = roundToBlock(provider.getPoint(flag.getMatch(), null)); if (flag.canDropAt(loc)) { return loc; } } } // could not find a good spot, fallback to the last provider return this.returnPoints.get(this.returnPoints.size() - 1).getPoint(flag.getMatch(), null); } else { Random random = new Random(); for (int i = 0; i < MAX_SPAWN_ATTEMPTS * this.returnPoints.size(); i++) { PointProvider provider = this.returnPoints.get(random.nextInt(this.returnPoints.size())); Location loc = roundToBlock(provider.getPoint(flag.getMatch(), null)); if (flag.canDropAt(loc)) { return loc; } } // could not find a good spot, settle for any spot PointProvider provider = this.returnPoints.get(random.nextInt(this.returnPoints.size())); return this.returnPoints .get(random.nextInt(this.returnPoints.size())) .getPoint(flag.getMatch(), null); } } private Location roundToBlock(Location loc) { Location newLoc = loc.clone(); newLoc.setX(Math.floor(loc.getX()) + 0.5); newLoc.setY(Math.floor(loc.getY())); newLoc.setZ(Math.floor(loc.getZ()) + 0.5); return newLoc; } }