Fix edge case errors with mutations

This commit is contained in:
Electroid 2017-04-02 19:09:35 -07:00
parent af1ebe598a
commit e2e5027c69
25 changed files with 702 additions and 346 deletions

View File

@ -198,6 +198,7 @@ mutation.type.explosive = Explosive
mutation.type.explosive.desc = tnt and fire bows
mutation.type.elytra = Elytra
mutation.type.elytra.desc = fly around with an elytra
mutation.type.elytra.land = Land immediately. Your elytra is being grounded in {0} seconds.
mutation.type.projectile = Projectile
mutation.type.projectile.desc = arrow potion effects
mutation.type.enchantment = Enchantment
@ -268,6 +269,9 @@ lives.remaining.individual.plural = You have {0} lives left
lives.remaining.team.singular = Your team has {0} life left
lives.remaining.team.plural = Your team has {0} lives left
lives.remaining.alive.singular = Your team has {0} player left
lives.remaining.alive.plural = Your team has {0} players left
lives.status.eliminated = eliminated
lives.status.alive = {0} alive
lives.status.lives = {0} lives

View File

@ -105,7 +105,11 @@ public class BlitzMatchModuleImpl extends MatchModule implements BlitzMatchModul
}
}
private void showLives(MatchPlayer player, boolean release, boolean activate) {
private void livesHotbar(MatchPlayer player) {
lives(player).map(Lives::remaining).ifPresent(player::sendHotbarMessage);
}
private void livesTitle(MatchPlayer player, boolean release, boolean activate) {
final Optional<Lives> lives = lives(player);
if(activated() && lives.isPresent()) {
player.showTitle(
@ -118,6 +122,10 @@ public class BlitzMatchModuleImpl extends MatchModule implements BlitzMatchModul
}
}
private void update() {
match.callEvent(new BlitzEvent(match, this));
}
@Override
public boolean activated() {
return activated;
@ -142,6 +150,7 @@ public class BlitzMatchModuleImpl extends MatchModule implements BlitzMatchModul
activated = false;
lives.clear();
eliminated.clear();
update();
}
@Override
@ -161,10 +170,10 @@ public class BlitzMatchModuleImpl extends MatchModule implements BlitzMatchModul
match.participants().forEach(player -> {
setup(player, false);
if(match.hasStarted()) {
showLives(player, false, true);
livesTitle(player, false, true);
}
});
match.callEvent(new BlitzEvent(match, this));
update();
}
}
@ -181,6 +190,7 @@ public class BlitzMatchModuleImpl extends MatchModule implements BlitzMatchModul
if(notify) {
player.showTitle(Components.blank(), life.change(lives), 0, 40, 10);
}
player.competitor().ifPresent(competitor -> competitor.participants().forEach(this::livesHotbar));
if(life.empty() && immediate) {
eliminate(player);
return true;
@ -225,9 +235,9 @@ public class BlitzMatchModuleImpl extends MatchModule implements BlitzMatchModul
ImmutableSet.copyOf(getMatch().getParticipatingPlayers())
.stream()
.filter(this::eliminated)
.forEach(participating -> {
match.setPlayerParty(participating, match.getDefaultParty());
world.spawnParticle(Particle.SMOKE_LARGE, player.getLocation(), 5);
.forEach(eliminated -> {
match.setPlayerParty(eliminated, match.getDefaultParty());
world.spawnParticle(Particle.SMOKE_LARGE, eliminated.getLocation(), 5);
});
victory.invalidateAndCheckEnd();
});
@ -269,15 +279,14 @@ public class BlitzMatchModuleImpl extends MatchModule implements BlitzMatchModul
@EventHandler(priority = EventPriority.HIGH, ignoreCancelled = true)
public void onDeath(MatchPlayerDeathEvent event) {
final MatchPlayer player = event.getVictim();
if(player.competitor().isPresent()) {
increment(player, -1, false, true);
}
event.getVictim()
.competitor()
.ifPresent(competitor -> increment(event.getVictim(), -1, false, true));
}
@EventHandler
public void onRelease(ParticipantReleaseEvent event) {
showLives(event.getPlayer(), event.wasFrozen(), false);
livesTitle(event.getPlayer(), event.wasFrozen(), false);
}
}

View File

@ -27,7 +27,7 @@ public abstract class LivesBase implements Lives {
update();
}
private void update() {
protected void update() {
competitor().getMatch().callEvent(new LivesEvent(this));
}
@ -67,7 +67,8 @@ public abstract class LivesBase implements Lives {
public BaseComponent remaining() {
return new Component(
new TranslatableComponent(
"lives.remaining." + type().name().toLowerCase() + "." + (current() == 1 ? "singular" : "plural"),
"lives.remaining." + type().name().toLowerCase() + "." + (current() == 1 ? "singular"
: "plural"),
new Component(current(), ChatColor.YELLOW)
),
ChatColor.AQUA
@ -103,7 +104,7 @@ public abstract class LivesBase implements Lives {
new TranslatableComponent(
(delta > 0 ? "lives.change.gained."
: "lives.change.lost.") + (absDelta == 1 ? "singular"
: "plural"),
: "plural"),
new Component(absDelta, ChatColor.AQUA)
),
ChatColor.WHITE

View File

@ -1,6 +1,11 @@
package tc.oc.pgm.blitz;
import net.md_5.bungee.api.ChatColor;
import net.md_5.bungee.api.chat.BaseComponent;
import net.md_5.bungee.api.chat.TranslatableComponent;
import tc.oc.api.docs.PlayerId;
import tc.oc.commons.core.chat.Component;
import tc.oc.commons.core.chat.Components;
import tc.oc.pgm.match.Competitor;
public class LivesTeam extends LivesBase {
@ -24,6 +29,24 @@ public class LivesTeam extends LivesBase {
return false;
}
public int alive() {
return (int) competitor().participants().count();
}
@Override
public BaseComponent remaining() {
int alive = alive() - 1;
if(alive == 0) return Components.blank();
return empty() ? new Component(
new TranslatableComponent(
"lives.remaining.alive." + (alive == 1 ? "singular"
: "plural"),
new Component(alive, ChatColor.YELLOW)
),
ChatColor.AQUA
) : super.remaining();
}
@Override
public int hashCode() {
return competitor().hashCode();

View File

@ -1,6 +1,7 @@
package tc.oc.pgm.modules;
import org.bukkit.event.EventHandler;
import org.bukkit.event.EventPriority;
import org.bukkit.event.Listener;
import org.bukkit.event.entity.CreatureSpawnEvent;
import tc.oc.pgm.events.ListenerScope;
@ -9,7 +10,6 @@ import tc.oc.pgm.filters.Filter;
import tc.oc.pgm.filters.query.EntitySpawnQuery;
import tc.oc.pgm.match.MatchModule;
import tc.oc.pgm.match.MatchScope;
import tc.oc.pgm.mutation.MutationMatchModule;
@ListenerScope(MatchScope.LOADED)
public class MobsMatchModule extends MatchModule implements Listener {
@ -37,10 +37,9 @@ public class MobsMatchModule extends MatchModule implements Listener {
getMatch().getWorld().setSpawnFlags(false, false);
}
@EventHandler(ignoreCancelled = true)
@EventHandler(ignoreCancelled = true, priority = EventPriority.LOWEST)
public void checkSpawn(final CreatureSpawnEvent event) {
if(!match.module(MutationMatchModule.class).map(mmm -> mmm.allowMob(event.getSpawnReason())).orElse(false) &&
event.getSpawnReason() != CreatureSpawnEvent.SpawnReason.CUSTOM) {
if(event.getSpawnReason() != CreatureSpawnEvent.SpawnReason.CUSTOM) {
event.setCancelled(mobsFilter.query(new EntitySpawnQuery(event, event.getEntity(), event.getSpawnReason())).isDenied());
}
}

View File

@ -4,7 +4,6 @@ import com.google.common.collect.Collections2;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Sets;
import org.bukkit.event.entity.CreatureSpawnEvent;
import tc.oc.commons.core.util.MapUtils;
import tc.oc.commons.core.random.RandomUtils;
import tc.oc.pgm.Config;
@ -157,17 +156,4 @@ public class MutationMatchModule extends MatchModule {
return mutationsActive().stream().anyMatch(m1 -> Stream.of(mutations).anyMatch(m2 -> m2.equals(m1)));
}
public boolean allowMob(CreatureSpawnEvent.SpawnReason reason) {
switch(reason) {
case NATURAL:
case DEFAULT:
case CHUNK_GEN:
case JOCKEY:
case MOUNT:
return false;
default:
return true;
}
}
}

View File

@ -0,0 +1,239 @@
package tc.oc.pgm.mutation.types;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import com.google.gson.reflect.TypeToken;
import org.bukkit.Location;
import org.bukkit.entity.Entity;
import org.bukkit.entity.LivingEntity;
import org.bukkit.event.EventHandler;
import org.bukkit.event.EventPriority;
import org.bukkit.event.entity.CreatureSpawnEvent;
import org.bukkit.event.player.PlayerSpawnEntityEvent;
import org.bukkit.inventory.EntityEquipment;
import tc.oc.commons.core.collection.WeakHashSet;
import tc.oc.pgm.events.MatchPlayerDeathEvent;
import tc.oc.pgm.events.PlayerChangePartyEvent;
import tc.oc.pgm.match.Match;
import tc.oc.pgm.match.MatchPlayer;
import javax.annotation.Nullable;
import java.time.Instant;
import java.util.Comparator;
import java.util.HashSet;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.WeakHashMap;
import java.util.stream.Stream;
import static tc.oc.commons.core.util.Optionals.cast;
/**
* A mutation module that tracks entity spawns.
*/
public class EntityMutation<E extends Entity> extends KitMutation {
final Set<E> entities;
final Map<Instant, Set<E>> entitiesByTime;
final Map<MatchPlayer, Set<E>> entitiesByPlayer;
final Map<E, MatchPlayer> playersByEntity;
public EntityMutation(Match match, boolean force) {
super(match, force);
this.entities = new WeakHashSet<>();
this.entitiesByTime = new WeakHashMap<>();
this.entitiesByPlayer = new WeakHashMap<>();
this.playersByEntity = new WeakHashMap<>();
}
/**
* Gets an immutable stream of all registered entities.
* @return stream of entities.
*/
public Stream<E> entities() {
return ImmutableSet.copyOf(entities).stream();
}
/**
* Gets an immutable stream of all registered entities
* by the time they spawned in ascending order.
* @return stream of entities.
*/
public Stream<E> entitiesByTime() {
return ImmutableMap.copyOf(entitiesByTime)
.entrySet()
.stream()
.sorted(Map.Entry.comparingByKey(Comparator.comparing(Instant::toEpochMilli)))
.flatMap(entry -> ImmutableSet.copyOf(entry.getValue()).stream());
}
/**
* Gets an immutable stream of all registered entities
* that are owned by a player.
*
* If the given player is null, this will return entities
* with no owner.
* @param player the optional player.
* @return stream of entities.
*/
public Stream<E> entitiesByPlayer(@Nullable MatchPlayer player) {
return ImmutableSet.copyOf(entitiesByPlayer.getOrDefault(player, new HashSet<>())).stream();
}
/**
* Gets the optional owner of an entity.
* @param entity the entity to find the owner of.
* @return the optional player.
*/
public Optional<MatchPlayer> playerByEntity(Entity entity) {
return Optional.ofNullable(playersByEntity.get(entity));
}
/**
* Are entities with this spawn reason allowed to spawn?
* @param reason the spawn reason.
* @return whether this reason is allowed.
*/
public boolean allowed(CreatureSpawnEvent.SpawnReason reason) {
switch(reason) {
case NATURAL:
case DEFAULT:
case CHUNK_GEN:
case JOCKEY:
case MOUNT:
return false;
default:
return true;
}
}
/**
* Register a new spawned entity with an optional owner.
* @param entity the entity to register.
* @param owner the optional owner.
* @return the entity.
*/
public E register(E entity, @Nullable MatchPlayer owner) {
entities.add(entity);
final Instant now = match().getInstantNow();
final Set<E> byTime = entitiesByTime.getOrDefault(now, new WeakHashSet<>());
byTime.add(entity);
entitiesByTime.put(now, byTime);
if(owner != null) {
final Set<E> byPlayer = entitiesByPlayer.getOrDefault(owner, new WeakHashSet<>());
byPlayer.add(entity);
entitiesByPlayer.put(owner, byPlayer);
playersByEntity.put(entity, owner);
}
return entity;
}
/**
* Removes the entity from the world.
*
* Typically this should be {@link Entity#remove()},
* but it can also expire the entity after a couple of seconds.
* @param entity the entity to remove.
*/
public void remove(E entity) {
entity.remove();
}
/**
* Unregister and remove the given entity.
* @param entity the entity.
*/
public void despawn(E entity) {
entities.remove(entity);
playersByEntity.remove(entity);
Stream.of(entitiesByTime, entitiesByPlayer)
.flatMap(map -> ImmutableList.copyOf(map.values()).stream())
.forEach(set -> set.remove(entity));
remove(entity);
}
/**
* Unregister and remove any entities owned by the given player.
* @param player the owner of the entities.
*/
public void despawn(MatchPlayer player) {
ImmutableSet.copyOf(entitiesByPlayer.getOrDefault(player, new HashSet<>())).forEach(this::despawn);
}
/**
* Spawn an entity at the given location with no owner.
* @see #spawn(Location, Class, MatchPlayer)
* @return the entity.
*/
public E spawn(Location location, Class<E> entityClass) {
return spawn(location, entityClass, null);
}
/**
* Spawn an entity at the given location.
* @param location the location to spawn the entity.
* @param entityClass the class of the entity.
* @param owner the optional owner of the entity.
* @return the entity.
*/
public E spawn(Location location, Class<E> entityClass, @Nullable MatchPlayer owner) {
E entity = world().spawn(location, entityClass);
cast(entity, LivingEntity.class).ifPresent(living -> {
living.setCanPickupItems(false);
living.setRemoveWhenFarAway(true);
EntityEquipment equipment = living.getEquipment();
equipment.setHelmetDropChance(0);
equipment.setChestplateDropChance(0);
equipment.setLeggingsDropChance(0);
equipment.setBootsDropChance(0);
});
return register(entity, owner);
}
@EventHandler(ignoreCancelled = false, priority = EventPriority.HIGHEST)
public void onPlayerSpawnEntity(PlayerSpawnEntityEvent event) {
match().participant(event.getPlayer())
.ifPresent(player -> cast(event.getEntity(), new TypeToken<E>(){}.getRawType())
.ifPresent(entity -> {
register((E) entity, player);
event.setCancelled(false);
}));
}
@EventHandler(ignoreCancelled = false, priority = EventPriority.HIGHEST)
public void onEntitySpawn(CreatureSpawnEvent event) {
boolean allowed = allowed(event.getSpawnReason());
event.setCancelled(!allowed);
if(allowed) {
cast(event.getEntity(), new TypeToken<E>(){}.getRawType())
.filter(entity -> !playerByEntity((E) entity).isPresent())
.ifPresent(entity -> register((E) entity, null));
}
}
@EventHandler(ignoreCancelled = true, priority = EventPriority.MONITOR)
public void onPlayerDeath(MatchPlayerDeathEvent event) {
despawn(event.getVictim());
}
@EventHandler(ignoreCancelled = true, priority = EventPriority.MONITOR)
public void onPartyChange(PlayerChangePartyEvent event) {
despawn(event.getPlayer());
}
@Override
public void remove(MatchPlayer player) {
despawn(player);
super.remove(player);
}
@Override
public void disable() {
entities().forEach(this::despawn);
Stream.of(entitiesByTime, entitiesByPlayer, playersByEntity).forEach(Map::clear);
super.disable();
}
}

View File

@ -1,9 +1,16 @@
package tc.oc.pgm.mutation.types;
import org.bukkit.Material;
import org.bukkit.entity.Item;
import org.bukkit.event.EventHandler;
import org.bukkit.event.EventPriority;
import org.bukkit.event.entity.ItemSpawnEvent;
import org.bukkit.inventory.ItemStack;
import tc.oc.commons.bukkit.inventory.Slot;
import tc.oc.commons.core.util.Optionals;
import tc.oc.pgm.killreward.KillReward;
import tc.oc.pgm.killreward.KillRewardMatchModule;
import tc.oc.pgm.kits.ItemKit;
import tc.oc.pgm.kits.Kit;
import tc.oc.pgm.kits.KitPlayerFacet;
import tc.oc.pgm.match.Match;
@ -11,19 +18,22 @@ import tc.oc.pgm.match.MatchPlayer;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.WeakHashMap;
import java.util.stream.Stream;
/**
* A mutation module that injects special kits on spawn and/or kill.
*/
public class KitMutation extends MutationModule {
public class KitMutation extends MutationModule.Impl {
protected final List<Kit> kits;
protected final Map<MatchPlayer, List<Kit>> playerKits;
protected final Map<MatchPlayer, Map<Slot, ItemStack>> savedSlots;
protected final Set<Material> itemRemove;
protected final List<KillReward> rewards;
protected final boolean force;
@ -32,6 +42,7 @@ public class KitMutation extends MutationModule {
this.kits = new ArrayList<>();
this.playerKits = new WeakHashMap<>();
this.savedSlots = new WeakHashMap<>();
this.itemRemove = new HashSet<>();
this.rewards = new ArrayList<>();
this.force = force;
}
@ -59,6 +70,7 @@ public class KitMutation extends MutationModule {
List<Kit> kits = new ArrayList<>();
kits(player, kits);
playerKits.put(player, kits);
kits.forEach(kit -> Optionals.cast(kit, ItemKit.class).ifPresent(items -> itemRemove.add(items.item().getType())));
saved().forEach(slot -> {
slot.item(player.getInventory()).ifPresent(item -> {
Map<Slot, ItemStack> slots = savedSlots.getOrDefault(player, new HashMap<>());
@ -88,22 +100,34 @@ public class KitMutation extends MutationModule {
return Stream.empty();
}
@EventHandler(ignoreCancelled = true, priority = EventPriority.HIGHEST)
public void onItemDrop(ItemSpawnEvent event) {
Item entity = event.getEntity();
if(entity != null) {
ItemStack item = entity.getItemStack();
if(item != null && itemRemove.contains(item.getType())) {
entity.remove();
}
}
}
@Override
public void enable() {
super.enable();
match.module(KillRewardMatchModule.class).get().rewards().addAll(rewards);
if(match.hasStarted()) {
match.participants().forEach(this::apply);
match().module(KillRewardMatchModule.class).get().rewards().addAll(rewards);
if(match().hasStarted()) {
match().participants().forEach(this::apply);
}
}
@Override
public void disable() {
match.module(KillRewardMatchModule.class).get().rewards().removeAll(rewards);
match.participants().forEach(this::remove);
match().module(KillRewardMatchModule.class).get().rewards().removeAll(rewards);
match().participants().forEach(this::remove);
kits.clear();
playerKits.clear();
savedSlots.clear();
itemRemove.clear();
rewards.clear();
super.disable();
}

View File

@ -1,12 +1,8 @@
package tc.oc.pgm.mutation.types;
import org.bukkit.Location;
import org.bukkit.Material;
import org.bukkit.World;
import org.bukkit.entity.Entity;
import org.bukkit.entity.LivingEntity;
import org.bukkit.event.Listener;
import org.bukkit.inventory.EntityEquipment;
import org.bukkit.inventory.ItemStack;
import tc.oc.commons.bukkit.item.ItemBuilder;
import tc.oc.commons.core.random.AdvancingEntropy;
@ -15,7 +11,6 @@ import tc.oc.pgm.events.ListenerScope;
import tc.oc.pgm.match.Match;
import tc.oc.pgm.match.MatchScope;
import tc.oc.pgm.mutation.Mutation;
import tc.oc.pgm.mutation.MutationMatchModule;
import java.util.Random;
@ -28,69 +23,59 @@ import java.util.Random;
* of breaking the match state.
*/
@ListenerScope(MatchScope.RUNNING)
public abstract class MutationModule implements Listener {
public interface MutationModule extends Listener {
protected final Match match;
protected final World world;
protected final Entropy entropy;
protected final Random random;
Match match();
/**
* Constructed when {@link MutationMatchModule#load()}
* has been called. This will only be constructed if its
* subsequent {@link Mutation} is enabled for the match.
*
* @param match the match for this module.
*/
public MutationModule(Match match) {
this.match = match;
this.world = match.getWorld();
this.entropy = new AdvancingEntropy();
this.random = new Random();
default void enable() {
match().registerEventsAndRepeatables(this);
}
/**
* Called when the match starts.
*
* However, this should be able to be called at any
* point before the match ends and still work as expected.
*/
public void enable() {
match.registerEventsAndRepeatables(this);
default void disable() {
match().unregisterEvents(this);
match().unregisterRepeatable(this);
}
/**
* Called when the match ends.
*
* However, this should be able to be called at any
* point during a match and still work as expected.
*/
public void disable() {
match.unregisterEvents(this);
match.unregisterRepeatable(this);
default World world() {
return match().getWorld();
}
protected static ItemStack item(Material material, int amount) {
return new ItemBuilder().material(material).amount(amount).unbreakable(true).shareable(false).get();
default Entropy entropy() {
return match().entropyForTick();
}
protected static ItemStack item(Material material) {
return item(material, 1);
default Random random() {
return match().getRandom();
}
protected <E extends Entity> E spawn(Location location, Class<E> entityClass) {
E entity = world.spawn(location, entityClass);
if(entity instanceof LivingEntity) {
LivingEntity living = (LivingEntity) entity;
living.setCanPickupItems(false);
living.setRemoveWhenFarAway(true);
EntityEquipment equipment = living.getEquipment();
equipment.setHelmetDropChance(0);
equipment.setChestplateDropChance(0);
equipment.setLeggingsDropChance(0);
equipment.setBootsDropChance(0);
abstract class Impl implements MutationModule {
private final Match match;
private final Entropy entropy;
public Impl(final Match match) {
this.match = match;
this.entropy = new AdvancingEntropy(match.entropyForTick().randomLong());
}
return entity;
@Override
public Match match() {
return match;
}
@Override
public Entropy entropy() {
return entropy;
}
protected static ItemStack item(Material material, int amount) {
return new ItemBuilder().material(material).amount(amount).unbreakable(true).shareable(false).get();
}
protected static ItemStack item(Material material) {
return item(material, 1);
}
}
}

View File

@ -1,64 +1,114 @@
package tc.oc.pgm.mutation.types;
import java.time.Duration;
import java.time.Instant;
import java.util.List;
import tc.oc.commons.core.scheduler.Task;
import tc.oc.commons.core.stream.Collectors;
import tc.oc.commons.core.util.TimeUtils;
import tc.oc.pgm.match.Match;
import tc.oc.pgm.match.MatchPlayer;
import tc.oc.pgm.match.MatchScope;
import tc.oc.pgm.match.Repeatable;
import tc.oc.time.Time;
/**
* A mutation module that executes a task on random {@link MatchPlayer}s.
*/
public abstract class TargetMutation extends MutationModule {
private final Duration frequency;
private Task task;
public TargetMutation(final Match match, Duration frequency) {
super(match);
this.frequency = frequency;
}
public interface TargetMutation extends MutationModule {
/**
* Execute a task on the given randomly selected players.
* @param players a list of players, which size is determined by {@link #targets()}.
*/
public abstract void execute(List<MatchPlayer> players);
void target(List<MatchPlayer> players);
/**
* Determine the number of random players to target.
*
* If there are no enough players on the server, it is possible
* that the number of targets is less than expected.
* @return number of targets.
*/
public abstract int targets();
int targets();
/**
* Get the next time {@link #target()} will be run.
* @return next target time.
*/
Instant next();
/**
* Set the next time {@link #target()} will be run.
* @param time next target time.
*/
void next(Instant time);
/**
* Get the frequency that {@link #target()} will be run.
* @return frequency between target times.
*/
Duration frequency();
/**
* Generate a list of random players.
* @return the random players.
*/
public List<MatchPlayer> search() {
return match.participants()
.filter(MatchPlayer::isSpawned)
.collect(Collectors.toRandomSubList(entropy, targets()));
default List<MatchPlayer> search() {
return match().participants()
.filter(MatchPlayer::isSpawned)
.collect(Collectors.toRandomSubList(entropy(), targets()));
}
/**
* Execute a task on randomly selected players and reset the
* next time the task will be executed.
*/
default void target() {
target(search());
next(match().getInstantNow().plus(frequency()));
}
@Override
public void enable() {
super.enable();
this.task = match.getScheduler(MatchScope.RUNNING).createRepeatingTask(frequency, () -> execute(search()));
default void enable() {
MutationModule.super.enable();
target();
}
@Override
public void disable() {
if(task != null) {
task.cancel();
task = null;
@Repeatable(interval = @Time(seconds = 1))
default void tick() {
Instant now = match().getInstantNow(), next = next();
if(next == null) {
next(now.plus(frequency()));
} else if(TimeUtils.isEqualOrBeforeNow(now, next)) {
target();
}
super.disable();
}
abstract class Impl extends MutationModule.Impl implements TargetMutation {
Duration frequency;
Instant next;
public Impl(Match match, Duration frequency) {
super(match);
this.frequency = frequency;
}
@Override
public Instant next() {
return next;
}
@Override
public void next(Instant time) {
next = time;
}
@Override
public Duration frequency() {
return frequency;
}
}
}

View File

@ -1,17 +1,31 @@
package tc.oc.pgm.mutation.types.kit;
import com.google.common.collect.ImmutableSet;
import net.md_5.bungee.api.ChatColor;
import org.bukkit.Material;
import tc.oc.commons.bukkit.chat.WarningComponent;
import tc.oc.commons.bukkit.inventory.ArmorType;
import tc.oc.commons.bukkit.inventory.Slot;
import tc.oc.commons.core.chat.Component;
import tc.oc.commons.core.chat.Components;
import tc.oc.commons.core.collection.WeakHashSet;
import tc.oc.commons.core.util.TimeUtils;
import tc.oc.pgm.doublejump.DoubleJumpKit;
import tc.oc.pgm.kits.ItemKit;
import tc.oc.pgm.kits.ItemKitApplicator;
import tc.oc.pgm.kits.KitNode;
import tc.oc.pgm.kits.KitPlayerFacet;
import tc.oc.pgm.kits.RemoveKit;
import tc.oc.pgm.kits.SlotItemKit;
import tc.oc.pgm.match.Match;
import tc.oc.pgm.match.MatchPlayer;
import tc.oc.pgm.match.MatchScope;
import tc.oc.pgm.mutation.Mutation;
import tc.oc.pgm.mutation.MutationMatchModule;
import tc.oc.pgm.mutation.types.KitMutation;
import java.time.Duration;
import java.time.Instant;
import java.util.stream.Collectors;
import java.util.stream.Stream;
public class ElytraMutation extends KitMutation {
@ -30,13 +44,57 @@ public class ElytraMutation extends KitMutation {
@Override
public void remove(MatchPlayer player) {
// If the player is mid-air, give them a totem so they don't fall and die
if(player.getBukkit().isGliding()) {
ItemKitApplicator applicator = new ItemKitApplicator();
applicator.put(Slot.OffHand.offHand(), item(Material.TOTEM), false);
applicator.apply(player);
// Anyone left gliding will be taken care of by the ground stop order
if(!player.getBukkit().isGliding()) {
super.remove(player);
}
super.remove(player);
}
@Override
public void disable() {
new GroundStop(match()).run();
super.disable();
}
/**
* A cleanup task to slowly remove elytras from players.
*
* This prevents players that are mid-glide from falling
* out of the sky and gives them time to land.
*/
private class GroundStop implements Runnable {
Match match;
WeakHashSet<MatchPlayer> gliding;
Instant end;
GroundStop(Match match) {
this.match = match;
this.gliding = new WeakHashSet<>(match.participants().filter(player -> player.getBukkit().isGliding()).collect(Collectors.toSet()));
this.end = match.getInstantNow().plus(Duration.ofSeconds(10));
}
@Override
public void run() {
if(match.isRunning() && !gliding.isEmpty() && !match.module(MutationMatchModule.class).get().enabled(Mutation.ELYTRA)) {
Instant now = match().getInstantNow();
for(MatchPlayer player : ImmutableSet.copyOf(gliding)) {
if(!player.isSpawned() || !player.getBukkit().isGliding() || TimeUtils.isEqualOrBeforeNow(now, end)) {
gliding.remove(player);
player.facet(KitPlayerFacet.class).applyKit(KitNode.of(new RemoveKit(ELYTRA), new RemoveKit(JUMP)), true);
player.sendHotbarMessage(Components.blank());
} else {
long seconds = Duration.between(now, end).getSeconds();
player.sendHotbarMessage(new WarningComponent("mutation.type.elytra.land", new Component(seconds, ChatColor.YELLOW)));
}
}
match.getScheduler(MatchScope.RUNNING).createDelayedTask(Duration.ofMillis(50), this::run);
} else {
gliding.clear();
match = null;
}
}
}
}

View File

@ -21,31 +21,28 @@ import java.util.WeakHashMap;
public class EnchantmentMutation extends KitMutation {
final static ImmutableMap<Integer, Integer> LEVELS_MAP = new ImmutableMap.Builder<Integer, Integer>()
.put(1, 10)
.put(2, 3)
.put(1, 25)
.put(2, 5)
.put(3, 1)
.build();
final static ImmutableMap<Enchantment, Integer> ARMOR_MAP = new ImmutableMap.Builder<Enchantment, Integer>()
.put(Enchantment.PROTECTION_ENVIRONMENTAL, 15)
.put(Enchantment.PROTECTION_PROJECTILE, 10)
.put(Enchantment.DURABILITY, 10)
.put(Enchantment.PROTECTION_EXPLOSIONS, 5)
.put(Enchantment.PROTECTION_FALL, 5)
.put(Enchantment.PROTECTION_FIRE, 5)
.put(Enchantment.THORNS, 1)
.build();
final static ImmutableMap<Enchantment, Integer> BOOTS_MAP = new ImmutableMap.Builder<Enchantment, Integer>()
.putAll(ARMOR_MAP)
.put(Enchantment.WATER_WORKER, 5)
.put(Enchantment.DEPTH_STRIDER, 3)
.put(Enchantment.FROST_WALKER, 1)
.put(Enchantment.PROTECTION_FALL, 10)
.put(Enchantment.DEPTH_STRIDER, 3)
.put(Enchantment.FROST_WALKER, 1)
.build();
final static ImmutableMap<Enchantment, Integer> WEAPONS_MAP = new ImmutableMap.Builder<Enchantment, Integer>()
.put(Enchantment.DAMAGE_ALL, 15)
.put(Enchantment.DURABILITY, 10)
.put(Enchantment.KNOCKBACK, 10)
.put(Enchantment.MENDING, 5)
.put(Enchantment.SWEEPING_EDGE, 5)
@ -53,7 +50,6 @@ public class EnchantmentMutation extends KitMutation {
.build();
final static ImmutableMap<Enchantment, Integer> TOOLS_MAP = new ImmutableMap.Builder<Enchantment, Integer>()
.put(Enchantment.DURABILITY, 10)
.put(Enchantment.DIG_SPEED, 10)
.put(Enchantment.SILK_TOUCH, 5)
.put(Enchantment.LOOT_BONUS_BLOCKS, 5)
@ -116,9 +112,9 @@ public class EnchantmentMutation extends KitMutation {
byEntity.put(item, ImmutableMap.copyOf(item.getEnchantments()));
savedEnchantments.put(entity, byEntity);
// Apply the new enchantments
int amountOfEnchants = LEVELS.choose(entropy);
int amountOfEnchants = LEVELS.choose(entropy());
for(int i = 0; i < amountOfEnchants; i++) {
item.addUnsafeEnchantment(chooser.choose(entropy), LEVELS.choose(entropy));
item.addUnsafeEnchantment(chooser.choose(entropy()), LEVELS.choose(entropy()));
}
}
@ -129,7 +125,7 @@ public class EnchantmentMutation extends KitMutation {
super.apply(player);
player.getInventory().forEach(item -> {
// Random number of enchantments on each item
int numberOfEnchants = LEVELS.choose(entropy);
int numberOfEnchants = LEVELS.choose(entropy());
for(int i = 0; i < numberOfEnchants; i++) {
apply(item, player.getBukkit().getEquipment());
}

View File

@ -4,12 +4,11 @@ import com.google.common.collect.ImmutableMap;
import org.bukkit.Location;
import org.bukkit.Material;
import org.bukkit.entity.AbstractHorse;
import org.bukkit.entity.Entity;
import org.bukkit.entity.EntityType;
import org.bukkit.entity.Horse;
import org.bukkit.event.EventHandler;
import org.bukkit.event.EventPriority;
import org.bukkit.event.player.PlayerSpawnEntityEvent;
import org.bukkit.event.entity.EntityDamageByEntityEvent;
import org.bukkit.inventory.HorseInventory;
import org.bukkit.inventory.ItemStack;
import org.bukkit.inventory.meta.SpawnEggMeta;
@ -17,23 +16,23 @@ import org.bukkit.potion.PotionEffect;
import org.bukkit.potion.PotionEffectType;
import tc.oc.commons.core.random.ImmutableWeightedRandomChooser;
import tc.oc.commons.core.random.WeightedRandomChooser;
import tc.oc.pgm.events.MatchPlayerDeathEvent;
import tc.oc.pgm.events.PlayerChangePartyEvent;
import tc.oc.pgm.kits.FreeItemKit;
import tc.oc.pgm.kits.Kit;
import tc.oc.pgm.match.Match;
import tc.oc.pgm.match.MatchPlayer;
import tc.oc.pgm.mutation.types.KitMutation;
import tc.oc.pgm.mutation.types.EntityMutation;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import java.util.List;
import java.util.Map;
import java.util.WeakHashMap;
public class EquestrianMutation extends KitMutation {
import static tc.oc.commons.core.util.Optionals.cast;
public class EquestrianMutation extends EntityMutation<AbstractHorse> {
final static ImmutableMap<EntityType, Integer> TYPE_MAP = new ImmutableMap.Builder<EntityType, Integer>()
.put(EntityType.HORSE, 100)
// FIXME: Saddle do not work on these horses
// FIXME: Saddles do not work on these horses
//.put(EntityType.SKELETON_HORSE, 5)
//.put(EntityType.ZOMBIE_HORSE, 5)
//.put(EntityType.LLAMA, 1)
@ -49,18 +48,15 @@ public class EquestrianMutation extends KitMutation {
final static WeightedRandomChooser<EntityType, Integer> TYPES = new ImmutableWeightedRandomChooser<>(TYPE_MAP);
final static WeightedRandomChooser<Material, Integer> ARMOR = new ImmutableWeightedRandomChooser<>(ARMOR_MAP);
final Map<MatchPlayer, AbstractHorse> horses;
public EquestrianMutation(Match match) {
super(match, false);
this.horses = new WeakHashMap<>();
}
@Override
public void disable() {
super.disable();
match.participants().forEach(this::remove);
horses.clear();
public void enable() {
super.enable();
this.itemRemove.add(Material.LEATHER);
this.itemRemove.addAll(ARMOR_MAP.keySet());
}
@Override
@ -72,7 +68,7 @@ public class EquestrianMutation extends KitMutation {
if(!spawnable(location)) {
ItemStack item = item(Material.MONSTER_EGG);
SpawnEggMeta egg = (SpawnEggMeta) item.getItemMeta();
egg.setSpawnedType(TYPES.choose(entropy));
egg.setSpawnedType(TYPES.choose(entropy()));
item.setItemMeta(egg);
kits.add(new FreeItemKit(item));
}
@ -83,38 +79,31 @@ public class EquestrianMutation extends KitMutation {
super.apply(player);
Location location = player.getLocation();
if(spawnable(location)) {
setup(player, spawn(location, (Class<? extends AbstractHorse>) TYPES.choose(match.entropyForTick()).getEntityClass()));
spawn(location, (Class<AbstractHorse>) TYPES.choose(entropy()).getEntityClass(), player);
}
}
@Override
public void remove(MatchPlayer player) {
super.remove(player);
AbstractHorse horse = horses.remove(player);
if(horse != null) {
horse.ejectAll();
horse.remove();
}
}
public void setup(MatchPlayer player, AbstractHorse horse) {
horses.put(player, horse);
horse.setAdult();
horse.setJumpStrength(2 * entropy.randomDouble());
horse.setDomestication(1);
horse.setMaxDomestication(1);
horse.setTamed(true);
horse.addPotionEffect(new PotionEffect(PotionEffectType.REGENERATION, Integer.MAX_VALUE, 0));
horse.setOwner(player.getBukkit());
horse.setPassenger(player.getBukkit());
if(horse instanceof Horse) {
Horse horsey = (Horse) horse;
horsey.setStyle(entropy.randomElement(Horse.Style.values()));
horsey.setColor(entropy.randomElement(Horse.Color.values()));
HorseInventory inventory = horsey.getInventory();
inventory.setSaddle(item(Material.SADDLE));
inventory.setArmor(item(ARMOR.choose(entropy)));
public AbstractHorse register(AbstractHorse horse, @Nullable MatchPlayer owner) {
super.register(horse, owner);
if(owner != null) {
horse.setAdult();
horse.setJumpStrength(2 * entropy().randomDouble());
horse.setDomestication(1);
horse.setMaxDomestication(1);
horse.setTamed(true);
horse.addPotionEffect(new PotionEffect(PotionEffectType.REGENERATION, Integer.MAX_VALUE, 0));
horse.setOwner(owner.getBukkit());
horse.setPassenger(owner.getBukkit());
cast(horse, Horse.class).ifPresent(horsey -> {
horsey.setStyle(entropy().randomElement(Horse.Style.values()));
horsey.setColor(entropy().randomElement(Horse.Color.values()));
HorseInventory inventory = horsey.getInventory();
inventory.setSaddle(item(Material.SADDLE));
inventory.setArmor(item(ARMOR.choose(entropy())));
});
}
return horse;
}
public boolean spawnable(Location location) {
@ -129,22 +118,11 @@ public class EquestrianMutation extends KitMutation {
}
@EventHandler(ignoreCancelled = true, priority = EventPriority.HIGHEST)
public void onEntitySpawn(PlayerSpawnEntityEvent event) {
Entity entity = event.getEntity();
if(TYPE_MAP.containsKey(entity.getType())) {
match.participant(event.getPlayer())
.ifPresent(player -> setup(player, (AbstractHorse) event.getEntity()));
public void onEntityDamage(EntityDamageByEntityEvent event) {
if(playerByEntity(event.getEntity()).flatMap(MatchPlayer::competitor)
.equals(match().participant(event.getDamager()).flatMap(MatchPlayer::competitor))) {
event.setCancelled(true);
}
}
@EventHandler(ignoreCancelled = true, priority = EventPriority.HIGHEST)
public void onPlayerDeath(MatchPlayerDeathEvent event) {
remove(event.getVictim());
}
@EventHandler(ignoreCancelled = true, priority = EventPriority.HIGHEST)
public void onPartyChange(PlayerChangePartyEvent event) {
remove(event.getPlayer());
}
}

View File

@ -3,13 +3,11 @@ package tc.oc.pgm.mutation.types.kit;
import com.google.common.collect.Range;
import org.bukkit.Material;
import org.bukkit.enchantments.Enchantment;
import org.bukkit.entity.TNTPrimed;
import org.bukkit.event.EventHandler;
import org.bukkit.event.EventPriority;
import org.bukkit.event.entity.ExplosionPrimeEvent;
import org.bukkit.inventory.PlayerInventory;
import tc.oc.commons.bukkit.item.ItemBuilder;
import tc.oc.commons.core.collection.WeakHashSet;
import tc.oc.pgm.killreward.KillReward;
import tc.oc.pgm.kits.FreeItemKit;
import tc.oc.pgm.kits.ItemKit;
@ -30,26 +28,17 @@ public class ExplosiveMutation extends KitMutation {
final static Range<Integer> RADIUS = Range.openClosed(0, 4);
final WeakHashSet<TNTPrimed> tracked;
public ExplosiveMutation(Match match) {
super(match, false);
this.tracked = new WeakHashSet<>();
this.rewards.add(new KillReward(TNT));
this.rewards.add(new KillReward(ARROWS));
}
@Override
public void disable() {
super.disable();
tracked.clear();
}
@Override
public void kits(MatchPlayer player, List<Kit> kits) {
super.kits(player, kits);
PlayerInventory inv = player.getInventory();
if(random.nextBoolean()) { // tnt and lighter kit
if(random().nextBoolean()) { // tnt and lighter kit
if(!inv.contains(Material.TNT)) kits.add(TNT);
if(!inv.contains(Material.FLINT_AND_STEEL)) kits.add(LIGHTER);
} else { // fire bow and arrows kit
@ -62,9 +51,15 @@ public class ExplosiveMutation extends KitMutation {
}
}
@Override
public void remove(MatchPlayer player) {
player.getInventory().all(Material.BOW).values().forEach(bow -> FIRE_BOW.item().getEnchantments().keySet().forEach(enchantment -> bow.removeEnchantment(enchantment)));
super.remove(player);
}
@EventHandler(ignoreCancelled = true, priority = EventPriority.HIGHEST)
public void onExplosionPrime(ExplosionPrimeEvent event) {
event.setRadius(event.getRadius() + entropy.randomInt(RADIUS));
event.setRadius(event.getRadius() + entropy().randomInt(RADIUS));
}
}

View File

@ -1,7 +1,6 @@
package tc.oc.pgm.mutation.types.kit;
import org.bukkit.Material;
import org.bukkit.inventory.ItemStack;
import tc.oc.pgm.gamerules.GameRulesMatchModule;
import tc.oc.pgm.killreward.KillReward;
import tc.oc.pgm.kits.FreeItemKit;
@ -26,13 +25,13 @@ public class HardcoreMutation extends KitMutation {
}
public GameRulesMatchModule rules() {
return match.module(GameRulesMatchModule.class).get();
return match().module(GameRulesMatchModule.class).get();
}
@Override
public void enable() {
super.enable();
previous = world.getGameRuleValue(KEY);
previous = world().getGameRuleValue(KEY);
rules().gameRules().put(KEY, "false");
}
@ -40,7 +39,7 @@ public class HardcoreMutation extends KitMutation {
public void disable() {
rules().gameRules().remove(KEY);
if(previous != null) {
world.setGameRuleValue(KEY, previous);
world().setGameRuleValue(KEY, previous);
}
super.disable();
}

View File

@ -1,7 +1,6 @@
package tc.oc.pgm.mutation.types.kit;
import org.bukkit.Material;
import org.bukkit.inventory.ItemStack;
import tc.oc.pgm.killreward.KillReward;
import tc.oc.pgm.kits.FreeItemKit;
import tc.oc.pgm.kits.HealthKit;

View File

@ -4,6 +4,7 @@ import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Range;
import org.bukkit.Material;
import org.bukkit.entity.EntityType;
import org.bukkit.entity.LivingEntity;
import org.bukkit.inventory.ItemStack;
import org.bukkit.inventory.meta.SpawnEggMeta;
import tc.oc.commons.core.random.ImmutableWeightedRandomChooser;
@ -12,11 +13,11 @@ import tc.oc.pgm.kits.FreeItemKit;
import tc.oc.pgm.kits.Kit;
import tc.oc.pgm.match.Match;
import tc.oc.pgm.match.MatchPlayer;
import tc.oc.pgm.mutation.types.KitMutation;
import tc.oc.pgm.mutation.types.EntityMutation;
import java.util.List;
public class MobsMutation extends KitMutation {
public class MobsMutation extends EntityMutation<LivingEntity> {
final static ImmutableMap<EntityType, Integer> TYPE_MAP = new ImmutableMap.Builder<EntityType, Integer>()
.put(EntityType.ZOMBIE, 50)
@ -37,17 +38,17 @@ public class MobsMutation extends KitMutation {
final static Range<Integer> AMOUNT = Range.closed(1, 3);
public MobsMutation(Match match) {
super(match, true);
super(match, false);
}
@Override
public void kits(MatchPlayer player, List<Kit> kits) {
super.kits(player, kits);
int eggs = entropy.randomInt(AMOUNT);
int eggs = entropy().randomInt(AMOUNT);
for(int i = 0; i < eggs; i++) {
ItemStack item = item(Material.MONSTER_EGG, entropy.randomInt(AMOUNT));
ItemStack item = item(Material.MONSTER_EGG, entropy().randomInt(AMOUNT));
SpawnEggMeta egg = (SpawnEggMeta) item.getItemMeta();
egg.setSpawnedType(TYPES.choose(entropy));
egg.setSpawnedType(TYPES.choose(entropy()));
item.setItemMeta(egg);
kits.add(new FreeItemKit(item));
}

View File

@ -22,7 +22,7 @@ public abstract class NoFallMutation extends KitMutation {
}
public DisableDamageMatchModule damage() {
return match.module(DisableDamageMatchModule.class).get();
return match().module(DisableDamageMatchModule.class).get();
}
@Override

View File

@ -68,13 +68,13 @@ public class PotionMutation extends KitMutation {
@Override
public void kits(MatchPlayer player, List<Kit> kits) {
super.kits(player, kits);
int numberOfPotions = entropy.randomInt(AMOUNT_RANGE);
int numberOfPotions = entropy().randomInt(AMOUNT_RANGE);
for(int i = 0; i < numberOfPotions; i++) {
WeightedRandomChooser<PotionEffectType, Integer> type;
WeightedRandomChooser<Material, Integer> material;
Range<Integer> range;
// Determine whether the potion will be "good" or "bad"
if(random.nextBoolean()) {
if(random().nextBoolean()) {
type = BAD;
material = BAD_BOTTLE;
range = BAD_DURATION_RANGE;
@ -84,10 +84,10 @@ public class PotionMutation extends KitMutation {
range = GOOD_DURATION_RANGE;
}
// Choose all the random attributes
PotionEffectType effect = type.choose(entropy);
Material bottle = material.choose(entropy);
int duration = 20 * entropy.randomInt(range);
int amplifier = entropy.randomInt(AMPLIFIER_RANGE);
PotionEffectType effect = type.choose(entropy());
Material bottle = material.choose(entropy());
int duration = 20 * entropy().randomInt(range);
int amplifier = entropy().randomInt(AMPLIFIER_RANGE);
// Apply the attributes to the item stack
ItemStack potion = item(bottle);
PotionMeta meta = (PotionMeta) potion.getItemMeta();

View File

@ -3,20 +3,17 @@ package tc.oc.pgm.mutation.types.kit;
import com.google.common.collect.Range;
import org.bukkit.Material;
import org.bukkit.enchantments.Enchantment;
import org.bukkit.entity.Arrow;
import org.bukkit.entity.Entity;
import org.bukkit.entity.TippedArrow;
import org.bukkit.entity.LivingEntity;
import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
import org.bukkit.event.EventPriority;
import org.bukkit.event.entity.EntityShootBowEvent;
import org.bukkit.event.entity.ProjectileLaunchEvent;
import org.bukkit.event.entity.ProjectileHitEvent;
import org.bukkit.inventory.Inventory;
import org.bukkit.metadata.FixedMetadataValue;
import org.bukkit.potion.PotionEffect;
import org.bukkit.potion.PotionEffectType;
import tc.oc.commons.core.random.ImmutableWeightedRandomChooser;
import tc.oc.commons.core.random.WeightedRandomChooser;
import tc.oc.pgm.PGM;
import tc.oc.commons.core.util.Optionals;
import tc.oc.pgm.killreward.KillReward;
import tc.oc.pgm.kits.FreeItemKit;
import tc.oc.pgm.kits.ItemKit;
@ -26,6 +23,7 @@ import tc.oc.pgm.match.MatchPlayer;
import tc.oc.pgm.mutation.types.KitMutation;
import java.util.List;
import java.util.Optional;
public class ProjectileMutation extends KitMutation {
@ -39,8 +37,6 @@ public class ProjectileMutation extends KitMutation {
final static ItemKit BOW = new FreeItemKit(item(Material.BOW));
final static ItemKit ARROWS = new FreeItemKit(item(Material.ARROW, 16));
final static String KEY = "is_modified_arrow";
public ProjectileMutation(Match match) {
super(match, false);
this.rewards.add(new KillReward(ARROWS));
@ -50,7 +46,7 @@ public class ProjectileMutation extends KitMutation {
public void apply(MatchPlayer player) {
super.apply(player);
Inventory inventory = player.getInventory();
inventory.all(Material.BOW).values().forEach(arrow -> arrow.addUnsafeEnchantment(ENCHANTMENTS.choose(entropy), entropy.randomInt(ENCHANT_RANGE)));
inventory.all(Material.BOW).values().forEach(arrow -> arrow.addUnsafeEnchantment(ENCHANTMENTS.choose(entropy()), entropy().randomInt(ENCHANT_RANGE)));
}
@Override
@ -62,23 +58,10 @@ public class ProjectileMutation extends KitMutation {
}
@EventHandler(ignoreCancelled = true, priority = EventPriority.HIGHEST)
public void onBowShoot(EntityShootBowEvent event) {
Entity projectile = event.getProjectile();
if(projectile instanceof Arrow && (!projectile.hasMetadata(KEY) || !projectile.getMetadata(KEY, PGM.get()).asBoolean())) {
Arrow arrow = (Arrow) projectile;
TippedArrow tipped = world.spawn(projectile.getLocation(), TippedArrow.class);
tipped.setMetadata(KEY, new FixedMetadataValue(PGM.get(), true));
tipped.setCritical(arrow.isCritical());
tipped.setKnockbackStrength(arrow.getKnockbackStrength());
tipped.setDamage(arrow.getDamage());
tipped.setShooter(arrow.getShooter());
tipped.setVelocity(projectile.getVelocity());
tipped.setPickupRule(Arrow.PickupRule.DISALLOWED);
tipped.addCustomEffect(new PotionEffect(POTIONS.choose(entropy), 20 * entropy.randomInt(DURATION_RANGE), entropy.randomInt(AMPLIFIER_RANGE)), true);
arrow.remove();
event.setCancelled(true);
match.callEvent(new ProjectileLaunchEvent(tipped));
}
public void onProjectileHit(ProjectileHitEvent event) {
Optionals.cast(Optional.ofNullable(event.getHitEntity()), LivingEntity.class)
.filter(entity -> Optional.ofNullable(event.getEntity().getShooter()).filter(shooter -> shooter instanceof Player).isPresent())
.ifPresent(entity -> entity.addPotionEffect(new PotionEffect(POTIONS.choose(entropy()), 20 * entropy().randomInt(DURATION_RANGE), entropy().randomInt(AMPLIFIER_RANGE)), true));
}
}

View File

@ -11,7 +11,7 @@ import tc.oc.pgm.match.MatchScope;
import tc.oc.pgm.mutation.types.MutationModule;
import tc.oc.pgm.teams.TeamMatchModule;
public class BlitzMutation extends MutationModule {
public class BlitzMutation extends MutationModule.Impl {
final static Range<Integer> LIVES = Range.closed(1, 3);
final static Fraction TEAM_CHANCE = Fraction.ONE_QUARTER;
@ -23,22 +23,22 @@ public class BlitzMutation extends MutationModule {
@Override
public void enable() {
super.enable();
int lives = match.entropyForTick().randomInt(LIVES);
int lives = entropy().randomInt(LIVES);
Lives.Type type;
if(match.module(TeamMatchModule.class).isPresent() && RandomUtils.nextBoolean(random, TEAM_CHANCE)) {
if(match().module(TeamMatchModule.class).isPresent() && RandomUtils.nextBoolean(random(), TEAM_CHANCE)) {
type = Lives.Type.TEAM;
lives *= match.module(TeamMatchModule.class).get().getFullestTeam().getSize();
lives *= match().module(TeamMatchModule.class).get().getFullestTeam().getSize();
} else {
type = Lives.Type.INDIVIDUAL;
}
match.module(BlitzMatchModuleImpl.class).get().activate(BlitzProperties.create(match, lives, type));
match().module(BlitzMatchModuleImpl.class).get().activate(BlitzProperties.create(match(), lives, type));
}
@Override
public void disable() {
match.getScheduler(MatchScope.LOADED).createTask(() -> {
if(!match.isFinished()) {
match.module(BlitzMatchModuleImpl.class).get().deactivate();
match().getScheduler(MatchScope.LOADED).createTask(() -> {
if(!match().isFinished()) {
match().module(BlitzMatchModuleImpl.class).get().deactivate();
}
});
super.disable();

View File

@ -7,7 +7,7 @@ import tc.oc.pgm.match.Match;
import tc.oc.pgm.mutation.types.MutationModule;
import tc.oc.pgm.rage.RageMatchModule;
public class RageMutation extends MutationModule {
public class RageMutation extends MutationModule.Impl {
RageMatchModule rage;
@ -26,4 +26,5 @@ public class RageMutation extends MutationModule {
super.disable();
rage = null;
}
}

View File

@ -21,6 +21,7 @@ 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;
@ -28,19 +29,17 @@ 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.Comparator;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.WeakHashMap;
import static tc.oc.commons.core.random.RandomUtils.nextBoolean;
public class ApocalypseMutation extends TargetMutation {
public class ApocalypseMutation extends EntityMutation<LivingEntity> implements TargetMutation {
final static ImmutableMap<Integer, Integer> AMOUNT_MAP = new ImmutableMap.Builder<Integer, Integer>()
.put(3, 25)
@ -104,6 +103,7 @@ public class ApocalypseMutation extends TargetMutation {
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
@ -111,33 +111,34 @@ public class ApocalypseMutation extends TargetMutation {
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
final WeakHashMap<Entity, Instant> entities;
long time;
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, Duration.ofSeconds(20));
this.entities = new WeakHashMap<>();
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);
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();
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()), new PointProviderAttributes()))).getPoint(match, null));
return Optional.ofNullable(new RandomPointProvider(Collections.singleton(new RegionPointProvider(new CuboidRegion(start.position(), end.position()), attributes))).getPoint(match(), null));
}
/**
@ -147,15 +148,15 @@ public class ApocalypseMutation extends TargetMutation {
*/
public void spawn(Location location, boolean ground) {
int slots = entitiesLeft();
int queued = AMOUNT.choose(entropy);
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);
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);
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;
@ -163,7 +164,7 @@ public class ApocalypseMutation extends TargetMutation {
if(air) {
if(stacked) {
if(ground) {
chooser = nextBoolean(random, SPECIAL_CHANCE) ? CUBE : FLYABLE;
chooser = nextBoolean(random(), SPECIAL_CHANCE) ? CUBE : FLYABLE;
} else {
chooser = FLYABLE;
}
@ -174,7 +175,7 @@ public class ApocalypseMutation extends TargetMutation {
if(stacked) {
chooser = GROUND;
} else {
chooser = random.nextBoolean() ? GROUND : RANGED;
chooser = random().nextBoolean() ? GROUND : RANGED;
}
}
// Select the specific entity types for the spawn,
@ -182,13 +183,13 @@ public class ApocalypseMutation extends TargetMutation {
// 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));
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, type);
Entity entity = spawn(location, (Class<LivingEntity>) type.getEntityClass());
if(last != null) {
last.setPassenger(entity);
}
@ -198,20 +199,14 @@ public class ApocalypseMutation extends TargetMutation {
}
/**
* Spawn an individual entitiy at a location given an entity type.
* @param location the location to spawn at.
* @param type the type of entity.
* @return the constructed entity.
*/
public LivingEntity spawn(Location location, EntityType type) {
EnchantmentMutation enchant = new EnchantmentMutation(match);
LivingEntity entity = (LivingEntity) spawn(location, type.getEntityClass());
@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());
entity.setAbsorption(5);
ItemStack held = null;
switch(type) {
switch(entity.getType()) {
case SKELETON:
case WITHER_SKELETON:
case STRAY:
@ -221,7 +216,7 @@ public class ApocalypseMutation extends TargetMutation {
case ZOMBIE_VILLAGER:
case HUSK:
Zombie zombie = (Zombie) entity;
zombie.setBaby(nextBoolean(random, SPECIAL_CHANCE));
zombie.setBaby(nextBoolean(random(), SPECIAL_CHANCE));
break;
case PIG_ZOMBIE:
PigZombie pigZombie = (PigZombie) entity;
@ -231,8 +226,8 @@ public class ApocalypseMutation extends TargetMutation {
break;
case CREEPER:
Creeper creeper = (Creeper) entity;
creeper.setPowered(nextBoolean(random, SPECIAL_CHANCE));
world.strikeLightningEffect(location);
creeper.setPowered(nextBoolean(random(), SPECIAL_CHANCE));
world().strikeLightningEffect(location);
break;
case PRIMED_TNT:
TNTPrimed tnt = (TNTPrimed) entity;
@ -244,14 +239,13 @@ public class ApocalypseMutation extends TargetMutation {
slime.setSize(slime.getSize() * SPECIAL_MULTIPLIER);
break;
case SKELETON_HORSE:
world.strikeLightning(location);
world().strikeLightning(location);
break;
}
if(held != null && random.nextBoolean()) {
if(held != null && random().nextBoolean()) {
enchant.apply(held, equipment);
equipment.setItemInMainHand(held);
}
entities.put(entity, Instant.now());
return entity;
}
@ -260,17 +254,12 @@ public class ApocalypseMutation extends TargetMutation {
* to make room for new entities.
* @param amount the amount of entities to despawn.
*/
public void despawn(int amount) {
entities.entrySet()
.stream()
.sorted(Map.Entry.comparingByValue(Comparator.comparing(Instant::toEpochMilli)))
.limit(Math.max(0, amount))
.map(Map.Entry::getKey)
.forEach(Entity::remove);
public void despawn(long amount) {
entitiesByTime().limit(Math.max(0, amount)).forEachOrdered(this::despawn);
}
@Override
public void execute(List<MatchPlayer> players) {
public void target(List<MatchPlayer> players) {
// At least one player is required to spawn mobs
if(players.size() >= 1) {
Location start, end;
@ -284,7 +273,7 @@ public class ApocalypseMutation extends TargetMutation {
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);
spawn(start.position().midpoint(end.position()).toLocation(world()), false);
}
}
}
@ -294,21 +283,38 @@ public class ApocalypseMutation extends TargetMutation {
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();
time = world.getTime();
TargetMutation.super.enable();
time = world().getTime();
}
@Repeatable
public void tick() {
world.setTime(16000); // Night time to prevent flaming entities
TargetMutation.super.tick();
world().setTime(16000); // Night time to prevent flaming entities
}
@Override
public void disable() {
world.setTime(time);
despawn(entities.size());
world().setTime(time);
despawn(entities().count());
super.disable();
}

View File

@ -4,63 +4,85 @@ import com.google.common.collect.Range;
import org.bukkit.Location;
import org.bukkit.entity.TNTPrimed;
import org.bukkit.util.Vector;
import tc.oc.commons.core.collection.WeakHashSet;
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.TargetMutation;
import java.time.Duration;
import java.time.Instant;
import java.util.List;
public class BomberMutation extends TargetMutation {
public class BomberMutation extends EntityMutation<TNTPrimed> implements TargetMutation {
final static Duration FREQUENCY = Duration.ofSeconds(30);
final static Range<Integer> TARGETS = Range.closed(1, 5);
final static Range<Integer> TARGETS = Range.closed(2, 5);
final static Range<Integer> HEIGHT = Range.closed(30, 60);
final static Range<Integer> TICKS = Range.closed(10, 30);
final WeakHashSet<TNTPrimed> falling;
Instant next;
public BomberMutation(Match match) {
super(match, FREQUENCY);
this.falling = new WeakHashSet<>();
super(match, false);
}
@Override
public void execute(List<MatchPlayer> players) {
public void target(List<MatchPlayer> players) {
players.forEach(player -> {
int bombs = entropy.randomInt(TARGETS);
int height = entropy.randomInt(HEIGHT);
int bombs = entropy().randomInt(TARGETS);
int height = entropy().randomInt(HEIGHT);
Location location = player.getLocation().clone().add(0, height, 0);
for(int i = 0; i < bombs; i++) {
TNTPrimed tnt = world.spawn(location, TNTPrimed.class);
TNTPrimed tnt = spawn(location, TNTPrimed.class);
tnt.setGlowing(true);
tnt.setIsIncendiary(false);
tnt.setFuseTicks(Integer.MAX_VALUE);
tnt.setFuseTicks(200);
tnt.setVelocity(
new Vector(
(random.nextBoolean() ? .5 : -.5) * entropy.randomDouble(),
-entropy.randomDouble(),
(random.nextBoolean() ? .5 : -.5) * entropy.randomDouble()
(random().nextBoolean() ? .5 : -.5) * entropy().randomDouble(),
-entropy().randomDouble(),
(random().nextBoolean() ? .5 : -.5) * entropy().randomDouble()
)
);
falling.add(tnt);
}
});
}
@Override
public int targets() {
return match.entropyForTick().randomInt(TARGETS);
return match().entropyForTick().randomInt(TARGETS);
}
@Repeatable
@Override
public Instant next() {
return next;
}
@Override
public void next(Instant time) {
next = time;
}
@Override
public Duration frequency() {
return FREQUENCY;
}
@Override
public void remove(TNTPrimed entity) {
entity.setFuseTicks(entropy().randomInt(TICKS));
}
@Override
public void enable() {
super.enable();
TargetMutation.super.enable();
}
@Override
public void tick() {
falling.stream()
.filter(TNTPrimed::isOnGround)
.forEach(tnt -> tnt.setFuseTicks(entropy.randomInt(TICKS)));
falling.removeIf(TNTPrimed::isOnGround);
TargetMutation.super.tick();
entities().filter(TNTPrimed::isOnGround).forEach(this::despawn);
}
}

View File

@ -2,7 +2,6 @@ package tc.oc.pgm.mutation.types.targetable;
import com.google.common.collect.Range;
import org.bukkit.Location;
import org.bukkit.World;
import org.bukkit.util.Vector;
import tc.oc.pgm.match.Match;
import tc.oc.pgm.match.MatchPlayer;
@ -11,32 +10,31 @@ import tc.oc.pgm.mutation.types.TargetMutation;
import java.time.Duration;
import java.util.List;
public class LightningMutation extends TargetMutation {
public class LightningMutation extends TargetMutation.Impl {
final static Duration FREQUENCY = Duration.ofSeconds(30);
final static Range<Integer> TARGETS = Range.closed(1, 5);
final static Range<Integer> STRIKES = Range.closed(0, 3);
final static Range<Integer> TARGETS = Range.closed(2, 5);
final static Range<Integer> STRIKES = Range.closed(1, 3);
public LightningMutation(Match match) {
super(match, FREQUENCY);
}
@Override
public void execute(List<MatchPlayer> players) {
public void target(List<MatchPlayer> players) {
players.forEach(player -> {
World world = match.getWorld();
Location location = player.getLocation();
world.strikeLightning(location.clone().add(Vector.getRandom()));
int strikes = match.entropyForTick().randomInt(STRIKES);
world().strikeLightning(location.clone().add(Vector.getRandom()));
int strikes = entropy().randomInt(STRIKES);
for(int i = 0; i < strikes; i++) {
world.strikeLightningEffect(location.clone().add(Vector.getRandom().multiply(Math.pow(i + 1, 2))));
world().strikeLightningEffect(location.clone().add(Vector.getRandom().multiply(Math.pow(i + 1, 2))));
}
});
}
@Override
public int targets() {
return match.entropyForTick().randomInt(TARGETS);
return match().entropyForTick().randomInt(TARGETS);
}
}