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.explosive.desc = tnt and fire bows
mutation.type.elytra = Elytra mutation.type.elytra = Elytra
mutation.type.elytra.desc = fly around with an 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 = Projectile
mutation.type.projectile.desc = arrow potion effects mutation.type.projectile.desc = arrow potion effects
mutation.type.enchantment = Enchantment 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.singular = Your team has {0} life left
lives.remaining.team.plural = Your team has {0} lives 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.eliminated = eliminated
lives.status.alive = {0} alive lives.status.alive = {0} alive
lives.status.lives = {0} lives 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); final Optional<Lives> lives = lives(player);
if(activated() && lives.isPresent()) { if(activated() && lives.isPresent()) {
player.showTitle( player.showTitle(
@ -118,6 +122,10 @@ public class BlitzMatchModuleImpl extends MatchModule implements BlitzMatchModul
} }
} }
private void update() {
match.callEvent(new BlitzEvent(match, this));
}
@Override @Override
public boolean activated() { public boolean activated() {
return activated; return activated;
@ -142,6 +150,7 @@ public class BlitzMatchModuleImpl extends MatchModule implements BlitzMatchModul
activated = false; activated = false;
lives.clear(); lives.clear();
eliminated.clear(); eliminated.clear();
update();
} }
@Override @Override
@ -161,10 +170,10 @@ public class BlitzMatchModuleImpl extends MatchModule implements BlitzMatchModul
match.participants().forEach(player -> { match.participants().forEach(player -> {
setup(player, false); setup(player, false);
if(match.hasStarted()) { 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) { if(notify) {
player.showTitle(Components.blank(), life.change(lives), 0, 40, 10); player.showTitle(Components.blank(), life.change(lives), 0, 40, 10);
} }
player.competitor().ifPresent(competitor -> competitor.participants().forEach(this::livesHotbar));
if(life.empty() && immediate) { if(life.empty() && immediate) {
eliminate(player); eliminate(player);
return true; return true;
@ -225,9 +235,9 @@ public class BlitzMatchModuleImpl extends MatchModule implements BlitzMatchModul
ImmutableSet.copyOf(getMatch().getParticipatingPlayers()) ImmutableSet.copyOf(getMatch().getParticipatingPlayers())
.stream() .stream()
.filter(this::eliminated) .filter(this::eliminated)
.forEach(participating -> { .forEach(eliminated -> {
match.setPlayerParty(participating, match.getDefaultParty()); match.setPlayerParty(eliminated, match.getDefaultParty());
world.spawnParticle(Particle.SMOKE_LARGE, player.getLocation(), 5); world.spawnParticle(Particle.SMOKE_LARGE, eliminated.getLocation(), 5);
}); });
victory.invalidateAndCheckEnd(); victory.invalidateAndCheckEnd();
}); });
@ -269,15 +279,14 @@ public class BlitzMatchModuleImpl extends MatchModule implements BlitzMatchModul
@EventHandler(priority = EventPriority.HIGH, ignoreCancelled = true) @EventHandler(priority = EventPriority.HIGH, ignoreCancelled = true)
public void onDeath(MatchPlayerDeathEvent event) { public void onDeath(MatchPlayerDeathEvent event) {
final MatchPlayer player = event.getVictim(); event.getVictim()
if(player.competitor().isPresent()) { .competitor()
increment(player, -1, false, true); .ifPresent(competitor -> increment(event.getVictim(), -1, false, true));
}
} }
@EventHandler @EventHandler
public void onRelease(ParticipantReleaseEvent event) { 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(); update();
} }
private void update() { protected void update() {
competitor().getMatch().callEvent(new LivesEvent(this)); competitor().getMatch().callEvent(new LivesEvent(this));
} }
@ -67,7 +67,8 @@ public abstract class LivesBase implements Lives {
public BaseComponent remaining() { public BaseComponent remaining() {
return new Component( return new Component(
new TranslatableComponent( 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) new Component(current(), ChatColor.YELLOW)
), ),
ChatColor.AQUA ChatColor.AQUA
@ -103,7 +104,7 @@ public abstract class LivesBase implements Lives {
new TranslatableComponent( new TranslatableComponent(
(delta > 0 ? "lives.change.gained." (delta > 0 ? "lives.change.gained."
: "lives.change.lost.") + (absDelta == 1 ? "singular" : "lives.change.lost.") + (absDelta == 1 ? "singular"
: "plural"), : "plural"),
new Component(absDelta, ChatColor.AQUA) new Component(absDelta, ChatColor.AQUA)
), ),
ChatColor.WHITE ChatColor.WHITE

View File

@ -1,6 +1,11 @@
package tc.oc.pgm.blitz; 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.api.docs.PlayerId;
import tc.oc.commons.core.chat.Component;
import tc.oc.commons.core.chat.Components;
import tc.oc.pgm.match.Competitor; import tc.oc.pgm.match.Competitor;
public class LivesTeam extends LivesBase { public class LivesTeam extends LivesBase {
@ -24,6 +29,24 @@ public class LivesTeam extends LivesBase {
return false; 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 @Override
public int hashCode() { public int hashCode() {
return competitor().hashCode(); return competitor().hashCode();

View File

@ -1,6 +1,7 @@
package tc.oc.pgm.modules; package tc.oc.pgm.modules;
import org.bukkit.event.EventHandler; import org.bukkit.event.EventHandler;
import org.bukkit.event.EventPriority;
import org.bukkit.event.Listener; import org.bukkit.event.Listener;
import org.bukkit.event.entity.CreatureSpawnEvent; import org.bukkit.event.entity.CreatureSpawnEvent;
import tc.oc.pgm.events.ListenerScope; 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.filters.query.EntitySpawnQuery;
import tc.oc.pgm.match.MatchModule; import tc.oc.pgm.match.MatchModule;
import tc.oc.pgm.match.MatchScope; import tc.oc.pgm.match.MatchScope;
import tc.oc.pgm.mutation.MutationMatchModule;
@ListenerScope(MatchScope.LOADED) @ListenerScope(MatchScope.LOADED)
public class MobsMatchModule extends MatchModule implements Listener { public class MobsMatchModule extends MatchModule implements Listener {
@ -37,10 +37,9 @@ public class MobsMatchModule extends MatchModule implements Listener {
getMatch().getWorld().setSpawnFlags(false, false); getMatch().getWorld().setSpawnFlags(false, false);
} }
@EventHandler(ignoreCancelled = true) @EventHandler(ignoreCancelled = true, priority = EventPriority.LOWEST)
public void checkSpawn(final CreatureSpawnEvent event) { public void checkSpawn(final CreatureSpawnEvent event) {
if(!match.module(MutationMatchModule.class).map(mmm -> mmm.allowMob(event.getSpawnReason())).orElse(false) && if(event.getSpawnReason() != CreatureSpawnEvent.SpawnReason.CUSTOM) {
event.getSpawnReason() != CreatureSpawnEvent.SpawnReason.CUSTOM) {
event.setCancelled(mobsFilter.query(new EntitySpawnQuery(event, event.getEntity(), event.getSpawnReason())).isDenied()); 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.ImmutableMap;
import com.google.common.collect.ImmutableSet; import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Sets; import com.google.common.collect.Sets;
import org.bukkit.event.entity.CreatureSpawnEvent;
import tc.oc.commons.core.util.MapUtils; import tc.oc.commons.core.util.MapUtils;
import tc.oc.commons.core.random.RandomUtils; import tc.oc.commons.core.random.RandomUtils;
import tc.oc.pgm.Config; 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))); 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; 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 org.bukkit.inventory.ItemStack;
import tc.oc.commons.bukkit.inventory.Slot; 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.KillReward;
import tc.oc.pgm.killreward.KillRewardMatchModule; import tc.oc.pgm.killreward.KillRewardMatchModule;
import tc.oc.pgm.kits.ItemKit;
import tc.oc.pgm.kits.Kit; import tc.oc.pgm.kits.Kit;
import tc.oc.pgm.kits.KitPlayerFacet; import tc.oc.pgm.kits.KitPlayerFacet;
import tc.oc.pgm.match.Match; import tc.oc.pgm.match.Match;
@ -11,19 +18,22 @@ import tc.oc.pgm.match.MatchPlayer;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.HashMap; import java.util.HashMap;
import java.util.HashSet;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Set;
import java.util.WeakHashMap; import java.util.WeakHashMap;
import java.util.stream.Stream; import java.util.stream.Stream;
/** /**
* A mutation module that injects special kits on spawn and/or kill. * 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 List<Kit> kits;
protected final Map<MatchPlayer, List<Kit>> playerKits; protected final Map<MatchPlayer, List<Kit>> playerKits;
protected final Map<MatchPlayer, Map<Slot, ItemStack>> savedSlots; protected final Map<MatchPlayer, Map<Slot, ItemStack>> savedSlots;
protected final Set<Material> itemRemove;
protected final List<KillReward> rewards; protected final List<KillReward> rewards;
protected final boolean force; protected final boolean force;
@ -32,6 +42,7 @@ public class KitMutation extends MutationModule {
this.kits = new ArrayList<>(); this.kits = new ArrayList<>();
this.playerKits = new WeakHashMap<>(); this.playerKits = new WeakHashMap<>();
this.savedSlots = new WeakHashMap<>(); this.savedSlots = new WeakHashMap<>();
this.itemRemove = new HashSet<>();
this.rewards = new ArrayList<>(); this.rewards = new ArrayList<>();
this.force = force; this.force = force;
} }
@ -59,6 +70,7 @@ public class KitMutation extends MutationModule {
List<Kit> kits = new ArrayList<>(); List<Kit> kits = new ArrayList<>();
kits(player, kits); kits(player, kits);
playerKits.put(player, kits); playerKits.put(player, kits);
kits.forEach(kit -> Optionals.cast(kit, ItemKit.class).ifPresent(items -> itemRemove.add(items.item().getType())));
saved().forEach(slot -> { saved().forEach(slot -> {
slot.item(player.getInventory()).ifPresent(item -> { slot.item(player.getInventory()).ifPresent(item -> {
Map<Slot, ItemStack> slots = savedSlots.getOrDefault(player, new HashMap<>()); Map<Slot, ItemStack> slots = savedSlots.getOrDefault(player, new HashMap<>());
@ -88,22 +100,34 @@ public class KitMutation extends MutationModule {
return Stream.empty(); 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 @Override
public void enable() { public void enable() {
super.enable(); super.enable();
match.module(KillRewardMatchModule.class).get().rewards().addAll(rewards); match().module(KillRewardMatchModule.class).get().rewards().addAll(rewards);
if(match.hasStarted()) { if(match().hasStarted()) {
match.participants().forEach(this::apply); match().participants().forEach(this::apply);
} }
} }
@Override @Override
public void disable() { public void disable() {
match.module(KillRewardMatchModule.class).get().rewards().removeAll(rewards); match().module(KillRewardMatchModule.class).get().rewards().removeAll(rewards);
match.participants().forEach(this::remove); match().participants().forEach(this::remove);
kits.clear(); kits.clear();
playerKits.clear(); playerKits.clear();
savedSlots.clear(); savedSlots.clear();
itemRemove.clear();
rewards.clear(); rewards.clear();
super.disable(); super.disable();
} }

View File

@ -1,12 +1,8 @@
package tc.oc.pgm.mutation.types; package tc.oc.pgm.mutation.types;
import org.bukkit.Location;
import org.bukkit.Material; import org.bukkit.Material;
import org.bukkit.World; import org.bukkit.World;
import org.bukkit.entity.Entity;
import org.bukkit.entity.LivingEntity;
import org.bukkit.event.Listener; import org.bukkit.event.Listener;
import org.bukkit.inventory.EntityEquipment;
import org.bukkit.inventory.ItemStack; import org.bukkit.inventory.ItemStack;
import tc.oc.commons.bukkit.item.ItemBuilder; import tc.oc.commons.bukkit.item.ItemBuilder;
import tc.oc.commons.core.random.AdvancingEntropy; 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.Match;
import tc.oc.pgm.match.MatchScope; import tc.oc.pgm.match.MatchScope;
import tc.oc.pgm.mutation.Mutation; import tc.oc.pgm.mutation.Mutation;
import tc.oc.pgm.mutation.MutationMatchModule;
import java.util.Random; import java.util.Random;
@ -28,69 +23,59 @@ import java.util.Random;
* of breaking the match state. * of breaking the match state.
*/ */
@ListenerScope(MatchScope.RUNNING) @ListenerScope(MatchScope.RUNNING)
public abstract class MutationModule implements Listener { public interface MutationModule extends Listener {
protected final Match match; Match match();
protected final World world;
protected final Entropy entropy;
protected final Random random;
/** default void enable() {
* Constructed when {@link MutationMatchModule#load()} match().registerEventsAndRepeatables(this);
* 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 disable() {
* Called when the match starts. match().unregisterEvents(this);
* match().unregisterRepeatable(this);
* 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 World world() {
* Called when the match ends. return match().getWorld();
*
* 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);
} }
protected static ItemStack item(Material material, int amount) { default Entropy entropy() {
return new ItemBuilder().material(material).amount(amount).unbreakable(true).shareable(false).get(); return match().entropyForTick();
} }
protected static ItemStack item(Material material) { default Random random() {
return item(material, 1); return match().getRandom();
} }
protected <E extends Entity> E spawn(Location location, Class<E> entityClass) { abstract class Impl implements MutationModule {
E entity = world.spawn(location, entityClass);
if(entity instanceof LivingEntity) { private final Match match;
LivingEntity living = (LivingEntity) entity; private final Entropy entropy;
living.setCanPickupItems(false);
living.setRemoveWhenFarAway(true); public Impl(final Match match) {
EntityEquipment equipment = living.getEquipment(); this.match = match;
equipment.setHelmetDropChance(0); this.entropy = new AdvancingEntropy(match.entropyForTick().randomLong());
equipment.setChestplateDropChance(0);
equipment.setLeggingsDropChance(0);
equipment.setBootsDropChance(0);
} }
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; package tc.oc.pgm.mutation.types;
import java.time.Duration; import java.time.Duration;
import java.time.Instant;
import java.util.List; import java.util.List;
import tc.oc.commons.core.scheduler.Task;
import tc.oc.commons.core.stream.Collectors; 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.Match;
import tc.oc.pgm.match.MatchPlayer; 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. * A mutation module that executes a task on random {@link MatchPlayer}s.
*/ */
public abstract class TargetMutation extends MutationModule { public interface TargetMutation extends MutationModule {
private final Duration frequency;
private Task task;
public TargetMutation(final Match match, Duration frequency) {
super(match);
this.frequency = frequency;
}
/** /**
* Execute a task on the given randomly selected players. * Execute a task on the given randomly selected players.
* @param players a list of players, which size is determined by {@link #targets()}. * @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. * Determine the number of random players to target.
*
* If there are no enough players on the server, it is possible * If there are no enough players on the server, it is possible
* that the number of targets is less than expected. * that the number of targets is less than expected.
* @return number of targets. * @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. * Generate a list of random players.
* @return the random players. * @return the random players.
*/ */
public List<MatchPlayer> search() { default List<MatchPlayer> search() {
return match.participants() return match().participants()
.filter(MatchPlayer::isSpawned) .filter(MatchPlayer::isSpawned)
.collect(Collectors.toRandomSubList(entropy, targets())); .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 @Override
public void enable() { default void enable() {
super.enable(); MutationModule.super.enable();
this.task = match.getScheduler(MatchScope.RUNNING).createRepeatingTask(frequency, () -> execute(search())); target();
} }
@Override @Repeatable(interval = @Time(seconds = 1))
public void disable() { default void tick() {
if(task != null) { Instant now = match().getInstantNow(), next = next();
task.cancel(); if(next == null) {
task = 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; 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 org.bukkit.Material;
import tc.oc.commons.bukkit.chat.WarningComponent;
import tc.oc.commons.bukkit.inventory.ArmorType; import tc.oc.commons.bukkit.inventory.ArmorType;
import tc.oc.commons.bukkit.inventory.Slot; 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.doublejump.DoubleJumpKit;
import tc.oc.pgm.kits.ItemKit; 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.kits.SlotItemKit;
import tc.oc.pgm.match.Match; import tc.oc.pgm.match.Match;
import tc.oc.pgm.match.MatchPlayer; 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 tc.oc.pgm.mutation.types.KitMutation;
import java.time.Duration; import java.time.Duration;
import java.time.Instant;
import java.util.stream.Collectors;
import java.util.stream.Stream; import java.util.stream.Stream;
public class ElytraMutation extends KitMutation { public class ElytraMutation extends KitMutation {
@ -30,13 +44,57 @@ public class ElytraMutation extends KitMutation {
@Override @Override
public void remove(MatchPlayer player) { public void remove(MatchPlayer player) {
// If the player is mid-air, give them a totem so they don't fall and die // Anyone left gliding will be taken care of by the ground stop order
if(player.getBukkit().isGliding()) { if(!player.getBukkit().isGliding()) {
ItemKitApplicator applicator = new ItemKitApplicator(); super.remove(player);
applicator.put(Slot.OffHand.offHand(), item(Material.TOTEM), false);
applicator.apply(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 { public class EnchantmentMutation extends KitMutation {
final static ImmutableMap<Integer, Integer> LEVELS_MAP = new ImmutableMap.Builder<Integer, Integer>() final static ImmutableMap<Integer, Integer> LEVELS_MAP = new ImmutableMap.Builder<Integer, Integer>()
.put(1, 10) .put(1, 25)
.put(2, 3) .put(2, 5)
.put(3, 1) .put(3, 1)
.build(); .build();
final static ImmutableMap<Enchantment, Integer> ARMOR_MAP = new ImmutableMap.Builder<Enchantment, Integer>() final static ImmutableMap<Enchantment, Integer> ARMOR_MAP = new ImmutableMap.Builder<Enchantment, Integer>()
.put(Enchantment.PROTECTION_ENVIRONMENTAL, 15) .put(Enchantment.PROTECTION_ENVIRONMENTAL, 15)
.put(Enchantment.PROTECTION_PROJECTILE, 10) .put(Enchantment.PROTECTION_PROJECTILE, 10)
.put(Enchantment.DURABILITY, 10)
.put(Enchantment.PROTECTION_EXPLOSIONS, 5) .put(Enchantment.PROTECTION_EXPLOSIONS, 5)
.put(Enchantment.PROTECTION_FALL, 5)
.put(Enchantment.PROTECTION_FIRE, 5) .put(Enchantment.PROTECTION_FIRE, 5)
.put(Enchantment.THORNS, 1) .put(Enchantment.THORNS, 1)
.build(); .build();
final static ImmutableMap<Enchantment, Integer> BOOTS_MAP = new ImmutableMap.Builder<Enchantment, Integer>() final static ImmutableMap<Enchantment, Integer> BOOTS_MAP = new ImmutableMap.Builder<Enchantment, Integer>()
.putAll(ARMOR_MAP) .putAll(ARMOR_MAP)
.put(Enchantment.WATER_WORKER, 5) .put(Enchantment.PROTECTION_FALL, 10)
.put(Enchantment.DEPTH_STRIDER, 3) .put(Enchantment.DEPTH_STRIDER, 3)
.put(Enchantment.FROST_WALKER, 1) .put(Enchantment.FROST_WALKER, 1)
.build(); .build();
final static ImmutableMap<Enchantment, Integer> WEAPONS_MAP = new ImmutableMap.Builder<Enchantment, Integer>() final static ImmutableMap<Enchantment, Integer> WEAPONS_MAP = new ImmutableMap.Builder<Enchantment, Integer>()
.put(Enchantment.DAMAGE_ALL, 15) .put(Enchantment.DAMAGE_ALL, 15)
.put(Enchantment.DURABILITY, 10)
.put(Enchantment.KNOCKBACK, 10) .put(Enchantment.KNOCKBACK, 10)
.put(Enchantment.MENDING, 5) .put(Enchantment.MENDING, 5)
.put(Enchantment.SWEEPING_EDGE, 5) .put(Enchantment.SWEEPING_EDGE, 5)
@ -53,7 +50,6 @@ public class EnchantmentMutation extends KitMutation {
.build(); .build();
final static ImmutableMap<Enchantment, Integer> TOOLS_MAP = new ImmutableMap.Builder<Enchantment, Integer>() final static ImmutableMap<Enchantment, Integer> TOOLS_MAP = new ImmutableMap.Builder<Enchantment, Integer>()
.put(Enchantment.DURABILITY, 10)
.put(Enchantment.DIG_SPEED, 10) .put(Enchantment.DIG_SPEED, 10)
.put(Enchantment.SILK_TOUCH, 5) .put(Enchantment.SILK_TOUCH, 5)
.put(Enchantment.LOOT_BONUS_BLOCKS, 5) .put(Enchantment.LOOT_BONUS_BLOCKS, 5)
@ -116,9 +112,9 @@ public class EnchantmentMutation extends KitMutation {
byEntity.put(item, ImmutableMap.copyOf(item.getEnchantments())); byEntity.put(item, ImmutableMap.copyOf(item.getEnchantments()));
savedEnchantments.put(entity, byEntity); savedEnchantments.put(entity, byEntity);
// Apply the new enchantments // Apply the new enchantments
int amountOfEnchants = LEVELS.choose(entropy); int amountOfEnchants = LEVELS.choose(entropy());
for(int i = 0; i < amountOfEnchants; i++) { 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); super.apply(player);
player.getInventory().forEach(item -> { player.getInventory().forEach(item -> {
// Random number of enchantments on each 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++) { for(int i = 0; i < numberOfEnchants; i++) {
apply(item, player.getBukkit().getEquipment()); apply(item, player.getBukkit().getEquipment());
} }

View File

@ -4,12 +4,11 @@ import com.google.common.collect.ImmutableMap;
import org.bukkit.Location; import org.bukkit.Location;
import org.bukkit.Material; import org.bukkit.Material;
import org.bukkit.entity.AbstractHorse; import org.bukkit.entity.AbstractHorse;
import org.bukkit.entity.Entity;
import org.bukkit.entity.EntityType; import org.bukkit.entity.EntityType;
import org.bukkit.entity.Horse; import org.bukkit.entity.Horse;
import org.bukkit.event.EventHandler; import org.bukkit.event.EventHandler;
import org.bukkit.event.EventPriority; 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.HorseInventory;
import org.bukkit.inventory.ItemStack; import org.bukkit.inventory.ItemStack;
import org.bukkit.inventory.meta.SpawnEggMeta; import org.bukkit.inventory.meta.SpawnEggMeta;
@ -17,23 +16,23 @@ import org.bukkit.potion.PotionEffect;
import org.bukkit.potion.PotionEffectType; import org.bukkit.potion.PotionEffectType;
import tc.oc.commons.core.random.ImmutableWeightedRandomChooser; import tc.oc.commons.core.random.ImmutableWeightedRandomChooser;
import tc.oc.commons.core.random.WeightedRandomChooser; 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.FreeItemKit;
import tc.oc.pgm.kits.Kit; import tc.oc.pgm.kits.Kit;
import tc.oc.pgm.match.Match; import tc.oc.pgm.match.Match;
import tc.oc.pgm.match.MatchPlayer; 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.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>() final static ImmutableMap<EntityType, Integer> TYPE_MAP = new ImmutableMap.Builder<EntityType, Integer>()
.put(EntityType.HORSE, 100) .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.SKELETON_HORSE, 5)
//.put(EntityType.ZOMBIE_HORSE, 5) //.put(EntityType.ZOMBIE_HORSE, 5)
//.put(EntityType.LLAMA, 1) //.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<EntityType, Integer> TYPES = new ImmutableWeightedRandomChooser<>(TYPE_MAP);
final static WeightedRandomChooser<Material, Integer> ARMOR = new ImmutableWeightedRandomChooser<>(ARMOR_MAP); final static WeightedRandomChooser<Material, Integer> ARMOR = new ImmutableWeightedRandomChooser<>(ARMOR_MAP);
final Map<MatchPlayer, AbstractHorse> horses;
public EquestrianMutation(Match match) { public EquestrianMutation(Match match) {
super(match, false); super(match, false);
this.horses = new WeakHashMap<>();
} }
@Override @Override
public void disable() { public void enable() {
super.disable(); super.enable();
match.participants().forEach(this::remove); this.itemRemove.add(Material.LEATHER);
horses.clear(); this.itemRemove.addAll(ARMOR_MAP.keySet());
} }
@Override @Override
@ -72,7 +68,7 @@ public class EquestrianMutation extends KitMutation {
if(!spawnable(location)) { if(!spawnable(location)) {
ItemStack item = item(Material.MONSTER_EGG); ItemStack item = item(Material.MONSTER_EGG);
SpawnEggMeta egg = (SpawnEggMeta) item.getItemMeta(); SpawnEggMeta egg = (SpawnEggMeta) item.getItemMeta();
egg.setSpawnedType(TYPES.choose(entropy)); egg.setSpawnedType(TYPES.choose(entropy()));
item.setItemMeta(egg); item.setItemMeta(egg);
kits.add(new FreeItemKit(item)); kits.add(new FreeItemKit(item));
} }
@ -83,38 +79,31 @@ public class EquestrianMutation extends KitMutation {
super.apply(player); super.apply(player);
Location location = player.getLocation(); Location location = player.getLocation();
if(spawnable(location)) { 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 @Override
public void remove(MatchPlayer player) { public AbstractHorse register(AbstractHorse horse, @Nullable MatchPlayer owner) {
super.remove(player); super.register(horse, owner);
AbstractHorse horse = horses.remove(player); if(owner != null) {
if(horse != null) { horse.setAdult();
horse.ejectAll(); horse.setJumpStrength(2 * entropy().randomDouble());
horse.remove(); horse.setDomestication(1);
} horse.setMaxDomestication(1);
} horse.setTamed(true);
horse.addPotionEffect(new PotionEffect(PotionEffectType.REGENERATION, Integer.MAX_VALUE, 0));
public void setup(MatchPlayer player, AbstractHorse horse) { horse.setOwner(owner.getBukkit());
horses.put(player, horse); horse.setPassenger(owner.getBukkit());
horse.setAdult(); cast(horse, Horse.class).ifPresent(horsey -> {
horse.setJumpStrength(2 * entropy.randomDouble()); horsey.setStyle(entropy().randomElement(Horse.Style.values()));
horse.setDomestication(1); horsey.setColor(entropy().randomElement(Horse.Color.values()));
horse.setMaxDomestication(1); HorseInventory inventory = horsey.getInventory();
horse.setTamed(true); inventory.setSaddle(item(Material.SADDLE));
horse.addPotionEffect(new PotionEffect(PotionEffectType.REGENERATION, Integer.MAX_VALUE, 0)); inventory.setArmor(item(ARMOR.choose(entropy())));
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)));
} }
return horse;
} }
public boolean spawnable(Location location) { public boolean spawnable(Location location) {
@ -129,22 +118,11 @@ public class EquestrianMutation extends KitMutation {
} }
@EventHandler(ignoreCancelled = true, priority = EventPriority.HIGHEST) @EventHandler(ignoreCancelled = true, priority = EventPriority.HIGHEST)
public void onEntitySpawn(PlayerSpawnEntityEvent event) { public void onEntityDamage(EntityDamageByEntityEvent event) {
Entity entity = event.getEntity(); if(playerByEntity(event.getEntity()).flatMap(MatchPlayer::competitor)
if(TYPE_MAP.containsKey(entity.getType())) { .equals(match().participant(event.getDamager()).flatMap(MatchPlayer::competitor))) {
match.participant(event.getPlayer()) event.setCancelled(true);
.ifPresent(player -> setup(player, (AbstractHorse) event.getEntity()));
} }
} }
@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 com.google.common.collect.Range;
import org.bukkit.Material; import org.bukkit.Material;
import org.bukkit.enchantments.Enchantment; import org.bukkit.enchantments.Enchantment;
import org.bukkit.entity.TNTPrimed;
import org.bukkit.event.EventHandler; import org.bukkit.event.EventHandler;
import org.bukkit.event.EventPriority; import org.bukkit.event.EventPriority;
import org.bukkit.event.entity.ExplosionPrimeEvent; import org.bukkit.event.entity.ExplosionPrimeEvent;
import org.bukkit.inventory.PlayerInventory; import org.bukkit.inventory.PlayerInventory;
import tc.oc.commons.bukkit.item.ItemBuilder; import tc.oc.commons.bukkit.item.ItemBuilder;
import tc.oc.commons.core.collection.WeakHashSet;
import tc.oc.pgm.killreward.KillReward; import tc.oc.pgm.killreward.KillReward;
import tc.oc.pgm.kits.FreeItemKit; import tc.oc.pgm.kits.FreeItemKit;
import tc.oc.pgm.kits.ItemKit; 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 static Range<Integer> RADIUS = Range.openClosed(0, 4);
final WeakHashSet<TNTPrimed> tracked;
public ExplosiveMutation(Match match) { public ExplosiveMutation(Match match) {
super(match, false); super(match, false);
this.tracked = new WeakHashSet<>();
this.rewards.add(new KillReward(TNT)); this.rewards.add(new KillReward(TNT));
this.rewards.add(new KillReward(ARROWS)); this.rewards.add(new KillReward(ARROWS));
} }
@Override
public void disable() {
super.disable();
tracked.clear();
}
@Override @Override
public void kits(MatchPlayer player, List<Kit> kits) { public void kits(MatchPlayer player, List<Kit> kits) {
super.kits(player, kits); super.kits(player, kits);
PlayerInventory inv = player.getInventory(); 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.TNT)) kits.add(TNT);
if(!inv.contains(Material.FLINT_AND_STEEL)) kits.add(LIGHTER); if(!inv.contains(Material.FLINT_AND_STEEL)) kits.add(LIGHTER);
} else { // fire bow and arrows kit } 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) @EventHandler(ignoreCancelled = true, priority = EventPriority.HIGHEST)
public void onExplosionPrime(ExplosionPrimeEvent event) { 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; package tc.oc.pgm.mutation.types.kit;
import org.bukkit.Material; import org.bukkit.Material;
import org.bukkit.inventory.ItemStack;
import tc.oc.pgm.gamerules.GameRulesMatchModule; import tc.oc.pgm.gamerules.GameRulesMatchModule;
import tc.oc.pgm.killreward.KillReward; import tc.oc.pgm.killreward.KillReward;
import tc.oc.pgm.kits.FreeItemKit; import tc.oc.pgm.kits.FreeItemKit;
@ -26,13 +25,13 @@ public class HardcoreMutation extends KitMutation {
} }
public GameRulesMatchModule rules() { public GameRulesMatchModule rules() {
return match.module(GameRulesMatchModule.class).get(); return match().module(GameRulesMatchModule.class).get();
} }
@Override @Override
public void enable() { public void enable() {
super.enable(); super.enable();
previous = world.getGameRuleValue(KEY); previous = world().getGameRuleValue(KEY);
rules().gameRules().put(KEY, "false"); rules().gameRules().put(KEY, "false");
} }
@ -40,7 +39,7 @@ public class HardcoreMutation extends KitMutation {
public void disable() { public void disable() {
rules().gameRules().remove(KEY); rules().gameRules().remove(KEY);
if(previous != null) { if(previous != null) {
world.setGameRuleValue(KEY, previous); world().setGameRuleValue(KEY, previous);
} }
super.disable(); super.disable();
} }

View File

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

View File

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

View File

@ -68,13 +68,13 @@ public class PotionMutation extends KitMutation {
@Override @Override
public void kits(MatchPlayer player, List<Kit> kits) { public void kits(MatchPlayer player, List<Kit> kits) {
super.kits(player, kits); super.kits(player, kits);
int numberOfPotions = entropy.randomInt(AMOUNT_RANGE); int numberOfPotions = entropy().randomInt(AMOUNT_RANGE);
for(int i = 0; i < numberOfPotions; i++) { for(int i = 0; i < numberOfPotions; i++) {
WeightedRandomChooser<PotionEffectType, Integer> type; WeightedRandomChooser<PotionEffectType, Integer> type;
WeightedRandomChooser<Material, Integer> material; WeightedRandomChooser<Material, Integer> material;
Range<Integer> range; Range<Integer> range;
// Determine whether the potion will be "good" or "bad" // Determine whether the potion will be "good" or "bad"
if(random.nextBoolean()) { if(random().nextBoolean()) {
type = BAD; type = BAD;
material = BAD_BOTTLE; material = BAD_BOTTLE;
range = BAD_DURATION_RANGE; range = BAD_DURATION_RANGE;
@ -84,10 +84,10 @@ public class PotionMutation extends KitMutation {
range = GOOD_DURATION_RANGE; range = GOOD_DURATION_RANGE;
} }
// Choose all the random attributes // Choose all the random attributes
PotionEffectType effect = type.choose(entropy); PotionEffectType effect = type.choose(entropy());
Material bottle = material.choose(entropy); Material bottle = material.choose(entropy());
int duration = 20 * entropy.randomInt(range); int duration = 20 * entropy().randomInt(range);
int amplifier = entropy.randomInt(AMPLIFIER_RANGE); int amplifier = entropy().randomInt(AMPLIFIER_RANGE);
// Apply the attributes to the item stack // Apply the attributes to the item stack
ItemStack potion = item(bottle); ItemStack potion = item(bottle);
PotionMeta meta = (PotionMeta) potion.getItemMeta(); 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 com.google.common.collect.Range;
import org.bukkit.Material; import org.bukkit.Material;
import org.bukkit.enchantments.Enchantment; import org.bukkit.enchantments.Enchantment;
import org.bukkit.entity.Arrow; import org.bukkit.entity.LivingEntity;
import org.bukkit.entity.Entity; import org.bukkit.entity.Player;
import org.bukkit.entity.TippedArrow;
import org.bukkit.event.EventHandler; import org.bukkit.event.EventHandler;
import org.bukkit.event.EventPriority; import org.bukkit.event.EventPriority;
import org.bukkit.event.entity.EntityShootBowEvent; import org.bukkit.event.entity.ProjectileHitEvent;
import org.bukkit.event.entity.ProjectileLaunchEvent;
import org.bukkit.inventory.Inventory; import org.bukkit.inventory.Inventory;
import org.bukkit.metadata.FixedMetadataValue;
import org.bukkit.potion.PotionEffect; import org.bukkit.potion.PotionEffect;
import org.bukkit.potion.PotionEffectType; import org.bukkit.potion.PotionEffectType;
import tc.oc.commons.core.random.ImmutableWeightedRandomChooser; import tc.oc.commons.core.random.ImmutableWeightedRandomChooser;
import tc.oc.commons.core.random.WeightedRandomChooser; 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.killreward.KillReward;
import tc.oc.pgm.kits.FreeItemKit; import tc.oc.pgm.kits.FreeItemKit;
import tc.oc.pgm.kits.ItemKit; import tc.oc.pgm.kits.ItemKit;
@ -26,6 +23,7 @@ import tc.oc.pgm.match.MatchPlayer;
import tc.oc.pgm.mutation.types.KitMutation; import tc.oc.pgm.mutation.types.KitMutation;
import java.util.List; import java.util.List;
import java.util.Optional;
public class ProjectileMutation extends KitMutation { 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 BOW = new FreeItemKit(item(Material.BOW));
final static ItemKit ARROWS = new FreeItemKit(item(Material.ARROW, 16)); final static ItemKit ARROWS = new FreeItemKit(item(Material.ARROW, 16));
final static String KEY = "is_modified_arrow";
public ProjectileMutation(Match match) { public ProjectileMutation(Match match) {
super(match, false); super(match, false);
this.rewards.add(new KillReward(ARROWS)); this.rewards.add(new KillReward(ARROWS));
@ -50,7 +46,7 @@ public class ProjectileMutation extends KitMutation {
public void apply(MatchPlayer player) { public void apply(MatchPlayer player) {
super.apply(player); super.apply(player);
Inventory inventory = player.getInventory(); 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 @Override
@ -62,23 +58,10 @@ public class ProjectileMutation extends KitMutation {
} }
@EventHandler(ignoreCancelled = true, priority = EventPriority.HIGHEST) @EventHandler(ignoreCancelled = true, priority = EventPriority.HIGHEST)
public void onBowShoot(EntityShootBowEvent event) { public void onProjectileHit(ProjectileHitEvent event) {
Entity projectile = event.getProjectile(); Optionals.cast(Optional.ofNullable(event.getHitEntity()), LivingEntity.class)
if(projectile instanceof Arrow && (!projectile.hasMetadata(KEY) || !projectile.getMetadata(KEY, PGM.get()).asBoolean())) { .filter(entity -> Optional.ofNullable(event.getEntity().getShooter()).filter(shooter -> shooter instanceof Player).isPresent())
Arrow arrow = (Arrow) projectile; .ifPresent(entity -> entity.addPotionEffect(new PotionEffect(POTIONS.choose(entropy()), 20 * entropy().randomInt(DURATION_RANGE), entropy().randomInt(AMPLIFIER_RANGE)), true));
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));
}
} }
} }

View File

@ -11,7 +11,7 @@ import tc.oc.pgm.match.MatchScope;
import tc.oc.pgm.mutation.types.MutationModule; import tc.oc.pgm.mutation.types.MutationModule;
import tc.oc.pgm.teams.TeamMatchModule; 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 Range<Integer> LIVES = Range.closed(1, 3);
final static Fraction TEAM_CHANCE = Fraction.ONE_QUARTER; final static Fraction TEAM_CHANCE = Fraction.ONE_QUARTER;
@ -23,22 +23,22 @@ public class BlitzMutation extends MutationModule {
@Override @Override
public void enable() { public void enable() {
super.enable(); super.enable();
int lives = match.entropyForTick().randomInt(LIVES); int lives = entropy().randomInt(LIVES);
Lives.Type type; 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; type = Lives.Type.TEAM;
lives *= match.module(TeamMatchModule.class).get().getFullestTeam().getSize(); lives *= match().module(TeamMatchModule.class).get().getFullestTeam().getSize();
} else { } else {
type = Lives.Type.INDIVIDUAL; 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 @Override
public void disable() { public void disable() {
match.getScheduler(MatchScope.LOADED).createTask(() -> { match().getScheduler(MatchScope.LOADED).createTask(() -> {
if(!match.isFinished()) { if(!match().isFinished()) {
match.module(BlitzMatchModuleImpl.class).get().deactivate(); match().module(BlitzMatchModuleImpl.class).get().deactivate();
} }
}); });
super.disable(); 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.mutation.types.MutationModule;
import tc.oc.pgm.rage.RageMatchModule; import tc.oc.pgm.rage.RageMatchModule;
public class RageMutation extends MutationModule { public class RageMutation extends MutationModule.Impl {
RageMatchModule rage; RageMatchModule rage;
@ -26,4 +26,5 @@ public class RageMutation extends MutationModule {
super.disable(); super.disable();
rage = null; 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.Match;
import tc.oc.pgm.match.MatchPlayer; import tc.oc.pgm.match.MatchPlayer;
import tc.oc.pgm.match.Repeatable; 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.kit.EnchantmentMutation;
import tc.oc.pgm.mutation.types.TargetMutation; import tc.oc.pgm.mutation.types.TargetMutation;
import tc.oc.pgm.points.PointProviderAttributes; 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.points.RegionPointProvider;
import tc.oc.pgm.regions.CuboidRegion; import tc.oc.pgm.regions.CuboidRegion;
import javax.annotation.Nullable;
import java.time.Duration; import java.time.Duration;
import java.time.Instant; import java.time.Instant;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
import java.util.Comparator;
import java.util.List; import java.util.List;
import java.util.Map;
import java.util.Optional; import java.util.Optional;
import java.util.WeakHashMap;
import static tc.oc.commons.core.random.RandomUtils.nextBoolean; 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>() final static ImmutableMap<Integer, Integer> AMOUNT_MAP = new ImmutableMap.Builder<Integer, Integer>()
.put(3, 25) .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> PASSENGER = new ImmutableWeightedRandomChooser<>(PASSENGER_MAP);
final static WeightedRandomChooser<EntityType, Integer> CUBE = new ImmutableWeightedRandomChooser<>(CUBE_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 DISTANCE = 15; // Max distance entities spawn from players
final static int PARTICIPANT_ENTITIES = 25; // Max entities on the field per participant 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 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 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 static int SPECIAL_MULTIPLIER = 3; // Multiplier for special attributes
final WeakHashMap<Entity, Instant> entities; long time; // world time
long time; Instant next; // next time to spawn entities
final PointProviderAttributes attributes; // attributes to choosing random points
public ApocalypseMutation(Match match) { public ApocalypseMutation(Match match) {
super(match, Duration.ofSeconds(20)); super(match, false);
this.entities = new WeakHashMap<>(); this.attributes = new PointProviderAttributes(null, null, true, false);
} }
/** /**
* Get the maximum amount of entities that can be spawned. * Get the maximum amount of entities that can be spawned.
*/ */
public int entitiesMax() { 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. * Get the number of available slots are left for additional entities to spawn.
*/ */
public int entitiesLeft() { 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. * Generate a random spawn point given two locations.
*/ */
public Optional<Location> location(Location start, Location end) { 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) { public void spawn(Location location, boolean ground) {
int slots = entitiesLeft(); int slots = entitiesLeft();
int queued = AMOUNT.choose(entropy); int queued = AMOUNT.choose(entropy());
// Remove any entities that may be over the max limit // Remove any entities that may be over the max limit
despawn(queued - slots); despawn(queued - slots);
// Determine whether the entities should be airborn // Determine whether the entities should be airborn
int stack = STACK.choose(entropy); int stack = STACK.choose(entropy());
boolean air = !ground || nextBoolean(random, SPECIAL_CHANCE); boolean air = !ground || nextBoolean(random(), SPECIAL_CHANCE);
if(air) { if(air) {
stack += (stack == 1 && random.nextBoolean() ? 1 : 0); stack += (stack == 1 && random().nextBoolean() ? 1 : 0);
location.add(0, entropy.randomInt(AIR_OFFSET), 0); location.add(0, entropy().randomInt(AIR_OFFSET), 0);
} }
// Select the random entity chooser based on ground, air, and stacked // Select the random entity chooser based on ground, air, and stacked
boolean stacked = stack > 1; boolean stacked = stack > 1;
@ -163,7 +164,7 @@ public class ApocalypseMutation extends TargetMutation {
if(air) { if(air) {
if(stacked) { if(stacked) {
if(ground) { if(ground) {
chooser = nextBoolean(random, SPECIAL_CHANCE) ? CUBE : FLYABLE; chooser = nextBoolean(random(), SPECIAL_CHANCE) ? CUBE : FLYABLE;
} else { } else {
chooser = FLYABLE; chooser = FLYABLE;
} }
@ -174,7 +175,7 @@ public class ApocalypseMutation extends TargetMutation {
if(stacked) { if(stacked) {
chooser = GROUND; chooser = GROUND;
} else { } else {
chooser = random.nextBoolean() ? GROUND : RANGED; chooser = random().nextBoolean() ? GROUND : RANGED;
} }
} }
// Select the specific entity types for the spawn, // 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. // but may have variations (like armor) between them.
List<EntityType> types = new ArrayList<>(); List<EntityType> types = new ArrayList<>();
for(int i = 0; i < stack; i++) { 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 // Spawn the mobs and stack them if required
for(int i = 0; i < queued; i++) { for(int i = 0; i < queued; i++) {
Entity last = null; Entity last = null;
for(EntityType type : types) { for(EntityType type : types) {
Entity entity = spawn(location, type); Entity entity = spawn(location, (Class<LivingEntity>) type.getEntityClass());
if(last != null) { if(last != null) {
last.setPassenger(entity); last.setPassenger(entity);
} }
@ -198,20 +199,14 @@ public class ApocalypseMutation extends TargetMutation {
} }
/** @Override
* Spawn an individual entitiy at a location given an entity type. public LivingEntity spawn(Location location, Class<LivingEntity> entityClass, @Nullable MatchPlayer owner) {
* @param location the location to spawn at. LivingEntity entity = super.spawn(location, entityClass, owner);
* @param type the type of entity. EnchantmentMutation enchant = new EnchantmentMutation(match());
* @return the constructed entity.
*/
public LivingEntity spawn(Location location, EntityType type) {
EnchantmentMutation enchant = new EnchantmentMutation(match);
LivingEntity entity = (LivingEntity) spawn(location, type.getEntityClass());
EntityEquipment equipment = entity.getEquipment(); EntityEquipment equipment = entity.getEquipment();
entity.setVelocity(Vector.getRandom()); entity.setVelocity(Vector.getRandom());
entity.setAbsorption(5);
ItemStack held = null; ItemStack held = null;
switch(type) { switch(entity.getType()) {
case SKELETON: case SKELETON:
case WITHER_SKELETON: case WITHER_SKELETON:
case STRAY: case STRAY:
@ -221,7 +216,7 @@ public class ApocalypseMutation extends TargetMutation {
case ZOMBIE_VILLAGER: case ZOMBIE_VILLAGER:
case HUSK: case HUSK:
Zombie zombie = (Zombie) entity; Zombie zombie = (Zombie) entity;
zombie.setBaby(nextBoolean(random, SPECIAL_CHANCE)); zombie.setBaby(nextBoolean(random(), SPECIAL_CHANCE));
break; break;
case PIG_ZOMBIE: case PIG_ZOMBIE:
PigZombie pigZombie = (PigZombie) entity; PigZombie pigZombie = (PigZombie) entity;
@ -231,8 +226,8 @@ public class ApocalypseMutation extends TargetMutation {
break; break;
case CREEPER: case CREEPER:
Creeper creeper = (Creeper) entity; Creeper creeper = (Creeper) entity;
creeper.setPowered(nextBoolean(random, SPECIAL_CHANCE)); creeper.setPowered(nextBoolean(random(), SPECIAL_CHANCE));
world.strikeLightningEffect(location); world().strikeLightningEffect(location);
break; break;
case PRIMED_TNT: case PRIMED_TNT:
TNTPrimed tnt = (TNTPrimed) entity; TNTPrimed tnt = (TNTPrimed) entity;
@ -244,14 +239,13 @@ public class ApocalypseMutation extends TargetMutation {
slime.setSize(slime.getSize() * SPECIAL_MULTIPLIER); slime.setSize(slime.getSize() * SPECIAL_MULTIPLIER);
break; break;
case SKELETON_HORSE: case SKELETON_HORSE:
world.strikeLightning(location); world().strikeLightning(location);
break; break;
} }
if(held != null && random.nextBoolean()) { if(held != null && random().nextBoolean()) {
enchant.apply(held, equipment); enchant.apply(held, equipment);
equipment.setItemInMainHand(held); equipment.setItemInMainHand(held);
} }
entities.put(entity, Instant.now());
return entity; return entity;
} }
@ -260,17 +254,12 @@ public class ApocalypseMutation extends TargetMutation {
* to make room for new entities. * to make room for new entities.
* @param amount the amount of entities to despawn. * @param amount the amount of entities to despawn.
*/ */
public void despawn(int amount) { public void despawn(long amount) {
entities.entrySet() entitiesByTime().limit(Math.max(0, amount)).forEachOrdered(this::despawn);
.stream()
.sorted(Map.Entry.comparingByValue(Comparator.comparing(Instant::toEpochMilli)))
.limit(Math.max(0, amount))
.map(Map.Entry::getKey)
.forEach(Entity::remove);
} }
@Override @Override
public void execute(List<MatchPlayer> players) { public void target(List<MatchPlayer> players) {
// At least one player is required to spawn mobs // At least one player is required to spawn mobs
if(players.size() >= 1) { if(players.size() >= 1) {
Location start, end; Location start, end;
@ -284,7 +273,7 @@ public class ApocalypseMutation extends TargetMutation {
if(location.isPresent()) { // if the location is safe (on ground) if(location.isPresent()) { // if the location is safe (on ground)
spawn(location.get(), true); spawn(location.get(), true);
} else { // if the location was not safe, generate a simple midpoint location } 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 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 @Override
public void enable() { public void enable() {
super.enable(); super.enable();
time = world.getTime(); TargetMutation.super.enable();
time = world().getTime();
} }
@Repeatable @Repeatable
public void tick() { 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 @Override
public void disable() { public void disable() {
world.setTime(time); world().setTime(time);
despawn(entities.size()); despawn(entities().count());
super.disable(); super.disable();
} }

View File

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

View File

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