package tc.oc.pgm.flag;
import java.util.Optional;
import java.util.Random;
import javax.annotation.Nullable;
import com.google.common.collect.ImmutableList;
import net.md_5.bungee.api.ChatColor;
import org.bukkit.Location;
import java.time.Duration;
import tc.oc.pgm.features.FeatureDefinition;
import tc.oc.pgm.features.FeatureInfo;
import tc.oc.pgm.filters.Filter;
import tc.oc.pgm.points.AngleProvider;
import tc.oc.pgm.points.PointProvider;
import tc.oc.pgm.points.PointProviderLocation;
import tc.oc.pgm.teams.TeamFactory;
import static com.google.common.base.Preconditions.checkArgument;
@FeatureInfo(name = "post")
public interface Post extends FeatureDefinition {
Duration DEFAULT_RETURN_TIME = Duration.ofSeconds(30);
double DEFAULT_RESPAWN_SPEED = 8;
Optional<TeamFactory> owner();
@Nullable TeamFactory getOwner();
ChatColor getColor();
Duration getRecoverTime();
Duration getRespawnTime(double distance);
ImmutableList<PointProvider> getReturnPoints();
boolean isSequential();
Boolean isPermanent();
double getPointsPerSecond();
Filter getPickupFilter();
Location getReturnPoint(Flag flag, AngleProvider yawProvider);
}
class PostImpl extends FeatureDefinition.Impl implements Post {
private static final int MAX_SPAWN_ATTEMPTS = 100;
private final @Inspect Optional<TeamFactory> owner; // Team that owns the post, affects various things
private final @Inspect Duration recoverTime; // Time between a flag dropping and being recovered, can be infinite
private final @Inspect @Nullable Duration respawnTime; // Fixed time between a flag being recovered and respawning at the post
private final @Inspect @Nullable Double respawnSpeed; // Makes respawn time proportional to distance, flag "moves" back at this m/s
private final @Inspect ImmutableList<PointProvider> returnPoints; // Spawn points for the flag
private final @Inspect boolean sequential; // Search for spawn points sequentially, see equivalent field in SpawnInfo
private final @Inspect boolean permanent; // Flag enters Completed state when at this post
private final @Inspect double pointsPerSecond; // Points awarded while any flag is at this post
private final @Inspect Filter pickupFilter; // Filter players who can pickup a flag at this post
public PostImpl(Optional<TeamFactory> owner,
Duration recoverTime,
@Nullable Duration respawnTime,
@Nullable Double respawnSpeed,
ImmutableList<PointProvider> returnPoints,
boolean sequential,
boolean permanent,
double pointsPerSecond,
Filter pickupFilter) {
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;
}
@Override
public Optional<TeamFactory> owner() {
return owner;
}
@Override
public @Nullable TeamFactory getOwner() {
return owner.orElse(null);
}
@Override
public ChatColor getColor() {
return owner.map(TeamFactory::getDefaultColor)
.orElse(ChatColor.WHITE);
}
@Override
public Duration getRecoverTime() {
return this.recoverTime;
}
@Override
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;
}
}
@Override
public ImmutableList<PointProvider> getReturnPoints() {
return this.returnPoints;
}
@Override
public boolean isSequential() {
return this.sequential;
}
@Override
public Boolean isPermanent() {
return this.permanent;
}
@Override
public double getPointsPerSecond() {
return this.pointsPerSecond;
}
@Override
public Filter getPickupFilter() {
return this.pickupFilter;
}
@Override
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;
}
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;
}
}