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_team_ids();
Collection<String> winning_user_ids(); Collection<String> winning_user_ids();
enum Mutation { Set<String> mutations();
BLITZ, UHC, EXPLOSIVES, NO_FALL, MOBS, STRENGTH, DOUBLE_JUMP, INVISIBILITY, LIGHTNING, RAGE, ELYTRA;
}
Set<Mutation> mutations();
@Serialize @Serialize
interface Team extends MapDoc.Team, CompetitorDoc { interface Team extends MapDoc.Team, CompetitorDoc {

View File

@ -138,7 +138,7 @@ public interface ServerDoc {
@Serialize @Serialize
interface Mutation extends Partial { 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>>(){}) gson.bindAdapter(new TypeLiteral<Set<MapDoc.Gamemode>>(){})
.to(new TypeLiteral<LenientEnumSetTypeAdapter<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 @Override
public Set<MatchDoc.Mutation> queued_mutations() { public Set<String> queued_mutations() {
return mutations != null ? mutations.queued_mutations() : Collections.emptySet(); 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.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 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.find = Unable to find mutation named '{0}'
command.mutation.error.enabled = All mutations have already been enabled command.mutation.error.enabled = {0} mutation is already enabled
command.mutation.error.disabled = All mutations have already been disabled 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.current = Current Mutations
command.mutation.list.queued = Queued 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 huddle.instructions = Your team now has {0} to strategize before the match starts
mutation.type.blitz = Blitz 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.blitz.desc = no respawning
mutation.type.uhc.desc = no natural regeneration mutation.type.rage = Rage
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.desc = instant kills 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.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.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} 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 @Override
public Set<Mutation> mutations() { public Set<String> mutations() {
return mutations.map(mmm -> mmm.getHistoricalMutations() return mutations.map(mmm -> mmm.mutationsHistorical()
.stream() .stream()
.map(tc.oc.pgm.mutation.Mutation::toApi) .map(tc.oc.pgm.mutation.Mutation::name)
.collect(Collectors.toImmutableSet())) .collect(Collectors.toImmutableSet()))
.orElse(ImmutableSet.of()); .orElse(ImmutableSet.of());
} }

View File

@ -65,7 +65,7 @@ public class BlitzMatchModule extends MatchModule implements Listener, JoinHandl
public BlitzMatchModule(Match match, BlitzConfig config) { public BlitzMatchModule(Match match, BlitzConfig config) {
super(match); 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()); this.lifeManager = new LifeManager(this.config.getNumLives());
} }

View File

@ -3,6 +3,7 @@ package tc.oc.pgm.damage;
import javax.annotation.Nullable; import javax.annotation.Nullable;
import javax.inject.Inject; import javax.inject.Inject;
import com.google.common.collect.ImmutableSetMultimap;
import com.google.common.collect.SetMultimap; import com.google.common.collect.SetMultimap;
import com.google.common.collect.Sets; import com.google.common.collect.Sets;
import org.bukkit.block.Block; import org.bukkit.block.Block;
@ -36,12 +37,12 @@ public class DisableDamageMatchModule extends MatchModule implements Listener {
this.causes = causes; this.causes = causes;
} }
@Override public SetMultimap<DamageCause, PlayerRelation> causes() {
public void load() { return causes;
super.load(); }
if(MutationMatchModule.check(match, Mutation.NO_FALL)) {
this.causes.putAll(DamageCause.FALL, Sets.newHashSet(PlayerRelation.values())); public ImmutableSetMultimap<DamageCause, PlayerRelation> causesImmutable() {
} return ImmutableSetMultimap.copyOf(causes());
} }
private static DamageCause getBlockDamageCause(Block block) { 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) { 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) { 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) @EventHandler(priority = EventPriority.NORMAL, ignoreCancelled = true)

View File

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

View File

@ -57,7 +57,7 @@ public class ProjectileTrailMatchModule extends MatchModule implements Listener
return Math.max(0.001, rgb / 255.0); 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) { public void onProjectileLaunch(ProjectileLaunchEvent event) {
match.player(event.getActor()).ifPresent(shooter -> { match.player(event.getActor()).ifPresent(shooter -> {
final Projectile projectile = event.getEntity(); final Projectile projectile = event.getEntity();

View File

@ -15,7 +15,7 @@ public class MatchMutationFilter extends TypedFilter.Impl<IMatchQuery> {
@Override @Override
public boolean matches(IMatchQuery query) { public boolean matches(IMatchQuery query) {
return query.module(MutationMatchModule.class) return query.module(MutationMatchModule.class)
.filter(mmm -> mmm.getActiveMutations().contains(mutation)) .filter(mmm -> mmm.enabled(mutation))
.isPresent(); .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.Match;
import tc.oc.pgm.match.MatchModule; import tc.oc.pgm.match.MatchModule;
import tc.oc.pgm.mutation.Mutation; import tc.oc.pgm.match.Repeatable;
import tc.oc.pgm.mutation.MutationMatchModule; import tc.oc.time.Time;
public class GameRulesMatchModule extends MatchModule { 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); super(match);
this.gameRules = Preconditions.checkNotNull(gameRules, "gamerules"); this.gameRules = Preconditions.checkNotNull(gameRules, "gamerules");
if(MutationMatchModule.check(match, Mutation.UHC)) {
this.gameRules.put(GameRule.NATURAL_REGENERATION, Boolean.FALSE);
}
} }
@Override @Override
public void load() { public void load() {
for (Map.Entry<GameRule, Boolean> gameRule : this.gameRules.entrySet()) { update();
this.match.getWorld().setGameRuleValue(gameRule.getKey().getValue(), gameRule.getValue().toString());
}
} }
public ImmutableMap<GameRule, Boolean> getGameRules() { @Repeatable(interval = @Time(seconds = 1))
return ImmutableMap.copyOf(gameRules); 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) @ModuleDescription(name="Gamerules", follows = MutationMapModule.class)
public class GameRulesModule implements MapModule, MatchModuleFactory<GameRulesMatchModule> { 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; 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 { 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 gameRulesElement : doc.getRootElement().getChildren("gamerules")) {
for (Element gameRuleElement : gameRulesElement.getChildren()) { for (Element gameRuleElement : gameRulesElement.getChildren()) {
GameRule gameRule = GameRule.forName(gameRuleElement.getName()); String gameRule = gameRuleElement.getName();
String value = gameRuleElement.getValue(); String value = gameRuleElement.getValue();
if (gameRule == null) { if(gameRule == null) {
throw new InvalidXMLException(gameRuleElement.getName() + " is not a valid gamerule", gameRuleElement); throw new InvalidXMLException(gameRuleElement.getName() + " is not a valid gamerule", gameRuleElement);
} }
if (value == null) { if(value == null) {
throw new InvalidXMLException("Missing value for gamerule " + gameRule.getValue(), gameRuleElement); throw new InvalidXMLException("Missing value for gamerule " + gameRule, gameRuleElement);
} else if (!(value.equalsIgnoreCase("true") || value.equalsIgnoreCase("false"))) { } else if (!(value.equalsIgnoreCase("true") || value.equalsIgnoreCase("false"))) {
throw new InvalidXMLException(gameRuleElement.getValue() + " is not a valid gamerule value", gameRuleElement); throw new InvalidXMLException(gameRuleElement.getValue() + " is not a valid gamerule value", gameRuleElement);
} }
if (gameRules.containsKey(gameRule)){ if(gameRules.containsKey(gameRule)){
throw new InvalidXMLException(gameRule.getValue() + " has already been specified", gameRuleElement); throw new InvalidXMLException(gameRule + " has already been specified", gameRuleElement);
} }
gameRules.put(gameRule, Boolean.valueOf(value)); gameRules.put(gameRule, value);
} }
} }
return new GameRulesModule(gameRules); return new GameRulesModule(gameRules);
} }
public ImmutableMap<GameRule, Boolean> getGameRules() { public ImmutableMap<String, String> getGameRules() {
return ImmutableMap.copyOf(this.gameRules); return ImmutableMap.copyOf(this.gameRules);
} }

View File

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

View File

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

View File

@ -1,8 +1,15 @@
package tc.oc.pgm.kits; package tc.oc.pgm.kits;
import org.bukkit.inventory.ItemStack; import org.bukkit.inventory.ItemStack;
import org.bukkit.inventory.PlayerInventory;
import tc.oc.commons.bukkit.inventory.Slot;
import tc.oc.pgm.match.MatchPlayer; 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 { public class FreeItemKit extends BaseItemKit {
protected final ItemStack item; protected final ItemStack item;
@ -20,4 +27,36 @@ public class FreeItemKit extends BaseItemKit {
public void apply(MatchPlayer player, boolean force, ItemKitApplicator items) { public void apply(MatchPlayer player, boolean force, ItemKitApplicator items) {
items.add(item); 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; package tc.oc.pgm.kits;
import com.google.common.base.Preconditions;
import tc.oc.pgm.match.MatchPlayer; import tc.oc.pgm.match.MatchPlayer;
public class HealthKit extends Kit.Impl { public class HealthKit extends Kit.Impl {
protected final int halfHearts; protected final int halfHearts;
public HealthKit(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; this.halfHearts = halfHearts;
} }

View File

@ -11,12 +11,27 @@ public class NaturalRegenerationKit extends Kit.Impl {
this.enabled = enabled; this.enabled = enabled;
} }
@Override public void toggle(MatchPlayer player, boolean enabled) {
public void apply(MatchPlayer player, boolean force, ItemKitApplicator items) {
if(fast) { if(fast) {
player.getBukkit().setFastNaturalRegeneration(enabled); player.getBukkit().setFastNaturalRegeneration(enabled);
} else { } else {
player.getBukkit().setSlowNaturalRegeneration(enabled); 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) { public void apply(MatchPlayer player, boolean force, ItemKitApplicator items) {
items.put(slot, item, force); 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); final MutationMatchModule mmm = viewer.getMatch().getMatchModule(MutationMatchModule.class);
if(mmm != null && mmm.getActiveMutations().size() > 0) { if(mmm != null && mmm.mutationsActive().size() > 0) {
viewer.sendMessage( viewer.sendMessage(
new Component(" ", ChatColor.DARK_GRAY).extra( new Component(" ", ChatColor.DARK_GRAY).extra(
new TranslatableComponent("broadcast.welcomeMessage.mutations", 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.MatchBeginEvent;
import tc.oc.pgm.events.MatchEndEvent; import tc.oc.pgm.events.MatchEndEvent;
import tc.oc.pgm.events.MatchLoadEvent; import tc.oc.pgm.events.MatchLoadEvent;
import tc.oc.pgm.gamerules.GameRule;
import tc.oc.pgm.gamerules.GameRulesModule; import tc.oc.pgm.gamerules.GameRulesModule;
import tc.oc.pgm.match.MatchManager; import tc.oc.pgm.match.MatchManager;
import tc.oc.pgm.modules.TimeLockModule; import tc.oc.pgm.modules.TimeLockModule;
@ -99,9 +98,11 @@ public class PGMListener implements PluginFacet, Listener {
// Time Lock // Time Lock
// lock time before, during (if time lock enabled), and after the match // lock time before, during (if time lock enabled), and after the match
// //
static final String DO_DAYLIGHT_CYCLE = "doDaylightCycle";
@EventHandler @EventHandler
public void lockTime(final MatchLoadEvent event) { 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 @EventHandler
@ -113,16 +114,16 @@ public class PGMListener implements PluginFacet, Listener {
GameRulesModule gameRulesModule = event.getMatch().getModuleContext().getModule(GameRulesModule.class); GameRulesModule gameRulesModule = event.getMatch().getModuleContext().getModule(GameRulesModule.class);
if (gameRulesModule != null && gameRulesModule.getGameRules().containsKey(GameRule.DO_DAYLIGHT_CYCLE)) { if (gameRulesModule != null && gameRulesModule.getGameRules().containsKey(DO_DAYLIGHT_CYCLE)) {
unlockTime = gameRulesModule.getGameRules().get(GameRule.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 @EventHandler
public void lockTime(final MatchEndEvent event) { 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 @EventHandler

View File

@ -4,22 +4,21 @@ import org.bukkit.event.EventHandler;
import org.bukkit.event.Listener; import org.bukkit.event.Listener;
import org.bukkit.event.entity.CreatureSpawnEvent; import org.bukkit.event.entity.CreatureSpawnEvent;
import tc.oc.pgm.events.ListenerScope; import tc.oc.pgm.events.ListenerScope;
import tc.oc.pgm.filters.matcher.StaticFilter;
import tc.oc.pgm.match.Match; import tc.oc.pgm.match.Match;
import tc.oc.pgm.filters.Filter; import tc.oc.pgm.filters.Filter;
import tc.oc.pgm.filters.query.EntitySpawnQuery; import tc.oc.pgm.filters.query.EntitySpawnQuery;
import tc.oc.pgm.match.MatchModule; import tc.oc.pgm.match.MatchModule;
import tc.oc.pgm.match.MatchScope; import tc.oc.pgm.match.MatchScope;
import tc.oc.pgm.mutation.Mutation;
import tc.oc.pgm.mutation.MutationMatchModule; import tc.oc.pgm.mutation.MutationMatchModule;
@ListenerScope(MatchScope.LOADED) @ListenerScope(MatchScope.LOADED)
public class MobsMatchModule extends MatchModule implements Listener { public class MobsMatchModule extends MatchModule implements Listener {
private final Filter mobsFilter; private final Filter mobsFilter;
public MobsMatchModule(Match match, Filter mobsFilter) { public MobsMatchModule(Match match, Filter mobsFilter) {
super(match); super(match);
this.mobsFilter = MutationMatchModule.check(match, Mutation.MOBS) ? StaticFilter.ALLOW : mobsFilter; this.mobsFilter = mobsFilter;
} }
@Override @Override
@ -40,8 +39,10 @@ public class MobsMatchModule extends MatchModule implements Listener {
@EventHandler(ignoreCancelled = true) @EventHandler(ignoreCancelled = true)
public void checkSpawn(final CreatureSpawnEvent event) { 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()); 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.BaseComponent;
import net.md_5.bungee.api.chat.HoverEvent; import net.md_5.bungee.api.chat.HoverEvent;
import net.md_5.bungee.api.chat.TranslatableComponent; 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.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 { public enum Mutation {
BLITZ (null, false), BLITZ (null),
UHC (null, false), RAGE (RageMutation.class),
EXPLOSIVES (Explosives.class, true), HARDCORE (HardcoreMutation.class),
NO_FALL (null, false), JUMP (JumpMutation.class),
MOBS (null, false), EXPLOSIVE (ExplosiveMutation.class),
STRENGTH (Strength.class, true), ELYTRA (ElytraMutation.class),
DOUBLE_JUMP (DoubleJump.class, true), PROJECTILE (ProjectileMutation.class),
INVISIBILITY(Invisibility.class, true), ENCHANTMENT(EnchantmentMutation.class),
LIGHTNING (Lightning.class, true), POTION (PotionMutation.class),
RAGE (Rage.class, true), EQUESTRIAN (EquestrianMutation.class),
ELYTRA (Elytra.class, true); 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 TYPE_KEY = "mutation.type.";
public static final String DESCRIPTION_KEY = ".desc"; public static final String DESCRIPTION_KEY = ".desc";
/** private final @Nullable Class<? extends MutationModule> loader;
* The module class that handles this mutation.
*/
private final @Nullable Class<? extends MutationModule> clazz;
/** Mutation(@Nullable Class<? extends MutationModule> loader) {
* Whether this mutation be changed during a match. this.loader = loader;
*/
private final boolean change;
Mutation(@Nullable Class<? extends MutationModule> clazz, boolean change) {
this.clazz = clazz;
this.change = change;
} }
public static Mutation fromApi(MatchDoc.Mutation mutation) { public Class<? extends MutationModule> loader() {
return values()[mutation.ordinal()]; return loader;
}
public MatchDoc.Mutation toApi() {
return MatchDoc.Mutation.values()[ordinal()];
}
public Class<? extends MutationModule> getModuleClass() {
return clazz;
}
public boolean isChangeable() {
return change;
} }
public String getName() { public String getName() {
@ -76,4 +79,14 @@ public enum Mutation {
public static Function<Mutation, BaseComponent> toComponent(final ChatColor color) { public static Function<Mutation, BaseComponent> toComponent(final ChatColor color) {
return mutation -> mutation.getComponent(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; package tc.oc.pgm.mutation;
import com.google.common.base.Predicate;
import com.google.common.collect.Collections2; import com.google.common.collect.Collections2;
import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet; import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Sets; import com.google.common.collect.Sets;
import org.bukkit.event.entity.CreatureSpawnEvent;
import tc.oc.commons.core.util.MapUtils; import tc.oc.commons.core.util.MapUtils;
import tc.oc.commons.core.random.RandomUtils; import tc.oc.commons.core.random.RandomUtils;
import tc.oc.pgm.Config; import tc.oc.pgm.Config;
import tc.oc.pgm.match.*; import tc.oc.pgm.match.*;
import tc.oc.pgm.mutation.command.MutationCommands; 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 tc.oc.commons.core.random.ImmutableWeightedRandomChooser;
import java.util.*; import java.util.*;
import java.util.logging.Level; import java.util.logging.Level;
import java.util.stream.Stream;
import javax.inject.Inject; import javax.inject.Inject;
public class MutationMatchModule extends MatchModule { public class MutationMatchModule extends MatchModule {
// TODO: send remote mutation alerts via an AMQP message
/** /**
* Chance that a mutation event will occur. * Chance that a mutation event will occur.
@ -63,39 +63,36 @@ public class MutationMatchModule extends MatchModule {
this.chance = options.chance; this.chance = options.chance;
this.amount = options.amount; this.amount = options.amount;
this.weightedSelector = new ImmutableWeightedRandomChooser<>(options.weights); this.weightedSelector = new ImmutableWeightedRandomChooser<>(options.weights);
this.mutations = getDefaultMutations(); this.mutations = mutationsDefault();
this.history = new HashSet<>(); this.history = new HashSet<>();
this.modules = new HashMap<>(); this.modules = new HashMap<>();
} }
public final ImmutableMap<Mutation, Boolean> getMutations() { public final ImmutableMap<Mutation, Boolean> mutations() {
return ImmutableMap.copyOf(mutations); return ImmutableMap.copyOf(mutations);
} }
public final ImmutableSet<Mutation> getActiveMutations() { public final ImmutableSet<Mutation> mutationsActive() {
return ImmutableSet.copyOf(Collections2.filter(getMutations().keySet(), new Predicate<Mutation>() { return ImmutableSet.copyOf(Collections2.filter(mutations().keySet(), mutations::get));
@Override
public boolean apply(Mutation mutation) {
return mutations.get(mutation);
}
}));
} }
public final ImmutableSet<Mutation> getHistoricalMutations() { public final ImmutableSet<Mutation> mutationsHistorical() {
return ImmutableSet.copyOf(history); 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()); return ImmutableSet.copyOf(modules.values());
} }
@Override
public boolean shouldLoad() {
return Config.Mutations.enabled();
}
@Override @Override
public void load() { public void load() {
if(!Config.Mutations.enabled()) return;
Random random = match.getRandom(); Random random = match.getRandom();
// Check if the api has any queued mutations // Check if the api has any queued mutations
Collection<Mutation> queuedMutations = mutationQueue.mutations(); Collection<Mutation> queuedMutations = mutationQueue.mutations();
@ -113,7 +110,7 @@ public class MutationMatchModule extends MatchModule {
mutationQueue.clear(); mutationQueue.clear();
} }
// Load the mutation modules for this match // Load the mutation modules for this match
for(Mutation mutation : getActiveMutations()) { for(Mutation mutation : mutationsActive()) {
try { try {
mutate(mutation); mutate(mutation);
} catch (Throwable throwable) { } catch (Throwable throwable) {
@ -124,43 +121,53 @@ public class MutationMatchModule extends MatchModule {
@Override @Override
public void enable() { public void enable() {
for(MutationModule module : modules.values()) { modules.values().forEach(MutationModule::enable);
module.enable(match.isRunning());
}
} }
@Override @Override
public void disable() { public void disable() {
for(MutationModule module : modules.values()) { modules.values().forEach(MutationModule::disable);
module.disable(match.isRunning()); }
}
public void register(Mutation mutation, boolean load) {
mutations.put(mutation, load);
} }
public void mutate(Mutation mutation) throws Throwable { public void mutate(Mutation mutation) throws Throwable {
Class<? extends MutationModule> clazz = mutation.getModuleClass(); Class<? extends MutationModule> loader = mutation.loader();
if(clazz == null || (match.isRunning() && !mutation.isChangeable())) return; if(loader == null) return;
MutationModule module = modules.containsKey(clazz) ? modules.get(clazz) : mutation.getModuleClass().getDeclaredConstructor(Match.class).newInstance(match); MutationModule module = modules.containsKey(loader) ? modules.get(loader) : loader.getDeclaredConstructor(Match.class).newInstance(match);
if(mutations.get(mutation)) { if(mutations.get(mutation)) {
module.enable(match.isRunning()); module.enable();
modules.put(clazz, module); modules.put(loader, module);
mutations.put(mutation, true); mutations.put(mutation, true);
history.add(mutation); history.add(mutation);
} else { } else {
module.disable(match.isRunning()); module.disable();
modules.remove(clazz); modules.remove(loader);
mutations.put(mutation, false); mutations.put(mutation, false);
} }
} }
private Map<Mutation, Boolean> getDefaultMutations() { public boolean enabled() {
Map<Mutation, Boolean> defaults = new HashMap<>(); return !mutationsActive().isEmpty();
MapUtils.putAll(defaults, Sets.newHashSet(Mutation.values()), false);
return defaults;
} }
public static boolean check(Match match, Mutation mutation) { public boolean enabled(Mutation... mutations) {
return Config.Mutations.enabled() && return mutationsActive().stream().anyMatch(m1 -> Stream.of(mutations).anyMatch(m2 -> m2.equals(m1)));
match.hasMatchModule(MutationMatchModule.class) &&
match.getMatchModule(MutationMatchModule.class).getActiveMutations().contains(mutation);
} }
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 java.util.stream.Collectors;
import javax.inject.Inject; import javax.inject.Inject;
import com.google.common.collect.Collections2;
import com.google.common.util.concurrent.ListenableFuture; import com.google.common.util.concurrent.ListenableFuture;
import tc.oc.api.docs.Server; import tc.oc.api.docs.Server;
import tc.oc.api.docs.virtual.ServerDoc; import tc.oc.api.docs.virtual.ServerDoc;
@ -28,12 +27,12 @@ public class MutationQueue {
.getLocalServer() .getLocalServer()
.queued_mutations() .queued_mutations()
.stream() .stream()
.map(mutation -> Mutation.values()[mutation.ordinal()]) .flatMap(Mutation::fromString)
.collect(Collectors.toList()); .collect(Collectors.toList());
} }
public ListenableFuture<Server> clear() { public ListenableFuture<Server> clear() {
return force(Collections.<Mutation>emptyList()); return force(Collections.emptyList());
} }
public ListenableFuture<Server> removeAll(final Collection<Mutation> mutations) { public ListenableFuture<Server> removeAll(final Collection<Mutation> mutations) {
@ -53,6 +52,7 @@ public class MutationQueue {
} }
private ListenableFuture<Server> force(final Collection<Mutation> mutations) { 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.Collection;
import java.util.HashSet; import java.util.HashSet;
import java.util.logging.Level;
import javax.inject.Inject; import javax.inject.Inject;
import com.google.common.collect.Collections2; import com.google.common.collect.Collections2;
import com.google.common.collect.Iterables; import com.google.common.collect.Iterables;
import com.google.common.collect.Sets; import com.google.common.collect.Sets;
import com.sk89q.minecraft.util.commands.Command; import com.sk89q.minecraft.util.commands.*;
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 net.md_5.bungee.api.ChatColor; import net.md_5.bungee.api.ChatColor;
import net.md_5.bungee.api.chat.BaseComponent; import net.md_5.bungee.api.chat.BaseComponent;
import net.md_5.bungee.api.chat.TranslatableComponent; import net.md_5.bungee.api.chat.TranslatableComponent;
import org.bukkit.command.CommandSender; 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.minecraft.scheduler.SyncExecutor;
import tc.oc.commons.bukkit.chat.Audiences; import tc.oc.commons.bukkit.chat.Audiences;
import tc.oc.commons.bukkit.chat.ListComponent; import tc.oc.commons.bukkit.chat.ListComponent;
@ -56,39 +57,42 @@ public class MutationCommands implements NestedCommands {
private final SyncExecutor syncExecutor; private final SyncExecutor syncExecutor;
private final Audiences audiences; private final Audiences audiences;
private final MutationQueue mutationQueue; private final MutationQueue mutationQueue;
private final IdentityProvider identityProvider;
@Inject @Inject MutationCommands(SyncExecutor syncExecutor, Audiences audiences, MutationQueue mutationQueue, IdentityProvider identityProvider) {
MutationCommands(SyncExecutor syncExecutor, Audiences audiences, MutationQueue mutationQueue) {
this.syncExecutor = syncExecutor; this.syncExecutor = syncExecutor;
this.audiences = audiences; this.audiences = audiences;
this.mutationQueue = mutationQueue; this.mutationQueue = mutationQueue;
this.identityProvider = identityProvider;
} }
@Command( @Command(
aliases = {"enable", "add"}, aliases = {"enable", "e"},
desc = "Adds a mutation to the upcoming match." + desc = "Adds a mutation to the upcoming match." +
"You can use '?' as a wildcard or " + "You can use '?' as a wildcard or " +
"'*' to use all.", "'*' to use all.",
usage = "<mutation|?|*>", usage = "<mutation|?|*>",
flags = "q",
min = 1, min = 1,
max = 1 max = 1
) )
@CommandPermissions(PERMISSION_SET) @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); set(args, sender, true);
} }
@Command( @Command(
aliases = {"disable", "remove"}, aliases = {"disable", "d"},
desc = "Remove a mutation to the upcoming match." + desc = "Remove a mutation to the upcoming match." +
"You can use '?' as a wildcard or " + "You can use '?' as a wildcard or " +
"'*' to use all.", "'*' to use all.",
usage = "<mutation|?|*>", usage = "<mutation|?|*>",
flags = "q",
min = 1, min = 1,
max = 1 max = 1
) )
@CommandPermissions(PERMISSION_SET) @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); set(args, sender, false);
} }
@ -105,8 +109,8 @@ public class MutationCommands implements NestedCommands {
public void list(final CommandContext args, CommandSender sender) throws CommandException { public void list(final CommandContext args, CommandSender sender) throws CommandException {
MutationMatchModule module = verify(sender); MutationMatchModule module = verify(sender);
final boolean queued = args.hasFlag('q'); final boolean queued = args.hasFlag('q');
final Collection<Mutation> active = queued ? mutationQueue.mutations() : module.getActiveMutations(); final Collection<Mutation> active = queued ? mutationQueue.mutations() : module.mutationsActive();
new Paginator<Mutation>() { new Paginator<Mutation>(Mutation.values().length / 2) {
@Override @Override
protected BaseComponent title() { protected BaseComponent title() {
return new TranslatableComponent(queued ? "command.mutation.list.queued" : "command.mutation.list.current"); 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); 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 MutationMatchModule module = verify(sender);
final Match match = module.getMatch(); final Match match = module.getMatch();
String action = args.getString(0); String action = args.getString(0);
boolean queued = args.hasFlag('q') || match.isFinished();
// Mutations that *will* be added or removed // Mutations that *will* be added or removed
final Collection<Mutation> mutations = new HashSet<>(); final Collection<Mutation> mutations = new HashSet<>();
// Mutations that *are allowed* to be added or removed // Mutations that *are allowed* to be added or removed
final Collection<Mutation> availableMutations = Sets.newHashSet(Mutation.values()); 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); if(value) availableMutations.removeAll(queue); else availableMutations.retainAll(queue);
// Check if all mutations have been enabled/disabled // Check if all mutations have been enabled/disabled
if((queue.size() == Mutation.values().length && value) || (queue.isEmpty() && !value)) { 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 // Get which action the user wants to preform
switch (action) { switch (action) {
@ -145,27 +155,50 @@ public class MutationCommands implements NestedCommands {
case "?": mutations.add(Iterables.get(availableMutations, RandomUtils.safeNextInt(match.getRandom(), availableMutations.size()))); break; case "?": mutations.add(Iterables.get(availableMutations, RandomUtils.safeNextInt(match.getRandom(), availableMutations.size()))); break;
default: default:
Mutation query = StringUtils.bestFuzzyMatch(action, Sets.newHashSet(Mutation.values()), 0.9); 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)); 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 { } else {
mutations.add(query); mutations.add(query);
} }
} }
Audience origin = audiences.get(sender);
// Send the queued changes off to the api Audience all = audiences.localServer();
syncExecutor.callback( String message = message(!queued, value, mutations.size() == 1);
value ? mutationQueue.mergeAll(mutations) ListComponent changed = new ListComponent(Collections2.transform(mutations, Mutation.toComponent(ChatColor.AQUA)));
: mutationQueue.removeAll(mutations), if(queued) {
result -> { // Send the queued changes off to the api
audiences.get(sender).sendMessage(new Component(new TranslatableComponent( syncExecutor.callback(
message(false, value, mutations.size() == 1), value ? mutationQueue.mergeAll(mutations)
new ListComponent(Collections2.transform(mutations, Mutation.toComponent(ChatColor.AQUA))) : mutationQueue.removeAll(mutations),
), ChatColor.WHITE)); 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) { public String message(boolean now, boolean enable, boolean singular) {
if(now) { if(now) {
if(enable) { 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.maxHealth = maxHealth;
this.rechargeDelay = rechargeDelay; 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.MatchPlayer;
import tc.oc.pgm.match.ParticipantState; import tc.oc.pgm.match.ParticipantState;
import tc.oc.pgm.mutation.MutationMatchModule; import tc.oc.pgm.mutation.MutationMatchModule;
import tc.oc.pgm.mutation.submodule.KitMutationModule; import tc.oc.pgm.mutation.types.KitMutation;
import tc.oc.pgm.mutation.submodule.MutationModule;
import tc.oc.pgm.spawns.Spawn; import tc.oc.pgm.spawns.Spawn;
import tc.oc.pgm.spawns.events.ParticipantDespawnEvent; import tc.oc.pgm.spawns.events.ParticipantDespawnEvent;
import tc.oc.pgm.spawns.events.ParticipantReleaseEvent; 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)); match.module(KillRewardMatchModule.class).ifPresent(krmm -> krmm.giveDeadPlayerRewards(player));
// Apply kit injections from KitMutationModules // Apply kit injections from KitMutationModules
match.module(MutationMatchModule.class).ifPresent(mmm -> { match.module(MutationMatchModule.class)
for(MutationModule module : mmm.getMutationModules()) { .ifPresent(mmm -> mmm.mutationModules().stream()
if(module instanceof KitMutationModule) { .filter(mm -> mm instanceof KitMutation)
KitMutationModule kitModule = ((KitMutationModule) module); .forEach(mm -> ((KitMutation) mm).apply(player)));
for(Kit kit : kitModule.getKits()) {
player.facet(KitPlayerFacet.class).applyKit(kit, kitModule.isForceful());
}
}
}
});
player.getBukkit().updateInventory(); 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) { public S lore(String lore) {
meta().setLore(meta().hasLore() ? ListUtils.append(meta().getLore(), lore) meta().setLore(meta().hasLore() ? ListUtils.append(meta().getLore(), lore)
: Collections.singletonList(lore)); : Collections.singletonList(lore));
return self(); return self();
} }
@ -172,4 +172,14 @@ public class ItemBuilder<S extends ItemBuilder<?>> {
meta(SkullMeta.class).setOwner(name, uuid, skin); meta(SkullMeta.class).setOwner(name, uuid, skin);
return self(); 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.Map;
import java.util.Optional; import java.util.Optional;
import java.util.Set; import java.util.Set;
import java.util.stream.Stream;
import javax.annotation.Nullable; import javax.annotation.Nullable;
import com.google.common.collect.ImmutableSet; import com.google.common.collect.ImmutableSet;
@ -100,6 +101,24 @@ public class ItemUtils {
return isNothing(stack) ? Optional.empty() : Optional.of(stack); 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) { public static boolean isWeapon(ItemStack stack) {
return isWeapon(stack.getData()); 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 java.util.List;
import com.google.common.collect.Iterables; import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
import com.google.common.collect.Range; import com.google.common.collect.Range;
import tc.oc.commons.core.util.Ranges; import tc.oc.commons.core.util.Ranges;
@ -26,6 +27,10 @@ public interface Entropy {
return min + (int) ((randomLong() & 0xffffffffL) * delta / 0x100000000L); 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) { default <T> T randomElement(Iterable<T> iterable) {
return Iterables.get(iterable, randomInt(Range.closedOpen(0, Iterables.size(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 java.util.Random;
import com.google.common.collect.Iterables; import com.google.common.collect.Iterables;
import org.apache.commons.lang.math.Fraction;
public class RandomUtils { public class RandomUtils {
@ -13,4 +14,8 @@ public class RandomUtils {
public static <T> T element(Random random, Iterable<? extends T> collection) { public static <T> T element(Random random, Iterable<? extends T> collection) {
return Iterables.get(collection, safeNextInt(random, Iterables.size(collection))); return Iterables.get(collection, safeNextInt(random, Iterables.size(collection)));
} }
public static boolean nextBoolean(Random random, Fraction chance) {
return random.nextDouble() < chance.doubleValue();
}
} }