ProjectAres/PGM/src/main/java/tc/oc/pgm/mutation/types/targetable/ApocalypseMutation.java

322 lines
13 KiB
Java

package tc.oc.pgm.mutation.types.targetable;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Range;
import org.apache.commons.lang.math.Fraction;
import org.bukkit.Location;
import org.bukkit.Material;
import org.bukkit.entity.Creeper;
import org.bukkit.entity.Entity;
import org.bukkit.entity.EntityType;
import org.bukkit.entity.LivingEntity;
import org.bukkit.entity.PigZombie;
import org.bukkit.entity.Slime;
import org.bukkit.entity.TNTPrimed;
import org.bukkit.entity.Zombie;
import org.bukkit.inventory.EntityEquipment;
import org.bukkit.inventory.ItemStack;
import org.bukkit.util.Vector;
import tc.oc.commons.core.random.ImmutableWeightedRandomChooser;
import tc.oc.commons.core.random.WeightedRandomChooser;
import tc.oc.pgm.match.Match;
import tc.oc.pgm.match.MatchPlayer;
import tc.oc.pgm.match.Repeatable;
import tc.oc.pgm.mutation.types.EntityMutation;
import tc.oc.pgm.mutation.types.kit.EnchantmentMutation;
import tc.oc.pgm.mutation.types.TargetMutation;
import tc.oc.pgm.points.PointProviderAttributes;
import tc.oc.pgm.points.RandomPointProvider;
import tc.oc.pgm.points.RegionPointProvider;
import tc.oc.pgm.regions.CuboidRegion;
import javax.annotation.Nullable;
import java.time.Duration;
import java.time.Instant;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Optional;
import static tc.oc.commons.core.random.RandomUtils.nextBoolean;
public class ApocalypseMutation extends EntityMutation<LivingEntity> implements TargetMutation {
final static ImmutableMap<Integer, Integer> AMOUNT_MAP = new ImmutableMap.Builder<Integer, Integer>()
.put(3, 25)
.put(5, 20)
.put(10, 15)
.put(20, 5)
.put(50, 1)
.build();
final static ImmutableMap<Integer, Integer> STACK_MAP = new ImmutableMap.Builder<Integer, Integer>()
.put(1, 100)
.put(2, 25)
.build();
final static ImmutableMap<EntityType, Integer> AERIAL_MAP = new ImmutableMap.Builder<EntityType, Integer>()
.put(EntityType.VEX, 5)
.put(EntityType.BLAZE, 1)
.build();
final static ImmutableMap<EntityType, Integer> GROUND_MAP = new ImmutableMap.Builder<EntityType, Integer>()
.put(EntityType.SPIDER, 50)
.put(EntityType.ZOMBIE, 40)
.put(EntityType.CREEPER, 30)
.put(EntityType.HUSK, 20)
.put(EntityType.CAVE_SPIDER, 10)
.put(EntityType.PIG_ZOMBIE, 1)
.build();
final static ImmutableMap<EntityType, Integer> RANGED_MAP = new ImmutableMap.Builder<EntityType, Integer>()
.put(EntityType.SKELETON, 50)
.put(EntityType.STRAY, 20)
.put(EntityType.BLAZE, 20)
.put(EntityType.GHAST, 10)
.put(EntityType.SHULKER, 5)
.put(EntityType.WITCH, 5)
.put(EntityType.WITHER_SKELETON, 1)
.build();
final static ImmutableMap<EntityType, Integer> FLYABLE_MAP = new ImmutableMap.Builder<EntityType, Integer>()
.putAll(AERIAL_MAP)
.put(EntityType.BAT, 10)
.build();
final static ImmutableMap<EntityType, Integer> PASSENGER_MAP = new ImmutableMap.Builder<EntityType, Integer>()
.putAll(RANGED_MAP)
.put(EntityType.CREEPER, 40)
.put(EntityType.PRIMED_TNT, 1)
.build();
final static ImmutableMap<EntityType, Integer> CUBE_MAP = new ImmutableMap.Builder<EntityType, Integer>()
.put(EntityType.SLIME, 10)
.put(EntityType.MAGMA_CUBE, 1)
.build();
final static WeightedRandomChooser<Integer, Integer> AMOUNT = new ImmutableWeightedRandomChooser<>(AMOUNT_MAP);
final static WeightedRandomChooser<Integer, Integer> STACK = new ImmutableWeightedRandomChooser<>(STACK_MAP);
final static WeightedRandomChooser<EntityType, Integer> AERIAL = new ImmutableWeightedRandomChooser<>(AERIAL_MAP);
final static WeightedRandomChooser<EntityType, Integer> GROUND = new ImmutableWeightedRandomChooser<>(GROUND_MAP);
final static WeightedRandomChooser<EntityType, Integer> RANGED = new ImmutableWeightedRandomChooser<>(RANGED_MAP);
final static WeightedRandomChooser<EntityType, Integer> FLYABLE = new ImmutableWeightedRandomChooser<>(FLYABLE_MAP);
final static WeightedRandomChooser<EntityType, Integer> PASSENGER = new ImmutableWeightedRandomChooser<>(PASSENGER_MAP);
final static WeightedRandomChooser<EntityType, Integer> CUBE = new ImmutableWeightedRandomChooser<>(CUBE_MAP);
final static Range<Integer> FREQUENCY = Range.closed(5, 30); // Seconds between entity spawns
final static int DISTANCE = 15; // Max distance entities spawn from players
final static int PARTICIPANT_ENTITIES = 25; // Max entities on the field per participant
final static int MAX_ENTITIES = 500; // Max total entities on the field
final static Range<Integer> AIR_OFFSET = Range.closed(DISTANCE / 4, DISTANCE); // Y-axis offset for spawning flying entities
final static Fraction SPECIAL_CHANCE = Fraction.ONE_FIFTH; // Chance of a special attribute occuring in an entity
final static int SPECIAL_MULTIPLIER = 3; // Multiplier for special attributes
long time; // world time
Instant next; // next time to spawn entities
final PointProviderAttributes attributes; // attributes to choosing random points
public ApocalypseMutation(Match match) {
super(match, false);
this.attributes = new PointProviderAttributes(null, null, true, false);
}
/**
* Get the maximum amount of entities that can be spawned.
*/
public int entitiesMax() {
return Math.min((int) match().participants().count() * PARTICIPANT_ENTITIES, MAX_ENTITIES);
}
/**
* Get the number of available slots are left for additional entities to spawn.
*/
public int entitiesLeft() {
return entitiesMax() - world().getLivingEntities().size() + (int) match().participants().count();
}
/**
* Generate a random spawn point given two locations.
*/
public Optional<Location> location(Location start, Location end) {
return Optional.ofNullable(new RandomPointProvider(Collections.singleton(new RegionPointProvider(new CuboidRegion(start.position(), end.position()), attributes))).getPoint(match(), null));
}
/**
* Spawn a cohort of entities at the given location.
* @param location location to spawn the entity.
* @param ground whether the location is on the ground.
*/
public void spawn(Location location, boolean ground) {
int slots = entitiesLeft();
int queued = AMOUNT.choose(entropy());
// Remove any entities that may be over the max limit
despawn(queued - slots);
// Determine whether the entities should be airborn
int stack = STACK.choose(entropy());
boolean air = !ground || nextBoolean(random(), SPECIAL_CHANCE);
if(air) {
stack += (stack == 1 && random().nextBoolean() ? 1 : 0);
location.add(0, entropy().randomInt(AIR_OFFSET), 0);
}
// Select the random entity chooser based on ground, air, and stacked
boolean stacked = stack > 1;
WeightedRandomChooser<EntityType, Integer> chooser;
if(air) {
if(stacked) {
if(ground) {
chooser = nextBoolean(random(), SPECIAL_CHANCE) ? CUBE : FLYABLE;
} else {
chooser = FLYABLE;
}
} else {
chooser = AERIAL;
}
} else {
if(stacked) {
chooser = GROUND;
} else {
chooser = random().nextBoolean() ? GROUND : RANGED;
}
}
// Select the specific entity types for the spawn,
// all entities will have the same sequence of entity type
// but may have variations (like armor) between them.
List<EntityType> types = new ArrayList<>();
for(int i = 0; i < stack; i++) {
types.add((i == 0 ? chooser : PASSENGER).choose(entropy()));
}
// Spawn the mobs and stack them if required
for(int i = 0; i < queued; i++) {
Entity last = null;
for(EntityType type : types) {
Entity entity = spawn(location, (Class<LivingEntity>) type.getEntityClass());
if(last != null) {
last.setPassenger(entity);
}
last = entity;
}
}
}
@Override
public LivingEntity spawn(Location location, Class<LivingEntity> entityClass, @Nullable MatchPlayer owner) {
LivingEntity entity = super.spawn(location, entityClass, owner);
EnchantmentMutation enchant = new EnchantmentMutation(match());
EntityEquipment equipment = entity.getEquipment();
entity.setVelocity(Vector.getRandom());
ItemStack held = null;
switch(entity.getType()) {
case SKELETON:
case WITHER_SKELETON:
case STRAY:
held = item(Material.BOW);
break;
case ZOMBIE:
case ZOMBIE_VILLAGER:
case HUSK:
Zombie zombie = (Zombie) entity;
zombie.setBaby(nextBoolean(random(), SPECIAL_CHANCE));
break;
case PIG_ZOMBIE:
PigZombie pigZombie = (PigZombie) entity;
pigZombie.setAngry(true);
pigZombie.setAnger(Integer.MAX_VALUE);
held = item(Material.GOLD_SWORD);
break;
case CREEPER:
Creeper creeper = (Creeper) entity;
creeper.setPowered(nextBoolean(random(), SPECIAL_CHANCE));
world().strikeLightningEffect(location);
break;
case PRIMED_TNT:
TNTPrimed tnt = (TNTPrimed) entity;
tnt.setFuseTicks(tnt.getFuseTicks() * SPECIAL_MULTIPLIER);
break;
case SLIME:
case MAGMA_CUBE:
Slime slime = (Slime) entity;
slime.setSize(slime.getSize() * SPECIAL_MULTIPLIER);
break;
case SKELETON_HORSE:
world().strikeLightning(location);
break;
}
if(held != null && random().nextBoolean()) {
enchant.apply(held, equipment);
equipment.setItemInMainHand(held);
}
return entity;
}
/**
* Select the entities that have lived the longest and remove them
* to make room for new entities.
* @param amount the amount of entities to despawn.
*/
public void despawn(long amount) {
entitiesByTime().limit(Math.max(0, amount)).forEachOrdered(this::despawn);
}
@Override
public void target(List<MatchPlayer> players) {
// At least one player is required to spawn mobs
if(players.size() >= 1) {
Location start, end;
start = players.get(0).getLocation(); // player 1 is the first location
if(players.size() >= 2) {
end = players.get(1).getLocation(); // if player 2 exists, they are the second location
} else { // if no player 2, generate a random location near player 1
end = start.clone().add(Vector.getRandom().multiply(DISTANCE));
}
Optional<Location> location = location(start, end);
if(location.isPresent()) { // if the location is safe (on ground)
spawn(location.get(), true);
} else { // if the location was not safe, generate a simple midpoint location
spawn(start.position().midpoint(end.position()).toLocation(world()), false);
}
}
}
@Override
public int targets() {
return 2; // Always require 2 targets to generate a spawn location between them
}
@Override
public Instant next() {
return next;
}
@Override
public void next(Instant time) {
next = time;
}
@Override
public Duration frequency() {
return Duration.ofSeconds(entropy().randomInt(FREQUENCY));
}
@Override
public void enable() {
super.enable();
TargetMutation.super.enable();
time = world().getTime();
}
@Repeatable
public void tick() {
TargetMutation.super.tick();
world().setTime(16000); // Night time to prevent flaming entities
}
@Override
public void disable() {
world().setTime(time);
despawn(entities().count());
super.disable();
}
}