diff --git a/API/api/src/main/java/tc/oc/api/docs/virtual/MatchDoc.java b/API/api/src/main/java/tc/oc/api/docs/virtual/MatchDoc.java index 01aea88..f52da24 100644 --- a/API/api/src/main/java/tc/oc/api/docs/virtual/MatchDoc.java +++ b/API/api/src/main/java/tc/oc/api/docs/virtual/MatchDoc.java @@ -30,11 +30,7 @@ public interface MatchDoc extends Model { Collection winning_team_ids(); Collection winning_user_ids(); - enum Mutation { - BLITZ, UHC, EXPLOSIVES, NO_FALL, MOBS, STRENGTH, DOUBLE_JUMP, INVISIBILITY, LIGHTNING, RAGE, ELYTRA; - } - - Set mutations(); + Set mutations(); @Serialize interface Team extends MapDoc.Team, CompetitorDoc { diff --git a/API/api/src/main/java/tc/oc/api/docs/virtual/ServerDoc.java b/API/api/src/main/java/tc/oc/api/docs/virtual/ServerDoc.java index c646e22..840dc6c 100644 --- a/API/api/src/main/java/tc/oc/api/docs/virtual/ServerDoc.java +++ b/API/api/src/main/java/tc/oc/api/docs/virtual/ServerDoc.java @@ -138,7 +138,7 @@ public interface ServerDoc { @Serialize interface Mutation extends Partial { - Set queued_mutations(); + Set queued_mutations(); } /** diff --git a/API/api/src/main/java/tc/oc/api/serialization/TypeAdaptersManifest.java b/API/api/src/main/java/tc/oc/api/serialization/TypeAdaptersManifest.java index 6418b96..f8ff35b 100644 --- a/API/api/src/main/java/tc/oc/api/serialization/TypeAdaptersManifest.java +++ b/API/api/src/main/java/tc/oc/api/serialization/TypeAdaptersManifest.java @@ -33,7 +33,5 @@ public class TypeAdaptersManifest extends Manifest { gson.bindAdapter(new TypeLiteral>(){}) .to(new TypeLiteral>(){}); - gson.bindAdapter(new TypeLiteral>(){}) - .to(new TypeLiteral>(){}); } } diff --git a/API/minecraft/src/main/java/tc/oc/api/minecraft/servers/LocalServerDocument.java b/API/minecraft/src/main/java/tc/oc/api/minecraft/servers/LocalServerDocument.java index d3b8eca..64498ad 100644 --- a/API/minecraft/src/main/java/tc/oc/api/minecraft/servers/LocalServerDocument.java +++ b/API/minecraft/src/main/java/tc/oc/api/minecraft/servers/LocalServerDocument.java @@ -279,7 +279,7 @@ public class LocalServerDocument extends StartupServerDocument implements Server } @Override - public Set queued_mutations() { + public Set queued_mutations() { return mutations != null ? mutations.queued_mutations() : Collections.emptySet(); } } diff --git a/Commons/core/src/main/i18n/templates/pgm/PGMMessages.properties b/Commons/core/src/main/i18n/templates/pgm/PGMMessages.properties index edec828..e509e6a 100644 --- a/Commons/core/src/main/i18n/templates/pgm/PGMMessages.properties +++ b/Commons/core/src/main/i18n/templates/pgm/PGMMessages.properties @@ -90,9 +90,19 @@ command.mutation.enable.later.plural = You have enabled the {0} mutations for th command.mutation.disable.later.singular = You have disabled the {0} mutation for the next match command.mutation.disable.later.plural = You have disabled the {0} mutations for the next match +# {0} = command sender +# {1} = mutation name(s) +command.mutation.enable.now.singular = {0} has enabled the {1} mutation +command.mutation.enable.now.plural = {0} has enabled the {1} mutations +command.mutation.disable.now.singular = {0} has disabled the {1} mutation +command.mutation.disable.now.plural = {0} has disabled the {1} mutations + +command.mutation.error.mutate = An internal error occured with the '{0}' mutation command.mutation.error.find = Unable to find mutation named '{0}' -command.mutation.error.enabled = All mutations have already been enabled -command.mutation.error.disabled = All mutations have already been disabled +command.mutation.error.enabled = {0} mutation is already enabled +command.mutation.error.enabled.all = All mutations have already been enabled +command.mutation.error.disabled = {0} mutation is already disabled +command.mutation.error.disabled.all = All mutations have already been disabled command.mutation.list.current = Current Mutations command.mutation.list.queued = Queued Mutations @@ -177,28 +187,41 @@ skillRequirement.fail.general = Play on unranked servers to improve your skill a huddle.instructions = Your team now has {0} to strategize before the match starts mutation.type.blitz = Blitz -mutation.type.uhc = UHC -mutation.type.explosives = Explosives -mutation.type.no_fall = No Fall -mutation.type.mobs = Mobs -mutation.type.strength = Strength -mutation.type.double_jump = Double Jump -mutation.type.invisibility = Invisibility -mutation.type.lightning = Lightning -mutation.type.rage = Rage -mutation.type.elytra = Elytra - mutation.type.blitz.desc = no respawning -mutation.type.uhc.desc = no natural regeneration -mutation.type.explosives.desc = stronger and more powerful explosions -mutation.type.no_fall.desc = fall all you want -mutation.type.mobs.desc = natural mob spawning -mutation.type.strength.desc = strength potions everywhere -mutation.type.double_jump.desc = super jump powers -mutation.type.invisibility.desc = enemy players cannot be seen -mutation.type.lightning.desc = lightning strikes from the sky +mutation.type.rage = Rage mutation.type.rage.desc = instant kills +mutation.type.hardcore = Hardcore +mutation.type.hardcore.desc = no natural regeneration +mutation.type.jump = Jump +mutation.type.jump.desc = double jump and no fall +mutation.type.explosive = Explosive +mutation.type.explosive.desc = tnt and fire bows +mutation.type.elytra = Elytra mutation.type.elytra.desc = fly around with an elytra +mutation.type.projectile = Projectile +mutation.type.projectile.desc = arrow potion effects +mutation.type.enchantment = Enchantment +mutation.type.enchantment.desc = random item enchantments +mutation.type.potion = Potion +mutation.type.potion.desc = random special potions +mutation.type.equestrian = Equestrian +mutation.type.equestrian.desc = ride a custom horse +mutation.type.health = Health +mutation.type.health.desc = double health and gold apples +mutation.type.glow = Glow +mutation.type.glow.desc = glowing effect on all players +mutation.type.stealth = Stealth +mutation.type.stealth.desc = invisibility with no armor +mutation.type.armor = Armor +mutation.type.armor.desc = heavy diamond armor +mutation.type.mobs = Mobs +mutation.type.mobs.desc = mob spawning eggs +mutation.type.lightning = Lightning +mutation.type.lightning.desc = lightning strikes from the sky +mutation.type.bomber = Bomber +mutation.type.bomber.desc = tnt rain +mutation.type.apocalypse = Apocalypse +mutation.type.apocalypse.desc = mob spawning chaos tnt.license.info.alreadyHas = You have a TNT license. You can surrender your license by typing {0} tnt.license.info.doesNotHave = You do not have a TNT license. You can request one by typing {0} diff --git a/PGM/src/main/java/tc/oc/pgm/api/MatchDocument.java b/PGM/src/main/java/tc/oc/pgm/api/MatchDocument.java index 6903a88..5254722 100644 --- a/PGM/src/main/java/tc/oc/pgm/api/MatchDocument.java +++ b/PGM/src/main/java/tc/oc/pgm/api/MatchDocument.java @@ -120,10 +120,10 @@ public class MatchDocument extends AbstractModel implements MatchDoc { } @Override - public Set mutations() { - return mutations.map(mmm -> mmm.getHistoricalMutations() + public Set mutations() { + return mutations.map(mmm -> mmm.mutationsHistorical() .stream() - .map(tc.oc.pgm.mutation.Mutation::toApi) + .map(tc.oc.pgm.mutation.Mutation::name) .collect(Collectors.toImmutableSet())) .orElse(ImmutableSet.of()); } diff --git a/PGM/src/main/java/tc/oc/pgm/blitz/BlitzMatchModule.java b/PGM/src/main/java/tc/oc/pgm/blitz/BlitzMatchModule.java index 76cc0c1..32a461f 100644 --- a/PGM/src/main/java/tc/oc/pgm/blitz/BlitzMatchModule.java +++ b/PGM/src/main/java/tc/oc/pgm/blitz/BlitzMatchModule.java @@ -65,7 +65,7 @@ public class BlitzMatchModule extends MatchModule implements Listener, JoinHandl public BlitzMatchModule(Match match, BlitzConfig config) { super(match); - this.config = MutationMatchModule.check(match, Mutation.BLITZ) ? new BlitzConfig(1, true) : config; + this.config = match.module(MutationMatchModule.class).get().enabled(Mutation.BLITZ) ? new BlitzConfig(1, true) : config; this.lifeManager = new LifeManager(this.config.getNumLives()); } diff --git a/PGM/src/main/java/tc/oc/pgm/damage/DisableDamageMatchModule.java b/PGM/src/main/java/tc/oc/pgm/damage/DisableDamageMatchModule.java index e37aa81..f23a4ff 100644 --- a/PGM/src/main/java/tc/oc/pgm/damage/DisableDamageMatchModule.java +++ b/PGM/src/main/java/tc/oc/pgm/damage/DisableDamageMatchModule.java @@ -3,6 +3,7 @@ package tc.oc.pgm.damage; import javax.annotation.Nullable; import javax.inject.Inject; +import com.google.common.collect.ImmutableSetMultimap; import com.google.common.collect.SetMultimap; import com.google.common.collect.Sets; import org.bukkit.block.Block; @@ -36,12 +37,12 @@ public class DisableDamageMatchModule extends MatchModule implements Listener { this.causes = causes; } - @Override - public void load() { - super.load(); - if(MutationMatchModule.check(match, Mutation.NO_FALL)) { - this.causes.putAll(DamageCause.FALL, Sets.newHashSet(PlayerRelation.values())); - } + public SetMultimap causes() { + return causes; + } + + public ImmutableSetMultimap causesImmutable() { + return ImmutableSetMultimap.copyOf(causes()); } private static DamageCause getBlockDamageCause(Block block) { @@ -59,11 +60,11 @@ public class DisableDamageMatchModule extends MatchModule implements Listener { } private boolean canDamage(DamageCause cause, MatchPlayer victim, @Nullable ParticipantState damager) { - return !this.causes.containsEntry(cause, PlayerRelation.get(victim.getParticipantState(), damager)); + return !causesImmutable().containsEntry(cause, PlayerRelation.get(victim.getParticipantState(), damager)); } private boolean canDamage(DamageCause cause, MatchPlayer victim, DamageInfo info) { - return !this.causes.containsEntry(cause, PlayerRelation.get(victim.getParticipantState(), info.getAttacker())); + return !causesImmutable().containsEntry(cause, PlayerRelation.get(victim.getParticipantState(), info.getAttacker())); } @EventHandler(priority = EventPriority.NORMAL, ignoreCancelled = true) diff --git a/PGM/src/main/java/tc/oc/pgm/doublejump/DoubleJumpKit.java b/PGM/src/main/java/tc/oc/pgm/doublejump/DoubleJumpKit.java index 9e26b24..ca666a0 100644 --- a/PGM/src/main/java/tc/oc/pgm/doublejump/DoubleJumpKit.java +++ b/PGM/src/main/java/tc/oc/pgm/doublejump/DoubleJumpKit.java @@ -21,10 +21,23 @@ public class DoubleJumpKit extends Kit.Impl { this.rechargeInAir = rechargeInAir; } + public DoubleJumpKit() { + this(true, DEFAULT_POWER, DEFAULT_RECHARGE, true); + } + @Override public void apply(MatchPlayer player, boolean force, ItemKitApplicator items) { - DoubleJumpMatchModule djmm = player.getMatch().getMatchModule(DoubleJumpMatchModule.class); - if(djmm != null) djmm.setKit(player.getBukkit(), this); + player.getMatch().module(DoubleJumpMatchModule.class).ifPresent(jump -> jump.setKit(player.getBukkit(), this)); + } + + @Override + public boolean isRemovable() { + return true; + } + + @Override + public void remove(MatchPlayer player) { + player.getMatch().module(DoubleJumpMatchModule.class).ifPresent(jump -> jump.setKit(player.getBukkit(), null)); } public float chargePerTick() { diff --git a/PGM/src/main/java/tc/oc/pgm/effect/ProjectileTrailMatchModule.java b/PGM/src/main/java/tc/oc/pgm/effect/ProjectileTrailMatchModule.java index fed8163..3fc9cfe 100644 --- a/PGM/src/main/java/tc/oc/pgm/effect/ProjectileTrailMatchModule.java +++ b/PGM/src/main/java/tc/oc/pgm/effect/ProjectileTrailMatchModule.java @@ -57,7 +57,7 @@ public class ProjectileTrailMatchModule extends MatchModule implements Listener return Math.max(0.001, rgb / 255.0); } - @EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true) + @EventHandler(priority = EventPriority.HIGH, ignoreCancelled = true) public void onProjectileLaunch(ProjectileLaunchEvent event) { match.player(event.getActor()).ifPresent(shooter -> { final Projectile projectile = event.getEntity(); diff --git a/PGM/src/main/java/tc/oc/pgm/filters/matcher/match/MatchMutationFilter.java b/PGM/src/main/java/tc/oc/pgm/filters/matcher/match/MatchMutationFilter.java index 80ce970..c5f7024 100644 --- a/PGM/src/main/java/tc/oc/pgm/filters/matcher/match/MatchMutationFilter.java +++ b/PGM/src/main/java/tc/oc/pgm/filters/matcher/match/MatchMutationFilter.java @@ -15,7 +15,7 @@ public class MatchMutationFilter extends TypedFilter.Impl { @Override public boolean matches(IMatchQuery query) { return query.module(MutationMatchModule.class) - .filter(mmm -> mmm.getActiveMutations().contains(mutation)) + .filter(mmm -> mmm.enabled(mutation)) .isPresent(); } } diff --git a/PGM/src/main/java/tc/oc/pgm/gamerules/GameRule.java b/PGM/src/main/java/tc/oc/pgm/gamerules/GameRule.java deleted file mode 100644 index 59bc83d..0000000 --- a/PGM/src/main/java/tc/oc/pgm/gamerules/GameRule.java +++ /dev/null @@ -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; - } -} diff --git a/PGM/src/main/java/tc/oc/pgm/gamerules/GameRulesMatchModule.java b/PGM/src/main/java/tc/oc/pgm/gamerules/GameRulesMatchModule.java index 85123f3..ebb20da 100644 --- a/PGM/src/main/java/tc/oc/pgm/gamerules/GameRulesMatchModule.java +++ b/PGM/src/main/java/tc/oc/pgm/gamerules/GameRulesMatchModule.java @@ -6,29 +6,38 @@ import com.google.common.collect.ImmutableMap; import tc.oc.pgm.match.Match; import tc.oc.pgm.match.MatchModule; -import tc.oc.pgm.mutation.Mutation; -import tc.oc.pgm.mutation.MutationMatchModule; +import tc.oc.pgm.match.Repeatable; +import tc.oc.time.Time; public class GameRulesMatchModule extends MatchModule { - private final Map gameRules; + private final Map gameRules; - public GameRulesMatchModule(Match match, Map gameRules) { + public GameRulesMatchModule(Match match, Map gameRules) { super(match); this.gameRules = Preconditions.checkNotNull(gameRules, "gamerules"); - if(MutationMatchModule.check(match, Mutation.UHC)) { - this.gameRules.put(GameRule.NATURAL_REGENERATION, Boolean.FALSE); - } } @Override public void load() { - for (Map.Entry gameRule : this.gameRules.entrySet()) { - this.match.getWorld().setGameRuleValue(gameRule.getKey().getValue(), gameRule.getValue().toString()); - } + update(); } - public ImmutableMap getGameRules() { - return ImmutableMap.copyOf(gameRules); + @Repeatable(interval = @Time(seconds = 1)) + public void tick() { + update(); } + + public void update() { + gameRulesImmutable().forEach((String rule, String val) -> match.getWorld().setGameRuleValue(rule, val)); + } + + public Map gameRules() { + return gameRules; + } + + public ImmutableMap gameRulesImmutable() { + return ImmutableMap.copyOf(gameRules()); + } + } diff --git a/PGM/src/main/java/tc/oc/pgm/gamerules/GameRulesModule.java b/PGM/src/main/java/tc/oc/pgm/gamerules/GameRulesModule.java index dfb1e37..b3191b9 100644 --- a/PGM/src/main/java/tc/oc/pgm/gamerules/GameRulesModule.java +++ b/PGM/src/main/java/tc/oc/pgm/gamerules/GameRulesModule.java @@ -18,9 +18,9 @@ import tc.oc.pgm.xml.InvalidXMLException; @ModuleDescription(name="Gamerules", follows = MutationMapModule.class) public class GameRulesModule implements MapModule, MatchModuleFactory { - private Map gameRules; + private Map gameRules; - private GameRulesModule(Map gamerules) { + private GameRulesModule(Map gamerules) { this.gameRules = gamerules; } @@ -33,32 +33,32 @@ public class GameRulesModule implements MapModule, MatchModuleFactory gameRules = new HashMap<>(); + Map gameRules = new HashMap<>(); for (Element gameRulesElement : doc.getRootElement().getChildren("gamerules")) { for (Element gameRuleElement : gameRulesElement.getChildren()) { - GameRule gameRule = GameRule.forName(gameRuleElement.getName()); + String gameRule = gameRuleElement.getName(); String value = gameRuleElement.getValue(); - if (gameRule == null) { + if(gameRule == null) { throw new InvalidXMLException(gameRuleElement.getName() + " is not a valid gamerule", gameRuleElement); } - if (value == null) { - throw new InvalidXMLException("Missing value for gamerule " + gameRule.getValue(), gameRuleElement); + if(value == null) { + throw new InvalidXMLException("Missing value for gamerule " + gameRule, gameRuleElement); } else if (!(value.equalsIgnoreCase("true") || value.equalsIgnoreCase("false"))) { throw new InvalidXMLException(gameRuleElement.getValue() + " is not a valid gamerule value", gameRuleElement); } - if (gameRules.containsKey(gameRule)){ - throw new InvalidXMLException(gameRule.getValue() + " has already been specified", gameRuleElement); + if(gameRules.containsKey(gameRule)){ + throw new InvalidXMLException(gameRule + " has already been specified", gameRuleElement); } - gameRules.put(gameRule, Boolean.valueOf(value)); + gameRules.put(gameRule, value); } } return new GameRulesModule(gameRules); } - public ImmutableMap getGameRules() { + public ImmutableMap getGameRules() { return ImmutableMap.copyOf(this.gameRules); } diff --git a/PGM/src/main/java/tc/oc/pgm/killreward/KillReward.java b/PGM/src/main/java/tc/oc/pgm/killreward/KillReward.java index 788621d..ad6072a 100644 --- a/PGM/src/main/java/tc/oc/pgm/killreward/KillReward.java +++ b/PGM/src/main/java/tc/oc/pgm/killreward/KillReward.java @@ -3,6 +3,8 @@ package tc.oc.pgm.killreward; import com.google.common.collect.ImmutableList; import org.bukkit.inventory.ItemStack; import tc.oc.pgm.filters.Filter; +import tc.oc.pgm.filters.matcher.StaticFilter; +import tc.oc.pgm.kits.ItemKit; import tc.oc.pgm.kits.Kit; public class KillReward { @@ -15,4 +17,8 @@ public class KillReward { this.filter = filter; this.kit = kit; } + + public KillReward(ItemKit kit) { + this(ImmutableList.of(kit.item()), StaticFilter.ALLOW, kit); + } } diff --git a/PGM/src/main/java/tc/oc/pgm/killreward/KillRewardMatchModule.java b/PGM/src/main/java/tc/oc/pgm/killreward/KillRewardMatchModule.java index 4832d13..ebca5ab 100644 --- a/PGM/src/main/java/tc/oc/pgm/killreward/KillRewardMatchModule.java +++ b/PGM/src/main/java/tc/oc/pgm/killreward/KillRewardMatchModule.java @@ -2,11 +2,10 @@ package tc.oc.pgm.killreward; import java.util.Collection; import java.util.List; +import java.util.stream.Collectors; import javax.annotation.Nullable; -import com.google.common.base.Predicate; import com.google.common.collect.ArrayListMultimap; -import com.google.common.collect.Collections2; import com.google.common.collect.ImmutableList; import com.google.common.collect.Multimap; import org.bukkit.event.Event; @@ -27,34 +26,38 @@ import tc.oc.pgm.tracker.damage.DamageInfo; @ListenerScope(MatchScope.RUNNING) public class KillRewardMatchModule extends MatchModule implements Listener { - protected final ImmutableList killRewards; - protected final Multimap deadPlayerRewards = ArrayListMultimap.create(); + + private final List killRewards; + private final Multimap deadPlayerRewards = ArrayListMultimap.create(); public KillRewardMatchModule(Match match, List killRewards) { super(match); - this.killRewards = ImmutableList.copyOf(killRewards); + this.killRewards = killRewards; } - private Collection getRewards(@Nullable Event event, ParticipantState victim, DamageInfo damageInfo) { + public List rewards() { + return killRewards; + } + + public ImmutableList rewardsImmutable() { + return ImmutableList.copyOf(killRewards); + } + + public List rewards(@Nullable Event event, ParticipantState victim, DamageInfo damageInfo) { final DamageQuery query = DamageQuery.attackerDefault(event, victim, damageInfo); - return Collections2.filter(killRewards, new Predicate() { - @Override - public boolean apply(KillReward killReward) { - return killReward.filter.query(query).isAllowed(); - } - }); + return rewardsImmutable().stream().filter(reward -> reward.filter.query(query).isAllowed()).collect(Collectors.toList()); } - private Collection getRewards(MatchPlayerDeathEvent event) { - return getRewards(event, event.getVictim().getParticipantState(), event.getDamageInfo()); + public List rewards(MatchPlayerDeathEvent event) { + return rewards(event, event.getVictim().getParticipantState(), event.getDamageInfo()); } - private void giveRewards(MatchPlayer killer, Collection rewards) { - for(KillReward reward : rewards) { + public void giveRewards(MatchPlayer killer, Collection rewards) { + rewards.forEach(reward -> { // Apply kit first so it can not override reward items reward.kit.apply(killer); reward.items.forEach(stack -> ItemKitApplicator.fireEventAndTransfer(killer, stack)); - } + }); } @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true) @@ -63,7 +66,7 @@ public class KillRewardMatchModule extends MatchModule implements Listener { MatchPlayer killer = event.getOnlineKiller(); if(killer == null) return; - Collection rewards = getRewards(event); + List rewards = rewards(event); if(killer.isDead()) { // If a player earns a KW while dead, give it to them when they respawn. Rationale: If they click respawn @@ -87,4 +90,5 @@ public class KillRewardMatchModule extends MatchModule implements Listener { public void onPartyChange(PlayerPartyChangeEvent event) { deadPlayerRewards.removeAll(event.getPlayer()); } + } diff --git a/PGM/src/main/java/tc/oc/pgm/killreward/KillRewardModule.java b/PGM/src/main/java/tc/oc/pgm/killreward/KillRewardModule.java index 9f9c251..125a230 100644 --- a/PGM/src/main/java/tc/oc/pgm/killreward/KillRewardModule.java +++ b/PGM/src/main/java/tc/oc/pgm/killreward/KillRewardModule.java @@ -1,5 +1,6 @@ package tc.oc.pgm.killreward; +import java.util.ArrayList; import java.util.List; import java.util.Optional; import java.util.logging.Logger; @@ -27,10 +28,10 @@ import tc.oc.pgm.xml.InvalidXMLException; @ModuleDescription(name="Kill Reward") public class KillRewardModule implements MapModule, MatchModuleFactory { - protected final ImmutableList rewards; + protected final List rewards; public KillRewardModule(List rewards) { - this.rewards = ImmutableList.copyOf(rewards); + this.rewards = rewards; } @Override @@ -43,7 +44,7 @@ public class KillRewardModule implements MapModule, MatchModuleFactory rewards = ImmutableList.builder(); + List rewards = new ArrayList<>(); final ItemParser itemParser = context.needModule(ItemParser.class); final Optional itemModifier = context.module(ItemModifyModule.class); @@ -61,11 +62,6 @@ public class KillRewardModule implements MapModule, MatchModuleFactory list = rewards.build(); - if(list.isEmpty()) { - return null; - } else { - return new KillRewardModule(list); - } + return new KillRewardModule(rewards); } } diff --git a/PGM/src/main/java/tc/oc/pgm/kits/FreeItemKit.java b/PGM/src/main/java/tc/oc/pgm/kits/FreeItemKit.java index 85f5456..0f72f00 100644 --- a/PGM/src/main/java/tc/oc/pgm/kits/FreeItemKit.java +++ b/PGM/src/main/java/tc/oc/pgm/kits/FreeItemKit.java @@ -1,8 +1,15 @@ package tc.oc.pgm.kits; import org.bukkit.inventory.ItemStack; +import org.bukkit.inventory.PlayerInventory; +import tc.oc.commons.bukkit.inventory.Slot; import tc.oc.pgm.match.MatchPlayer; +import java.util.Map; +import java.util.Optional; +import java.util.function.Function; +import java.util.stream.Collectors; + public class FreeItemKit extends BaseItemKit { protected final ItemStack item; @@ -20,4 +27,36 @@ public class FreeItemKit extends BaseItemKit { public void apply(MatchPlayer player, boolean force, ItemKitApplicator items) { items.add(item); } + + @Override + public boolean isRemovable() { + return true; + } + + @Override + public void remove(MatchPlayer player) { + int left = item.getAmount(); + PlayerInventory inv = player.getInventory(); + for(Map.Entry> entry : Slot.Player.player() + .collect(Collectors.toMap(Function.identity(), slot -> slot.item(inv))).entrySet()) { + Slot.Player slot = entry.getKey(); + Optional 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; + } + } + } + } + } + } diff --git a/PGM/src/main/java/tc/oc/pgm/kits/HealthKit.java b/PGM/src/main/java/tc/oc/pgm/kits/HealthKit.java index bcbf76e..7eae5a4 100644 --- a/PGM/src/main/java/tc/oc/pgm/kits/HealthKit.java +++ b/PGM/src/main/java/tc/oc/pgm/kits/HealthKit.java @@ -1,13 +1,11 @@ package tc.oc.pgm.kits; -import com.google.common.base.Preconditions; import tc.oc.pgm.match.MatchPlayer; public class HealthKit extends Kit.Impl { protected final int halfHearts; public HealthKit(int halfHearts) { - Preconditions.checkArgument(0 < halfHearts && halfHearts <= 20, "halfHearts must be greater than 0 and less than or equal to 20"); this.halfHearts = halfHearts; } diff --git a/PGM/src/main/java/tc/oc/pgm/kits/NaturalRegenerationKit.java b/PGM/src/main/java/tc/oc/pgm/kits/NaturalRegenerationKit.java index 7b76875..1f41f51 100644 --- a/PGM/src/main/java/tc/oc/pgm/kits/NaturalRegenerationKit.java +++ b/PGM/src/main/java/tc/oc/pgm/kits/NaturalRegenerationKit.java @@ -11,12 +11,27 @@ public class NaturalRegenerationKit extends Kit.Impl { this.enabled = enabled; } - @Override - public void apply(MatchPlayer player, boolean force, ItemKitApplicator items) { + public void toggle(MatchPlayer player, boolean enabled) { if(fast) { player.getBukkit().setFastNaturalRegeneration(enabled); } else { player.getBukkit().setSlowNaturalRegeneration(enabled); } } + + @Override + public void apply(MatchPlayer player, boolean force, ItemKitApplicator items) { + toggle(player, enabled); + } + + @Override + public boolean isRemovable() { + return true; + } + + @Override + public void remove(MatchPlayer player) { + toggle(player, !enabled); + } + } diff --git a/PGM/src/main/java/tc/oc/pgm/kits/SlotItemKit.java b/PGM/src/main/java/tc/oc/pgm/kits/SlotItemKit.java index 621be9d..22bd186 100644 --- a/PGM/src/main/java/tc/oc/pgm/kits/SlotItemKit.java +++ b/PGM/src/main/java/tc/oc/pgm/kits/SlotItemKit.java @@ -21,4 +21,5 @@ public class SlotItemKit extends FreeItemKit { public void apply(MatchPlayer player, boolean force, ItemKitApplicator items) { items.put(slot, item, force); } + } diff --git a/PGM/src/main/java/tc/oc/pgm/listeners/MatchAnnouncer.java b/PGM/src/main/java/tc/oc/pgm/listeners/MatchAnnouncer.java index 9f16bb9..79a7f22 100644 --- a/PGM/src/main/java/tc/oc/pgm/listeners/MatchAnnouncer.java +++ b/PGM/src/main/java/tc/oc/pgm/listeners/MatchAnnouncer.java @@ -164,11 +164,11 @@ public class MatchAnnouncer implements PluginFacet, Listener { } final MutationMatchModule mmm = viewer.getMatch().getMatchModule(MutationMatchModule.class); - if(mmm != null && mmm.getActiveMutations().size() > 0) { + if(mmm != null && mmm.mutationsActive().size() > 0) { viewer.sendMessage( new Component(" ", ChatColor.DARK_GRAY).extra( new TranslatableComponent("broadcast.welcomeMessage.mutations", - new ListComponent(Collections2.transform(mmm.getActiveMutations(), Mutation.toComponent(ChatColor.GREEN))) + new ListComponent(Collections2.transform(mmm.mutationsActive(), Mutation.toComponent(ChatColor.GREEN))) ) ) ); diff --git a/PGM/src/main/java/tc/oc/pgm/listeners/PGMListener.java b/PGM/src/main/java/tc/oc/pgm/listeners/PGMListener.java index 8a8f29d..9f6a047 100644 --- a/PGM/src/main/java/tc/oc/pgm/listeners/PGMListener.java +++ b/PGM/src/main/java/tc/oc/pgm/listeners/PGMListener.java @@ -25,7 +25,6 @@ import tc.oc.pgm.Config; import tc.oc.pgm.events.MatchBeginEvent; import tc.oc.pgm.events.MatchEndEvent; import tc.oc.pgm.events.MatchLoadEvent; -import tc.oc.pgm.gamerules.GameRule; import tc.oc.pgm.gamerules.GameRulesModule; import tc.oc.pgm.match.MatchManager; import tc.oc.pgm.modules.TimeLockModule; @@ -99,9 +98,11 @@ public class PGMListener implements PluginFacet, Listener { // Time Lock // lock time before, during (if time lock enabled), and after the match // + static final String DO_DAYLIGHT_CYCLE = "doDaylightCycle"; + @EventHandler public void lockTime(final MatchLoadEvent event) { - event.getMatch().getWorld().setGameRuleValue(GameRule.DO_DAYLIGHT_CYCLE.getValue(), Boolean.toString(false)); + event.getMatch().getWorld().setGameRuleValue(DO_DAYLIGHT_CYCLE, Boolean.toString(false)); } @EventHandler @@ -113,16 +114,16 @@ public class PGMListener implements PluginFacet, Listener { GameRulesModule gameRulesModule = event.getMatch().getModuleContext().getModule(GameRulesModule.class); - if (gameRulesModule != null && gameRulesModule.getGameRules().containsKey(GameRule.DO_DAYLIGHT_CYCLE)) { - unlockTime = gameRulesModule.getGameRules().get(GameRule.DO_DAYLIGHT_CYCLE); + if (gameRulesModule != null && gameRulesModule.getGameRules().containsKey(DO_DAYLIGHT_CYCLE)) { + unlockTime = Boolean.valueOf(gameRulesModule.getGameRules().get(DO_DAYLIGHT_CYCLE)); } - event.getMatch().getWorld().setGameRuleValue(GameRule.DO_DAYLIGHT_CYCLE.getValue(), Boolean.toString(unlockTime)); + event.getMatch().getWorld().setGameRuleValue(DO_DAYLIGHT_CYCLE, Boolean.toString(unlockTime)); } @EventHandler public void lockTime(final MatchEndEvent event) { - event.getMatch().getWorld().setGameRuleValue(GameRule.DO_DAYLIGHT_CYCLE.getValue(), Boolean.toString(false)); + event.getMatch().getWorld().setGameRuleValue(DO_DAYLIGHT_CYCLE, Boolean.toString(false)); } @EventHandler diff --git a/PGM/src/main/java/tc/oc/pgm/modules/MobsMatchModule.java b/PGM/src/main/java/tc/oc/pgm/modules/MobsMatchModule.java index a7a28d9..500ae8b 100644 --- a/PGM/src/main/java/tc/oc/pgm/modules/MobsMatchModule.java +++ b/PGM/src/main/java/tc/oc/pgm/modules/MobsMatchModule.java @@ -4,22 +4,21 @@ import org.bukkit.event.EventHandler; import org.bukkit.event.Listener; import org.bukkit.event.entity.CreatureSpawnEvent; import tc.oc.pgm.events.ListenerScope; -import tc.oc.pgm.filters.matcher.StaticFilter; import tc.oc.pgm.match.Match; import tc.oc.pgm.filters.Filter; import tc.oc.pgm.filters.query.EntitySpawnQuery; import tc.oc.pgm.match.MatchModule; import tc.oc.pgm.match.MatchScope; -import tc.oc.pgm.mutation.Mutation; import tc.oc.pgm.mutation.MutationMatchModule; @ListenerScope(MatchScope.LOADED) public class MobsMatchModule extends MatchModule implements Listener { + private final Filter mobsFilter; public MobsMatchModule(Match match, Filter mobsFilter) { super(match); - this.mobsFilter = MutationMatchModule.check(match, Mutation.MOBS) ? StaticFilter.ALLOW : mobsFilter; + this.mobsFilter = mobsFilter; } @Override @@ -40,8 +39,10 @@ public class MobsMatchModule extends MatchModule implements Listener { @EventHandler(ignoreCancelled = true) public void checkSpawn(final CreatureSpawnEvent event) { - if(event.getSpawnReason() != CreatureSpawnEvent.SpawnReason.CUSTOM) { + if(!match.module(MutationMatchModule.class).map(mmm -> mmm.allowMob(event.getSpawnReason())).orElse(false) && + event.getSpawnReason() != CreatureSpawnEvent.SpawnReason.CUSTOM) { event.setCancelled(mobsFilter.query(new EntitySpawnQuery(event, event.getEntity(), event.getSpawnReason())).isDenied()); } } + } diff --git a/PGM/src/main/java/tc/oc/pgm/mutation/Mutation.java b/PGM/src/main/java/tc/oc/pgm/mutation/Mutation.java index be781b7..575af58 100644 --- a/PGM/src/main/java/tc/oc/pgm/mutation/Mutation.java +++ b/PGM/src/main/java/tc/oc/pgm/mutation/Mutation.java @@ -7,58 +7,61 @@ import net.md_5.bungee.api.ChatColor; import net.md_5.bungee.api.chat.BaseComponent; import net.md_5.bungee.api.chat.HoverEvent; import net.md_5.bungee.api.chat.TranslatableComponent; -import tc.oc.api.docs.virtual.MatchDoc; import tc.oc.commons.core.chat.Component; -import tc.oc.pgm.mutation.submodule.MutationModule; +import tc.oc.pgm.PGM; +import tc.oc.pgm.mutation.types.MutationModule; +import tc.oc.pgm.mutation.types.kit.ArmorMutation; +import tc.oc.pgm.mutation.types.kit.ElytraMutation; +import tc.oc.pgm.mutation.types.kit.EnchantmentMutation; +import tc.oc.pgm.mutation.types.kit.EquestrianMutation; +import tc.oc.pgm.mutation.types.kit.ExplosiveMutation; +import tc.oc.pgm.mutation.types.kit.GlowMutation; +import tc.oc.pgm.mutation.types.kit.HardcoreMutation; +import tc.oc.pgm.mutation.types.kit.HealthMutation; +import tc.oc.pgm.mutation.types.kit.JumpMutation; +import tc.oc.pgm.mutation.types.kit.MobsMutation; +import tc.oc.pgm.mutation.types.kit.PotionMutation; +import tc.oc.pgm.mutation.types.kit.ProjectileMutation; +import tc.oc.pgm.mutation.types.kit.StealthMutation; +import tc.oc.pgm.mutation.types.other.RageMutation; +import tc.oc.pgm.mutation.types.targetable.ApocalypseMutation; +import tc.oc.pgm.mutation.types.targetable.BomberMutation; +import tc.oc.pgm.mutation.types.targetable.LightningMutation; -import static tc.oc.pgm.mutation.submodule.MutationModules.*; +import java.util.stream.Stream; public enum Mutation { - BLITZ (null, false), - UHC (null, false), - EXPLOSIVES (Explosives.class, true), - NO_FALL (null, false), - MOBS (null, false), - STRENGTH (Strength.class, true), - DOUBLE_JUMP (DoubleJump.class, true), - INVISIBILITY(Invisibility.class, true), - LIGHTNING (Lightning.class, true), - RAGE (Rage.class, true), - ELYTRA (Elytra.class, true); + BLITZ (null), + RAGE (RageMutation.class), + HARDCORE (HardcoreMutation.class), + JUMP (JumpMutation.class), + EXPLOSIVE (ExplosiveMutation.class), + ELYTRA (ElytraMutation.class), + PROJECTILE (ProjectileMutation.class), + ENCHANTMENT(EnchantmentMutation.class), + POTION (PotionMutation.class), + EQUESTRIAN (EquestrianMutation.class), + HEALTH (HealthMutation.class), + GLOW (GlowMutation.class), + STEALTH (StealthMutation.class), + ARMOR (ArmorMutation.class), + MOBS (MobsMutation.class), + LIGHTNING (LightningMutation.class), + BOMBER (BomberMutation.class), + APOCALYPSE (ApocalypseMutation.class); public static final String TYPE_KEY = "mutation.type."; public static final String DESCRIPTION_KEY = ".desc"; - /** - * The module class that handles this mutation. - */ - private final @Nullable Class clazz; + private final @Nullable Class loader; - /** - * Whether this mutation be changed during a match. - */ - private final boolean change; - - Mutation(@Nullable Class clazz, boolean change) { - this.clazz = clazz; - this.change = change; + Mutation(@Nullable Class loader) { + this.loader = loader; } - public static Mutation fromApi(MatchDoc.Mutation mutation) { - return values()[mutation.ordinal()]; - } - - public MatchDoc.Mutation toApi() { - return MatchDoc.Mutation.values()[ordinal()]; - } - - public Class getModuleClass() { - return clazz; - } - - public boolean isChangeable() { - return change; + public Class loader() { + return loader; } public String getName() { @@ -76,4 +79,14 @@ public enum Mutation { public static Function toComponent(final ChatColor color) { return mutation -> mutation.getComponent(color); } + + public static Stream 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(); + } + } + } diff --git a/PGM/src/main/java/tc/oc/pgm/mutation/MutationMatchModule.java b/PGM/src/main/java/tc/oc/pgm/mutation/MutationMatchModule.java index d99d82c..db3046f 100644 --- a/PGM/src/main/java/tc/oc/pgm/mutation/MutationMatchModule.java +++ b/PGM/src/main/java/tc/oc/pgm/mutation/MutationMatchModule.java @@ -1,24 +1,24 @@ package tc.oc.pgm.mutation; -import com.google.common.base.Predicate; import com.google.common.collect.Collections2; import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableSet; import com.google.common.collect.Sets; +import org.bukkit.event.entity.CreatureSpawnEvent; import tc.oc.commons.core.util.MapUtils; import tc.oc.commons.core.random.RandomUtils; import tc.oc.pgm.Config; import tc.oc.pgm.match.*; import tc.oc.pgm.mutation.command.MutationCommands; -import tc.oc.pgm.mutation.submodule.MutationModule; +import tc.oc.pgm.mutation.types.MutationModule; import tc.oc.commons.core.random.ImmutableWeightedRandomChooser; import java.util.*; import java.util.logging.Level; +import java.util.stream.Stream; import javax.inject.Inject; public class MutationMatchModule extends MatchModule { - // TODO: send remote mutation alerts via an AMQP message /** * Chance that a mutation event will occur. @@ -63,39 +63,36 @@ public class MutationMatchModule extends MatchModule { this.chance = options.chance; this.amount = options.amount; this.weightedSelector = new ImmutableWeightedRandomChooser<>(options.weights); - this.mutations = getDefaultMutations(); + this.mutations = mutationsDefault(); this.history = new HashSet<>(); this.modules = new HashMap<>(); } - public final ImmutableMap getMutations() { + public final ImmutableMap mutations() { return ImmutableMap.copyOf(mutations); } - public final ImmutableSet getActiveMutations() { - return ImmutableSet.copyOf(Collections2.filter(getMutations().keySet(), new Predicate() { - @Override - public boolean apply(Mutation mutation) { - return mutations.get(mutation); - } - })); + public final ImmutableSet mutationsActive() { + return ImmutableSet.copyOf(Collections2.filter(mutations().keySet(), mutations::get)); } - public final ImmutableSet getHistoricalMutations() { + public final ImmutableSet mutationsHistorical() { return ImmutableSet.copyOf(history); } - public final ImmutableSet getMutationModules() { + private Map mutationsDefault() { + Map defaults = new HashMap<>(); + MapUtils.putAll(defaults, Sets.newHashSet(Mutation.values()), false); + return defaults; + } + + public final ImmutableSet mutationModules() { return ImmutableSet.copyOf(modules.values()); } - @Override - public boolean shouldLoad() { - return Config.Mutations.enabled(); - } - @Override public void load() { + if(!Config.Mutations.enabled()) return; Random random = match.getRandom(); // Check if the api has any queued mutations Collection queuedMutations = mutationQueue.mutations(); @@ -113,7 +110,7 @@ public class MutationMatchModule extends MatchModule { mutationQueue.clear(); } // Load the mutation modules for this match - for(Mutation mutation : getActiveMutations()) { + for(Mutation mutation : mutationsActive()) { try { mutate(mutation); } catch (Throwable throwable) { @@ -124,43 +121,53 @@ public class MutationMatchModule extends MatchModule { @Override public void enable() { - for(MutationModule module : modules.values()) { - module.enable(match.isRunning()); - } + modules.values().forEach(MutationModule::enable); } @Override public void disable() { - for(MutationModule module : modules.values()) { - module.disable(match.isRunning()); - } + modules.values().forEach(MutationModule::disable); + } + + public void register(Mutation mutation, boolean load) { + mutations.put(mutation, load); } public void mutate(Mutation mutation) throws Throwable { - Class clazz = mutation.getModuleClass(); - if(clazz == null || (match.isRunning() && !mutation.isChangeable())) return; - MutationModule module = modules.containsKey(clazz) ? modules.get(clazz) : mutation.getModuleClass().getDeclaredConstructor(Match.class).newInstance(match); + Class loader = mutation.loader(); + if(loader == null) return; + MutationModule module = modules.containsKey(loader) ? modules.get(loader) : loader.getDeclaredConstructor(Match.class).newInstance(match); if(mutations.get(mutation)) { - module.enable(match.isRunning()); - modules.put(clazz, module); + module.enable(); + modules.put(loader, module); mutations.put(mutation, true); history.add(mutation); } else { - module.disable(match.isRunning()); - modules.remove(clazz); + module.disable(); + modules.remove(loader); mutations.put(mutation, false); } } - private Map getDefaultMutations() { - Map defaults = new HashMap<>(); - MapUtils.putAll(defaults, Sets.newHashSet(Mutation.values()), false); - return defaults; + public boolean enabled() { + return !mutationsActive().isEmpty(); } - public static boolean check(Match match, Mutation mutation) { - return Config.Mutations.enabled() && - match.hasMatchModule(MutationMatchModule.class) && - match.getMatchModule(MutationMatchModule.class).getActiveMutations().contains(mutation); + public boolean enabled(Mutation... mutations) { + return mutationsActive().stream().anyMatch(m1 -> Stream.of(mutations).anyMatch(m2 -> m2.equals(m1))); } + + public boolean allowMob(CreatureSpawnEvent.SpawnReason reason) { + switch(reason) { + case NATURAL: + case DEFAULT: + case CHUNK_GEN: + case JOCKEY: + case MOUNT: + return false; + default: + return true; + } + } + } diff --git a/PGM/src/main/java/tc/oc/pgm/mutation/MutationQueue.java b/PGM/src/main/java/tc/oc/pgm/mutation/MutationQueue.java index 2990517..1cc2fe4 100644 --- a/PGM/src/main/java/tc/oc/pgm/mutation/MutationQueue.java +++ b/PGM/src/main/java/tc/oc/pgm/mutation/MutationQueue.java @@ -6,7 +6,6 @@ import java.util.HashSet; import java.util.stream.Collectors; import javax.inject.Inject; -import com.google.common.collect.Collections2; import com.google.common.util.concurrent.ListenableFuture; import tc.oc.api.docs.Server; import tc.oc.api.docs.virtual.ServerDoc; @@ -28,12 +27,12 @@ public class MutationQueue { .getLocalServer() .queued_mutations() .stream() - .map(mutation -> Mutation.values()[mutation.ordinal()]) + .flatMap(Mutation::fromString) .collect(Collectors.toList()); } public ListenableFuture clear() { - return force(Collections.emptyList()); + return force(Collections.emptyList()); } public ListenableFuture removeAll(final Collection mutations) { @@ -53,6 +52,7 @@ public class MutationQueue { } private ListenableFuture force(final Collection 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())); } + } diff --git a/PGM/src/main/java/tc/oc/pgm/mutation/command/MutationCommands.java b/PGM/src/main/java/tc/oc/pgm/mutation/command/MutationCommands.java index cecae18..80856f0 100644 --- a/PGM/src/main/java/tc/oc/pgm/mutation/command/MutationCommands.java +++ b/PGM/src/main/java/tc/oc/pgm/mutation/command/MutationCommands.java @@ -2,20 +2,21 @@ package tc.oc.pgm.mutation.command; import java.util.Collection; import java.util.HashSet; +import java.util.logging.Level; import javax.inject.Inject; import com.google.common.collect.Collections2; import com.google.common.collect.Iterables; import com.google.common.collect.Sets; -import com.sk89q.minecraft.util.commands.Command; -import com.sk89q.minecraft.util.commands.CommandContext; -import com.sk89q.minecraft.util.commands.CommandException; -import com.sk89q.minecraft.util.commands.CommandPermissions; -import com.sk89q.minecraft.util.commands.NestedCommand; +import com.sk89q.minecraft.util.commands.*; import net.md_5.bungee.api.ChatColor; import net.md_5.bungee.api.chat.BaseComponent; import net.md_5.bungee.api.chat.TranslatableComponent; import org.bukkit.command.CommandSender; +import tc.oc.commons.bukkit.chat.PlayerComponent; +import tc.oc.commons.bukkit.chat.WarningComponent; +import tc.oc.commons.bukkit.nick.IdentityProvider; +import tc.oc.commons.core.chat.Audience; import tc.oc.minecraft.scheduler.SyncExecutor; import tc.oc.commons.bukkit.chat.Audiences; import tc.oc.commons.bukkit.chat.ListComponent; @@ -56,39 +57,42 @@ public class MutationCommands implements NestedCommands { private final SyncExecutor syncExecutor; private final Audiences audiences; private final MutationQueue mutationQueue; + private final IdentityProvider identityProvider; - @Inject - MutationCommands(SyncExecutor syncExecutor, Audiences audiences, MutationQueue mutationQueue) { + @Inject MutationCommands(SyncExecutor syncExecutor, Audiences audiences, MutationQueue mutationQueue, IdentityProvider identityProvider) { this.syncExecutor = syncExecutor; this.audiences = audiences; this.mutationQueue = mutationQueue; + this.identityProvider = identityProvider; } @Command( - aliases = {"enable", "add"}, + aliases = {"enable", "e"}, desc = "Adds a mutation to the upcoming match." + "You can use '?' as a wildcard or " + "'*' to use all.", usage = "", + flags = "q", min = 1, max = 1 ) @CommandPermissions(PERMISSION_SET) - public void enable(CommandContext args, CommandSender sender) throws CommandException { + public void enable(CommandContext args, CommandSender sender) throws CommandException, SuggestException { set(args, sender, true); } @Command( - aliases = {"disable", "remove"}, + aliases = {"disable", "d"}, desc = "Remove a mutation to the upcoming match." + "You can use '?' as a wildcard or " + "'*' to use all.", usage = "", + flags = "q", min = 1, max = 1 ) @CommandPermissions(PERMISSION_SET) - public void disable(CommandContext args, CommandSender sender) throws CommandException { + public void disable(CommandContext args, CommandSender sender) throws CommandException, SuggestException { set(args, sender, false); } @@ -105,8 +109,8 @@ public class MutationCommands implements NestedCommands { public void list(final CommandContext args, CommandSender sender) throws CommandException { MutationMatchModule module = verify(sender); final boolean queued = args.hasFlag('q'); - final Collection active = queued ? mutationQueue.mutations() : module.getActiveMutations(); - new Paginator() { + final Collection active = queued ? mutationQueue.mutations() : module.mutationsActive(); + new Paginator(Mutation.values().length / 2) { @Override protected BaseComponent title() { return new TranslatableComponent(queued ? "command.mutation.list.queued" : "command.mutation.list.current"); @@ -124,20 +128,26 @@ public class MutationCommands implements NestedCommands { return CommandUtils.getMatchModule(MutationMatchModule.class, sender); } - public void set(CommandContext args, final CommandSender sender, final boolean value) throws CommandException { + public void set(CommandContext args, final CommandSender sender, final boolean value) throws CommandException, SuggestException { final MutationMatchModule module = verify(sender); final Match match = module.getMatch(); String action = args.getString(0); + boolean queued = args.hasFlag('q') || match.isFinished(); // Mutations that *will* be added or removed final Collection mutations = new HashSet<>(); // Mutations that *are allowed* to be added or removed final Collection availableMutations = Sets.newHashSet(Mutation.values()); - final Collection queue = mutationQueue.mutations(); + final Collection queue = queued ? mutationQueue.mutations() : module.mutationsActive(); if(value) availableMutations.removeAll(queue); else availableMutations.retainAll(queue); // Check if all mutations have been enabled/disabled if((queue.size() == Mutation.values().length && value) || (queue.isEmpty() && !value)) { - throw newCommandException(sender, new TranslatableComponent(value ? "command.mutation.error.enabled" : "command.mutation.error.disabled")); + throw newCommandException(sender, new TranslatableComponent(value ? "command.mutation.error.enabled.all" : "command.mutation.error.disabled.all")); + } + // Suggest mutations for the user to choose + final SuggestionContext context = args.getSuggestionContext(); + if(context != null) { + context.suggestArgument(0, StringUtils.complete(context.getPrefix(), availableMutations.stream().map(mutation -> mutation.name().toLowerCase()))); } // Get which action the user wants to preform switch (action) { @@ -145,27 +155,50 @@ public class MutationCommands implements NestedCommands { case "?": mutations.add(Iterables.get(availableMutations, RandomUtils.safeNextInt(match.getRandom(), availableMutations.size()))); break; default: Mutation query = StringUtils.bestFuzzyMatch(action, Sets.newHashSet(Mutation.values()), 0.9); - if (query == null) { + if(query == null) { throw newCommandException(sender, new TranslatableComponent("command.mutation.error.find", action)); + } else if(value == queue.contains(query)) { + throw newCommandException(sender, new TranslatableComponent(value ? "command.mutation.error.enabled" : "command.mutation.error.disabled", query.getComponent(ChatColor.RED))); } else { mutations.add(query); } } - - // Send the queued changes off to the api - syncExecutor.callback( - value ? mutationQueue.mergeAll(mutations) - : mutationQueue.removeAll(mutations), - result -> { - audiences.get(sender).sendMessage(new Component(new TranslatableComponent( - message(false, value, mutations.size() == 1), - new ListComponent(Collections2.transform(mutations, Mutation.toComponent(ChatColor.AQUA))) - ), ChatColor.WHITE)); + Audience origin = audiences.get(sender); + Audience all = audiences.localServer(); + String message = message(!queued, value, mutations.size() == 1); + ListComponent changed = new ListComponent(Collections2.transform(mutations, Mutation.toComponent(ChatColor.AQUA))); + if(queued) { + // Send the queued changes off to the api + syncExecutor.callback( + value ? mutationQueue.mergeAll(mutations) + : mutationQueue.removeAll(mutations), + result -> { + origin.sendMessage(new Component(new TranslatableComponent(message, changed), ChatColor.WHITE)); + } + ); + } else { + // Make the changes immediately + for(Mutation mutation : mutations) { + try { + module.register(mutation, value); + module.mutate(mutation); + } catch(Throwable t) { + module.register(mutation, !value); + origin.sendMessage( + new WarningComponent( + "command.mutation.error.mutate", + mutation.getComponent(ChatColor.RED) + ) + ); + module.getLogger().log(Level.SEVERE, "Unable to enable/disable mutation", t); + return; + } } - ); + PlayerComponent player = new PlayerComponent(identityProvider.currentIdentity(sender)); + all.sendMessage(new Component(new TranslatableComponent(message, player, changed))); + } } - // TODO: force enabling mutations public String message(boolean now, boolean enable, boolean singular) { if(now) { if(enable) { diff --git a/PGM/src/main/java/tc/oc/pgm/mutation/submodule/KitMutationModule.java b/PGM/src/main/java/tc/oc/pgm/mutation/submodule/KitMutationModule.java deleted file mode 100644 index 965e2f7..0000000 --- a/PGM/src/main/java/tc/oc/pgm/mutation/submodule/KitMutationModule.java +++ /dev/null @@ -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; - } - -} diff --git a/PGM/src/main/java/tc/oc/pgm/mutation/submodule/MutationModule.java b/PGM/src/main/java/tc/oc/pgm/mutation/submodule/MutationModule.java deleted file mode 100644 index 0b9b5f0..0000000 --- a/PGM/src/main/java/tc/oc/pgm/mutation/submodule/MutationModule.java +++ /dev/null @@ -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; - } - -} diff --git a/PGM/src/main/java/tc/oc/pgm/mutation/submodule/MutationModules.java b/PGM/src/main/java/tc/oc/pgm/mutation/submodule/MutationModules.java deleted file mode 100644 index 68bb6ab..0000000 --- a/PGM/src/main/java/tc/oc/pgm/mutation/submodule/MutationModules.java +++ /dev/null @@ -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); - } - } - -} diff --git a/PGM/src/main/java/tc/oc/pgm/mutation/submodule/TargetableMutationModule.java b/PGM/src/main/java/tc/oc/pgm/mutation/submodule/TargetableMutationModule.java deleted file mode 100644 index 2097ed6..0000000 --- a/PGM/src/main/java/tc/oc/pgm/mutation/submodule/TargetableMutationModule.java +++ /dev/null @@ -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); - -} diff --git a/PGM/src/main/java/tc/oc/pgm/mutation/types/KitMutation.java b/PGM/src/main/java/tc/oc/pgm/mutation/types/KitMutation.java new file mode 100644 index 0000000..813022b --- /dev/null +++ b/PGM/src/main/java/tc/oc/pgm/mutation/types/KitMutation.java @@ -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 kits; + protected final Map> playerKits; + protected final Map> savedSlots; + protected final List 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 kits) { + kits.addAll(this.kits); + } + + /** + * Apply the kits to the player. + * @param player the player. + */ + public void apply(MatchPlayer player) { + List kits = new ArrayList<>(); + kits(player, kits); + playerKits.put(player, kits); + saved().forEach(slot -> { + slot.item(player.getInventory()).ifPresent(item -> { + Map 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 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(); + } + +} diff --git a/PGM/src/main/java/tc/oc/pgm/mutation/types/MutationModule.java b/PGM/src/main/java/tc/oc/pgm/mutation/types/MutationModule.java new file mode 100644 index 0000000..712073f --- /dev/null +++ b/PGM/src/main/java/tc/oc/pgm/mutation/types/MutationModule.java @@ -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 spawn(Location location, Class 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; + } + +} diff --git a/PGM/src/main/java/tc/oc/pgm/mutation/types/TargetMutation.java b/PGM/src/main/java/tc/oc/pgm/mutation/types/TargetMutation.java new file mode 100644 index 0000000..96f61f7 --- /dev/null +++ b/PGM/src/main/java/tc/oc/pgm/mutation/types/TargetMutation.java @@ -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 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 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(); + } + +} diff --git a/PGM/src/main/java/tc/oc/pgm/mutation/types/kit/ArmorMutation.java b/PGM/src/main/java/tc/oc/pgm/mutation/types/kit/ArmorMutation.java new file mode 100644 index 0000000..2f777bf --- /dev/null +++ b/PGM/src/main/java/tc/oc/pgm/mutation/types/kit/ArmorMutation.java @@ -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 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 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 saved() { + return Slot.Armor.armor(); + } + + @Override + public void disable() { + super.disable(); + weapons.clear(); + } + +} diff --git a/PGM/src/main/java/tc/oc/pgm/mutation/types/kit/ElytraMutation.java b/PGM/src/main/java/tc/oc/pgm/mutation/types/kit/ElytraMutation.java new file mode 100644 index 0000000..1040b95 --- /dev/null +++ b/PGM/src/main/java/tc/oc/pgm/mutation/types/kit/ElytraMutation.java @@ -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 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); + } + +} diff --git a/PGM/src/main/java/tc/oc/pgm/mutation/types/kit/EnchantmentMutation.java b/PGM/src/main/java/tc/oc/pgm/mutation/types/kit/EnchantmentMutation.java new file mode 100644 index 0000000..7675061 --- /dev/null +++ b/PGM/src/main/java/tc/oc/pgm/mutation/types/kit/EnchantmentMutation.java @@ -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 LEVELS_MAP = new ImmutableMap.Builder() + .put(1, 10) + .put(2, 3) + .put(3, 1) + .build(); + + final static ImmutableMap ARMOR_MAP = new ImmutableMap.Builder() + .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 BOOTS_MAP = new ImmutableMap.Builder() + .putAll(ARMOR_MAP) + .put(Enchantment.WATER_WORKER, 5) + .put(Enchantment.DEPTH_STRIDER, 3) + .put(Enchantment.FROST_WALKER, 1) + .build(); + + final static ImmutableMap WEAPONS_MAP = new ImmutableMap.Builder() + .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 TOOLS_MAP = new ImmutableMap.Builder() + .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 BOWS_MAP = new ImmutableMap.Builder() + .put(Enchantment.ARROW_DAMAGE, 10) + .put(Enchantment.ARROW_KNOCKBACK, 5) + .put(Enchantment.ARROW_FIRE, 1) + .build(); + + final static Map FISHING_MAP = new ImmutableMap.Builder() + .put(Enchantment.KNOCKBACK, 3) + .put(Enchantment.LURE, 1) + .build(); + + final static WeightedRandomChooser LEVELS = new ImmutableWeightedRandomChooser<>(LEVELS_MAP); + final static WeightedRandomChooser ARMOR = new ImmutableWeightedRandomChooser<>(ARMOR_MAP); + final static WeightedRandomChooser BOOTS = new ImmutableWeightedRandomChooser<>(BOOTS_MAP); + final static WeightedRandomChooser WEAPONS = new ImmutableWeightedRandomChooser<>(WEAPONS_MAP); + final static WeightedRandomChooser TOOLS = new ImmutableWeightedRandomChooser<>(TOOLS_MAP); + final static WeightedRandomChooser BOWS = new ImmutableWeightedRandomChooser<>(BOWS_MAP); + final static WeightedRandomChooser FISHING = new ImmutableWeightedRandomChooser<>(FISHING_MAP); + + Map>> 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 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> 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 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(); + } + +} diff --git a/PGM/src/main/java/tc/oc/pgm/mutation/types/kit/EquestrianMutation.java b/PGM/src/main/java/tc/oc/pgm/mutation/types/kit/EquestrianMutation.java new file mode 100644 index 0000000..61d4922 --- /dev/null +++ b/PGM/src/main/java/tc/oc/pgm/mutation/types/kit/EquestrianMutation.java @@ -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 TYPE_MAP = new ImmutableMap.Builder() + .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 ARMOR_MAP = new ImmutableMap.Builder() + .put(Material.SADDLE, 25) + .put(Material.GOLD_BARDING, 10) + .put(Material.IRON_BARDING, 5) + .put(Material.DIAMOND_BARDING, 1) + .build(); + + final static WeightedRandomChooser TYPES = new ImmutableWeightedRandomChooser<>(TYPE_MAP); + final static WeightedRandomChooser ARMOR = new ImmutableWeightedRandomChooser<>(ARMOR_MAP); + + final Map 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 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) 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()); + } + +} diff --git a/PGM/src/main/java/tc/oc/pgm/mutation/types/kit/ExplosiveMutation.java b/PGM/src/main/java/tc/oc/pgm/mutation/types/kit/ExplosiveMutation.java new file mode 100644 index 0000000..d71e969 --- /dev/null +++ b/PGM/src/main/java/tc/oc/pgm/mutation/types/kit/ExplosiveMutation.java @@ -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 RADIUS = Range.openClosed(0, 4); + + final WeakHashSet 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 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)); + } + +} diff --git a/PGM/src/main/java/tc/oc/pgm/mutation/types/kit/GlowMutation.java b/PGM/src/main/java/tc/oc/pgm/mutation/types/kit/GlowMutation.java new file mode 100644 index 0000000..dff9ce0 --- /dev/null +++ b/PGM/src/main/java/tc/oc/pgm/mutation/types/kit/GlowMutation.java @@ -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); + } + +} diff --git a/PGM/src/main/java/tc/oc/pgm/mutation/types/kit/HardcoreMutation.java b/PGM/src/main/java/tc/oc/pgm/mutation/types/kit/HardcoreMutation.java new file mode 100644 index 0000000..e5e5196 --- /dev/null +++ b/PGM/src/main/java/tc/oc/pgm/mutation/types/kit/HardcoreMutation.java @@ -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(); + } + +} diff --git a/PGM/src/main/java/tc/oc/pgm/mutation/types/kit/HealthMutation.java b/PGM/src/main/java/tc/oc/pgm/mutation/types/kit/HealthMutation.java new file mode 100644 index 0000000..afcdfba --- /dev/null +++ b/PGM/src/main/java/tc/oc/pgm/mutation/types/kit/HealthMutation.java @@ -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)); + } + +} diff --git a/PGM/src/main/java/tc/oc/pgm/mutation/types/kit/JumpMutation.java b/PGM/src/main/java/tc/oc/pgm/mutation/types/kit/JumpMutation.java new file mode 100644 index 0000000..489f280 --- /dev/null +++ b/PGM/src/main/java/tc/oc/pgm/mutation/types/kit/JumpMutation.java @@ -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); + } + +} diff --git a/PGM/src/main/java/tc/oc/pgm/mutation/types/kit/MobsMutation.java b/PGM/src/main/java/tc/oc/pgm/mutation/types/kit/MobsMutation.java new file mode 100644 index 0000000..9855a17 --- /dev/null +++ b/PGM/src/main/java/tc/oc/pgm/mutation/types/kit/MobsMutation.java @@ -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 TYPE_MAP = new ImmutableMap.Builder() + .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 TYPES = new ImmutableWeightedRandomChooser<>(TYPE_MAP); + + final static Range AMOUNT = Range.closed(1, 3); + + public MobsMutation(Match match) { + super(match, true); + } + + @Override + public void kits(MatchPlayer player, List 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)); + } + } + +} diff --git a/PGM/src/main/java/tc/oc/pgm/mutation/types/kit/NoFallMutation.java b/PGM/src/main/java/tc/oc/pgm/mutation/types/kit/NoFallMutation.java new file mode 100644 index 0000000..eb3363b --- /dev/null +++ b/PGM/src/main/java/tc/oc/pgm/mutation/types/kit/NoFallMutation.java @@ -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 RELATIONS = Stream.of(PlayerRelation.values()).collect(Collectors.toList()); + + Iterable 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(); + } + +} diff --git a/PGM/src/main/java/tc/oc/pgm/mutation/types/kit/PotionMutation.java b/PGM/src/main/java/tc/oc/pgm/mutation/types/kit/PotionMutation.java new file mode 100644 index 0000000..6a51913 --- /dev/null +++ b/PGM/src/main/java/tc/oc/pgm/mutation/types/kit/PotionMutation.java @@ -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 BAD_MAP = new ImmutableMap.Builder() + .put(PotionEffectType.WEAKNESS, 15) + .put(PotionEffectType.SLOW, 10) + .put(PotionEffectType.POISON, 10) + .put(PotionEffectType.BLINDNESS, 3) + .put(PotionEffectType.LEVITATION, 1) + .build(); + + final static ImmutableMap GOOD_MAP = new ImmutableMap.Builder() + .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 BOTTLE_BAD_MAP = new ImmutableMap.Builder() + .put(Material.SPLASH_POTION, 5) + .put(Material.LINGERING_POTION, 1) + .build(); + + final static ImmutableMap BOTTLE_GOOD_MAP = new ImmutableMap.Builder() + .putAll(BOTTLE_BAD_MAP) + .put(Material.POTION, 10) + .build(); + + + final static WeightedRandomChooser BAD = new ImmutableWeightedRandomChooser<>(BAD_MAP); + final static WeightedRandomChooser GOOD = new ImmutableWeightedRandomChooser<>(GOOD_MAP); + + final static WeightedRandomChooser BAD_BOTTLE = new ImmutableWeightedRandomChooser<>(BOTTLE_BAD_MAP); + final static WeightedRandomChooser GOOD_BOTTLE = new ImmutableWeightedRandomChooser<>(BOTTLE_GOOD_MAP); + + final static Range BAD_DURATION_RANGE = Range.closed(3, 10); + final static Range GOOD_DURATION_RANGE = Range.closed(10, 45); + + final static Range AMOUNT_RANGE = Range.closed(1, 3); + final static Range AMPLIFIER_RANGE = Range.closed(0, 2); + + public PotionMutation(Match match) { + super(match, false); + } + + @Override + public void kits(MatchPlayer player, List kits) { + super.kits(player, kits); + int numberOfPotions = entropy.randomInt(AMOUNT_RANGE); + for(int i = 0; i < numberOfPotions; i++) { + WeightedRandomChooser type; + WeightedRandomChooser material; + Range 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)); + } + } + +} diff --git a/PGM/src/main/java/tc/oc/pgm/mutation/types/kit/ProjectileMutation.java b/PGM/src/main/java/tc/oc/pgm/mutation/types/kit/ProjectileMutation.java new file mode 100644 index 0000000..60cf9cc --- /dev/null +++ b/PGM/src/main/java/tc/oc/pgm/mutation/types/kit/ProjectileMutation.java @@ -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 ENCHANTMENTS = new ImmutableWeightedRandomChooser<>(EnchantmentMutation.BOWS_MAP); + final static WeightedRandomChooser POTIONS = new ImmutableWeightedRandomChooser<>(PotionMutation.BAD_MAP); + + final static Range ENCHANT_RANGE = Range.closed(1, 3); + final static Range AMPLIFIER_RANGE = Range.closed(0, 3); + final static Range 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 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)); + } + } + +} diff --git a/PGM/src/main/java/tc/oc/pgm/mutation/types/kit/StealthMutation.java b/PGM/src/main/java/tc/oc/pgm/mutation/types/kit/StealthMutation.java new file mode 100644 index 0000000..9d02ec0 --- /dev/null +++ b/PGM/src/main/java/tc/oc/pgm/mutation/types/kit/StealthMutation.java @@ -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 saved() { + return Slot.Armor.armor(); + } + +} diff --git a/PGM/src/main/java/tc/oc/pgm/mutation/types/other/RageMutation.java b/PGM/src/main/java/tc/oc/pgm/mutation/types/other/RageMutation.java new file mode 100644 index 0000000..66988d2 --- /dev/null +++ b/PGM/src/main/java/tc/oc/pgm/mutation/types/other/RageMutation.java @@ -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; + } +} diff --git a/PGM/src/main/java/tc/oc/pgm/mutation/types/targetable/ApocalypseMutation.java b/PGM/src/main/java/tc/oc/pgm/mutation/types/targetable/ApocalypseMutation.java new file mode 100644 index 0000000..7733a81 --- /dev/null +++ b/PGM/src/main/java/tc/oc/pgm/mutation/types/targetable/ApocalypseMutation.java @@ -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 AMOUNT_MAP = new ImmutableMap.Builder() + .put(3, 25) + .put(5, 20) + .put(10, 15) + .put(20, 5) + .put(50, 1) + .build(); + + final static ImmutableMap STACK_MAP = new ImmutableMap.Builder() + .put(1, 100) + .put(2, 25) + .build(); + + final static ImmutableMap AERIAL_MAP = new ImmutableMap.Builder() + .put(EntityType.VEX, 5) + .put(EntityType.BLAZE, 1) + .build(); + + final static ImmutableMap GROUND_MAP = new ImmutableMap.Builder() + .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 RANGED_MAP = new ImmutableMap.Builder() + .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 FLYABLE_MAP = new ImmutableMap.Builder() + .putAll(AERIAL_MAP) + .put(EntityType.BAT, 10) + .build(); + + final static ImmutableMap PASSENGER_MAP = new ImmutableMap.Builder() + .putAll(RANGED_MAP) + .put(EntityType.CREEPER, 40) + .put(EntityType.PRIMED_TNT, 1) + .build(); + + final static ImmutableMap CUBE_MAP = new ImmutableMap.Builder() + .put(EntityType.SLIME, 10) + .put(EntityType.MAGMA_CUBE, 1) + .build(); + + final static WeightedRandomChooser AMOUNT = new ImmutableWeightedRandomChooser<>(AMOUNT_MAP); + final static WeightedRandomChooser STACK = new ImmutableWeightedRandomChooser<>(STACK_MAP); + final static WeightedRandomChooser AERIAL = new ImmutableWeightedRandomChooser<>(AERIAL_MAP); + final static WeightedRandomChooser GROUND = new ImmutableWeightedRandomChooser<>(GROUND_MAP); + final static WeightedRandomChooser RANGED = new ImmutableWeightedRandomChooser<>(RANGED_MAP); + final static WeightedRandomChooser FLYABLE = new ImmutableWeightedRandomChooser<>(FLYABLE_MAP); + final static WeightedRandomChooser PASSENGER = new ImmutableWeightedRandomChooser<>(PASSENGER_MAP); + final static WeightedRandomChooser 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 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 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 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 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 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 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(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(); + } + +} diff --git a/PGM/src/main/java/tc/oc/pgm/mutation/types/targetable/BomberMutation.java b/PGM/src/main/java/tc/oc/pgm/mutation/types/targetable/BomberMutation.java new file mode 100644 index 0000000..6cd8b40 --- /dev/null +++ b/PGM/src/main/java/tc/oc/pgm/mutation/types/targetable/BomberMutation.java @@ -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 TARGETS = Range.closed(1, 5); + final static Range HEIGHT = Range.closed(30, 60); + final static Range TICKS = Range.closed(10, 30); + + final WeakHashSet falling; + + public BomberMutation(Match match) { + super(match, FREQUENCY); + this.falling = new WeakHashSet<>(); + } + + @Override + public void execute(List 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); + } + +} diff --git a/PGM/src/main/java/tc/oc/pgm/mutation/types/targetable/LightningMutation.java b/PGM/src/main/java/tc/oc/pgm/mutation/types/targetable/LightningMutation.java new file mode 100644 index 0000000..583d931 --- /dev/null +++ b/PGM/src/main/java/tc/oc/pgm/mutation/types/targetable/LightningMutation.java @@ -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 TARGETS = Range.closed(1, 5); + final static Range STRIKES = Range.closed(0, 3); + + public LightningMutation(Match match) { + super(match, FREQUENCY); + } + + @Override + public void execute(List 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); + } + +} diff --git a/PGM/src/main/java/tc/oc/pgm/shield/ShieldParameters.java b/PGM/src/main/java/tc/oc/pgm/shield/ShieldParameters.java index 2b3d132..8f03821 100644 --- a/PGM/src/main/java/tc/oc/pgm/shield/ShieldParameters.java +++ b/PGM/src/main/java/tc/oc/pgm/shield/ShieldParameters.java @@ -14,4 +14,8 @@ public class ShieldParameters { this.maxHealth = maxHealth; this.rechargeDelay = rechargeDelay; } + + public ShieldParameters() { + this(DEFAULT_HEALTH, DEFAULT_DELAY); + } } diff --git a/PGM/src/main/java/tc/oc/pgm/spawns/states/Alive.java b/PGM/src/main/java/tc/oc/pgm/spawns/states/Alive.java index 4c1851a..6aad31d 100644 --- a/PGM/src/main/java/tc/oc/pgm/spawns/states/Alive.java +++ b/PGM/src/main/java/tc/oc/pgm/spawns/states/Alive.java @@ -21,8 +21,7 @@ import tc.oc.pgm.match.Competitor; import tc.oc.pgm.match.MatchPlayer; import tc.oc.pgm.match.ParticipantState; import tc.oc.pgm.mutation.MutationMatchModule; -import tc.oc.pgm.mutation.submodule.KitMutationModule; -import tc.oc.pgm.mutation.submodule.MutationModule; +import tc.oc.pgm.mutation.types.KitMutation; import tc.oc.pgm.spawns.Spawn; import tc.oc.pgm.spawns.events.ParticipantDespawnEvent; import tc.oc.pgm.spawns.events.ParticipantReleaseEvent; @@ -86,16 +85,10 @@ public class Alive extends Participating { match.module(KillRewardMatchModule.class).ifPresent(krmm -> krmm.giveDeadPlayerRewards(player)); // Apply kit injections from KitMutationModules - match.module(MutationMatchModule.class).ifPresent(mmm -> { - for(MutationModule module : mmm.getMutationModules()) { - if(module instanceof KitMutationModule) { - KitMutationModule kitModule = ((KitMutationModule) module); - for(Kit kit : kitModule.getKits()) { - player.facet(KitPlayerFacet.class).applyKit(kit, kitModule.isForceful()); - } - } - } - }); + match.module(MutationMatchModule.class) + .ifPresent(mmm -> mmm.mutationModules().stream() + .filter(mm -> mm instanceof KitMutation) + .forEach(mm -> ((KitMutation) mm).apply(player))); player.getBukkit().updateInventory(); diff --git a/PGM/src/test/java/tc/oc/pgm/mutation/MutationTest.java b/PGM/src/test/java/tc/oc/pgm/mutation/MutationTest.java deleted file mode 100644 index 7de5b55..0000000 --- a/PGM/src/test/java/tc/oc/pgm/mutation/MutationTest.java +++ /dev/null @@ -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())); - } - } - -} diff --git a/Util/bukkit/src/main/java/tc/oc/commons/bukkit/item/ItemBuilder.java b/Util/bukkit/src/main/java/tc/oc/commons/bukkit/item/ItemBuilder.java index fc8ff0c..29c5bdf 100644 --- a/Util/bukkit/src/main/java/tc/oc/commons/bukkit/item/ItemBuilder.java +++ b/Util/bukkit/src/main/java/tc/oc/commons/bukkit/item/ItemBuilder.java @@ -123,7 +123,7 @@ public class ItemBuilder> { */ public S lore(String lore) { meta().setLore(meta().hasLore() ? ListUtils.append(meta().getLore(), lore) - : Collections.singletonList(lore)); + : Collections.singletonList(lore)); return self(); } @@ -172,4 +172,14 @@ public class ItemBuilder> { meta(SkullMeta.class).setOwner(name, uuid, skin); return self(); } + + public S shareable(boolean yes) { + new BooleanItemTag("prevent-sharing", false).set(stack, !yes); + return self(); + } + + public S locked(boolean yes) { + new BooleanItemTag("locked", false).set(stack, !yes); + return self(); + } } diff --git a/Util/bukkit/src/main/java/tc/oc/commons/bukkit/item/ItemUtils.java b/Util/bukkit/src/main/java/tc/oc/commons/bukkit/item/ItemUtils.java index 1e40358..7c38ef4 100644 --- a/Util/bukkit/src/main/java/tc/oc/commons/bukkit/item/ItemUtils.java +++ b/Util/bukkit/src/main/java/tc/oc/commons/bukkit/item/ItemUtils.java @@ -7,6 +7,7 @@ import java.util.List; import java.util.Map; import java.util.Optional; import java.util.Set; +import java.util.stream.Stream; import javax.annotation.Nullable; import com.google.common.collect.ImmutableSet; @@ -100,6 +101,24 @@ public class ItemUtils { return isNothing(stack) ? Optional.empty() : Optional.of(stack); } + private static final String[] TOOLS = {"axe", "hoe", "spade"}; + + public static boolean isTool(ItemStack stack) { + return isTool(stack.getData()); + } + + public static boolean isTool(MaterialData item) { + return Stream.of(TOOLS).anyMatch(query -> item.getItemType().name().toLowerCase().contains(query)); + } + + public static boolean isArmor(ItemStack stack) { + return isArmor(stack.getData()); + } + + public static boolean isArmor(MaterialData item) { + return !Bukkit.getItemFactory().getAttributeModifiers(item, Attribute.GENERIC_ARMOR).isEmpty(); + } + public static boolean isWeapon(ItemStack stack) { return isWeapon(stack.getData()); } diff --git a/Util/core/src/main/java/tc/oc/commons/core/collection/ForwardingSet.java b/Util/core/src/main/java/tc/oc/commons/core/collection/ForwardingSet.java new file mode 100644 index 0000000..0371afb --- /dev/null +++ b/Util/core/src/main/java/tc/oc/commons/core/collection/ForwardingSet.java @@ -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 implements Set { + + protected final Map backend; + + public ForwardingSet(Map 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 iterator() { + return backend.keySet().iterator(); + } + + @Override + public Object[] toArray() { + return backend.keySet().toArray(); + } + + @Override + public 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 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(); + } + +} diff --git a/Util/core/src/main/java/tc/oc/commons/core/collection/WeakHashSet.java b/Util/core/src/main/java/tc/oc/commons/core/collection/WeakHashSet.java new file mode 100644 index 0000000..6cd6084 --- /dev/null +++ b/Util/core/src/main/java/tc/oc/commons/core/collection/WeakHashSet.java @@ -0,0 +1,17 @@ +package tc.oc.commons.core.collection; + +import java.util.Collection; +import java.util.WeakHashMap; + +public class WeakHashSet extends ForwardingSet { + + public WeakHashSet() { + super(new WeakHashMap<>()); + } + + public WeakHashSet(Collection initial) { + this(); + this.addAll(initial); + } + +} diff --git a/Util/core/src/main/java/tc/oc/commons/core/random/Entropy.java b/Util/core/src/main/java/tc/oc/commons/core/random/Entropy.java index b172658..3640852 100644 --- a/Util/core/src/main/java/tc/oc/commons/core/random/Entropy.java +++ b/Util/core/src/main/java/tc/oc/commons/core/random/Entropy.java @@ -3,6 +3,7 @@ package tc.oc.commons.core.random; import java.util.List; import com.google.common.collect.Iterables; +import com.google.common.collect.Lists; import com.google.common.collect.Range; import tc.oc.commons.core.util.Ranges; @@ -26,6 +27,10 @@ public interface Entropy { return min + (int) ((randomLong() & 0xffffffffL) * delta / 0x100000000L); } + default T randomElement(T... array) { + return randomElement(Lists.newArrayList(array)); + } + default T randomElement(Iterable iterable) { return Iterables.get(iterable, randomInt(Range.closedOpen(0, Iterables.size(iterable)))); } diff --git a/Util/core/src/main/java/tc/oc/commons/core/random/RandomUtils.java b/Util/core/src/main/java/tc/oc/commons/core/random/RandomUtils.java index caf9983..0859be3 100644 --- a/Util/core/src/main/java/tc/oc/commons/core/random/RandomUtils.java +++ b/Util/core/src/main/java/tc/oc/commons/core/random/RandomUtils.java @@ -3,6 +3,7 @@ package tc.oc.commons.core.random; import java.util.Random; import com.google.common.collect.Iterables; +import org.apache.commons.lang.math.Fraction; public class RandomUtils { @@ -13,4 +14,8 @@ public class RandomUtils { public static T element(Random random, Iterable collection) { return Iterables.get(collection, safeNextInt(random, Iterables.size(collection))); } + + public static boolean nextBoolean(Random random, Fraction chance) { + return random.nextDouble() < chance.doubleValue(); + } }