Mutation overhaul

This commit is contained in:
Electroid 2017-03-31 14:25:14 -07:00
parent b883ef5799
commit e83ec05e0d
62 changed files with 2171 additions and 542 deletions

View File

@ -30,11 +30,7 @@ public interface MatchDoc extends Model {
Collection<String> winning_team_ids();
Collection<String> winning_user_ids();
enum Mutation {
BLITZ, UHC, EXPLOSIVES, NO_FALL, MOBS, STRENGTH, DOUBLE_JUMP, INVISIBILITY, LIGHTNING, RAGE, ELYTRA;
}
Set<Mutation> mutations();
Set<String> mutations();
@Serialize
interface Team extends MapDoc.Team, CompetitorDoc {

View File

@ -138,7 +138,7 @@ public interface ServerDoc {
@Serialize
interface Mutation extends Partial {
Set<MatchDoc.Mutation> queued_mutations();
Set<String> queued_mutations();
}
/**

View File

@ -33,7 +33,5 @@ public class TypeAdaptersManifest extends Manifest {
gson.bindAdapter(new TypeLiteral<Set<MapDoc.Gamemode>>(){})
.to(new TypeLiteral<LenientEnumSetTypeAdapter<MapDoc.Gamemode>>(){});
gson.bindAdapter(new TypeLiteral<Set<MatchDoc.Mutation>>(){})
.to(new TypeLiteral<LenientEnumSetTypeAdapter<MatchDoc.Mutation>>(){});
}
}

View File

@ -279,7 +279,7 @@ public class LocalServerDocument extends StartupServerDocument implements Server
}
@Override
public Set<MatchDoc.Mutation> queued_mutations() {
public Set<String> queued_mutations() {
return mutations != null ? mutations.queued_mutations() : Collections.emptySet();
}
}

View File

@ -90,9 +90,19 @@ command.mutation.enable.later.plural = You have enabled the {0} mutations for th
command.mutation.disable.later.singular = You have disabled the {0} mutation for the next match
command.mutation.disable.later.plural = You have disabled the {0} mutations for the next match
# {0} = command sender
# {1} = mutation name(s)
command.mutation.enable.now.singular = {0} has enabled the {1} mutation
command.mutation.enable.now.plural = {0} has enabled the {1} mutations
command.mutation.disable.now.singular = {0} has disabled the {1} mutation
command.mutation.disable.now.plural = {0} has disabled the {1} mutations
command.mutation.error.mutate = An internal error occured with the '{0}' mutation
command.mutation.error.find = Unable to find mutation named '{0}'
command.mutation.error.enabled = All mutations have already been enabled
command.mutation.error.disabled = All mutations have already been disabled
command.mutation.error.enabled = {0} mutation is already enabled
command.mutation.error.enabled.all = All mutations have already been enabled
command.mutation.error.disabled = {0} mutation is already disabled
command.mutation.error.disabled.all = All mutations have already been disabled
command.mutation.list.current = Current Mutations
command.mutation.list.queued = Queued Mutations
@ -177,28 +187,41 @@ skillRequirement.fail.general = Play on unranked servers to improve your skill a
huddle.instructions = Your team now has {0} to strategize before the match starts
mutation.type.blitz = Blitz
mutation.type.uhc = UHC
mutation.type.explosives = Explosives
mutation.type.no_fall = No Fall
mutation.type.mobs = Mobs
mutation.type.strength = Strength
mutation.type.double_jump = Double Jump
mutation.type.invisibility = Invisibility
mutation.type.lightning = Lightning
mutation.type.rage = Rage
mutation.type.elytra = Elytra
mutation.type.blitz.desc = no respawning
mutation.type.uhc.desc = no natural regeneration
mutation.type.explosives.desc = stronger and more powerful explosions
mutation.type.no_fall.desc = fall all you want
mutation.type.mobs.desc = natural mob spawning
mutation.type.strength.desc = strength potions everywhere
mutation.type.double_jump.desc = super jump powers
mutation.type.invisibility.desc = enemy players cannot be seen
mutation.type.lightning.desc = lightning strikes from the sky
mutation.type.rage = Rage
mutation.type.rage.desc = instant kills
mutation.type.hardcore = Hardcore
mutation.type.hardcore.desc = no natural regeneration
mutation.type.jump = Jump
mutation.type.jump.desc = double jump and no fall
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.projectile = Projectile
mutation.type.projectile.desc = arrow potion effects
mutation.type.enchantment = Enchantment
mutation.type.enchantment.desc = random item enchantments
mutation.type.potion = Potion
mutation.type.potion.desc = random special potions
mutation.type.equestrian = Equestrian
mutation.type.equestrian.desc = ride a custom horse
mutation.type.health = Health
mutation.type.health.desc = double health and gold apples
mutation.type.glow = Glow
mutation.type.glow.desc = glowing effect on all players
mutation.type.stealth = Stealth
mutation.type.stealth.desc = invisibility with no armor
mutation.type.armor = Armor
mutation.type.armor.desc = heavy diamond armor
mutation.type.mobs = Mobs
mutation.type.mobs.desc = mob spawning eggs
mutation.type.lightning = Lightning
mutation.type.lightning.desc = lightning strikes from the sky
mutation.type.bomber = Bomber
mutation.type.bomber.desc = tnt rain
mutation.type.apocalypse = Apocalypse
mutation.type.apocalypse.desc = mob spawning chaos
tnt.license.info.alreadyHas = You have a TNT license. You can surrender your license by typing {0}
tnt.license.info.doesNotHave = You do not have a TNT license. You can request one by typing {0}

View File

@ -120,10 +120,10 @@ public class MatchDocument extends AbstractModel implements MatchDoc {
}
@Override
public Set<Mutation> mutations() {
return mutations.map(mmm -> mmm.getHistoricalMutations()
public Set<String> mutations() {
return mutations.map(mmm -> mmm.mutationsHistorical()
.stream()
.map(tc.oc.pgm.mutation.Mutation::toApi)
.map(tc.oc.pgm.mutation.Mutation::name)
.collect(Collectors.toImmutableSet()))
.orElse(ImmutableSet.of());
}

View File

@ -65,7 +65,7 @@ public class BlitzMatchModule extends MatchModule implements Listener, JoinHandl
public BlitzMatchModule(Match match, BlitzConfig config) {
super(match);
this.config = MutationMatchModule.check(match, Mutation.BLITZ) ? new BlitzConfig(1, true) : config;
this.config = match.module(MutationMatchModule.class).get().enabled(Mutation.BLITZ) ? new BlitzConfig(1, true) : config;
this.lifeManager = new LifeManager(this.config.getNumLives());
}

View File

@ -3,6 +3,7 @@ package tc.oc.pgm.damage;
import javax.annotation.Nullable;
import javax.inject.Inject;
import com.google.common.collect.ImmutableSetMultimap;
import com.google.common.collect.SetMultimap;
import com.google.common.collect.Sets;
import org.bukkit.block.Block;
@ -36,12 +37,12 @@ public class DisableDamageMatchModule extends MatchModule implements Listener {
this.causes = causes;
}
@Override
public void load() {
super.load();
if(MutationMatchModule.check(match, Mutation.NO_FALL)) {
this.causes.putAll(DamageCause.FALL, Sets.newHashSet(PlayerRelation.values()));
}
public SetMultimap<DamageCause, PlayerRelation> causes() {
return causes;
}
public ImmutableSetMultimap<DamageCause, PlayerRelation> causesImmutable() {
return ImmutableSetMultimap.copyOf(causes());
}
private static DamageCause getBlockDamageCause(Block block) {
@ -59,11 +60,11 @@ public class DisableDamageMatchModule extends MatchModule implements Listener {
}
private boolean canDamage(DamageCause cause, MatchPlayer victim, @Nullable ParticipantState damager) {
return !this.causes.containsEntry(cause, PlayerRelation.get(victim.getParticipantState(), damager));
return !causesImmutable().containsEntry(cause, PlayerRelation.get(victim.getParticipantState(), damager));
}
private boolean canDamage(DamageCause cause, MatchPlayer victim, DamageInfo info) {
return !this.causes.containsEntry(cause, PlayerRelation.get(victim.getParticipantState(), info.getAttacker()));
return !causesImmutable().containsEntry(cause, PlayerRelation.get(victim.getParticipantState(), info.getAttacker()));
}
@EventHandler(priority = EventPriority.NORMAL, ignoreCancelled = true)

View File

@ -21,10 +21,23 @@ public class DoubleJumpKit extends Kit.Impl {
this.rechargeInAir = rechargeInAir;
}
public DoubleJumpKit() {
this(true, DEFAULT_POWER, DEFAULT_RECHARGE, true);
}
@Override
public void apply(MatchPlayer player, boolean force, ItemKitApplicator items) {
DoubleJumpMatchModule djmm = player.getMatch().getMatchModule(DoubleJumpMatchModule.class);
if(djmm != null) djmm.setKit(player.getBukkit(), this);
player.getMatch().module(DoubleJumpMatchModule.class).ifPresent(jump -> jump.setKit(player.getBukkit(), this));
}
@Override
public boolean isRemovable() {
return true;
}
@Override
public void remove(MatchPlayer player) {
player.getMatch().module(DoubleJumpMatchModule.class).ifPresent(jump -> jump.setKit(player.getBukkit(), null));
}
public float chargePerTick() {

View File

@ -57,7 +57,7 @@ public class ProjectileTrailMatchModule extends MatchModule implements Listener
return Math.max(0.001, rgb / 255.0);
}
@EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true)
@EventHandler(priority = EventPriority.HIGH, ignoreCancelled = true)
public void onProjectileLaunch(ProjectileLaunchEvent event) {
match.player(event.getActor()).ifPresent(shooter -> {
final Projectile projectile = event.getEntity();

View File

@ -15,7 +15,7 @@ public class MatchMutationFilter extends TypedFilter.Impl<IMatchQuery> {
@Override
public boolean matches(IMatchQuery query) {
return query.module(MutationMatchModule.class)
.filter(mmm -> mmm.getActiveMutations().contains(mutation))
.filter(mmm -> mmm.enabled(mutation))
.isPresent();
}
}

View File

@ -1,30 +0,0 @@
package tc.oc.pgm.gamerules;
public enum GameRule {
DO_FIRE_TICK("doFireTick"),
DO_MOB_LOOT("doMobLoot"),
DO_TILE_DROPS("doTileDrops"),
MOB_GRIEFING("mobGriefing"),
NATURAL_REGENERATION("naturalRegeneration"),
DO_DAYLIGHT_CYCLE("doDaylightCycle");
private String value;
GameRule(String value) {
this.value = value;
}
public String getValue() {
return this.value;
}
public static GameRule forName(String query) {
for (GameRule gamerule : values()) {
if (gamerule.getValue().equalsIgnoreCase(query)) {
return gamerule;
}
}
return null;
}
}

View File

@ -6,29 +6,38 @@ import com.google.common.collect.ImmutableMap;
import tc.oc.pgm.match.Match;
import tc.oc.pgm.match.MatchModule;
import tc.oc.pgm.mutation.Mutation;
import tc.oc.pgm.mutation.MutationMatchModule;
import tc.oc.pgm.match.Repeatable;
import tc.oc.time.Time;
public class GameRulesMatchModule extends MatchModule {
private final Map<GameRule, Boolean> gameRules;
private final Map<String, String> gameRules;
public GameRulesMatchModule(Match match, Map<GameRule, Boolean> gameRules) {
public GameRulesMatchModule(Match match, Map<String, String> gameRules) {
super(match);
this.gameRules = Preconditions.checkNotNull(gameRules, "gamerules");
if(MutationMatchModule.check(match, Mutation.UHC)) {
this.gameRules.put(GameRule.NATURAL_REGENERATION, Boolean.FALSE);
}
}
@Override
public void load() {
for (Map.Entry<GameRule, Boolean> gameRule : this.gameRules.entrySet()) {
this.match.getWorld().setGameRuleValue(gameRule.getKey().getValue(), gameRule.getValue().toString());
}
update();
}
public ImmutableMap<GameRule, Boolean> getGameRules() {
return ImmutableMap.copyOf(gameRules);
@Repeatable(interval = @Time(seconds = 1))
public void tick() {
update();
}
public void update() {
gameRulesImmutable().forEach((String rule, String val) -> match.getWorld().setGameRuleValue(rule, val));
}
public Map<String, String> gameRules() {
return gameRules;
}
public ImmutableMap<String, String> gameRulesImmutable() {
return ImmutableMap.copyOf(gameRules());
}
}

View File

@ -18,9 +18,9 @@ import tc.oc.pgm.xml.InvalidXMLException;
@ModuleDescription(name="Gamerules", follows = MutationMapModule.class)
public class GameRulesModule implements MapModule, MatchModuleFactory<GameRulesMatchModule> {
private Map<GameRule, Boolean> gameRules;
private Map<String, String> gameRules;
private GameRulesModule(Map<GameRule, Boolean> gamerules) {
private GameRulesModule(Map<String, String> gamerules) {
this.gameRules = gamerules;
}
@ -33,32 +33,32 @@ public class GameRulesModule implements MapModule, MatchModuleFactory<GameRulesM
// ---------------------
public static GameRulesModule parse(MapModuleContext context, Logger logger, Document doc) throws InvalidXMLException {
Map<GameRule, Boolean> gameRules = new HashMap<>();
Map<String, String> gameRules = new HashMap<>();
for (Element gameRulesElement : doc.getRootElement().getChildren("gamerules")) {
for (Element gameRuleElement : gameRulesElement.getChildren()) {
GameRule gameRule = GameRule.forName(gameRuleElement.getName());
String gameRule = gameRuleElement.getName();
String value = gameRuleElement.getValue();
if (gameRule == null) {
if(gameRule == null) {
throw new InvalidXMLException(gameRuleElement.getName() + " is not a valid gamerule", gameRuleElement);
}
if (value == null) {
throw new InvalidXMLException("Missing value for gamerule " + gameRule.getValue(), gameRuleElement);
if(value == null) {
throw new InvalidXMLException("Missing value for gamerule " + gameRule, gameRuleElement);
} else if (!(value.equalsIgnoreCase("true") || value.equalsIgnoreCase("false"))) {
throw new InvalidXMLException(gameRuleElement.getValue() + " is not a valid gamerule value", gameRuleElement);
}
if (gameRules.containsKey(gameRule)){
throw new InvalidXMLException(gameRule.getValue() + " has already been specified", gameRuleElement);
if(gameRules.containsKey(gameRule)){
throw new InvalidXMLException(gameRule + " has already been specified", gameRuleElement);
}
gameRules.put(gameRule, Boolean.valueOf(value));
gameRules.put(gameRule, value);
}
}
return new GameRulesModule(gameRules);
}
public ImmutableMap<GameRule, Boolean> getGameRules() {
public ImmutableMap<String, String> getGameRules() {
return ImmutableMap.copyOf(this.gameRules);
}

View File

@ -3,6 +3,8 @@ package tc.oc.pgm.killreward;
import com.google.common.collect.ImmutableList;
import org.bukkit.inventory.ItemStack;
import tc.oc.pgm.filters.Filter;
import tc.oc.pgm.filters.matcher.StaticFilter;
import tc.oc.pgm.kits.ItemKit;
import tc.oc.pgm.kits.Kit;
public class KillReward {
@ -15,4 +17,8 @@ public class KillReward {
this.filter = filter;
this.kit = kit;
}
public KillReward(ItemKit kit) {
this(ImmutableList.of(kit.item()), StaticFilter.ALLOW, kit);
}
}

View File

@ -2,11 +2,10 @@ package tc.oc.pgm.killreward;
import java.util.Collection;
import java.util.List;
import java.util.stream.Collectors;
import javax.annotation.Nullable;
import com.google.common.base.Predicate;
import com.google.common.collect.ArrayListMultimap;
import com.google.common.collect.Collections2;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Multimap;
import org.bukkit.event.Event;
@ -27,34 +26,38 @@ import tc.oc.pgm.tracker.damage.DamageInfo;
@ListenerScope(MatchScope.RUNNING)
public class KillRewardMatchModule extends MatchModule implements Listener {
protected final ImmutableList<KillReward> killRewards;
protected final Multimap<MatchPlayer, KillReward> deadPlayerRewards = ArrayListMultimap.create();
private final List<KillReward> killRewards;
private final Multimap<MatchPlayer, KillReward> deadPlayerRewards = ArrayListMultimap.create();
public KillRewardMatchModule(Match match, List<KillReward> killRewards) {
super(match);
this.killRewards = ImmutableList.copyOf(killRewards);
this.killRewards = killRewards;
}
private Collection<KillReward> getRewards(@Nullable Event event, ParticipantState victim, DamageInfo damageInfo) {
public List<KillReward> rewards() {
return killRewards;
}
public ImmutableList<KillReward> rewardsImmutable() {
return ImmutableList.copyOf(killRewards);
}
public List<KillReward> rewards(@Nullable Event event, ParticipantState victim, DamageInfo damageInfo) {
final DamageQuery query = DamageQuery.attackerDefault(event, victim, damageInfo);
return Collections2.filter(killRewards, new Predicate<KillReward>() {
@Override
public boolean apply(KillReward killReward) {
return killReward.filter.query(query).isAllowed();
}
});
return rewardsImmutable().stream().filter(reward -> reward.filter.query(query).isAllowed()).collect(Collectors.toList());
}
private Collection<KillReward> getRewards(MatchPlayerDeathEvent event) {
return getRewards(event, event.getVictim().getParticipantState(), event.getDamageInfo());
public List<KillReward> rewards(MatchPlayerDeathEvent event) {
return rewards(event, event.getVictim().getParticipantState(), event.getDamageInfo());
}
private void giveRewards(MatchPlayer killer, Collection<KillReward> rewards) {
for(KillReward reward : rewards) {
public void giveRewards(MatchPlayer killer, Collection<KillReward> rewards) {
rewards.forEach(reward -> {
// Apply kit first so it can not override reward items
reward.kit.apply(killer);
reward.items.forEach(stack -> ItemKitApplicator.fireEventAndTransfer(killer, stack));
}
});
}
@EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
@ -63,7 +66,7 @@ public class KillRewardMatchModule extends MatchModule implements Listener {
MatchPlayer killer = event.getOnlineKiller();
if(killer == null) return;
Collection<KillReward> rewards = getRewards(event);
List<KillReward> rewards = rewards(event);
if(killer.isDead()) {
// If a player earns a KW while dead, give it to them when they respawn. Rationale: If they click respawn
@ -87,4 +90,5 @@ public class KillRewardMatchModule extends MatchModule implements Listener {
public void onPartyChange(PlayerPartyChangeEvent event) {
deadPlayerRewards.removeAll(event.getPlayer());
}
}

View File

@ -1,5 +1,6 @@
package tc.oc.pgm.killreward;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import java.util.logging.Logger;
@ -27,10 +28,10 @@ import tc.oc.pgm.xml.InvalidXMLException;
@ModuleDescription(name="Kill Reward")
public class KillRewardModule implements MapModule, MatchModuleFactory<KillRewardMatchModule> {
protected final ImmutableList<KillReward> rewards;
protected final List<KillReward> rewards;
public KillRewardModule(List<KillReward> rewards) {
this.rewards = ImmutableList.copyOf(rewards);
this.rewards = rewards;
}
@Override
@ -43,7 +44,7 @@ public class KillRewardModule implements MapModule, MatchModuleFactory<KillRewar
// ---------------------
public static KillRewardModule parse(MapModuleContext context, Logger logger, Document doc) throws InvalidXMLException {
ImmutableList.Builder<KillReward> rewards = ImmutableList.builder();
List<KillReward> rewards = new ArrayList<>();
final ItemParser itemParser = context.needModule(ItemParser.class);
final Optional<ItemModifyModule> itemModifier = context.module(ItemModifyModule.class);
@ -61,11 +62,6 @@ public class KillRewardModule implements MapModule, MatchModuleFactory<KillRewar
rewards.add(new KillReward(items.build(), filter, kit));
}
ImmutableList<KillReward> list = rewards.build();
if(list.isEmpty()) {
return null;
} else {
return new KillRewardModule(list);
}
return new KillRewardModule(rewards);
}
}

View File

@ -1,8 +1,15 @@
package tc.oc.pgm.kits;
import org.bukkit.inventory.ItemStack;
import org.bukkit.inventory.PlayerInventory;
import tc.oc.commons.bukkit.inventory.Slot;
import tc.oc.pgm.match.MatchPlayer;
import java.util.Map;
import java.util.Optional;
import java.util.function.Function;
import java.util.stream.Collectors;
public class FreeItemKit extends BaseItemKit {
protected final ItemStack item;
@ -20,4 +27,36 @@ public class FreeItemKit extends BaseItemKit {
public void apply(MatchPlayer player, boolean force, ItemKitApplicator items) {
items.add(item);
}
@Override
public boolean isRemovable() {
return true;
}
@Override
public void remove(MatchPlayer player) {
int left = item.getAmount();
PlayerInventory inv = player.getInventory();
for(Map.Entry<Slot.Player, Optional<ItemStack>> entry : Slot.Player.player()
.collect(Collectors.toMap(Function.identity(), slot -> slot.item(inv))).entrySet()) {
Slot.Player slot = entry.getKey();
Optional<ItemStack> itemMaybe = entry.getValue();
if(itemMaybe.isPresent() && this.item.isSimilar(itemMaybe.get())) {
ItemStack item = itemMaybe.get();
int delta = item.getAmount() - left;
if(delta > 0) {
ItemStack replaced = item.clone();
replaced.setAmount(delta);
slot.putItem(inv, replaced);
break;
} else {
slot.putItem(inv, null);
if(delta < 0) {
left = -delta;
}
}
}
}
}
}

View File

@ -1,13 +1,11 @@
package tc.oc.pgm.kits;
import com.google.common.base.Preconditions;
import tc.oc.pgm.match.MatchPlayer;
public class HealthKit extends Kit.Impl {
protected final int halfHearts;
public HealthKit(int halfHearts) {
Preconditions.checkArgument(0 < halfHearts && halfHearts <= 20, "halfHearts must be greater than 0 and less than or equal to 20");
this.halfHearts = halfHearts;
}

View File

@ -11,12 +11,27 @@ public class NaturalRegenerationKit extends Kit.Impl {
this.enabled = enabled;
}
@Override
public void apply(MatchPlayer player, boolean force, ItemKitApplicator items) {
public void toggle(MatchPlayer player, boolean enabled) {
if(fast) {
player.getBukkit().setFastNaturalRegeneration(enabled);
} else {
player.getBukkit().setSlowNaturalRegeneration(enabled);
}
}
@Override
public void apply(MatchPlayer player, boolean force, ItemKitApplicator items) {
toggle(player, enabled);
}
@Override
public boolean isRemovable() {
return true;
}
@Override
public void remove(MatchPlayer player) {
toggle(player, !enabled);
}
}

View File

@ -21,4 +21,5 @@ public class SlotItemKit extends FreeItemKit {
public void apply(MatchPlayer player, boolean force, ItemKitApplicator items) {
items.put(slot, item, force);
}
}

View File

@ -164,11 +164,11 @@ public class MatchAnnouncer implements PluginFacet, Listener {
}
final MutationMatchModule mmm = viewer.getMatch().getMatchModule(MutationMatchModule.class);
if(mmm != null && mmm.getActiveMutations().size() > 0) {
if(mmm != null && mmm.mutationsActive().size() > 0) {
viewer.sendMessage(
new Component(" ", ChatColor.DARK_GRAY).extra(
new TranslatableComponent("broadcast.welcomeMessage.mutations",
new ListComponent(Collections2.transform(mmm.getActiveMutations(), Mutation.toComponent(ChatColor.GREEN)))
new ListComponent(Collections2.transform(mmm.mutationsActive(), Mutation.toComponent(ChatColor.GREEN)))
)
)
);

View File

@ -25,7 +25,6 @@ import tc.oc.pgm.Config;
import tc.oc.pgm.events.MatchBeginEvent;
import tc.oc.pgm.events.MatchEndEvent;
import tc.oc.pgm.events.MatchLoadEvent;
import tc.oc.pgm.gamerules.GameRule;
import tc.oc.pgm.gamerules.GameRulesModule;
import tc.oc.pgm.match.MatchManager;
import tc.oc.pgm.modules.TimeLockModule;
@ -99,9 +98,11 @@ public class PGMListener implements PluginFacet, Listener {
// Time Lock
// lock time before, during (if time lock enabled), and after the match
//
static final String DO_DAYLIGHT_CYCLE = "doDaylightCycle";
@EventHandler
public void lockTime(final MatchLoadEvent event) {
event.getMatch().getWorld().setGameRuleValue(GameRule.DO_DAYLIGHT_CYCLE.getValue(), Boolean.toString(false));
event.getMatch().getWorld().setGameRuleValue(DO_DAYLIGHT_CYCLE, Boolean.toString(false));
}
@EventHandler
@ -113,16 +114,16 @@ public class PGMListener implements PluginFacet, Listener {
GameRulesModule gameRulesModule = event.getMatch().getModuleContext().getModule(GameRulesModule.class);
if (gameRulesModule != null && gameRulesModule.getGameRules().containsKey(GameRule.DO_DAYLIGHT_CYCLE)) {
unlockTime = gameRulesModule.getGameRules().get(GameRule.DO_DAYLIGHT_CYCLE);
if (gameRulesModule != null && gameRulesModule.getGameRules().containsKey(DO_DAYLIGHT_CYCLE)) {
unlockTime = Boolean.valueOf(gameRulesModule.getGameRules().get(DO_DAYLIGHT_CYCLE));
}
event.getMatch().getWorld().setGameRuleValue(GameRule.DO_DAYLIGHT_CYCLE.getValue(), Boolean.toString(unlockTime));
event.getMatch().getWorld().setGameRuleValue(DO_DAYLIGHT_CYCLE, Boolean.toString(unlockTime));
}
@EventHandler
public void lockTime(final MatchEndEvent event) {
event.getMatch().getWorld().setGameRuleValue(GameRule.DO_DAYLIGHT_CYCLE.getValue(), Boolean.toString(false));
event.getMatch().getWorld().setGameRuleValue(DO_DAYLIGHT_CYCLE, Boolean.toString(false));
}
@EventHandler

View File

@ -4,22 +4,21 @@ import org.bukkit.event.EventHandler;
import org.bukkit.event.Listener;
import org.bukkit.event.entity.CreatureSpawnEvent;
import tc.oc.pgm.events.ListenerScope;
import tc.oc.pgm.filters.matcher.StaticFilter;
import tc.oc.pgm.match.Match;
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.Mutation;
import tc.oc.pgm.mutation.MutationMatchModule;
@ListenerScope(MatchScope.LOADED)
public class MobsMatchModule extends MatchModule implements Listener {
private final Filter mobsFilter;
public MobsMatchModule(Match match, Filter mobsFilter) {
super(match);
this.mobsFilter = MutationMatchModule.check(match, Mutation.MOBS) ? StaticFilter.ALLOW : mobsFilter;
this.mobsFilter = mobsFilter;
}
@Override
@ -40,8 +39,10 @@ public class MobsMatchModule extends MatchModule implements Listener {
@EventHandler(ignoreCancelled = true)
public void checkSpawn(final CreatureSpawnEvent event) {
if(event.getSpawnReason() != CreatureSpawnEvent.SpawnReason.CUSTOM) {
if(!match.module(MutationMatchModule.class).map(mmm -> mmm.allowMob(event.getSpawnReason())).orElse(false) &&
event.getSpawnReason() != CreatureSpawnEvent.SpawnReason.CUSTOM) {
event.setCancelled(mobsFilter.query(new EntitySpawnQuery(event, event.getEntity(), event.getSpawnReason())).isDenied());
}
}
}

View File

@ -7,58 +7,61 @@ import net.md_5.bungee.api.ChatColor;
import net.md_5.bungee.api.chat.BaseComponent;
import net.md_5.bungee.api.chat.HoverEvent;
import net.md_5.bungee.api.chat.TranslatableComponent;
import tc.oc.api.docs.virtual.MatchDoc;
import tc.oc.commons.core.chat.Component;
import tc.oc.pgm.mutation.submodule.MutationModule;
import tc.oc.pgm.PGM;
import tc.oc.pgm.mutation.types.MutationModule;
import tc.oc.pgm.mutation.types.kit.ArmorMutation;
import tc.oc.pgm.mutation.types.kit.ElytraMutation;
import tc.oc.pgm.mutation.types.kit.EnchantmentMutation;
import tc.oc.pgm.mutation.types.kit.EquestrianMutation;
import tc.oc.pgm.mutation.types.kit.ExplosiveMutation;
import tc.oc.pgm.mutation.types.kit.GlowMutation;
import tc.oc.pgm.mutation.types.kit.HardcoreMutation;
import tc.oc.pgm.mutation.types.kit.HealthMutation;
import tc.oc.pgm.mutation.types.kit.JumpMutation;
import tc.oc.pgm.mutation.types.kit.MobsMutation;
import tc.oc.pgm.mutation.types.kit.PotionMutation;
import tc.oc.pgm.mutation.types.kit.ProjectileMutation;
import tc.oc.pgm.mutation.types.kit.StealthMutation;
import tc.oc.pgm.mutation.types.other.RageMutation;
import tc.oc.pgm.mutation.types.targetable.ApocalypseMutation;
import tc.oc.pgm.mutation.types.targetable.BomberMutation;
import tc.oc.pgm.mutation.types.targetable.LightningMutation;
import static tc.oc.pgm.mutation.submodule.MutationModules.*;
import java.util.stream.Stream;
public enum Mutation {
BLITZ (null, false),
UHC (null, false),
EXPLOSIVES (Explosives.class, true),
NO_FALL (null, false),
MOBS (null, false),
STRENGTH (Strength.class, true),
DOUBLE_JUMP (DoubleJump.class, true),
INVISIBILITY(Invisibility.class, true),
LIGHTNING (Lightning.class, true),
RAGE (Rage.class, true),
ELYTRA (Elytra.class, true);
BLITZ (null),
RAGE (RageMutation.class),
HARDCORE (HardcoreMutation.class),
JUMP (JumpMutation.class),
EXPLOSIVE (ExplosiveMutation.class),
ELYTRA (ElytraMutation.class),
PROJECTILE (ProjectileMutation.class),
ENCHANTMENT(EnchantmentMutation.class),
POTION (PotionMutation.class),
EQUESTRIAN (EquestrianMutation.class),
HEALTH (HealthMutation.class),
GLOW (GlowMutation.class),
STEALTH (StealthMutation.class),
ARMOR (ArmorMutation.class),
MOBS (MobsMutation.class),
LIGHTNING (LightningMutation.class),
BOMBER (BomberMutation.class),
APOCALYPSE (ApocalypseMutation.class);
public static final String TYPE_KEY = "mutation.type.";
public static final String DESCRIPTION_KEY = ".desc";
/**
* The module class that handles this mutation.
*/
private final @Nullable Class<? extends MutationModule> clazz;
private final @Nullable Class<? extends MutationModule> loader;
/**
* Whether this mutation be changed during a match.
*/
private final boolean change;
Mutation(@Nullable Class<? extends MutationModule> clazz, boolean change) {
this.clazz = clazz;
this.change = change;
Mutation(@Nullable Class<? extends MutationModule> loader) {
this.loader = loader;
}
public static Mutation fromApi(MatchDoc.Mutation mutation) {
return values()[mutation.ordinal()];
}
public MatchDoc.Mutation toApi() {
return MatchDoc.Mutation.values()[ordinal()];
}
public Class<? extends MutationModule> getModuleClass() {
return clazz;
}
public boolean isChangeable() {
return change;
public Class<? extends MutationModule> loader() {
return loader;
}
public String getName() {
@ -76,4 +79,14 @@ public enum Mutation {
public static Function<Mutation, BaseComponent> toComponent(final ChatColor color) {
return mutation -> mutation.getComponent(color);
}
public static Stream<Mutation> fromString(final String name) {
try {
return Stream.of(Mutation.valueOf(name));
} catch(IllegalArgumentException iae) {
PGM.get().getLogger().warning("Unable to find mutation named '" + name + "'");
return Stream.empty();
}
}
}

View File

@ -1,24 +1,24 @@
package tc.oc.pgm.mutation;
import com.google.common.base.Predicate;
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;
import tc.oc.pgm.match.*;
import tc.oc.pgm.mutation.command.MutationCommands;
import tc.oc.pgm.mutation.submodule.MutationModule;
import tc.oc.pgm.mutation.types.MutationModule;
import tc.oc.commons.core.random.ImmutableWeightedRandomChooser;
import java.util.*;
import java.util.logging.Level;
import java.util.stream.Stream;
import javax.inject.Inject;
public class MutationMatchModule extends MatchModule {
// TODO: send remote mutation alerts via an AMQP message
/**
* Chance that a mutation event will occur.
@ -63,39 +63,36 @@ public class MutationMatchModule extends MatchModule {
this.chance = options.chance;
this.amount = options.amount;
this.weightedSelector = new ImmutableWeightedRandomChooser<>(options.weights);
this.mutations = getDefaultMutations();
this.mutations = mutationsDefault();
this.history = new HashSet<>();
this.modules = new HashMap<>();
}
public final ImmutableMap<Mutation, Boolean> getMutations() {
public final ImmutableMap<Mutation, Boolean> mutations() {
return ImmutableMap.copyOf(mutations);
}
public final ImmutableSet<Mutation> getActiveMutations() {
return ImmutableSet.copyOf(Collections2.filter(getMutations().keySet(), new Predicate<Mutation>() {
@Override
public boolean apply(Mutation mutation) {
return mutations.get(mutation);
}
}));
public final ImmutableSet<Mutation> mutationsActive() {
return ImmutableSet.copyOf(Collections2.filter(mutations().keySet(), mutations::get));
}
public final ImmutableSet<Mutation> getHistoricalMutations() {
public final ImmutableSet<Mutation> mutationsHistorical() {
return ImmutableSet.copyOf(history);
}
public final ImmutableSet<MutationModule> getMutationModules() {
private Map<Mutation, Boolean> mutationsDefault() {
Map<Mutation, Boolean> defaults = new HashMap<>();
MapUtils.putAll(defaults, Sets.newHashSet(Mutation.values()), false);
return defaults;
}
public final ImmutableSet<MutationModule> mutationModules() {
return ImmutableSet.copyOf(modules.values());
}
@Override
public boolean shouldLoad() {
return Config.Mutations.enabled();
}
@Override
public void load() {
if(!Config.Mutations.enabled()) return;
Random random = match.getRandom();
// Check if the api has any queued mutations
Collection<Mutation> queuedMutations = mutationQueue.mutations();
@ -113,7 +110,7 @@ public class MutationMatchModule extends MatchModule {
mutationQueue.clear();
}
// Load the mutation modules for this match
for(Mutation mutation : getActiveMutations()) {
for(Mutation mutation : mutationsActive()) {
try {
mutate(mutation);
} catch (Throwable throwable) {
@ -124,43 +121,53 @@ public class MutationMatchModule extends MatchModule {
@Override
public void enable() {
for(MutationModule module : modules.values()) {
module.enable(match.isRunning());
}
modules.values().forEach(MutationModule::enable);
}
@Override
public void disable() {
for(MutationModule module : modules.values()) {
module.disable(match.isRunning());
}
modules.values().forEach(MutationModule::disable);
}
public void register(Mutation mutation, boolean load) {
mutations.put(mutation, load);
}
public void mutate(Mutation mutation) throws Throwable {
Class<? extends MutationModule> clazz = mutation.getModuleClass();
if(clazz == null || (match.isRunning() && !mutation.isChangeable())) return;
MutationModule module = modules.containsKey(clazz) ? modules.get(clazz) : mutation.getModuleClass().getDeclaredConstructor(Match.class).newInstance(match);
Class<? extends MutationModule> loader = mutation.loader();
if(loader == null) return;
MutationModule module = modules.containsKey(loader) ? modules.get(loader) : loader.getDeclaredConstructor(Match.class).newInstance(match);
if(mutations.get(mutation)) {
module.enable(match.isRunning());
modules.put(clazz, module);
module.enable();
modules.put(loader, module);
mutations.put(mutation, true);
history.add(mutation);
} else {
module.disable(match.isRunning());
modules.remove(clazz);
module.disable();
modules.remove(loader);
mutations.put(mutation, false);
}
}
private Map<Mutation, Boolean> getDefaultMutations() {
Map<Mutation, Boolean> defaults = new HashMap<>();
MapUtils.putAll(defaults, Sets.newHashSet(Mutation.values()), false);
return defaults;
public boolean enabled() {
return !mutationsActive().isEmpty();
}
public static boolean check(Match match, Mutation mutation) {
return Config.Mutations.enabled() &&
match.hasMatchModule(MutationMatchModule.class) &&
match.getMatchModule(MutationMatchModule.class).getActiveMutations().contains(mutation);
public boolean enabled(Mutation... mutations) {
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

@ -6,7 +6,6 @@ import java.util.HashSet;
import java.util.stream.Collectors;
import javax.inject.Inject;
import com.google.common.collect.Collections2;
import com.google.common.util.concurrent.ListenableFuture;
import tc.oc.api.docs.Server;
import tc.oc.api.docs.virtual.ServerDoc;
@ -28,12 +27,12 @@ public class MutationQueue {
.getLocalServer()
.queued_mutations()
.stream()
.map(mutation -> Mutation.values()[mutation.ordinal()])
.flatMap(Mutation::fromString)
.collect(Collectors.toList());
}
public ListenableFuture<Server> clear() {
return force(Collections.<Mutation>emptyList());
return force(Collections.emptyList());
}
public ListenableFuture<Server> removeAll(final Collection<Mutation> mutations) {
@ -53,6 +52,7 @@ public class MutationQueue {
}
private ListenableFuture<Server> force(final Collection<Mutation> mutations) {
return minecraftService.updateLocalServer((ServerDoc.Mutation) () -> mutations.stream().map(Mutation::toApi).collect(Collectors.toSet()));
return minecraftService.updateLocalServer((ServerDoc.Mutation) () -> mutations.stream().map(Mutation::name).collect(Collectors.toSet()));
}
}

View File

@ -2,20 +2,21 @@ package tc.oc.pgm.mutation.command;
import java.util.Collection;
import java.util.HashSet;
import java.util.logging.Level;
import javax.inject.Inject;
import com.google.common.collect.Collections2;
import com.google.common.collect.Iterables;
import com.google.common.collect.Sets;
import com.sk89q.minecraft.util.commands.Command;
import com.sk89q.minecraft.util.commands.CommandContext;
import com.sk89q.minecraft.util.commands.CommandException;
import com.sk89q.minecraft.util.commands.CommandPermissions;
import com.sk89q.minecraft.util.commands.NestedCommand;
import com.sk89q.minecraft.util.commands.*;
import net.md_5.bungee.api.ChatColor;
import net.md_5.bungee.api.chat.BaseComponent;
import net.md_5.bungee.api.chat.TranslatableComponent;
import org.bukkit.command.CommandSender;
import tc.oc.commons.bukkit.chat.PlayerComponent;
import tc.oc.commons.bukkit.chat.WarningComponent;
import tc.oc.commons.bukkit.nick.IdentityProvider;
import tc.oc.commons.core.chat.Audience;
import tc.oc.minecraft.scheduler.SyncExecutor;
import tc.oc.commons.bukkit.chat.Audiences;
import tc.oc.commons.bukkit.chat.ListComponent;
@ -56,39 +57,42 @@ public class MutationCommands implements NestedCommands {
private final SyncExecutor syncExecutor;
private final Audiences audiences;
private final MutationQueue mutationQueue;
private final IdentityProvider identityProvider;
@Inject
MutationCommands(SyncExecutor syncExecutor, Audiences audiences, MutationQueue mutationQueue) {
@Inject MutationCommands(SyncExecutor syncExecutor, Audiences audiences, MutationQueue mutationQueue, IdentityProvider identityProvider) {
this.syncExecutor = syncExecutor;
this.audiences = audiences;
this.mutationQueue = mutationQueue;
this.identityProvider = identityProvider;
}
@Command(
aliases = {"enable", "add"},
aliases = {"enable", "e"},
desc = "Adds a mutation to the upcoming match." +
"You can use '?' as a wildcard or " +
"'*' to use all.",
usage = "<mutation|?|*>",
flags = "q",
min = 1,
max = 1
)
@CommandPermissions(PERMISSION_SET)
public void enable(CommandContext args, CommandSender sender) throws CommandException {
public void enable(CommandContext args, CommandSender sender) throws CommandException, SuggestException {
set(args, sender, true);
}
@Command(
aliases = {"disable", "remove"},
aliases = {"disable", "d"},
desc = "Remove a mutation to the upcoming match." +
"You can use '?' as a wildcard or " +
"'*' to use all.",
usage = "<mutation|?|*>",
flags = "q",
min = 1,
max = 1
)
@CommandPermissions(PERMISSION_SET)
public void disable(CommandContext args, CommandSender sender) throws CommandException {
public void disable(CommandContext args, CommandSender sender) throws CommandException, SuggestException {
set(args, sender, false);
}
@ -105,8 +109,8 @@ public class MutationCommands implements NestedCommands {
public void list(final CommandContext args, CommandSender sender) throws CommandException {
MutationMatchModule module = verify(sender);
final boolean queued = args.hasFlag('q');
final Collection<Mutation> active = queued ? mutationQueue.mutations() : module.getActiveMutations();
new Paginator<Mutation>() {
final Collection<Mutation> active = queued ? mutationQueue.mutations() : module.mutationsActive();
new Paginator<Mutation>(Mutation.values().length / 2) {
@Override
protected BaseComponent title() {
return new TranslatableComponent(queued ? "command.mutation.list.queued" : "command.mutation.list.current");
@ -124,20 +128,26 @@ public class MutationCommands implements NestedCommands {
return CommandUtils.getMatchModule(MutationMatchModule.class, sender);
}
public void set(CommandContext args, final CommandSender sender, final boolean value) throws CommandException {
public void set(CommandContext args, final CommandSender sender, final boolean value) throws CommandException, SuggestException {
final MutationMatchModule module = verify(sender);
final Match match = module.getMatch();
String action = args.getString(0);
boolean queued = args.hasFlag('q') || match.isFinished();
// Mutations that *will* be added or removed
final Collection<Mutation> mutations = new HashSet<>();
// Mutations that *are allowed* to be added or removed
final Collection<Mutation> availableMutations = Sets.newHashSet(Mutation.values());
final Collection<Mutation> queue = mutationQueue.mutations();
final Collection<Mutation> queue = queued ? mutationQueue.mutations() : module.mutationsActive();
if(value) availableMutations.removeAll(queue); else availableMutations.retainAll(queue);
// Check if all mutations have been enabled/disabled
if((queue.size() == Mutation.values().length && value) || (queue.isEmpty() && !value)) {
throw newCommandException(sender, new TranslatableComponent(value ? "command.mutation.error.enabled" : "command.mutation.error.disabled"));
throw newCommandException(sender, new TranslatableComponent(value ? "command.mutation.error.enabled.all" : "command.mutation.error.disabled.all"));
}
// Suggest mutations for the user to choose
final SuggestionContext context = args.getSuggestionContext();
if(context != null) {
context.suggestArgument(0, StringUtils.complete(context.getPrefix(), availableMutations.stream().map(mutation -> mutation.name().toLowerCase())));
}
// Get which action the user wants to preform
switch (action) {
@ -145,27 +155,50 @@ public class MutationCommands implements NestedCommands {
case "?": mutations.add(Iterables.get(availableMutations, RandomUtils.safeNextInt(match.getRandom(), availableMutations.size()))); break;
default:
Mutation query = StringUtils.bestFuzzyMatch(action, Sets.newHashSet(Mutation.values()), 0.9);
if (query == null) {
if(query == null) {
throw newCommandException(sender, new TranslatableComponent("command.mutation.error.find", action));
} else if(value == queue.contains(query)) {
throw newCommandException(sender, new TranslatableComponent(value ? "command.mutation.error.enabled" : "command.mutation.error.disabled", query.getComponent(ChatColor.RED)));
} else {
mutations.add(query);
}
}
// Send the queued changes off to the api
syncExecutor.callback(
value ? mutationQueue.mergeAll(mutations)
: mutationQueue.removeAll(mutations),
result -> {
audiences.get(sender).sendMessage(new Component(new TranslatableComponent(
message(false, value, mutations.size() == 1),
new ListComponent(Collections2.transform(mutations, Mutation.toComponent(ChatColor.AQUA)))
), ChatColor.WHITE));
Audience origin = audiences.get(sender);
Audience all = audiences.localServer();
String message = message(!queued, value, mutations.size() == 1);
ListComponent changed = new ListComponent(Collections2.transform(mutations, Mutation.toComponent(ChatColor.AQUA)));
if(queued) {
// Send the queued changes off to the api
syncExecutor.callback(
value ? mutationQueue.mergeAll(mutations)
: mutationQueue.removeAll(mutations),
result -> {
origin.sendMessage(new Component(new TranslatableComponent(message, changed), ChatColor.WHITE));
}
);
} else {
// Make the changes immediately
for(Mutation mutation : mutations) {
try {
module.register(mutation, value);
module.mutate(mutation);
} catch(Throwable t) {
module.register(mutation, !value);
origin.sendMessage(
new WarningComponent(
"command.mutation.error.mutate",
mutation.getComponent(ChatColor.RED)
)
);
module.getLogger().log(Level.SEVERE, "Unable to enable/disable mutation", t);
return;
}
}
);
PlayerComponent player = new PlayerComponent(identityProvider.currentIdentity(sender));
all.sendMessage(new Component(new TranslatableComponent(message, player, changed)));
}
}
// TODO: force enabling mutations
public String message(boolean now, boolean enable, boolean singular) {
if(now) {
if(enable) {

View File

@ -1,28 +0,0 @@
package tc.oc.pgm.mutation.submodule;
import tc.oc.pgm.kits.Kit;
import tc.oc.pgm.match.Match;
/**
* A mutation modules that injects special kits on participant spawn.
*/
public class KitMutationModule extends MutationModule {
protected final Kit[] kits;
protected final boolean force;
public KitMutationModule(Match match, boolean force, Kit... kits) {
super(match);
this.kits = kits;
this.force = force;
}
public Kit[] getKits() {
return enabled ? kits : new Kit[0];
}
public boolean isForceful() {
return force;
}
}

View File

@ -1,68 +0,0 @@
package tc.oc.pgm.mutation.submodule;
import org.bukkit.event.HandlerList;
import org.bukkit.event.Listener;
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;
/**
* Bits of immutable code that manage a {@link Mutation}.
*
* This should be able to load at any time during the match
* and not cause any problems. This will allow mutations
* to be forcefully loaded on the fly without any worries
* of breaking the match state.
*
* TODO: Force loading is not been enabled yet, but all mutation
* modules should be ready for the switch.
*/
@ListenerScope(MatchScope.RUNNING)
public abstract class MutationModule implements Listener {
protected final Match match;
protected boolean enabled;
/**
* 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.enabled = false;
}
/**
* 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.
* @param late called after the match starts.
*
* {@link MutationMatchModule#enable()}
*/
public void enable(boolean late) {
match.registerEvents(this);
enabled = true;
}
/**
* Called when the match ends.
*
* However, this should be able to be called at any
* point during a match and still work as expected.
* @param premature called before the match ends.
*
* {@link MutationMatchModule#disable()}
*/
public void disable(boolean premature) {
match.unregisterEvents(this);
enabled = false;
}
}

View File

@ -1,110 +0,0 @@
package tc.oc.pgm.mutation.submodule;
import org.bukkit.Material;
import org.bukkit.entity.Entity;
import org.bukkit.entity.Player;
import org.bukkit.entity.Projectile;
import org.bukkit.event.EventHandler;
import org.bukkit.event.EventPriority;
import org.bukkit.event.entity.EntityDamageByEntityEvent;
import org.bukkit.event.entity.ExplosionPrimeEvent;
import org.bukkit.inventory.ItemStack;
import org.bukkit.potion.PotionEffect;
import org.bukkit.potion.PotionEffectType;
import org.bukkit.util.Vector;
import java.time.Duration;
import tc.oc.commons.bukkit.inventory.ArmorType;
import tc.oc.commons.bukkit.inventory.Slot;
import tc.oc.commons.bukkit.item.ItemUtils;
import tc.oc.pgm.doublejump.DoubleJumpKit;
import tc.oc.pgm.kits.FreeItemKit;
import tc.oc.pgm.kits.Kit;
import tc.oc.pgm.kits.KitNode;
import tc.oc.pgm.kits.PotionKit;
import tc.oc.pgm.kits.SlotItemKit;
import tc.oc.pgm.match.Match;
import tc.oc.pgm.match.ParticipantState;
public class MutationModules {
public static class Explosives extends KitMutationModule {
public static final Float MULTIPLIER = 1.75f;
private static final Kit KIT = KitNode.of(new FreeItemKit(new ItemStack(Material.FLINT_AND_STEEL)),
new FreeItemKit(new ItemStack(Material.TNT, 16)));
public Explosives(Match match) {
super(match, false, KIT);
}
@EventHandler(ignoreCancelled = true, priority = EventPriority.HIGHEST)
public void onExplosionPrime(ExplosionPrimeEvent event) {
event.setRadius(event.getRadius() * MULTIPLIER);
}
}
public static class Strength extends KitMutationModule {
public static final PotionEffect EFFECT = new PotionEffect(PotionEffectType.INCREASE_DAMAGE, Integer.MAX_VALUE, 0, false, false);
public static final PotionKit KIT = new PotionKit(EFFECT);
public Strength(Match match) {
super(match, true, KIT);
}
}
public static class DoubleJump extends KitMutationModule {
public static final DoubleJumpKit KIT = new DoubleJumpKit(true, 3f, Duration.ofSeconds(5), true);
public DoubleJump(Match match) {
super(match, false, KIT);
}
}
public static class Invisibility extends KitMutationModule {
public static final PotionEffect EFFECT = new PotionEffect(PotionEffectType.INVISIBILITY, Integer.MAX_VALUE, 0, false, false);
public static final PotionKit KIT = new PotionKit(EFFECT);
public Invisibility(Match match) {
super(match, true, KIT);
}
}
public static class Lightning extends TargetableMutationModule {
public static final Duration FREQUENCY = Duration.ofSeconds(30);
public Lightning(Match match) {
super(match, FREQUENCY, 3);
}
@Override
public void execute(ParticipantState player) {
match.getWorld().strikeLightning(player.getLocation().clone().add(Vector.getRandom()));
}
}
public static class Rage extends MutationModule {
public static final Integer DAMAGE = Integer.MAX_VALUE;
public Rage(Match match) {
super(match);
}
@EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true)
public void onPlayerDamage(EntityDamageByEntityEvent event) {
Entity damager = event.getDamager();
if ((damager instanceof Player && ItemUtils.isWeapon(((Player) damager).getItemInHand())) ||
(damager instanceof Projectile && ((Projectile) damager).getShooter() instanceof Player)) {
event.setDamage(DAMAGE);
}
}
}
public static class Elytra extends KitMutationModule {
public static final Kit KIT = KitNode.of(new SlotItemKit(new ItemStack(Material.ELYTRA), Slot.Armor.forType(ArmorType.CHESTPLATE)),
new DoubleJumpKit(true, 6f, Duration.ofSeconds(30), true));
public Elytra(Match match) {
super(match, true, KIT);
}
}
}

View File

@ -1,46 +0,0 @@
package tc.oc.pgm.mutation.submodule;
import com.google.common.collect.Range;
import java.time.Duration;
import tc.oc.commons.core.random.Entropy;
import tc.oc.commons.core.scheduler.Task;
import tc.oc.commons.core.stream.Collectors;
import tc.oc.pgm.match.Match;
import tc.oc.pgm.match.MatchPlayer;
import tc.oc.pgm.match.MatchScope;
import tc.oc.pgm.match.ParticipantState;
import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkNotNull;
/**
* A mutation module that executes a task on a random {@link ParticipantState}s.
*/
public abstract class TargetableMutationModule extends MutationModule {
protected final Duration frequency;
protected final int targets;
protected Task task;
public TargetableMutationModule(final Match match, Duration frequency, int targets) {
super(match);
this.frequency = checkNotNull(frequency, "frequency cannot be null");
this.targets = targets; checkArgument(targets >= 1, "amount of targets cannot be less than 1");
this.task = match.getScheduler(MatchScope.RUNNING).createRepeatingTask(frequency, () -> {
final Entropy entropy = match.entropyForTick();
match.participants()
.filter(MatchPlayer::isSpawned)
.collect(Collectors.toRandomSubList(entropy, entropy.randomInt(Range.closed(1, targets))))
.forEach(player -> execute(player.getParticipantState()));
});
}
@Override
public void disable(boolean premature) {
task.cancel();
super.disable(premature);
}
public abstract void execute(ParticipantState player);
}

View File

@ -0,0 +1,111 @@
package tc.oc.pgm.mutation.types;
import org.bukkit.inventory.ItemStack;
import tc.oc.commons.bukkit.inventory.Slot;
import tc.oc.pgm.killreward.KillReward;
import tc.oc.pgm.killreward.KillRewardMatchModule;
import tc.oc.pgm.kits.Kit;
import tc.oc.pgm.kits.KitPlayerFacet;
import tc.oc.pgm.match.Match;
import tc.oc.pgm.match.MatchPlayer;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
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 {
protected final List<Kit> kits;
protected final Map<MatchPlayer, List<Kit>> playerKits;
protected final Map<MatchPlayer, Map<Slot, ItemStack>> savedSlots;
protected final List<KillReward> rewards;
protected final boolean force;
public KitMutation(Match match, boolean force) {
super(match);
this.kits = new ArrayList<>();
this.playerKits = new WeakHashMap<>();
this.savedSlots = new WeakHashMap<>();
this.rewards = new ArrayList<>();
this.force = force;
}
public KitMutation(Match match, boolean force, Kit... kits) {
this(match, force);
Stream.of(kits).forEachOrdered(this.kits::add);
}
/**
* Generates a list of kits to apply for the player.
* Called inside {@link #apply(MatchPlayer)}.
* @param player the player that will receive the kits
* @param kits a mutable list of kits.
*/
public void kits(MatchPlayer player, List<Kit> kits) {
kits.addAll(this.kits);
}
/**
* Apply the kits to the player.
* @param player the player.
*/
public void apply(MatchPlayer player) {
List<Kit> kits = new ArrayList<>();
kits(player, kits);
playerKits.put(player, kits);
saved().forEach(slot -> {
slot.item(player.getInventory()).ifPresent(item -> {
Map<Slot, ItemStack> slots = savedSlots.getOrDefault(player, new HashMap<>());
slots.put(slot, (ItemStack) item);
savedSlots.put(player, slots);
slot.putItem(player.getInventory(), null);
});
});
kits.forEach(kit -> player.facet(KitPlayerFacet.class).applyKit(kit, force));
}
/**
* Forcefuly remove kits from the player.
* @param player the player.
*/
public void remove(MatchPlayer player) {
playerKits.getOrDefault(player, new ArrayList<>()).stream().filter(Kit::isRemovable).forEach(kit -> kit.remove(player));
savedSlots.getOrDefault(player, new HashMap<>()).forEach((Slot slot, ItemStack item) -> slot.putItem(player.getInventory(), item));
}
/**
* Any slots in the player's inventory that should be saved
* before {@link #apply(MatchPlayer)} and restored after {@link #remove(MatchPlayer)}.
* @return the saved slots.
*/
public Stream<? extends Slot> saved() {
return Stream.empty();
}
@Override
public void enable() {
super.enable();
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);
kits.clear();
playerKits.clear();
savedSlots.clear();
rewards.clear();
super.disable();
}
}

View File

@ -0,0 +1,96 @@
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;
import tc.oc.commons.core.random.Entropy;
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;
/**
* Bits of immutable code that manage a {@link Mutation}.
*
* This should be able to load at any time during the match
* and not cause any problems. This will allow mutations
* to be forcefully loaded on the fly without any worries
* of breaking the match state.
*/
@ListenerScope(MatchScope.RUNNING)
public abstract class MutationModule implements Listener {
protected final Match match;
protected final World world;
protected final Entropy entropy;
protected final Random random;
/**
* 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();
}
/**
* 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);
}
/**
* 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);
}
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);
}
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);
}
return entity;
}
}

View File

@ -0,0 +1,64 @@
package tc.oc.pgm.mutation.types;
import java.time.Duration;
import java.util.List;
import tc.oc.commons.core.scheduler.Task;
import tc.oc.commons.core.stream.Collectors;
import tc.oc.pgm.match.Match;
import tc.oc.pgm.match.MatchPlayer;
import tc.oc.pgm.match.MatchScope;
/**
* 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;
}
/**
* 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);
/**
* 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();
/**
* 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()));
}
@Override
public void enable() {
super.enable();
this.task = match.getScheduler(MatchScope.RUNNING).createRepeatingTask(frequency, () -> execute(search()));
}
@Override
public void disable() {
if(task != null) {
task.cancel();
task = null;
}
super.disable();
}
}

View File

@ -0,0 +1,78 @@
package tc.oc.pgm.mutation.types.kit;
import org.bukkit.Material;
import org.bukkit.inventory.ItemStack;
import tc.oc.commons.bukkit.inventory.ArmorType;
import tc.oc.commons.bukkit.inventory.Slot;
import tc.oc.commons.bukkit.item.ItemUtils;
import tc.oc.pgm.kits.FreeItemKit;
import tc.oc.pgm.kits.ItemKitApplicator;
import tc.oc.pgm.kits.SlotItemKit;
import tc.oc.pgm.match.Match;
import tc.oc.pgm.match.MatchPlayer;
import tc.oc.pgm.mutation.types.KitMutation;
import java.util.List;
import java.util.WeakHashMap;
import java.util.stream.Collectors;
import java.util.stream.Stream;
public class ArmorMutation extends KitMutation {
final static FreeItemKit SWORD = new FreeItemKit(item(Material.DIAMOND_SWORD));
final static SlotItemKit[] ARMOR = new SlotItemKit[] {
new SlotItemKit(item(Material.DIAMOND_HELMET), Slot.Armor.forType(ArmorType.HELMET)),
new SlotItemKit(item(Material.DIAMOND_CHESTPLATE), Slot.Armor.forType(ArmorType.CHESTPLATE)),
new SlotItemKit(item(Material.DIAMOND_LEGGINGS), Slot.Armor.forType(ArmorType.LEGGINGS)),
new SlotItemKit(item(Material.DIAMOND_BOOTS), Slot.Armor.forType(ArmorType.BOOTS)),
};
final WeakHashMap<MatchPlayer, ItemStack> weapons;
public ArmorMutation(Match match) {
super(match, true, ARMOR);
this.kits.add(SWORD);
weapons = new WeakHashMap<>();
}
@Override
public void apply(MatchPlayer player) {
// Find the player's first weapon and store it for later
List<ItemStack> hotbar = Slot.Hotbar.hotbar()
.map(slot -> slot.getItem(player.getInventory()))
.collect(Collectors.toList());
for(ItemStack item : hotbar) {
if(item != null && ItemUtils.isWeapon(item)) {
weapons.put(player, item);
player.getInventory().remove(item);
break;
}
}
super.apply(player);
}
@Override
public void remove(MatchPlayer player) {
super.remove(player);
// Restore the player's old weapon
ItemStack weapon = weapons.remove(player);
if(weapon != null) {
ItemKitApplicator applicator = new ItemKitApplicator();
applicator.add(weapon);
applicator.apply(player);
}
}
@Override
public Stream<? extends Slot> saved() {
return Slot.Armor.armor();
}
@Override
public void disable() {
super.disable();
weapons.clear();
}
}

View File

@ -0,0 +1,42 @@
package tc.oc.pgm.mutation.types.kit;
import org.bukkit.Material;
import tc.oc.commons.bukkit.inventory.ArmorType;
import tc.oc.commons.bukkit.inventory.Slot;
import tc.oc.pgm.doublejump.DoubleJumpKit;
import tc.oc.pgm.kits.ItemKit;
import tc.oc.pgm.kits.ItemKitApplicator;
import tc.oc.pgm.kits.SlotItemKit;
import tc.oc.pgm.match.Match;
import tc.oc.pgm.match.MatchPlayer;
import tc.oc.pgm.mutation.types.KitMutation;
import java.time.Duration;
import java.util.stream.Stream;
public class ElytraMutation extends KitMutation {
final static ItemKit ELYTRA = new SlotItemKit(item(Material.ELYTRA), Slot.Armor.forType(ArmorType.CHESTPLATE));
final static DoubleJumpKit JUMP = new DoubleJumpKit(true, 6f, Duration.ofSeconds(30), true);
public ElytraMutation(Match match) {
super(match, true, ELYTRA, JUMP);
}
@Override
public Stream<? extends Slot> saved() {
return Stream.of(Slot.Armor.forType(ArmorType.CHESTPLATE));
}
@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);
}
super.remove(player);
}
}

View File

@ -0,0 +1,159 @@
package tc.oc.pgm.mutation.types.kit;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import org.bukkit.Material;
import org.bukkit.enchantments.Enchantment;
import org.bukkit.entity.Entity;
import org.bukkit.inventory.EntityEquipment;
import org.bukkit.inventory.ItemStack;
import tc.oc.commons.bukkit.item.ItemUtils;
import tc.oc.commons.core.random.ImmutableWeightedRandomChooser;
import tc.oc.commons.core.random.WeightedRandomChooser;
import tc.oc.pgm.match.Match;
import tc.oc.pgm.match.MatchPlayer;
import tc.oc.pgm.mutation.types.KitMutation;
import java.util.HashMap;
import java.util.Map;
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(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)
.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)
.put(Enchantment.FIRE_ASPECT, 1)
.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)
.put(Enchantment.LOOT_BONUS_MOBS, 5)
.put(Enchantment.LUCK, 1)
.build();
final static ImmutableMap<Enchantment, Integer> BOWS_MAP = new ImmutableMap.Builder<Enchantment, Integer>()
.put(Enchantment.ARROW_DAMAGE, 10)
.put(Enchantment.ARROW_KNOCKBACK, 5)
.put(Enchantment.ARROW_FIRE, 1)
.build();
final static Map<Enchantment, Integer> FISHING_MAP = new ImmutableMap.Builder<Enchantment, Integer>()
.put(Enchantment.KNOCKBACK, 3)
.put(Enchantment.LURE, 1)
.build();
final static WeightedRandomChooser<Integer, Integer> LEVELS = new ImmutableWeightedRandomChooser<>(LEVELS_MAP);
final static WeightedRandomChooser<Enchantment, Integer> ARMOR = new ImmutableWeightedRandomChooser<>(ARMOR_MAP);
final static WeightedRandomChooser<Enchantment, Integer> BOOTS = new ImmutableWeightedRandomChooser<>(BOOTS_MAP);
final static WeightedRandomChooser<Enchantment, Integer> WEAPONS = new ImmutableWeightedRandomChooser<>(WEAPONS_MAP);
final static WeightedRandomChooser<Enchantment, Integer> TOOLS = new ImmutableWeightedRandomChooser<>(TOOLS_MAP);
final static WeightedRandomChooser<Enchantment, Integer> BOWS = new ImmutableWeightedRandomChooser<>(BOWS_MAP);
final static WeightedRandomChooser<Enchantment, Integer> FISHING = new ImmutableWeightedRandomChooser<>(FISHING_MAP);
Map<Entity, Map<ItemStack, Map<Enchantment, Integer>>> savedEnchantments;
public EnchantmentMutation(Match match) {
super(match, true);
this.savedEnchantments = new WeakHashMap<>();
}
public void apply(ItemStack item, EntityEquipment equipment) {
// Pick the enchantment chooser depending on the item's material
WeightedRandomChooser<Enchantment, Integer> chooser;
if(item == null || ItemUtils.isNothing(item)) {
return;
} else if(ItemUtils.isWeapon(item)) {
chooser = WEAPONS;
} else if(ItemUtils.isArmor(item)) {
if(equipment.getBoots().equals(item)) {
chooser = BOOTS;
} else {
chooser = ARMOR;
}
} else if(ItemUtils.isTool(item)) {
chooser = TOOLS;
} else if(Material.FISHING_ROD.equals(item.getType())) {
chooser = FISHING;
} else if(Material.BOW.equals(item.getType())) {
chooser = BOWS;
} else {
chooser = null;
}
if(chooser != null) {
// Save the item's enchantments if they need to be restored
Entity entity = equipment.getHolder();
Map<ItemStack, Map<Enchantment, Integer>> byEntity = savedEnchantments.getOrDefault(entity, new WeakHashMap<>());
byEntity.put(item, ImmutableMap.copyOf(item.getEnchantments()));
savedEnchantments.put(entity, byEntity);
// Apply the new enchantments
int amountOfEnchants = LEVELS.choose(entropy);
for(int i = 0; i < amountOfEnchants; i++) {
item.addUnsafeEnchantment(chooser.choose(entropy), LEVELS.choose(entropy));
}
}
}
@Override
public void apply(MatchPlayer player) {
super.apply(player);
player.getInventory().forEach(item -> {
// Random number of enchantments on each item
int numberOfEnchants = LEVELS.choose(entropy);
for(int i = 0; i < numberOfEnchants; i++) {
apply(item, player.getBukkit().getEquipment());
}
});
}
@Override
public void remove(MatchPlayer player) {
super.remove(player);
remove(player.getBukkit());
}
public void remove(Entity entity) {
savedEnchantments.getOrDefault(entity, new HashMap<>()).forEach((ItemStack item, Map<Enchantment, Integer> old) -> {
item.getEnchantments().keySet().forEach(item::removeEnchantment); // Clear the current enchantments
item.addUnsafeEnchantments(old); // Add the old enchantments back
});
savedEnchantments.remove(entity);
}
@Override
public void disable() {
ImmutableSet.copyOf(savedEnchantments.keySet()).forEach(this::remove);
savedEnchantments.clear();
}
}

View File

@ -0,0 +1,150 @@
package tc.oc.pgm.mutation.types.kit;
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.inventory.HorseInventory;
import org.bukkit.inventory.ItemStack;
import org.bukkit.inventory.meta.SpawnEggMeta;
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 java.util.List;
import java.util.Map;
import java.util.WeakHashMap;
public class EquestrianMutation extends KitMutation {
final static ImmutableMap<EntityType, Integer> TYPE_MAP = new ImmutableMap.Builder<EntityType, Integer>()
.put(EntityType.HORSE, 100)
// FIXME: Saddle do not work on these horses
//.put(EntityType.SKELETON_HORSE, 5)
//.put(EntityType.ZOMBIE_HORSE, 5)
//.put(EntityType.LLAMA, 1)
.build();
final static ImmutableMap<Material, Integer> ARMOR_MAP = new ImmutableMap.Builder<Material, Integer>()
.put(Material.SADDLE, 25)
.put(Material.GOLD_BARDING, 10)
.put(Material.IRON_BARDING, 5)
.put(Material.DIAMOND_BARDING, 1)
.build();
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();
}
@Override
public void kits(MatchPlayer player, List<Kit> kits) {
super.kits(player, kits);
Location location = player.getLocation();
// If there is not enough room to spawn a horse, give the player
// an egg so they can spawn it later
if(!spawnable(location)) {
ItemStack item = item(Material.MONSTER_EGG);
SpawnEggMeta egg = (SpawnEggMeta) item.getItemMeta();
egg.setSpawnedType(TYPES.choose(entropy));
item.setItemMeta(egg);
kits.add(new FreeItemKit(item));
}
}
@Override
public void apply(MatchPlayer player) {
super.apply(player);
Location location = player.getLocation();
if(spawnable(location)) {
setup(player, spawn(location, (Class<? extends AbstractHorse>) TYPES.choose(match.entropyForTick()).getEntityClass()));
}
}
@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 boolean spawnable(Location location) {
// Allow at least 4 blocks of air from the feet of the player
// to allow a horse to be spawned
for(int i = 0; i <= 4; i++) {
if(!location.clone().add(0, i, 0).getBlock().isEmpty()) {
return false;
}
}
return true;
}
@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()));
}
}
@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

@ -0,0 +1,70 @@
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;
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 java.util.List;
public class ExplosiveMutation extends KitMutation {
final static ItemKit TNT = new FreeItemKit(item(Material.TNT, 8));
final static ItemKit LIGHTER = new FreeItemKit(item(Material.FLINT_AND_STEEL));
final static ItemKit FIRE_BOW = new FreeItemKit(new ItemBuilder(item(Material.BOW)).enchant(Enchantment.ARROW_FIRE, 1).get());
final static ItemKit ARROWS = new FreeItemKit(item(Material.ARROW, 8));
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(!inv.contains(Material.TNT)) kits.add(TNT);
if(!inv.contains(Material.FLINT_AND_STEEL)) kits.add(LIGHTER);
} else { // fire bow and arrows kit
if(!inv.contains(Material.ARROW)) kits.add(ARROWS);
if(!inv.contains(Material.BOW)) {
kits.add(FIRE_BOW);
} else {
inv.all(Material.BOW).values().forEach(bow -> bow.addUnsafeEnchantments(FIRE_BOW.item().getEnchantments()));
}
}
}
@EventHandler(ignoreCancelled = true, priority = EventPriority.HIGHEST)
public void onExplosionPrime(ExplosionPrimeEvent event) {
event.setRadius(event.getRadius() + entropy.randomInt(RADIUS));
}
}

View File

@ -0,0 +1,17 @@
package tc.oc.pgm.mutation.types.kit;
import org.bukkit.potion.PotionEffect;
import org.bukkit.potion.PotionEffectType;
import tc.oc.pgm.kits.PotionKit;
import tc.oc.pgm.match.Match;
import tc.oc.pgm.mutation.types.KitMutation;
public class GlowMutation extends KitMutation {
final static PotionKit GLOW = new PotionKit(new PotionEffect(PotionEffectType.GLOWING, Integer.MAX_VALUE, 0));
public GlowMutation(Match match) {
super(match, true, GLOW);
}
}

View File

@ -0,0 +1,48 @@
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;
import tc.oc.pgm.kits.ItemKit;
import tc.oc.pgm.match.Match;
import tc.oc.pgm.mutation.types.KitMutation;
import tc.oc.pgm.shield.ShieldKit;
import tc.oc.pgm.shield.ShieldParameters;
public class HardcoreMutation extends KitMutation {
final static String KEY = "naturalRegeneration";
final static ShieldKit SHIELD = new ShieldKit(new ShieldParameters());
final static ItemKit APPLE = new FreeItemKit(item(Material.GOLDEN_APPLE));
String previous; // Stores the previous game rule setting
public HardcoreMutation(Match match) {
super(match, false);
this.kits.add(SHIELD);
this.rewards.add(new KillReward(APPLE));
}
public GameRulesMatchModule rules() {
return match.module(GameRulesMatchModule.class).get();
}
@Override
public void enable() {
super.enable();
previous = world.getGameRuleValue(KEY);
rules().gameRules().put(KEY, "false");
}
@Override
public void disable() {
rules().gameRules().remove(KEY);
if(previous != null) {
world.setGameRuleValue(KEY, previous);
}
super.disable();
}
}

View File

@ -0,0 +1,24 @@
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;
import tc.oc.pgm.kits.ItemKit;
import tc.oc.pgm.kits.MaxHealthKit;
import tc.oc.pgm.match.Match;
import tc.oc.pgm.mutation.types.KitMutation;
public class HealthMutation extends KitMutation {
final static HealthKit HEALTH = new HealthKit(40);
final static MaxHealthKit MAX_HEALTH = new MaxHealthKit(40);
final static ItemKit APPLE = new FreeItemKit(item(Material.GOLDEN_APPLE, 3));
public HealthMutation(Match match) {
super(match, false, MAX_HEALTH, HEALTH, APPLE);
this.rewards.add(new KillReward(APPLE));
}
}

View File

@ -0,0 +1,14 @@
package tc.oc.pgm.mutation.types.kit;
import tc.oc.pgm.doublejump.DoubleJumpKit;
import tc.oc.pgm.match.Match;
public class JumpMutation extends NoFallMutation {
final static DoubleJumpKit JUMP = new DoubleJumpKit();
public JumpMutation(Match match) {
super(match, false, JUMP);
}
}

View File

@ -0,0 +1,56 @@
package tc.oc.pgm.mutation.types.kit;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Range;
import org.bukkit.Material;
import org.bukkit.entity.EntityType;
import org.bukkit.inventory.ItemStack;
import org.bukkit.inventory.meta.SpawnEggMeta;
import tc.oc.commons.core.random.ImmutableWeightedRandomChooser;
import tc.oc.commons.core.random.WeightedRandomChooser;
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 java.util.List;
public class MobsMutation extends KitMutation {
final static ImmutableMap<EntityType, Integer> TYPE_MAP = new ImmutableMap.Builder<EntityType, Integer>()
.put(EntityType.ZOMBIE, 50)
.put(EntityType.SKELETON, 40)
.put(EntityType.SPIDER, 40)
.put(EntityType.CREEPER, 30)
.put(EntityType.BLAZE, 20)
.put(EntityType.GHAST, 20)
.put(EntityType.SHULKER, 20)
.put(EntityType.WITCH, 10)
.put(EntityType.ENDERMAN, 10)
.put(EntityType.PIG_ZOMBIE, 5)
.put(EntityType.WITHER_SKELETON, 1)
.build();
final static WeightedRandomChooser<EntityType, Integer> TYPES = new ImmutableWeightedRandomChooser<>(TYPE_MAP);
final static Range<Integer> AMOUNT = Range.closed(1, 3);
public MobsMutation(Match match) {
super(match, true);
}
@Override
public void kits(MatchPlayer player, List<Kit> kits) {
super.kits(player, kits);
int eggs = entropy.randomInt(AMOUNT);
for(int i = 0; i < eggs; i++) {
ItemStack item = item(Material.MONSTER_EGG, entropy.randomInt(AMOUNT));
SpawnEggMeta egg = (SpawnEggMeta) item.getItemMeta();
egg.setSpawnedType(TYPES.choose(entropy));
item.setItemMeta(egg);
kits.add(new FreeItemKit(item));
}
}
}

View File

@ -0,0 +1,44 @@
package tc.oc.pgm.mutation.types.kit;
import org.bukkit.event.entity.EntityDamageEvent;
import tc.oc.pgm.damage.DisableDamageMatchModule;
import tc.oc.pgm.kits.Kit;
import tc.oc.pgm.match.Match;
import tc.oc.pgm.match.PlayerRelation;
import tc.oc.pgm.mutation.types.KitMutation;
import java.util.stream.Collectors;
import java.util.stream.Stream;
public abstract class NoFallMutation extends KitMutation {
final static EntityDamageEvent.DamageCause FALL = EntityDamageEvent.DamageCause.FALL;
final static Iterable<PlayerRelation> RELATIONS = Stream.of(PlayerRelation.values()).collect(Collectors.toList());
Iterable<PlayerRelation> previous;
public NoFallMutation(Match match, boolean force, Kit... kits) {
super(match, force, kits);
}
public DisableDamageMatchModule damage() {
return match.module(DisableDamageMatchModule.class).get();
}
@Override
public void enable() {
super.enable();
previous = damage().causes().get(FALL);
damage().causes().putAll(FALL, RELATIONS);
}
@Override
public void disable() {
damage().causes().removeAll(FALL);
if(previous != null) {
damage().causes().putAll(FALL, previous);
}
super.disable();
}
}

View File

@ -0,0 +1,100 @@
package tc.oc.pgm.mutation.types.kit;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Range;
import org.bukkit.Material;
import org.bukkit.inventory.ItemStack;
import org.bukkit.inventory.meta.PotionMeta;
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.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 java.util.List;
public class PotionMutation extends KitMutation {
final static ImmutableMap<PotionEffectType, Integer> BAD_MAP = new ImmutableMap.Builder<PotionEffectType, Integer>()
.put(PotionEffectType.WEAKNESS, 15)
.put(PotionEffectType.SLOW, 10)
.put(PotionEffectType.POISON, 10)
.put(PotionEffectType.BLINDNESS, 3)
.put(PotionEffectType.LEVITATION, 1)
.build();
final static ImmutableMap<PotionEffectType, Integer> GOOD_MAP = new ImmutableMap.Builder<PotionEffectType, Integer>()
.put(PotionEffectType.SPEED, 15)
.put(PotionEffectType.INCREASE_DAMAGE, 15)
.put(PotionEffectType.DAMAGE_RESISTANCE, 10)
.put(PotionEffectType.REGENERATION, 10)
.put(PotionEffectType.FIRE_RESISTANCE, 10)
.put(PotionEffectType.HEALTH_BOOST, 5)
.put(PotionEffectType.JUMP, 5)
.put(PotionEffectType.INVISIBILITY, 1)
.build();
final static ImmutableMap<Material, Integer> BOTTLE_BAD_MAP = new ImmutableMap.Builder<Material, Integer>()
.put(Material.SPLASH_POTION, 5)
.put(Material.LINGERING_POTION, 1)
.build();
final static ImmutableMap<Material, Integer> BOTTLE_GOOD_MAP = new ImmutableMap.Builder<Material, Integer>()
.putAll(BOTTLE_BAD_MAP)
.put(Material.POTION, 10)
.build();
final static WeightedRandomChooser<PotionEffectType, Integer> BAD = new ImmutableWeightedRandomChooser<>(BAD_MAP);
final static WeightedRandomChooser<PotionEffectType, Integer> GOOD = new ImmutableWeightedRandomChooser<>(GOOD_MAP);
final static WeightedRandomChooser<Material, Integer> BAD_BOTTLE = new ImmutableWeightedRandomChooser<>(BOTTLE_BAD_MAP);
final static WeightedRandomChooser<Material, Integer> GOOD_BOTTLE = new ImmutableWeightedRandomChooser<>(BOTTLE_GOOD_MAP);
final static Range<Integer> BAD_DURATION_RANGE = Range.closed(3, 10);
final static Range<Integer> GOOD_DURATION_RANGE = Range.closed(10, 45);
final static Range<Integer> AMOUNT_RANGE = Range.closed(1, 3);
final static Range<Integer> AMPLIFIER_RANGE = Range.closed(0, 2);
public PotionMutation(Match match) {
super(match, false);
}
@Override
public void kits(MatchPlayer player, List<Kit> kits) {
super.kits(player, kits);
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()) {
type = BAD;
material = BAD_BOTTLE;
range = BAD_DURATION_RANGE;
} else {
type = GOOD;
material = GOOD_BOTTLE;
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);
// Apply the attributes to the item stack
ItemStack potion = item(bottle);
PotionMeta meta = (PotionMeta) potion.getItemMeta();
meta.addCustomEffect(new PotionEffect(effect, duration, amplifier), true);
potion.setItemMeta(meta);
kits.add(new FreeItemKit(potion));
}
}
}

View File

@ -0,0 +1,84 @@
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.event.EventHandler;
import org.bukkit.event.EventPriority;
import org.bukkit.event.entity.EntityShootBowEvent;
import org.bukkit.event.entity.ProjectileLaunchEvent;
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.pgm.killreward.KillReward;
import tc.oc.pgm.kits.FreeItemKit;
import tc.oc.pgm.kits.ItemKit;
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 java.util.List;
public class ProjectileMutation extends KitMutation {
final static WeightedRandomChooser<Enchantment, Integer> ENCHANTMENTS = new ImmutableWeightedRandomChooser<>(EnchantmentMutation.BOWS_MAP);
final static WeightedRandomChooser<PotionEffectType, Integer> POTIONS = new ImmutableWeightedRandomChooser<>(PotionMutation.BAD_MAP);
final static Range<Integer> ENCHANT_RANGE = Range.closed(1, 3);
final static Range<Integer> AMPLIFIER_RANGE = Range.closed(0, 3);
final static Range<Integer> DURATION_RANGE = Range.closed(3, 10);
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));
}
@Override
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)));
}
@Override
public void kits(MatchPlayer player, List<Kit> kits) {
super.kits(player, kits);
Inventory inventory = player.getInventory();
if(!inventory.contains(Material.BOW)) kits.add(BOW);
if(!inventory.contains(Material.ARROW)) kits.add(ARROWS);
}
@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));
}
}
}

View File

@ -0,0 +1,25 @@
package tc.oc.pgm.mutation.types.kit;
import org.bukkit.potion.PotionEffect;
import org.bukkit.potion.PotionEffectType;
import tc.oc.commons.bukkit.inventory.Slot;
import tc.oc.pgm.kits.PotionKit;
import tc.oc.pgm.match.Match;
import tc.oc.pgm.mutation.types.KitMutation;
import java.util.stream.Stream;
public class StealthMutation extends KitMutation {
final static PotionKit INVISIBILITY = new PotionKit(new PotionEffect(PotionEffectType.INVISIBILITY, Integer.MAX_VALUE, 0));
public StealthMutation(Match match) {
super(match, true, INVISIBILITY);
}
@Override
public Stream<? extends Slot> saved() {
return Slot.Armor.armor();
}
}

View File

@ -0,0 +1,29 @@
package tc.oc.pgm.mutation.types.other;
import org.bukkit.event.EventHandler;
import org.bukkit.event.EventPriority;
import org.bukkit.event.entity.EntityDamageByEntityEvent;
import tc.oc.pgm.match.Match;
import tc.oc.pgm.mutation.types.MutationModule;
import tc.oc.pgm.rage.RageMatchModule;
public class RageMutation extends MutationModule {
RageMatchModule rage;
public RageMutation(Match match) {
super(match);
this.rage = match.module(RageMatchModule.class).orElse(new RageMatchModule(match));
}
@EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true)
public void onPlayerDamage(EntityDamageByEntityEvent event) {
rage.handlePlayerDamage(event);
}
@Override
public void disable() {
super.disable();
rage = null;
}
}

View File

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

View File

@ -0,0 +1,66 @@
package tc.oc.pgm.mutation.types.targetable;
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.TargetMutation;
import java.time.Duration;
import java.util.List;
public class BomberMutation extends TargetMutation {
final static Duration FREQUENCY = Duration.ofSeconds(30);
final static Range<Integer> TARGETS = Range.closed(1, 5);
final static Range<Integer> HEIGHT = Range.closed(30, 60);
final static Range<Integer> TICKS = Range.closed(10, 30);
final WeakHashSet<TNTPrimed> falling;
public BomberMutation(Match match) {
super(match, FREQUENCY);
this.falling = new WeakHashSet<>();
}
@Override
public void execute(List<MatchPlayer> players) {
players.forEach(player -> {
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);
tnt.setGlowing(true);
tnt.setIsIncendiary(false);
tnt.setFuseTicks(Integer.MAX_VALUE);
tnt.setVelocity(
new Vector(
(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);
}
@Repeatable
public void tick() {
falling.stream()
.filter(TNTPrimed::isOnGround)
.forEach(tnt -> tnt.setFuseTicks(entropy.randomInt(TICKS)));
falling.removeIf(TNTPrimed::isOnGround);
}
}

View File

@ -0,0 +1,42 @@
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;
import tc.oc.pgm.mutation.types.TargetMutation;
import java.time.Duration;
import java.util.List;
public class LightningMutation extends TargetMutation {
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);
public LightningMutation(Match match) {
super(match, FREQUENCY);
}
@Override
public void execute(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);
for(int i = 0; i < strikes; i++) {
world.strikeLightningEffect(location.clone().add(Vector.getRandom().multiply(Math.pow(i + 1, 2))));
}
});
}
@Override
public int targets() {
return match.entropyForTick().randomInt(TARGETS);
}
}

View File

@ -14,4 +14,8 @@ public class ShieldParameters {
this.maxHealth = maxHealth;
this.rechargeDelay = rechargeDelay;
}
public ShieldParameters() {
this(DEFAULT_HEALTH, DEFAULT_DELAY);
}
}

View File

@ -21,8 +21,7 @@ import tc.oc.pgm.match.Competitor;
import tc.oc.pgm.match.MatchPlayer;
import tc.oc.pgm.match.ParticipantState;
import tc.oc.pgm.mutation.MutationMatchModule;
import tc.oc.pgm.mutation.submodule.KitMutationModule;
import tc.oc.pgm.mutation.submodule.MutationModule;
import tc.oc.pgm.mutation.types.KitMutation;
import tc.oc.pgm.spawns.Spawn;
import tc.oc.pgm.spawns.events.ParticipantDespawnEvent;
import tc.oc.pgm.spawns.events.ParticipantReleaseEvent;
@ -86,16 +85,10 @@ public class Alive extends Participating {
match.module(KillRewardMatchModule.class).ifPresent(krmm -> krmm.giveDeadPlayerRewards(player));
// Apply kit injections from KitMutationModules
match.module(MutationMatchModule.class).ifPresent(mmm -> {
for(MutationModule module : mmm.getMutationModules()) {
if(module instanceof KitMutationModule) {
KitMutationModule kitModule = ((KitMutationModule) module);
for(Kit kit : kitModule.getKits()) {
player.facet(KitPlayerFacet.class).applyKit(kit, kitModule.isForceful());
}
}
}
});
match.module(MutationMatchModule.class)
.ifPresent(mmm -> mmm.mutationModules().stream()
.filter(mm -> mm instanceof KitMutation)
.forEach(mm -> ((KitMutation) mm).apply(player)));
player.getBukkit().updateInventory();

View File

@ -1,21 +0,0 @@
package tc.oc.pgm.mutation;
import static org.junit.Assert.assertTrue;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
import tc.oc.api.docs.virtual.MatchDoc;
@RunWith(JUnit4.class)
public class MutationTest {
@Test
public void testMutationEnumsAreSynced() {
assertTrue("Mutation enums must be equal in size", Mutation.values().length == MatchDoc.Mutation.values().length);
for(int i = 0; i < Mutation.values().length; i++) {
assertTrue("Mutation enums of ordinal (" + i + ") do not match", Mutation.values()[i].name().equals(MatchDoc.Mutation.values()[i].name()));
}
}
}

View File

@ -123,7 +123,7 @@ public class ItemBuilder<S extends ItemBuilder<?>> {
*/
public S lore(String lore) {
meta().setLore(meta().hasLore() ? ListUtils.append(meta().getLore(), lore)
: Collections.singletonList(lore));
: Collections.singletonList(lore));
return self();
}
@ -172,4 +172,14 @@ public class ItemBuilder<S extends ItemBuilder<?>> {
meta(SkullMeta.class).setOwner(name, uuid, skin);
return self();
}
public S shareable(boolean yes) {
new BooleanItemTag("prevent-sharing", false).set(stack, !yes);
return self();
}
public S locked(boolean yes) {
new BooleanItemTag("locked", false).set(stack, !yes);
return self();
}
}

View File

@ -7,6 +7,7 @@ import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Stream;
import javax.annotation.Nullable;
import com.google.common.collect.ImmutableSet;
@ -100,6 +101,24 @@ public class ItemUtils {
return isNothing(stack) ? Optional.empty() : Optional.of(stack);
}
private static final String[] TOOLS = {"axe", "hoe", "spade"};
public static boolean isTool(ItemStack stack) {
return isTool(stack.getData());
}
public static boolean isTool(MaterialData item) {
return Stream.of(TOOLS).anyMatch(query -> item.getItemType().name().toLowerCase().contains(query));
}
public static boolean isArmor(ItemStack stack) {
return isArmor(stack.getData());
}
public static boolean isArmor(MaterialData item) {
return !Bukkit.getItemFactory().getAttributeModifiers(item, Attribute.GENERIC_ARMOR).isEmpty();
}
public static boolean isWeapon(ItemStack stack) {
return isWeapon(stack.getData());
}

View File

@ -0,0 +1,91 @@
package tc.oc.commons.core.collection;
import java.util.Collection;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
public class ForwardingSet<E> implements Set<E> {
protected final Map<E, ?> backend;
public ForwardingSet(Map<E, ?> backend) {
this.backend = backend;
}
@Override
public int size() {
return backend.size();
}
@Override
public boolean isEmpty() {
return backend.isEmpty();
}
@Override
public boolean contains(Object o) {
return backend.containsKey(o);
}
@Override
public Iterator<E> iterator() {
return backend.keySet().iterator();
}
@Override
public Object[] toArray() {
return backend.keySet().toArray();
}
@Override
public <T> T[] toArray(T[] a) {
return backend.keySet().toArray(a);
}
@Override
public boolean add(E e) {
if(backend.containsKey(e)) {
return false;
} else {
backend.put(e, null);
return true;
}
}
@Override
public boolean remove(Object o) {
if(!backend.containsKey(o)) {
return false;
} else {
backend.remove(o);
return true;
}
}
@Override
public boolean containsAll(Collection<?> c) {
return c.stream().allMatch(this::contains);
}
@Override
public boolean addAll(Collection<? extends E> c) {
return c.stream().anyMatch(this::add);
}
@Override
public boolean retainAll(Collection<?> c) {
return c.stream().filter(i -> !contains(i)).anyMatch(this::remove);
}
@Override
public boolean removeAll(Collection<?> c) {
return c.stream().anyMatch(this::remove);
}
@Override
public void clear() {
backend.clear();
}
}

View File

@ -0,0 +1,17 @@
package tc.oc.commons.core.collection;
import java.util.Collection;
import java.util.WeakHashMap;
public class WeakHashSet<E> extends ForwardingSet<E> {
public WeakHashSet() {
super(new WeakHashMap<>());
}
public WeakHashSet(Collection<? extends E> initial) {
this();
this.addAll(initial);
}
}

View File

@ -3,6 +3,7 @@ package tc.oc.commons.core.random;
import java.util.List;
import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
import com.google.common.collect.Range;
import tc.oc.commons.core.util.Ranges;
@ -26,6 +27,10 @@ public interface Entropy {
return min + (int) ((randomLong() & 0xffffffffL) * delta / 0x100000000L);
}
default <T> T randomElement(T... array) {
return randomElement(Lists.newArrayList(array));
}
default <T> T randomElement(Iterable<T> iterable) {
return Iterables.get(iterable, randomInt(Range.closedOpen(0, Iterables.size(iterable))));
}

View File

@ -3,6 +3,7 @@ package tc.oc.commons.core.random;
import java.util.Random;
import com.google.common.collect.Iterables;
import org.apache.commons.lang.math.Fraction;
public class RandomUtils {
@ -13,4 +14,8 @@ public class RandomUtils {
public static <T> T element(Random random, Iterable<? extends T> collection) {
return Iterables.get(collection, safeNextInt(random, Iterables.size(collection)));
}
public static boolean nextBoolean(Random random, Fraction chance) {
return random.nextDouble() < chance.doubleValue();
}
}