From 69d41c07bfa94183071341104401c354bd56197f Mon Sep 17 00:00:00 2001 From: Electroid Date: Fri, 31 Mar 2017 16:20:27 -0700 Subject: [PATCH] Blitz overhaul --- .../commons/bukkit/commands/CommandUtils.java | 26 +- .../i18n/templates/commons/Commons.properties | 1 + .../i18n/templates/pgm/PGMMessages.properties | 21 ++ .../main/i18n/templates/pgm/PGMUI.properties | 6 - .../java/tc/oc/pgm/MapModulesManifest.java | 4 +- .../java/tc/oc/pgm/PGMModulesManifest.java | 2 + .../java/tc/oc/pgm/api/MatchDocument.java | 8 +- .../pgm/api/MatchPublishingMatchModule.java | 16 +- .../java/tc/oc/pgm/blitz/BlitzConfig.java | 31 -- .../main/java/tc/oc/pgm/blitz/BlitzEvent.java | 34 +++ .../java/tc/oc/pgm/blitz/BlitzManifest.java | 17 ++ .../tc/oc/pgm/blitz/BlitzMatchModule.java | 267 +++++------------ .../tc/oc/pgm/blitz/BlitzMatchModuleImpl.java | 283 ++++++++++++++++++ .../tc/oc/pgm/blitz/BlitzMatchResult.java | 2 + .../java/tc/oc/pgm/blitz/BlitzModule.java | 69 ++--- .../java/tc/oc/pgm/blitz/BlitzParser.java | 72 +++++ .../java/tc/oc/pgm/blitz/BlitzProperties.java | 64 ++++ .../oc/pgm/blitz/BlitzVictoryCondition.java | 22 ++ .../java/tc/oc/pgm/blitz/LifeManager.java | 45 --- PGM/src/main/java/tc/oc/pgm/blitz/Lives.java | 73 +++++ .../main/java/tc/oc/pgm/blitz/LivesBase.java | 113 +++++++ .../main/java/tc/oc/pgm/blitz/LivesEvent.java | 32 ++ .../java/tc/oc/pgm/blitz/LivesIndividual.java | 46 +++ .../main/java/tc/oc/pgm/blitz/LivesKit.java | 20 ++ .../main/java/tc/oc/pgm/blitz/LivesTeam.java | 39 +++ .../oc/pgm/countdowns/CountdownContext.java | 4 + .../ghostsquadron/GhostSquadronModule.java | 2 +- .../inventory/ViewInventoryMatchModule.java | 10 +- .../tc/oc/pgm/kits/KitDefinitionParser.java | 6 + .../pgm/mapratings/MapRatingsMatchModule.java | 7 +- .../java/tc/oc/pgm/mutation/Mutation.java | 3 +- .../mutation/types/other/BlitzMutation.java | 47 +++ .../tc/oc/pgm/picker/PickerMatchModule.java | 17 +- .../main/java/tc/oc/pgm/rage/RageModule.java | 2 +- .../java/tc/oc/pgm/score/ScoreModule.java | 4 +- .../oc/pgm/scoreboard/SidebarMatchModule.java | 37 ++- .../java/tc/oc/pgm/teams/TeamFactory.java | 6 + .../anxuiz/tourney/listener/TeamListener.java | 5 +- .../commons/core/formatting/StringUtils.java | 4 + 39 files changed, 1103 insertions(+), 364 deletions(-) delete mode 100644 PGM/src/main/java/tc/oc/pgm/blitz/BlitzConfig.java create mode 100644 PGM/src/main/java/tc/oc/pgm/blitz/BlitzEvent.java create mode 100644 PGM/src/main/java/tc/oc/pgm/blitz/BlitzManifest.java create mode 100644 PGM/src/main/java/tc/oc/pgm/blitz/BlitzMatchModuleImpl.java create mode 100644 PGM/src/main/java/tc/oc/pgm/blitz/BlitzParser.java create mode 100644 PGM/src/main/java/tc/oc/pgm/blitz/BlitzProperties.java create mode 100644 PGM/src/main/java/tc/oc/pgm/blitz/BlitzVictoryCondition.java delete mode 100644 PGM/src/main/java/tc/oc/pgm/blitz/LifeManager.java create mode 100644 PGM/src/main/java/tc/oc/pgm/blitz/Lives.java create mode 100644 PGM/src/main/java/tc/oc/pgm/blitz/LivesBase.java create mode 100644 PGM/src/main/java/tc/oc/pgm/blitz/LivesEvent.java create mode 100644 PGM/src/main/java/tc/oc/pgm/blitz/LivesIndividual.java create mode 100644 PGM/src/main/java/tc/oc/pgm/blitz/LivesKit.java create mode 100644 PGM/src/main/java/tc/oc/pgm/blitz/LivesTeam.java create mode 100644 PGM/src/main/java/tc/oc/pgm/mutation/types/other/BlitzMutation.java diff --git a/Commons/bukkit/src/main/java/tc/oc/commons/bukkit/commands/CommandUtils.java b/Commons/bukkit/src/main/java/tc/oc/commons/bukkit/commands/CommandUtils.java index dda3716..10de6e3 100644 --- a/Commons/bukkit/src/main/java/tc/oc/commons/bukkit/commands/CommandUtils.java +++ b/Commons/bukkit/src/main/java/tc/oc/commons/bukkit/commands/CommandUtils.java @@ -124,14 +124,14 @@ public abstract class CommandUtils { } public static Duration getDuration(CommandContext args, int index, Duration def) throws CommandException { - return args.argsLength() > index ? getDuration(args.getString(index), null) : def; + return getDuration(args.getString(index, null), def); } - public static @Nullable Duration getDuration(@Nullable String text) throws CommandException { + public static @Nullable Duration getDuration(String text) throws CommandException { return getDuration(text, null); } - public static Duration getDuration(@Nullable String text, Duration def) throws CommandException { + public static Duration getDuration(String text, Duration def) throws CommandException { if(text == null) { return def; } else { @@ -143,6 +143,26 @@ public abstract class CommandUtils { } } + public static @Nullable > E getEnum(CommandContext args, CommandSender sender, int index, Class type) throws CommandException { + return getEnum(args, sender, index, type, null); + } + + public static > E getEnum(CommandContext args, CommandSender sender, int index, Class type, E def) throws CommandException { + return getEnum(args.getString(index, null), sender, type, def); + } + + public static > E getEnum(String text, CommandSender sender, Class type, E def) throws CommandException { + if(text == null) { + return def; + } else { + try { + return Enum.valueOf(type, text.toUpperCase().replace(' ', '_')); + } catch(IllegalArgumentException e) { + throw newCommandException(sender, new TranslatableComponent("command.error.invalidEnum", text)); + } + } + } + public static String getDisplayName(CommandSender target) { return getDisplayName(target, null); } diff --git a/Commons/core/src/main/i18n/templates/commons/Commons.properties b/Commons/core/src/main/i18n/templates/commons/Commons.properties index d3966ab..ed2d2af 100644 --- a/Commons/core/src/main/i18n/templates/commons/Commons.properties +++ b/Commons/core/src/main/i18n/templates/commons/Commons.properties @@ -10,6 +10,7 @@ command.admin.cancelRestart.noActionTaken = No active or queued restart countdow command.error.notEnoughArguments = Not enough arguments command.error.unexpectedArgument = Unexpected argument '{0}' command.error.invalidTimePeriod = Invalid time period '{0}' +command.error.invalidEnum = Invalid enum option '{0}' command.error.invalidNumber = Invalid number '{0}' command.error.invalidPage = There is no page {0}. Pages run from 1 to {1}. command.error.emptyResult = Empty result diff --git a/Commons/core/src/main/i18n/templates/pgm/PGMMessages.properties b/Commons/core/src/main/i18n/templates/pgm/PGMMessages.properties index e509e6a..6025635 100644 --- a/Commons/core/src/main/i18n/templates/pgm/PGMMessages.properties +++ b/Commons/core/src/main/i18n/templates/pgm/PGMMessages.properties @@ -248,5 +248,26 @@ item.locked = This item cannot be removed from its slot stats.hotbar = {0} kills ({1} streak) {2} deaths {3} K/D + announce.online = Announced server as online announce.offline = Announced server as offline + +blitz.countdown = Blitz mode will activate in {0} +blitz.activated = Blitz mode + +blitz.active = Blitz mode is already enabled +blitz.queued = Blitz mode is already queued to activate + +lives.change.gained.singular = You gained {0} more life +lives.change.gained.plural = You gained {0} more lives +lives.change.lost.singular = You lost {0} life +lives.change.lost.plural = You lost {0} lives + +lives.remaining.individual.singular = You have {0} life left +lives.remaining.individual.plural = You have {0} lives left +lives.remaining.team.singular = Your team has {0} life left +lives.remaining.team.plural = Your team has {0} lives left + +lives.status.eliminated = eliminated +lives.status.alive = {0} alive +lives.status.lives = {0} lives diff --git a/Commons/core/src/main/i18n/templates/pgm/PGMUI.properties b/Commons/core/src/main/i18n/templates/pgm/PGMUI.properties index f53aa6f..599e99a 100644 --- a/Commons/core/src/main/i18n/templates/pgm/PGMUI.properties +++ b/Commons/core/src/main/i18n/templates/pgm/PGMUI.properties @@ -230,12 +230,6 @@ match.score.scorebox.individual = {0} scored {1} points.singularCompound = {0} point points.pluralCompound = {0} points -# {0} = singular / plural substitution -match.blitz.livesRemaining.message = You have {0} remaining. -match.blitz.livesRemaining.singularLives = 1 life -# {0} = number of lives -match.blitz.livesRemaining.pluralLives = {0} lives - # {0} = time left in match match.timeRemaining = Time Remaining: {0} diff --git a/PGM/src/main/java/tc/oc/pgm/MapModulesManifest.java b/PGM/src/main/java/tc/oc/pgm/MapModulesManifest.java index a01aa27..01b1d9b 100644 --- a/PGM/src/main/java/tc/oc/pgm/MapModulesManifest.java +++ b/PGM/src/main/java/tc/oc/pgm/MapModulesManifest.java @@ -1,7 +1,6 @@ package tc.oc.pgm; import tc.oc.commons.core.inject.HybridManifest; -import tc.oc.pgm.blitz.BlitzModule; import tc.oc.pgm.blockdrops.BlockDropsModule; import tc.oc.pgm.crafting.CraftingModule; import tc.oc.pgm.eventrules.EventRuleModule; @@ -13,6 +12,7 @@ import tc.oc.pgm.goals.GoalModule; import tc.oc.pgm.hunger.HungerModule; import tc.oc.pgm.itemmeta.ItemModifyModule; import tc.oc.pgm.killreward.KillRewardModule; +import tc.oc.pgm.blitz.BlitzModule; import tc.oc.pgm.map.MapModuleFactory; import tc.oc.pgm.map.StaticMethodMapModuleFactory; import tc.oc.pgm.modules.DiscardPotionBottlesModule; @@ -55,6 +55,7 @@ public class MapModulesManifest extends HybridManifest { install(new ProjectileModule.Factory()); install(new SpawnModule.Factory()); install(new TimeLimitModule.Factory()); + install(new BlitzModule.Factory()); // MapModules with static parse methods install(new StaticMethodMapModuleFactory(){}); @@ -68,7 +69,6 @@ public class MapModulesManifest extends HybridManifest { install(new StaticMethodMapModuleFactory(){}); install(new StaticMethodMapModuleFactory(){}); install(new StaticMethodMapModuleFactory(){}); - install(new StaticMethodMapModuleFactory(){}); install(new StaticMethodMapModuleFactory(){}); install(new StaticMethodMapModuleFactory(){}); install(new StaticMethodMapModuleFactory(){}); diff --git a/PGM/src/main/java/tc/oc/pgm/PGMModulesManifest.java b/PGM/src/main/java/tc/oc/pgm/PGMModulesManifest.java index ab00d3e..673a31a 100644 --- a/PGM/src/main/java/tc/oc/pgm/PGMModulesManifest.java +++ b/PGM/src/main/java/tc/oc/pgm/PGMModulesManifest.java @@ -12,6 +12,7 @@ import tc.oc.pgm.flag.FlagManifest; import tc.oc.pgm.itemkeep.ItemKeepManifest; import tc.oc.pgm.kits.KitManifest; import tc.oc.pgm.lane.LaneManifest; +import tc.oc.pgm.blitz.BlitzManifest; import tc.oc.pgm.loot.LootManifest; import tc.oc.pgm.modes.ObjectiveModeManifest; import tc.oc.pgm.physics.PlayerPhysicsManifest; @@ -62,5 +63,6 @@ public class PGMModulesManifest extends HybridManifest { install(new StatsManifest()); install(new RaindropManifest()); install(new ObjectiveModeManifest()); + install(new BlitzManifest()); } } 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 5254722..d1f84ea 100644 --- a/PGM/src/main/java/tc/oc/pgm/api/MatchDocument.java +++ b/PGM/src/main/java/tc/oc/pgm/api/MatchDocument.java @@ -19,9 +19,9 @@ import tc.oc.api.docs.virtual.Model; import tc.oc.api.docs.virtual.ServerDoc; import tc.oc.commons.core.stream.Collectors; import tc.oc.commons.core.util.Streams; -import tc.oc.pgm.blitz.BlitzMatchModule; import tc.oc.pgm.goals.GoalMatchModule; import tc.oc.pgm.join.JoinMatchModule; +import tc.oc.pgm.blitz.BlitzMatchModule; import tc.oc.pgm.match.Competitor; import tc.oc.pgm.match.Match; import tc.oc.pgm.match.MatchState; @@ -36,10 +36,10 @@ public class MatchDocument extends AbstractModel implements MatchDoc { private final VictoryMatchModule victory; private final Optional mutations; private final Optional goals; - private final Optional blitz; + private final BlitzMatchModule blitz; private final Optional join; - @Inject MatchDocument(ServerDoc.Identity localServer, MapDoc map, Match match, VictoryMatchModule victory, Optional mutations, Optional goals, Optional blitz, Optional join) { + @Inject MatchDocument(ServerDoc.Identity localServer, MapDoc map, Match match, VictoryMatchModule victory, Optional mutations, Optional goals, BlitzMatchModule blitz, Optional join) { this.match = match; this.localServer = localServer; this.map = map; @@ -87,7 +87,7 @@ public class MatchDocument extends AbstractModel implements MatchDoc { @Override public boolean join_mid_match() { - return !blitz.isPresent() && join.isPresent() && join.get().canJoinMid(); + return !blitz.activated() && join.isPresent() && join.get().canJoinMid(); } @Override public MapDoc map() { diff --git a/PGM/src/main/java/tc/oc/pgm/api/MatchPublishingMatchModule.java b/PGM/src/main/java/tc/oc/pgm/api/MatchPublishingMatchModule.java index 03806c0..ea84d87 100644 --- a/PGM/src/main/java/tc/oc/pgm/api/MatchPublishingMatchModule.java +++ b/PGM/src/main/java/tc/oc/pgm/api/MatchPublishingMatchModule.java @@ -18,6 +18,7 @@ import tc.oc.pgm.events.SetNextMapEvent; import tc.oc.pgm.ffa.events.MatchResizeEvent; import tc.oc.pgm.goals.events.GoalCompleteEvent; import tc.oc.pgm.goals.events.GoalTouchEvent; +import tc.oc.pgm.blitz.BlitzEvent; import tc.oc.pgm.match.Match; import tc.oc.pgm.match.MatchManager; import tc.oc.pgm.match.MatchModule; @@ -62,19 +63,25 @@ public class MatchPublishingMatchModule extends MatchModule implements Listener private final MinecraftService minecraftService; private final UpdateService matchService; private final MatchDoc matchDocument; + private final BlitzMatchModule blitz; private int initialParticipants; // Number of participants at match start (for blitz) - @Inject MatchPublishingMatchModule(Match match, MatchManager mm, MinecraftService minecraftService, UpdateService matchService, MatchDoc matchDocument) { + @Inject MatchPublishingMatchModule(Match match, MatchManager mm, MinecraftService minecraftService, UpdateService matchService, MatchDoc matchDocument, BlitzMatchModule blitz) { super(match); this.mm = mm; this.minecraftService = minecraftService; this.matchService = matchService; this.matchDocument = matchDocument; + this.blitz = blitz; } public boolean isBlitz() { - return match.hasMatchModule(BlitzMatchModule.class); + return blitz.activated(); + } + + private void countPlayers() { + this.initialParticipants = getMatch().getParticipatingPlayers().size(); } private void update() { @@ -90,7 +97,7 @@ public class MatchPublishingMatchModule extends MatchModule implements Listener @Override public void enable() { super.enable(); - this.initialParticipants = getMatch().getParticipatingPlayers().size(); + countPlayers(); update(); } @@ -111,7 +118,7 @@ public class MatchPublishingMatchModule extends MatchModule implements Listener @EventHandler(priority = EventPriority.MONITOR) public void onPartyChange(final PlayerPartyChangeEvent event) { if(!event.getMatch().hasStarted()) { - this.initialParticipants = event.getMatch().getParticipatingPlayers().size(); + countPlayers(); } update(); } @@ -122,4 +129,5 @@ public class MatchPublishingMatchModule extends MatchModule implements Listener @EventHandler(priority = EventPriority.MONITOR) public void onTeamResize(TeamResizeEvent event) { update(); } @EventHandler(priority = EventPriority.MONITOR) public void onGoalComplete(GoalCompleteEvent event) { update(); } @EventHandler(priority = EventPriority.MONITOR) public void onGoalTouch(GoalTouchEvent event) { update(); } + @EventHandler(priority = EventPriority.MONITOR) public void onBlitzEnable(BlitzEvent event) { countPlayers(); update(); } } diff --git a/PGM/src/main/java/tc/oc/pgm/blitz/BlitzConfig.java b/PGM/src/main/java/tc/oc/pgm/blitz/BlitzConfig.java deleted file mode 100644 index ecbe531..0000000 --- a/PGM/src/main/java/tc/oc/pgm/blitz/BlitzConfig.java +++ /dev/null @@ -1,31 +0,0 @@ -package tc.oc.pgm.blitz; - -import static com.google.common.base.Preconditions.checkArgument; - -/** - * Represents information needed to run the Blitz game type. - */ -public class BlitzConfig { - public BlitzConfig(int lives, boolean broadcastLives) { - checkArgument(lives > 0, "lives must be greater than zero"); - - this.lives = lives; - this.broadcastLives = broadcastLives; - } - - /** - * Number of lives a player has during the match. - * - * @return Number of lives - */ - public int getNumLives() { - return this.lives; - } - - public boolean getBroadcastLives() { - return this.broadcastLives; - } - - final int lives; - final boolean broadcastLives; -} diff --git a/PGM/src/main/java/tc/oc/pgm/blitz/BlitzEvent.java b/PGM/src/main/java/tc/oc/pgm/blitz/BlitzEvent.java new file mode 100644 index 0000000..874a2d7 --- /dev/null +++ b/PGM/src/main/java/tc/oc/pgm/blitz/BlitzEvent.java @@ -0,0 +1,34 @@ +package tc.oc.pgm.blitz; + +import org.bukkit.event.HandlerList; +import tc.oc.pgm.events.MatchEvent; +import tc.oc.pgm.match.Match; + +/** + * Called when {@link BlitzMatchModule} is enabled or disabled, even during a match. + */ +public class BlitzEvent extends MatchEvent { + + private final BlitzMatchModule blitz; + + public BlitzEvent(Match match, BlitzMatchModule blitz) { + super(match); + this.blitz = blitz; + } + + public BlitzMatchModule blitz() { + return blitz; + } + + private static final HandlerList handlers = new HandlerList(); + + public static HandlerList getHandlerList() { + return handlers; + } + + @Override + public HandlerList getHandlers() { + return handlers; + } + +} diff --git a/PGM/src/main/java/tc/oc/pgm/blitz/BlitzManifest.java b/PGM/src/main/java/tc/oc/pgm/blitz/BlitzManifest.java new file mode 100644 index 0000000..cba923c --- /dev/null +++ b/PGM/src/main/java/tc/oc/pgm/blitz/BlitzManifest.java @@ -0,0 +1,17 @@ +package tc.oc.pgm.blitz; + +import tc.oc.commons.core.inject.HybridManifest; +import tc.oc.pgm.map.inject.MapBinders; +import tc.oc.pgm.match.inject.MatchBinders; +import tc.oc.pgm.match.inject.MatchModuleFixtureManifest; + +public class BlitzManifest extends HybridManifest implements MapBinders, MatchBinders { + + @Override + protected void configure() { + bindRootElementParser(BlitzProperties.class).to(BlitzParser.class); + bind(BlitzMatchModule.class).to(BlitzMatchModuleImpl.class); + install(new MatchModuleFixtureManifest(){}); + } + +} 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 32a461f..81805dc 100644 --- a/PGM/src/main/java/tc/oc/pgm/blitz/BlitzMatchModule.java +++ b/PGM/src/main/java/tc/oc/pgm/blitz/BlitzMatchModule.java @@ -1,210 +1,93 @@ package tc.oc.pgm.blitz; -import java.util.Set; -import java.util.UUID; -import javax.annotation.Nullable; - -import com.google.api.client.util.Sets; -import com.google.common.collect.ImmutableSet; -import net.md_5.bungee.api.ChatColor; -import net.md_5.bungee.api.chat.TranslatableComponent; -import org.bukkit.Effect; -import org.bukkit.Location; -import org.bukkit.World; -import org.bukkit.event.EventHandler; -import org.bukkit.event.EventPriority; -import org.bukkit.event.Listener; -import org.bukkit.util.Vector; -import tc.oc.commons.core.chat.Component; -import tc.oc.commons.core.chat.Components; -import tc.oc.pgm.events.ListenerScope; -import tc.oc.pgm.events.MatchPlayerDeathEvent; -import tc.oc.pgm.events.PartyAddEvent; -import tc.oc.pgm.events.PlayerLeavePartyEvent; -import tc.oc.pgm.join.JoinDenied; -import tc.oc.pgm.join.JoinHandler; -import tc.oc.pgm.join.JoinMatchModule; -import tc.oc.pgm.join.JoinMethod; -import tc.oc.pgm.join.JoinRequest; -import tc.oc.pgm.join.JoinResult; -import tc.oc.pgm.listeners.MatchAnnouncer; import tc.oc.pgm.match.Competitor; -import tc.oc.pgm.match.Match; -import tc.oc.pgm.match.MatchModule; import tc.oc.pgm.match.MatchPlayer; -import tc.oc.pgm.match.MatchScope; -import tc.oc.pgm.mutation.Mutation; -import tc.oc.pgm.mutation.MutationMatchModule; -import tc.oc.pgm.spawns.events.ParticipantReleaseEvent; -import tc.oc.pgm.victory.AbstractVictoryCondition; -import tc.oc.pgm.victory.VictoryCondition; -import tc.oc.pgm.victory.VictoryMatchModule; -@ListenerScope(MatchScope.LOADED) -public class BlitzMatchModule extends MatchModule implements Listener, JoinHandler { +import javax.annotation.Nullable; +import java.util.Optional; - final BlitzConfig config; - public final LifeManager lifeManager; - private final Set eliminatedPlayers = Sets.newHashSet(); - private int maxCompetitors; // Maximum number of non-empty Competitors that have been in the match at once +public interface BlitzMatchModule { - public class BlitzVictoryCondition extends AbstractVictoryCondition { - public BlitzVictoryCondition() { - super(Priority.BLITZ, new BlitzMatchResult()); - } + /** + * Get the properties for the blitz module. + * + * It may change during a match from {@link #activate(BlitzProperties)}. + */ + BlitzProperties properties(); - @Override public boolean isCompleted() { - // At least one competitor must be eliminated before the match can end. - // This allows maps to be tested with one or zero competitors present. - final int count = remainingCompetitors(); - return count <= 1 && count < maxCompetitors; - } + /** + * Is the blitz module *really* activated? + * + * Since the module is always loaded on the chance it should + * be activated during a match, it is only actively enforcing + * its rules when this returns true. + */ + boolean activated(); + + /** + * Activate the blitz module with a new set of properties. + * + * If the properties are null, it will default to its current + * {@link #properties()}. + */ + void activate(@Nullable BlitzProperties properties); + + default void activate() { + activate(null); } - final VictoryCondition victoryCondition = new BlitzVictoryCondition(); + /** + * Deactivate the blitz module by clearing all of its data. + * + * The module can be activated and deactivated as many times + * as you need. + */ + void deactivate(); - public BlitzMatchModule(Match match, BlitzConfig config) { - super(match); - this.config = match.module(MutationMatchModule.class).get().enabled(Mutation.BLITZ) ? new BlitzConfig(1, true) : config; - this.lifeManager = new LifeManager(this.config.getNumLives()); - } + /** + * Increment the number of lives for a player if + * {@link #eliminated(MatchPlayer)} is false. + * + * If notify is true, the player will get a message + * explaining how much lives they gained or lost. + * + * If immediate is true and the player's lives are empty, + * {@link #eliminate(MatchPlayer)} will be called and the + * player will be out of the game. + * + * @return Whether the player is now {@link #eliminated(MatchPlayer)}. + */ + boolean increment(MatchPlayer player, int lives, boolean notify, boolean immediate); - @Override - public boolean shouldLoad() { - return super.shouldLoad() && config.lives != Integer.MAX_VALUE; - } + /** + * Is the player eliminated from the match? + * + * This value can change back to false if the player were + * forced onto another team after being eliminated. + */ + boolean eliminated(MatchPlayer player); - @Override - public void load() { - super.load(); - match.needMatchModule(JoinMatchModule.class).registerHandler(this); - match.needMatchModule(VictoryMatchModule.class).setVictoryCondition(victoryCondition); - } + /** + * Eliminate the player from this match by moving + * them to the default party and preventing them from respawning. + */ + void eliminate(MatchPlayer player); - @Override - public void enable() { - super.enable(); - updateMaxCompetitors(); - } + /** + * Try to get the lives for this player. + */ + Optional lives(MatchPlayer player); - private int remainingCompetitors() { - return (int) match.getCompetitors() - .stream() - .filter(c -> !c.getPlayers().isEmpty()) - .count(); - } + /** + * Get the amount of lives a player has left. + * + * @throws IllegalStateException if {@link #lives(MatchPlayer)} is not present. + */ + int livesCount(MatchPlayer player); - @EventHandler - public void onPartyAdd(PartyAddEvent event) { - if(event.getParty() instanceof Competitor) { - updateMaxCompetitors(); - } - } - - private void updateMaxCompetitors() { - maxCompetitors = Math.max(maxCompetitors, remainingCompetitors()); - } - - public BlitzConfig getConfig() { - return this.config; - } - - /** Whether or not the player participated in the match and was eliminated. */ - public boolean isPlayerEliminated(UUID player) { - return this.eliminatedPlayers.contains(player); - } - - public int getRemainingPlayers(Competitor competitor) { - // TODO: this becomes a bit more complex when eliminated players are not forced to observers - return competitor.getPlayers().size(); - } - - @Override - public @Nullable JoinResult queryJoin(MatchPlayer joining, JoinRequest request) { - if(getMatch().hasStarted() && request.method() != JoinMethod.FORCE) { - // This message should NOT look like an error, because remotely joining players will see it often. - // It also should not say "Blitz" because not all maps that use this module want to be labelled "Blitz". - return JoinDenied.friendly("command.gameplay.join.matchStarted"); - } - return null; - } - - @EventHandler(priority = EventPriority.HIGH, ignoreCancelled = true) - public void handleDeath(final MatchPlayerDeathEvent event) { - MatchPlayer victim = event.getVictim(); - if(victim.getParty() instanceof Competitor) { - int lives = this.lifeManager.addLives(event.getVictim().getPlayerId(), -1); - if(lives <= 0) { - this.handleElimination(victim); - } - } - } - - @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true) - public void handleLeave(final PlayerLeavePartyEvent event) { - if(!match.isRunning()) return; - int lives = this.lifeManager.getLives(event.getPlayer().getPlayerId()); - if (event.getOldParty() instanceof Competitor && lives > 0) { - // Player switching teams, check if match needs to end - if (event.getNewParty() instanceof Competitor) checkEnd(); - // Player is going to obs, eliminate it - else handleElimination(event.getPlayer()); - } - } - - @EventHandler - public void handleSpawn(final ParticipantReleaseEvent event) { - if(this.config.broadcastLives) { - int lives = this.lifeManager.getLives(event.getPlayer().getPlayerId()); - event.getPlayer().showTitle( - // Fake the "Go!" title at match start - event.wasFrozen() ? MatchAnnouncer.GO : Components.blank(), - new Component( - new TranslatableComponent( - "match.blitz.livesRemaining.message", - new Component( - new TranslatableComponent( - lives == 1 ? "match.blitz.livesRemaining.singularLives" - : "match.blitz.livesRemaining.pluralLives", - Integer.toString(lives) - ), - ChatColor.AQUA - ) - ), - ChatColor.RED - ), - 0, 60, 20 - ); - } - } - - private void handleElimination(final MatchPlayer player) { - if (!eliminatedPlayers.add(player.getBukkit().getUniqueId())) return; - - World world = player.getMatch().getWorld(); - Location death = player.getBukkit().getLocation(); - - double radius = 0.1; - int n = 8; - for(int i = 0; i < 6; i++) { - double angle = 2 * Math.PI * i / n; - Location base = death.clone().add(new Vector(radius * Math.cos(angle), 0, radius * Math.sin(angle))); - for(int j = 0; j <= 8; j++) { - world.playEffect(base, Effect.SMOKE, j); - } - } - checkEnd(); - } - - private void checkEnd() { - // Process eliminations within the same tick simultaneously, so that ties are properly detected - getMatch().getScheduler(MatchScope.RUNNING).debounceTask(() -> { - ImmutableSet.copyOf(getMatch().getParticipatingPlayers()) - .stream() - .filter(participating -> eliminatedPlayers.contains(participating.getBukkit().getUniqueId())) - .forEach(participating -> match.setPlayerParty(participating, match.getDefaultParty())); - match.needMatchModule(VictoryMatchModule.class).invalidateAndCheckEnd(); - }); - } + /** + * Try to get the team lives for this competitor. + */ + Optional lives(Competitor competitor); } diff --git a/PGM/src/main/java/tc/oc/pgm/blitz/BlitzMatchModuleImpl.java b/PGM/src/main/java/tc/oc/pgm/blitz/BlitzMatchModuleImpl.java new file mode 100644 index 0000000..cd7bad7 --- /dev/null +++ b/PGM/src/main/java/tc/oc/pgm/blitz/BlitzMatchModuleImpl.java @@ -0,0 +1,283 @@ +package tc.oc.pgm.blitz; + +import com.google.common.collect.ImmutableSet; +import net.md_5.bungee.api.ChatColor; +import net.md_5.bungee.api.chat.TranslatableComponent; +import org.bukkit.Particle; +import org.bukkit.World; +import org.bukkit.event.EventException; +import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; +import org.bukkit.event.Listener; +import tc.oc.api.docs.PlayerId; +import tc.oc.commons.core.chat.Component; +import tc.oc.commons.core.chat.Components; +import tc.oc.pgm.events.ListenerScope; +import tc.oc.pgm.events.MatchPlayerDeathEvent; +import tc.oc.pgm.events.PartyAddEvent; +import tc.oc.pgm.events.PlayerChangePartyEvent; +import tc.oc.pgm.join.JoinDenied; +import tc.oc.pgm.join.JoinHandler; +import tc.oc.pgm.join.JoinMatchModule; +import tc.oc.pgm.join.JoinMethod; +import tc.oc.pgm.join.JoinRequest; +import tc.oc.pgm.join.JoinResult; +import tc.oc.pgm.listeners.MatchAnnouncer; +import tc.oc.pgm.match.Competitor; +import tc.oc.pgm.match.Match; +import tc.oc.pgm.match.MatchModule; +import tc.oc.pgm.match.MatchPlayer; +import tc.oc.pgm.match.MatchScope; +import tc.oc.pgm.spawns.events.ParticipantReleaseEvent; +import tc.oc.pgm.teams.TeamMatchModule; +import tc.oc.pgm.victory.VictoryMatchModule; + +import javax.annotation.Nullable; +import javax.inject.Inject; +import java.util.EnumSet; +import java.util.HashSet; +import java.util.Optional; +import java.util.Set; + +@ListenerScope(MatchScope.LOADED) +public class BlitzMatchModuleImpl extends MatchModule implements BlitzMatchModule, Listener, JoinHandler { + + private final Match match; + private final World world; + private final JoinMatchModule join; + private final VictoryMatchModule victory; + private final Optional teams; + private BlitzProperties properties; + + private final Set lives = new HashSet<>(); + private final Set eliminated = new HashSet<>(); + private boolean activated = false; + private int competitors = 0; + + @Inject BlitzMatchModuleImpl(Match match, World world, JoinMatchModule join, VictoryMatchModule victory, Optional teams, BlitzProperties properties) { + this.match = match; + this.world = world; + this.join = join; + this.victory = victory; + this.teams = teams; + this.properties = properties; + } + + private void preload() { + if(!properties().empty()) { + activate(); + } + } + + protected int competitors() { + return competitors; + } + + protected int remainingCompetitors() { + return (int) match.getCompetitors() + .stream() + .filter(c -> !c.getPlayers().isEmpty()) + .count(); + } + + private void updateCompetitors() { + competitors = Math.max(competitors(), remainingCompetitors()); + } + + private void setup(MatchPlayer player, boolean force) { + if(force) { + eliminated.remove(player.getPlayerId()); + lives.removeIf(life -> life.owner(player.getPlayerId())); + } + switch(properties().type) { + case INDIVIDUAL: + properties().individuals.forEach((filter, count) -> { + if(filter.allows(player)) { + lives.add(new LivesIndividual(player, count)); + } + }); break; + case TEAM: + properties().teams.forEach((teamFactory, count) -> { + if(teams.get().team(teamFactory).equals(player.getCompetitor())) { + lives.add(new LivesTeam(player.getCompetitor(), count)); + } + }); break; + } + } + + private void showLives(MatchPlayer player, boolean release, boolean activate) { + final Optional lives = lives(player); + if(activated() && lives.isPresent()) { + player.showTitle( + release ? MatchAnnouncer.GO + : activate ? new Component(new TranslatableComponent("blitz.activated"), ChatColor.GREEN) + : Components.blank(), + lives.get().remaining(), + 0, 60, 20 + ); + } + } + + @Override + public boolean activated() { + return activated; + } + + @Override + public void activate(@Nullable BlitzProperties newProperties) { + if(!activated) { + activated = true; + if(newProperties != null) { + properties = newProperties; + } + load(); + if(match.hasStarted()) { + enable(); + } + } + } + + @Override + public void deactivate() { + activated = false; + lives.clear(); + eliminated.clear(); + } + + @Override + public void load() { + if(activated()) { + join.registerHandler(this); + victory.setVictoryCondition(new BlitzVictoryCondition(this)); + } else { + preload(); + } + } + + @Override + public void enable() { + if(activated()) { + updateCompetitors(); + match.participants().forEach(player -> { + setup(player, false); + if(match.hasStarted()) { + showLives(player, false, true); + } + }); + match.callEvent(new BlitzEvent(match, this)); + } + } + + @Override + public BlitzProperties properties() { + return properties; + } + + @Override + public boolean increment(MatchPlayer player, int lives, boolean notify, boolean immediate) { + if(!eliminated(player)) { + return lives(player).map(life -> { + life.add(player.getPlayerId(), lives); + if(notify) { + player.showTitle(Components.blank(), life.change(lives), 0, 40, 10); + } + if(life.empty() && immediate) { + eliminate(player); + return true; + } + return false; + }).orElse(false); + } + return true; + } + + @Override + public int livesCount(MatchPlayer player) { + return lives(player).map(Lives::current) + .orElseThrow(() -> new IllegalStateException(player + " has no lives present to count")); + } + + @Override + public Optional lives(MatchPlayer player) { + return lives.stream() + .filter(lives -> lives.applicableTo(player.getPlayerId())) + .findFirst(); + } + + @Override + public Optional lives(Competitor competitor) { + return lives.stream() + .filter(lives -> lives.type().equals(Lives.Type.TEAM) && lives.competitor().equals(competitor)) + .findFirst(); + } + + @Override + public boolean eliminated(MatchPlayer player) { + return eliminated.contains(player.getPlayerId()); + } + + @Override + public void eliminate(MatchPlayer player) { + if(activated() && !eliminated(player)) { + eliminated.add(player.getPlayerId()); + // Process eliminations within the same tick simultaneously, so that ties are properly detected + match.getScheduler(MatchScope.RUNNING).debounceTask(() -> { + ImmutableSet.copyOf(getMatch().getParticipatingPlayers()) + .stream() + .filter(this::eliminated) + .forEach(participating -> { + match.setPlayerParty(participating, match.getDefaultParty()); + world.spawnParticle(Particle.SMOKE_LARGE, player.getLocation(), 5); + }); + victory.invalidateAndCheckEnd(); + }); + } + } + + @Override + public JoinResult queryJoin(MatchPlayer joining, JoinRequest request) { + if(activated() && + match.hasStarted() && + !EnumSet.of(JoinMethod.FORCE, JoinMethod.REMOTE).contains(request.method())) { + // This message should NOT look like an error, because remotely joining players will see it often. + // It also should not say "Blitz" because not all maps that use this module want to be labelled "Blitz". + return JoinDenied.friendly("command.gameplay.join.matchStarted"); + } + return null; + } + + @EventHandler + public void onPartyAdd(PartyAddEvent event) { + if(event.getParty() instanceof Competitor) { + updateCompetitors(); + } + } + + @EventHandler(priority = EventPriority.HIGH, ignoreCancelled = true) + public void onPartyChange(PlayerChangePartyEvent event) throws EventException { + final MatchPlayer player = event.getPlayer(); + if(event.getNewParty() == null) { + if(event.getOldParty() instanceof Competitor && match.hasStarted() && !increment(player, -1, false, true)) { + eliminate(player); + } + } else if(event.getNewParty() instanceof Competitor) { + event.yield(); + setup(player, true); + updateCompetitors(); + } + } + + @EventHandler(priority = EventPriority.HIGH, ignoreCancelled = true) + public void onDeath(MatchPlayerDeathEvent event) { + final MatchPlayer player = event.getVictim(); + if(player.competitor().isPresent()) { + increment(player, -1, false, true); + } + } + + @EventHandler + public void onRelease(ParticipantReleaseEvent event) { + showLives(event.getPlayer(), event.wasFrozen(), false); + } + +} diff --git a/PGM/src/main/java/tc/oc/pgm/blitz/BlitzMatchResult.java b/PGM/src/main/java/tc/oc/pgm/blitz/BlitzMatchResult.java index 4550ec0..19e82f7 100644 --- a/PGM/src/main/java/tc/oc/pgm/blitz/BlitzMatchResult.java +++ b/PGM/src/main/java/tc/oc/pgm/blitz/BlitzMatchResult.java @@ -6,6 +6,7 @@ import tc.oc.pgm.match.Competitor; import tc.oc.pgm.victory.MatchResult; public class BlitzMatchResult implements MatchResult { + @Override public int compare(Competitor a, Competitor b) { return Integer.compare(b.getPlayers().size(), a.getPlayers().size()); @@ -15,4 +16,5 @@ public class BlitzMatchResult implements MatchResult { public BaseComponent describeResult() { return new Component("most survivors"); } + } diff --git a/PGM/src/main/java/tc/oc/pgm/blitz/BlitzModule.java b/PGM/src/main/java/tc/oc/pgm/blitz/BlitzModule.java index 5b67b75..3cf8b6d 100644 --- a/PGM/src/main/java/tc/oc/pgm/blitz/BlitzModule.java +++ b/PGM/src/main/java/tc/oc/pgm/blitz/BlitzModule.java @@ -1,85 +1,60 @@ package tc.oc.pgm.blitz; import java.util.Collections; -import java.util.List; import java.util.Set; import java.util.logging.Logger; -import com.google.common.collect.Range; import net.md_5.bungee.api.chat.BaseComponent; import net.md_5.bungee.api.chat.TranslatableComponent; import org.jdom2.Document; -import org.jdom2.Element; import tc.oc.api.docs.virtual.MapDoc; -import tc.oc.pgm.ffa.FreeForAllModule; import tc.oc.pgm.map.MapModule; import tc.oc.pgm.map.MapModuleContext; -import tc.oc.pgm.match.Match; -import tc.oc.pgm.match.MatchModuleFactory; -import tc.oc.pgm.module.ModuleDescription; -import tc.oc.pgm.mutation.MutationMapModule; -import tc.oc.pgm.mutation.MutationMatchModule; -import tc.oc.pgm.teams.TeamModule; -import tc.oc.pgm.utils.XMLUtils; +import tc.oc.pgm.map.MapModuleFactory; import tc.oc.pgm.xml.InvalidXMLException; -import tc.oc.pgm.xml.Node; -import static com.google.common.base.Preconditions.checkNotNull; +import javax.inject.Inject; +import javax.inject.Provider; +public class BlitzModule implements MapModule { -@ModuleDescription(name = "Blitz", follows = MutationMapModule.class) -public class BlitzModule implements MapModule, MatchModuleFactory { - final BlitzConfig config; + private final BlitzProperties properties; - public BlitzModule(BlitzConfig config) { - this.config = checkNotNull(config); + public BlitzModule(BlitzProperties properties) { + this.properties = properties; + } + + public boolean active() { + return !properties.empty(); } @Override public Set getGamemodes(MapModuleContext context) { - return isEnabled() ? Collections.singleton(MapDoc.Gamemode.blitz) : Collections.emptySet(); + return active() ? Collections.singleton(MapDoc.Gamemode.blitz) : Collections.emptySet(); } @Override public BaseComponent getGameName(MapModuleContext context) { - if(!isEnabled()) return null; - if (context.hasModule(TeamModule.class)) { + if(!active()) { + return null; + } else if(!properties.multipleLives()) { return new TranslatableComponent("match.scoreboard.playersRemaining.title"); - } else if (context.hasModule(FreeForAllModule.class) && config.getNumLives() > 1) { + } else if(properties.teams.isEmpty()) { return new TranslatableComponent("match.scoreboard.livesRemaining.title"); } else { return new TranslatableComponent("match.scoreboard.blitz.title"); } } - @Override - public BlitzMatchModule createMatchModule(Match match) { - return new BlitzMatchModule(match, this.config); - } + public static class Factory extends MapModuleFactory { - /** - * In order to support {@link MutationMatchModule}, this module - * will always create a {@link BlitzMatchModule}. However, if the lives are set to - * {@link Integer#MAX_VALUE}, then it will fail to load on {@link BlitzMatchModule#shouldLoad()}. - */ - public boolean isEnabled() { - return config.lives != Integer.MAX_VALUE; - } + @Inject Provider propertiesProvider; - // --------------------- - // ---- XML Parsing ---- - // --------------------- - - public static BlitzModule parse(MapModuleContext context, Logger logger, Document doc) throws InvalidXMLException { - List blitzElements = doc.getRootElement().getChildren("blitz"); - BlitzConfig config = new BlitzConfig(Integer.MAX_VALUE, false); - - for(Element blitzEl : blitzElements) { - boolean broadcastLives = XMLUtils.parseBoolean(blitzEl.getChild("broadcastLives"), true); - int lives = XMLUtils.parseNumber(Node.fromChildOrAttr(blitzEl, "lives"), Integer.class, Range.atLeast(1), 1); - config = new BlitzConfig(lives, broadcastLives); + @Override + public BlitzModule parse(MapModuleContext context, Logger logger, Document doc) throws InvalidXMLException { + return new BlitzModule(propertiesProvider.get()); } - return new BlitzModule(config); } + } diff --git a/PGM/src/main/java/tc/oc/pgm/blitz/BlitzParser.java b/PGM/src/main/java/tc/oc/pgm/blitz/BlitzParser.java new file mode 100644 index 0000000..81fa586 --- /dev/null +++ b/PGM/src/main/java/tc/oc/pgm/blitz/BlitzParser.java @@ -0,0 +1,72 @@ +package tc.oc.pgm.blitz; + +import com.google.common.collect.Range; +import com.google.inject.Provider; +import org.jdom2.Element; +import tc.oc.pgm.filters.Filter; +import tc.oc.pgm.filters.matcher.StaticFilter; +import tc.oc.pgm.filters.parser.FilterParser; +import tc.oc.pgm.teams.TeamFactory; +import tc.oc.pgm.utils.XMLUtils; +import tc.oc.pgm.xml.InvalidXMLException; +import tc.oc.pgm.xml.Node; +import tc.oc.pgm.xml.parser.ElementParser; + +import javax.inject.Inject; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.function.Function; +import java.util.stream.Collectors; + +import static tc.oc.pgm.blitz.BlitzProperties.*; + +public class BlitzParser implements ElementParser { + + private final FilterParser filters; + private final Provider> factories; + + @Inject private BlitzParser(FilterParser filters, Provider> factories) { + this.filters = filters; + this.factories = factories; + } + + @Override + public BlitzProperties parseElement(Element element) throws InvalidXMLException { + boolean broadcast = true; + int global = -1; + + Map individuals = new HashMap<>(); + Map teams = factories.get().stream() + .filter(team -> team.getLives().isPresent()) + .collect(Collectors.toMap(Function.identity(), team -> team.getLives().get())); + + for(Element el : XMLUtils.getChildren(element, "blitz")) { + broadcast = XMLUtils.parseBoolean(Node.fromChildOrAttr(el, "broadcast", "broadcastLives"), broadcast); + global = XMLUtils.parseNumber(Node.fromChildOrAttr(el, "lives"), Integer.class, Range.atLeast(1), global); + if(global != -1) { + individuals.put(StaticFilter.ALLOW, global); + } else { + for(Element e : XMLUtils.getChildren(el, "rule")) { + individuals.put( + filters.parse(Node.fromChildOrAttr(e, "filter")), + XMLUtils.parseNumber(Node.fromChildOrAttr(e, "lives"), Integer.class, Range.atLeast(1), 1) + ); + } + } + } + + if(!individuals.isEmpty() && teams.isEmpty()) { + return individuals(individuals, broadcast); + } else if(individuals.isEmpty() && !teams.isEmpty()) { + return teams(teams, broadcast); + } else if(!individuals.isEmpty() && !teams.isEmpty()) { + throw new InvalidXMLException("Cannot define both team respawns and blitz"); + } else { + return none(); + } + + } + +} diff --git a/PGM/src/main/java/tc/oc/pgm/blitz/BlitzProperties.java b/PGM/src/main/java/tc/oc/pgm/blitz/BlitzProperties.java new file mode 100644 index 0000000..d818249 --- /dev/null +++ b/PGM/src/main/java/tc/oc/pgm/blitz/BlitzProperties.java @@ -0,0 +1,64 @@ +package tc.oc.pgm.blitz; + +import tc.oc.commons.core.IterableUtils; +import tc.oc.commons.core.util.MapUtils; +import tc.oc.pgm.filters.Filter; +import tc.oc.pgm.filters.matcher.StaticFilter; +import tc.oc.pgm.match.Match; +import tc.oc.pgm.teams.Team; +import tc.oc.pgm.teams.TeamFactory; + +import java.util.HashMap; +import java.util.Map; +import java.util.function.Function; +import java.util.stream.Collectors; + +public class BlitzProperties { + + public final Map teams; + public final Map individuals; + public final Lives.Type type; + public final boolean broadcast; + + private final boolean multi; + private final boolean empty; + + public BlitzProperties(Map teams, Map individuals, Lives.Type type, boolean broadcast) { + this.teams = teams; + this.individuals = individuals; + this.type = type; + this.broadcast = broadcast; + this.multi = IterableUtils.any(IterableUtils.concat(teams.values(), individuals.values()), i -> i != 1); + this.empty = teams.isEmpty() && individuals.isEmpty(); + } + + public static BlitzProperties none() { + return new BlitzProperties(new HashMap<>(), new HashMap<>(), Lives.Type.INDIVIDUAL, false); + } + + public static BlitzProperties individuals(Map individuals, boolean broadcast) { + return new BlitzProperties(new HashMap<>(), individuals, Lives.Type.INDIVIDUAL, broadcast); + } + + public static BlitzProperties teams(Map teams, boolean broadcast) { + return new BlitzProperties(teams, new HashMap<>(), Lives.Type.TEAM, broadcast); + } + + public static BlitzProperties create(Match match, int lives, Lives.Type type) { + return new BlitzProperties( + match.competitors().filter(c -> c instanceof Team).map(c -> ((Team) c).getDefinition()).collect(Collectors.toMap(Function.identity(), c -> lives)), + MapUtils.merge(new HashMap<>(), StaticFilter.ALLOW, lives), + type, + true + ); + } + + public boolean multipleLives() { + return multi; + } + + public boolean empty() { + return empty; + } + +} diff --git a/PGM/src/main/java/tc/oc/pgm/blitz/BlitzVictoryCondition.java b/PGM/src/main/java/tc/oc/pgm/blitz/BlitzVictoryCondition.java new file mode 100644 index 0000000..1e4a6e5 --- /dev/null +++ b/PGM/src/main/java/tc/oc/pgm/blitz/BlitzVictoryCondition.java @@ -0,0 +1,22 @@ +package tc.oc.pgm.blitz; + +import tc.oc.pgm.victory.AbstractVictoryCondition; + +public class BlitzVictoryCondition extends AbstractVictoryCondition { + + private final BlitzMatchModuleImpl blitz; + + protected BlitzVictoryCondition(BlitzMatchModuleImpl blitz) { + super(Priority.BLITZ, new BlitzMatchResult()); + this.blitz = blitz; + } + + @Override + public boolean isCompleted() { + // At least one competitor must be eliminated before the match can end. + // This allows maps to be tested with one or zero competitors present. + final int count = blitz.remainingCompetitors(); + return blitz.activated() && count <= 1 && count < blitz.competitors(); + } + +} diff --git a/PGM/src/main/java/tc/oc/pgm/blitz/LifeManager.java b/PGM/src/main/java/tc/oc/pgm/blitz/LifeManager.java deleted file mode 100644 index 2c1b005..0000000 --- a/PGM/src/main/java/tc/oc/pgm/blitz/LifeManager.java +++ /dev/null @@ -1,45 +0,0 @@ -package tc.oc.pgm.blitz; - -import static com.google.common.base.Preconditions.checkArgument; -import static com.google.common.base.Preconditions.checkNotNull; - -import java.util.Map; - -import com.google.common.collect.Maps; -import tc.oc.api.docs.PlayerId; - -public class LifeManager { - - final int lives; - final Map livesLeft = Maps.newHashMap(); - - public LifeManager(int lives) { - checkArgument(lives > 0, "lives must be greater than zero"); - - this.lives = lives; - } - - public int getLives() { - return this.lives; - } - - public int getLives(PlayerId player) { - checkNotNull(player, "player id"); - - Integer livesLeft = this.livesLeft.get(player); - if(livesLeft != null) { - return livesLeft; - } else { - return this.lives; - } - } - - public int addLives(PlayerId player, int dlives) { - checkNotNull(player, "player id"); - - int lives = Math.max(0, this.getLives(player) + dlives); - this.livesLeft.put(player, lives); - - return lives; - } -} diff --git a/PGM/src/main/java/tc/oc/pgm/blitz/Lives.java b/PGM/src/main/java/tc/oc/pgm/blitz/Lives.java new file mode 100644 index 0000000..0de7562 --- /dev/null +++ b/PGM/src/main/java/tc/oc/pgm/blitz/Lives.java @@ -0,0 +1,73 @@ +package tc.oc.pgm.blitz; + +import net.md_5.bungee.api.chat.BaseComponent; +import tc.oc.api.docs.PlayerId; +import tc.oc.pgm.match.Competitor; + +import javax.annotation.Nullable; + +public interface Lives { + + /** + * Original amount of lives. + */ + int original(); + + /** + * Current amount of lives (may be larger than {@link #original()}, + * due to players getting addition lives via kits). + */ + int current(); + + /** + * Add more to the current lives and include the player that + * caused this change if applicable. + */ + void add(@Nullable PlayerId cause, int delta); + + /** + * Get the delta number of life changes this player has caused. + */ + int changesBy(PlayerId player); + + /** + * Are the amount of lives reduced when this player dies? + */ + boolean applicableTo(PlayerId player); + + /** + * Is this player the sole owner of these lives? + */ + boolean owner(PlayerId playerId); + + /** + * Are there no lives left? + */ + boolean empty(); + + /** + * Get the competitor relation of these lives. + */ + Competitor competitor(); + + /** + * Message sent to players notifying them how many lives they have left. + */ + BaseComponent remaining(); + + /** + * Sidebar status of how many respawns a competitor has left. + */ + BaseComponent status(); + + /** + * Message sent when a player gains or loses lives. + */ + BaseComponent change(int delta); + + /** + * Implementations of lives as an enum. + */ + Type type(); enum Type { TEAM, INDIVIDUAL } + +} diff --git a/PGM/src/main/java/tc/oc/pgm/blitz/LivesBase.java b/PGM/src/main/java/tc/oc/pgm/blitz/LivesBase.java new file mode 100644 index 0000000..77d7154 --- /dev/null +++ b/PGM/src/main/java/tc/oc/pgm/blitz/LivesBase.java @@ -0,0 +1,113 @@ +package tc.oc.pgm.blitz; + +import net.md_5.bungee.api.ChatColor; +import net.md_5.bungee.api.chat.BaseComponent; +import net.md_5.bungee.api.chat.TranslatableComponent; +import tc.oc.api.docs.PlayerId; +import tc.oc.commons.core.chat.Component; +import tc.oc.pgm.match.Competitor; + +import javax.annotation.Nullable; +import java.util.HashMap; +import java.util.Map; +import java.util.stream.Stream; + +public abstract class LivesBase implements Lives { + + private final Map deltas; + private final Competitor competitor; + private final int original; + private int current; + + public LivesBase(int lives, Competitor competitor) { + this.deltas = new HashMap<>(); + this.competitor = competitor; + this.original = lives; + this.current = lives; + update(); + } + + private void update() { + competitor().getMatch().callEvent(new LivesEvent(this)); + } + + @Override + public Competitor competitor() { + return competitor; + } + + @Override + public int original() { + return original; + } + + @Override + public int current() { + return current; + } + + @Override + public boolean empty() { + return current() <= 0; + } + + @Override + public void add(@Nullable PlayerId cause, int delta) { + current = Math.max(0, current() + delta); + deltas.put(cause, changesBy(cause) + delta); + update(); + } + + @Override + public int changesBy(PlayerId player) { + return deltas.getOrDefault(player, 0); + } + + @Override + public BaseComponent remaining() { + return new Component( + new TranslatableComponent( + "lives.remaining." + type().name().toLowerCase() + "." + (current() == 1 ? "singular" : "plural"), + new Component(current(), ChatColor.YELLOW) + ), + ChatColor.AQUA + ); + } + + @Override + public BaseComponent status() { + int alive = (int) competitor().players().count(); + return new Component( + Stream.of( + new Component("("), + new TranslatableComponent( + empty() ? alive == 0 ? "lives.status.eliminated" + : "lives.status.alive" + : "lives.status.lives", + new Component( + empty() ? alive : current(), + ChatColor.WHITE + ) + ), + new Component(")") + ), + ChatColor.GRAY, + ChatColor.ITALIC + ); + } + + @Override + public BaseComponent change(int delta) { + int absDelta = Math.abs(delta); + return new Component( + new TranslatableComponent( + (delta > 0 ? "lives.change.gained." + : "lives.change.lost.") + (absDelta == 1 ? "singular" + : "plural"), + new Component(absDelta, ChatColor.AQUA) + ), + ChatColor.WHITE + ); + } + +} diff --git a/PGM/src/main/java/tc/oc/pgm/blitz/LivesEvent.java b/PGM/src/main/java/tc/oc/pgm/blitz/LivesEvent.java new file mode 100644 index 0000000..bebe2c1 --- /dev/null +++ b/PGM/src/main/java/tc/oc/pgm/blitz/LivesEvent.java @@ -0,0 +1,32 @@ +package tc.oc.pgm.blitz; + +import org.bukkit.event.Event; +import org.bukkit.event.HandlerList; + +/** + * Called when the amount of {@link Lives} changes. + */ +public class LivesEvent extends Event { + + private final Lives lives; + + public LivesEvent(Lives lives) { + this.lives = lives; + } + + public Lives lives() { + return lives; + } + + private static final HandlerList handlers = new HandlerList(); + + public static HandlerList getHandlerList() { + return handlers; + } + + @Override + public HandlerList getHandlers() { + return handlers; + } + +} diff --git a/PGM/src/main/java/tc/oc/pgm/blitz/LivesIndividual.java b/PGM/src/main/java/tc/oc/pgm/blitz/LivesIndividual.java new file mode 100644 index 0000000..5205401 --- /dev/null +++ b/PGM/src/main/java/tc/oc/pgm/blitz/LivesIndividual.java @@ -0,0 +1,46 @@ +package tc.oc.pgm.blitz; + +import tc.oc.api.docs.PlayerId; +import tc.oc.pgm.match.MatchPlayer; + +public class LivesIndividual extends LivesBase { + + private final PlayerId player; + + public LivesIndividual(MatchPlayer player, int lives) { + super(lives, player.getCompetitor()); + this.player = player.getPlayerId(); + } + + public PlayerId player() { + return player; + } + + @Override + public Type type() { + return Type.INDIVIDUAL; + } + + @Override + public boolean applicableTo(PlayerId player) { + return player().equals(player); + } + + @Override + public boolean owner(PlayerId playerId) { + return player().equals(playerId); + } + + @Override + public int hashCode() { + return player().hashCode(); + } + + @Override + public boolean equals(Object obj) { + return obj != null && + obj instanceof LivesIndividual && + player().equals(((LivesIndividual) obj).player()); + } + +} diff --git a/PGM/src/main/java/tc/oc/pgm/blitz/LivesKit.java b/PGM/src/main/java/tc/oc/pgm/blitz/LivesKit.java new file mode 100644 index 0000000..9324f4e --- /dev/null +++ b/PGM/src/main/java/tc/oc/pgm/blitz/LivesKit.java @@ -0,0 +1,20 @@ +package tc.oc.pgm.blitz; + +import tc.oc.pgm.kits.ItemKitApplicator; +import tc.oc.pgm.kits.Kit; +import tc.oc.pgm.match.MatchPlayer; + +public class LivesKit extends Kit.Impl { + + private final int lives; + + public LivesKit(int lives) { + this.lives = lives; + } + + @Override + public void apply(MatchPlayer player, boolean force, ItemKitApplicator items) { + player.getMatch().module(BlitzMatchModuleImpl.class).ifPresent(blitz -> blitz.increment(player, lives, true, force)); + } + +} diff --git a/PGM/src/main/java/tc/oc/pgm/blitz/LivesTeam.java b/PGM/src/main/java/tc/oc/pgm/blitz/LivesTeam.java new file mode 100644 index 0000000..f989a87 --- /dev/null +++ b/PGM/src/main/java/tc/oc/pgm/blitz/LivesTeam.java @@ -0,0 +1,39 @@ +package tc.oc.pgm.blitz; + +import tc.oc.api.docs.PlayerId; +import tc.oc.pgm.match.Competitor; + +public class LivesTeam extends LivesBase { + + public LivesTeam(Competitor competitor, int lives) { + super(lives, competitor); + } + + @Override + public Type type() { + return Type.TEAM; + } + + @Override + public boolean applicableTo(PlayerId player) { + return competitor().players().anyMatch(matchPlayer -> matchPlayer.getPlayerId().equals(player)); + } + + @Override + public boolean owner(PlayerId playerId) { + return false; + } + + @Override + public int hashCode() { + return competitor().hashCode(); + } + + @Override + public boolean equals(Object obj) { + return obj != null && + obj instanceof LivesTeam && + competitor().equals(((LivesTeam) obj).competitor()); + } + +} diff --git a/PGM/src/main/java/tc/oc/pgm/countdowns/CountdownContext.java b/PGM/src/main/java/tc/oc/pgm/countdowns/CountdownContext.java index 851d5b0..4f09adc 100644 --- a/PGM/src/main/java/tc/oc/pgm/countdowns/CountdownContext.java +++ b/PGM/src/main/java/tc/oc/pgm/countdowns/CountdownContext.java @@ -110,6 +110,10 @@ public abstract class CountdownContext implements Listener { return countdowns().anyMatch(test); } + public boolean anyRunning(Class countdownClass) { + return anyRunning(countdown -> countdown.getClass().equals(countdownClass)); + } + public void cancelAll() { cancelAll(false); } diff --git a/PGM/src/main/java/tc/oc/pgm/ghostsquadron/GhostSquadronModule.java b/PGM/src/main/java/tc/oc/pgm/ghostsquadron/GhostSquadronModule.java index fb7b344..75d1833 100644 --- a/PGM/src/main/java/tc/oc/pgm/ghostsquadron/GhostSquadronModule.java +++ b/PGM/src/main/java/tc/oc/pgm/ghostsquadron/GhostSquadronModule.java @@ -9,9 +9,9 @@ import net.md_5.bungee.api.chat.TranslatableComponent; import org.jdom2.Document; import org.jdom2.Element; import tc.oc.api.docs.virtual.MapDoc; -import tc.oc.pgm.blitz.BlitzModule; import tc.oc.pgm.classes.ClassMatchModule; import tc.oc.pgm.classes.ClassModule; +import tc.oc.pgm.blitz.BlitzModule; import tc.oc.pgm.map.MapModule; import tc.oc.pgm.map.MapModuleContext; import tc.oc.pgm.match.Match; diff --git a/PGM/src/main/java/tc/oc/pgm/inventory/ViewInventoryMatchModule.java b/PGM/src/main/java/tc/oc/pgm/inventory/ViewInventoryMatchModule.java index 9a6c527..ffb82f7 100644 --- a/PGM/src/main/java/tc/oc/pgm/inventory/ViewInventoryMatchModule.java +++ b/PGM/src/main/java/tc/oc/pgm/inventory/ViewInventoryMatchModule.java @@ -38,17 +38,17 @@ import org.bukkit.inventory.ItemStack; import org.bukkit.inventory.PlayerInventory; import org.bukkit.inventory.meta.ItemMeta; import org.bukkit.potion.PotionEffect; -import tc.oc.api.bukkit.users.Users; import tc.oc.commons.bukkit.util.BukkitUtils; import tc.oc.commons.core.commands.CommandBinder; import tc.oc.pgm.PGMTranslations; -import tc.oc.pgm.blitz.BlitzMatchModule; +import tc.oc.pgm.blitz.BlitzMatchModuleImpl; import tc.oc.pgm.doublejump.DoubleJumpMatchModule; import tc.oc.pgm.events.ListenerScope; import tc.oc.pgm.events.ObserverInteractEvent; import tc.oc.pgm.events.PlayerBlockTransformEvent; import tc.oc.pgm.events.PlayerPartyChangeEvent; import tc.oc.pgm.kits.WalkSpeedKit; +import tc.oc.pgm.blitz.BlitzMatchModule; import tc.oc.pgm.match.MatchModule; import tc.oc.pgm.match.MatchPlayer; import tc.oc.pgm.match.MatchScope; @@ -269,9 +269,9 @@ public class ViewInventoryMatchModule extends MatchModule implements Listener { MatchPlayer matchHolder = this.match.getPlayer(holder); if (matchHolder != null && matchHolder.isParticipating()) { - BlitzMatchModule module = matchHolder.getMatch().getMatchModule(BlitzMatchModule.class); - if (module != null) { - int livesLeft = module.lifeManager.getLives(Users.playerId(holder)); + BlitzMatchModule module = matchHolder.getMatch().getMatchModule(BlitzMatchModuleImpl.class); + if(module != null) { + int livesLeft = module.livesCount(matchHolder); ItemStack lives = new ItemStack(Material.EGG, livesLeft); ItemMeta lifeMeta = lives.getItemMeta(); lifeMeta.addItemFlags(ItemFlag.values()); diff --git a/PGM/src/main/java/tc/oc/pgm/kits/KitDefinitionParser.java b/PGM/src/main/java/tc/oc/pgm/kits/KitDefinitionParser.java index de4cd95..dc654ec 100644 --- a/PGM/src/main/java/tc/oc/pgm/kits/KitDefinitionParser.java +++ b/PGM/src/main/java/tc/oc/pgm/kits/KitDefinitionParser.java @@ -10,6 +10,7 @@ import org.bukkit.inventory.ItemStack; import org.jdom2.Element; import tc.oc.commons.bukkit.inventory.ArmorType; import tc.oc.commons.bukkit.inventory.Slot; +import tc.oc.pgm.blitz.LivesKit; import tc.oc.pgm.compose.CompositionParser; import tc.oc.pgm.doublejump.DoubleJumpKit; import tc.oc.pgm.features.FeatureDefinitionContext; @@ -254,4 +255,9 @@ public class KitDefinitionParser extends MagicMethodFeatureParser implement return new ForceKit(XMLUtils.parseVector(new Node(el)), parseRelativeFlags(el)); } + + @MethodParser + private Kit lives(Element el) throws InvalidXMLException { + return new LivesKit(XMLUtils.parseNumber(new Node(el), Integer.class, false, +1)); + } } diff --git a/PGM/src/main/java/tc/oc/pgm/mapratings/MapRatingsMatchModule.java b/PGM/src/main/java/tc/oc/pgm/mapratings/MapRatingsMatchModule.java index affc605..98a9bba 100644 --- a/PGM/src/main/java/tc/oc/pgm/mapratings/MapRatingsMatchModule.java +++ b/PGM/src/main/java/tc/oc/pgm/mapratings/MapRatingsMatchModule.java @@ -37,9 +37,9 @@ import tc.oc.commons.core.commands.CommandFutureCallback; import tc.oc.commons.core.formatting.StringUtils; import tc.oc.commons.core.util.Comparables; import tc.oc.pgm.PGMTranslations; -import tc.oc.pgm.blitz.BlitzMatchModule; import tc.oc.pgm.events.ListenerScope; import tc.oc.pgm.events.PlayerLeaveMatchEvent; +import tc.oc.pgm.blitz.BlitzMatchModule; import tc.oc.pgm.match.Match; import tc.oc.pgm.match.MatchExecutor; import tc.oc.pgm.match.MatchModule; @@ -98,6 +98,7 @@ public class MapRatingsMatchModule extends MatchModule implements Listener { @Inject private MapRatingsConfiguration config; @Inject private MapService mapService; @Inject private MatchExecutor matchExecutor; + @Inject private BlitzMatchModule blitz; private final Map playerRatings = new HashMap<>(); @@ -140,10 +141,8 @@ public class MapRatingsMatchModule extends MatchModule implements Listener { return PGMTranslations.t("noPermission", player); } - BlitzMatchModule blitz = player.getMatch().getMatchModule(BlitzMatchModule.class); - if(Comparables.lessThan(player.getCumulativeParticipationTime(), MIN_PARTICIPATION_TIME) && - !(blitz != null && blitz.isPlayerEliminated(player.getBukkit().getUniqueId())) && + !(blitz.eliminated(player)) && !(this.getMatch().isFinished() && player.getCumulativeParticipationPercent() > MIN_PARTICIPATION_PERCENT)) { return PGMTranslations.t("rating.lowParticipation", player); } 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 575af58..35404d9 100644 --- a/PGM/src/main/java/tc/oc/pgm/mutation/Mutation.java +++ b/PGM/src/main/java/tc/oc/pgm/mutation/Mutation.java @@ -23,6 +23,7 @@ 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.BlitzMutation; import tc.oc.pgm.mutation.types.other.RageMutation; import tc.oc.pgm.mutation.types.targetable.ApocalypseMutation; import tc.oc.pgm.mutation.types.targetable.BomberMutation; @@ -32,7 +33,7 @@ import java.util.stream.Stream; public enum Mutation { - BLITZ (null), + BLITZ (BlitzMutation.class), RAGE (RageMutation.class), HARDCORE (HardcoreMutation.class), JUMP (JumpMutation.class), diff --git a/PGM/src/main/java/tc/oc/pgm/mutation/types/other/BlitzMutation.java b/PGM/src/main/java/tc/oc/pgm/mutation/types/other/BlitzMutation.java new file mode 100644 index 0000000..7062f61 --- /dev/null +++ b/PGM/src/main/java/tc/oc/pgm/mutation/types/other/BlitzMutation.java @@ -0,0 +1,47 @@ +package tc.oc.pgm.mutation.types.other; + +import com.google.common.collect.Range; +import org.apache.commons.lang.math.Fraction; +import tc.oc.commons.core.random.RandomUtils; +import tc.oc.pgm.blitz.BlitzMatchModuleImpl; +import tc.oc.pgm.blitz.BlitzProperties; +import tc.oc.pgm.blitz.Lives; +import tc.oc.pgm.match.Match; +import tc.oc.pgm.match.MatchScope; +import tc.oc.pgm.mutation.types.MutationModule; +import tc.oc.pgm.teams.TeamMatchModule; + +public class BlitzMutation extends MutationModule { + + final static Range LIVES = Range.closed(1, 3); + final static Fraction TEAM_CHANCE = Fraction.ONE_QUARTER; + + public BlitzMutation(Match match) { + super(match); + } + + @Override + public void enable() { + super.enable(); + int lives = match.entropyForTick().randomInt(LIVES); + Lives.Type type; + if(match.module(TeamMatchModule.class).isPresent() && RandomUtils.nextBoolean(random, TEAM_CHANCE)) { + type = Lives.Type.TEAM; + lives *= match.module(TeamMatchModule.class).get().getFullestTeam().getSize(); + } else { + type = Lives.Type.INDIVIDUAL; + } + match.module(BlitzMatchModuleImpl.class).get().activate(BlitzProperties.create(match, lives, type)); + } + + @Override + public void disable() { + match.getScheduler(MatchScope.LOADED).createTask(() -> { + if(!match.isFinished()) { + match.module(BlitzMatchModuleImpl.class).get().deactivate(); + } + }); + super.disable(); + } + +} diff --git a/PGM/src/main/java/tc/oc/pgm/picker/PickerMatchModule.java b/PGM/src/main/java/tc/oc/pgm/picker/PickerMatchModule.java index bc9fe51..809bdea 100644 --- a/PGM/src/main/java/tc/oc/pgm/picker/PickerMatchModule.java +++ b/PGM/src/main/java/tc/oc/pgm/picker/PickerMatchModule.java @@ -40,7 +40,7 @@ import tc.oc.commons.core.chat.ChatUtils; import tc.oc.commons.core.chat.Component; import tc.oc.commons.core.formatting.StringUtils; import tc.oc.pgm.PGMTranslations; -import tc.oc.pgm.blitz.BlitzModule; +import tc.oc.pgm.blitz.BlitzEvent; import tc.oc.pgm.classes.ClassMatchModule; import tc.oc.pgm.classes.ClassModule; import tc.oc.pgm.classes.PlayerClass; @@ -53,6 +53,7 @@ import tc.oc.pgm.events.PlayerPartyChangeEvent; import tc.oc.pgm.join.JoinMatchModule; import tc.oc.pgm.join.JoinRequest; import tc.oc.pgm.join.JoinResult; +import tc.oc.pgm.blitz.BlitzMatchModule; import tc.oc.pgm.match.MatchModule; import tc.oc.pgm.match.MatchPlayer; import tc.oc.pgm.match.MatchScope; @@ -94,18 +95,18 @@ public class PickerMatchModule extends MatchModule implements Listener { private final ComponentRenderContext renderer; private final JoinMatchModule jmm; + private final BlitzMatchModule bmm; private final boolean hasTeams; private final boolean hasClasses; - private final boolean isBlitz; private final Set picking = new HashSet<>(); - @Inject PickerMatchModule(ComponentRenderContext renderer, JoinMatchModule jmm, Optional teamModule, Optional classModule, Optional blitzModule) { + @Inject PickerMatchModule(ComponentRenderContext renderer, JoinMatchModule jmm, BlitzMatchModule bmm, Optional teamModule, Optional classModule) { this.renderer = renderer; this.jmm = jmm; + this.bmm = bmm; this.hasTeams = teamModule.isPresent(); this.hasClasses = classModule.isPresent(); - this.isBlitz = blitzModule.filter(BlitzModule::isEnabled).isPresent(); } protected boolean settingEnabled(MatchPlayer player) { @@ -142,7 +143,7 @@ public class PickerMatchModule extends MatchModule implements Listener { if(player == null) return false; // Player is eliminated from Blitz - if(isBlitz && getMatch().isRunning()) return false; + if(bmm.activated() && getMatch().isRunning()) return false; // Player is not observing or dead if(!(player.isObserving() || player.isDead())) return false; @@ -346,6 +347,12 @@ public class PickerMatchModule extends MatchModule implements Listener { refreshKitAll(); } + @EventHandler + public void blitzEnable(final BlitzEvent event) { + refreshCountsAll(); + refreshKitAll(); + } + /** * Open the window for the given player, or refresh its contents * if they already have it open, and return the current contents. diff --git a/PGM/src/main/java/tc/oc/pgm/rage/RageModule.java b/PGM/src/main/java/tc/oc/pgm/rage/RageModule.java index ea26d2f..ecb8fd8 100644 --- a/PGM/src/main/java/tc/oc/pgm/rage/RageModule.java +++ b/PGM/src/main/java/tc/oc/pgm/rage/RageModule.java @@ -47,7 +47,7 @@ public class RageModule implements MapModule, MatchModuleFactory { private final ScoreConfig config; diff --git a/PGM/src/main/java/tc/oc/pgm/scoreboard/SidebarMatchModule.java b/PGM/src/main/java/tc/oc/pgm/scoreboard/SidebarMatchModule.java index 55d2797..ced0f09 100644 --- a/PGM/src/main/java/tc/oc/pgm/scoreboard/SidebarMatchModule.java +++ b/PGM/src/main/java/tc/oc/pgm/scoreboard/SidebarMatchModule.java @@ -31,7 +31,7 @@ import tc.oc.commons.bukkit.util.NullCommandSender; import tc.oc.commons.core.chat.Component; import tc.oc.commons.core.scheduler.Task; import tc.oc.pgm.Config; -import tc.oc.pgm.blitz.BlitzMatchModule; +import tc.oc.pgm.blitz.LivesEvent; import tc.oc.pgm.destroyable.Destroyable; import tc.oc.pgm.events.FeatureChangeEvent; import tc.oc.pgm.events.ListenerScope; @@ -50,6 +50,9 @@ import tc.oc.pgm.goals.events.GoalCompleteEvent; import tc.oc.pgm.goals.events.GoalProximityChangeEvent; import tc.oc.pgm.goals.events.GoalStatusChangeEvent; import tc.oc.pgm.goals.events.GoalTouchEvent; +import tc.oc.pgm.blitz.Lives; +import tc.oc.pgm.blitz.BlitzEvent; +import tc.oc.pgm.blitz.BlitzMatchModule; import tc.oc.pgm.match.Competitor; import tc.oc.pgm.match.Match; import tc.oc.pgm.match.MatchModule; @@ -72,6 +75,7 @@ public class SidebarMatchModule extends MatchModule implements Listener { public static final int MAX_SUFFIX = 16; // Max chars in a team suffix @Inject private List wools; + @Inject private BlitzMatchModule blitz; private final String legacyTitle; @@ -172,8 +176,8 @@ public class SidebarMatchModule extends MatchModule implements Listener { return getMatch().getMatchModule(ScoreMatchModule.class) != null; } - private boolean isBlitz() { - return getMatch().getMatchModule(BlitzMatchModule.class) != null; + private boolean lives(Lives.Type type) { + return blitz.activated() && blitz.properties().type.equals(type); } private boolean isCompactWool() { @@ -290,6 +294,16 @@ public class SidebarMatchModule extends MatchModule implements Listener { renderSidebarDebounce(); } + @EventHandler(priority = EventPriority.MONITOR) + public void blitzEnable(BlitzEvent event) { + renderSidebarDebounce(); + } + + @EventHandler(priority = EventPriority.MONITOR) + public void livesChange(LivesEvent event) { + renderSidebarDebounce(); + } + private String renderGoal(Goal goal, @Nullable Competitor competitor, Party viewingParty) { StringBuilder sb = new StringBuilder(" "); @@ -325,11 +339,10 @@ public class SidebarMatchModule extends MatchModule implements Listener { } private String renderBlitz(Competitor competitor, Party viewingParty) { - BlitzMatchModule bmm = getMatch().needMatchModule(BlitzMatchModule.class); if(competitor instanceof tc.oc.pgm.teams.Team) { - return ChatColor.WHITE.toString() + bmm.getRemainingPlayers(competitor); - } else if(competitor instanceof Tribute && bmm.getConfig().getNumLives() > 1) { - return ChatColor.WHITE.toString() + bmm.lifeManager.getLives(competitor.getPlayers().iterator().next().getPlayerId()); + return ChatColor.WHITE.toString() + competitor.getPlayers().size(); + } else if(competitor instanceof Tribute && blitz.properties().multipleLives()) { + return ChatColor.WHITE.toString() + blitz.livesCount(competitor.getPlayers().iterator().next()); } else { return ""; } @@ -341,7 +354,8 @@ public class SidebarMatchModule extends MatchModule implements Listener { private void renderSidebar() { final boolean hasScores = hasScores(); - final boolean isBlitz = isBlitz(); + final boolean hasIndividualLives = lives(Lives.Type.INDIVIDUAL); + final boolean hasTeamLives = lives(Lives.Type.TEAM); final GoalMatchModule gmm = match.needMatchModule(GoalMatchModule.class); Set competitorsWithGoals = new HashSet<>(); @@ -367,7 +381,7 @@ public class SidebarMatchModule extends MatchModule implements Listener { List rows = new ArrayList<>(MAX_ROWS); // Scores/Blitz - if(hasScores || isBlitz) { + if(hasScores || hasIndividualLives || (hasTeamLives && competitorsWithGoals.isEmpty())) { for(Competitor competitor : getMatch().needMatchModule(VictoryMatchModule.class).rankedCompetitors()) { String text; if(hasScores) { @@ -419,6 +433,11 @@ public class SidebarMatchModule extends MatchModule implements Listener { rows.add(ComponentRenderers.toLegacyText(competitor.getStyledName(NameStyle.GAME), NullCommandSender.INSTANCE)); + // Add lives status under the team name + if(hasTeamLives) { + blitz.lives(competitor).ifPresent(l -> rows.add(ComponentRenderers.toLegacyText(l.status(), NullCommandSender.INSTANCE))); + } + if(isCompactWool()) { String woolText = " "; boolean firstWool = true; diff --git a/PGM/src/main/java/tc/oc/pgm/teams/TeamFactory.java b/PGM/src/main/java/tc/oc/pgm/teams/TeamFactory.java index 3145412..54e9446 100644 --- a/PGM/src/main/java/tc/oc/pgm/teams/TeamFactory.java +++ b/PGM/src/main/java/tc/oc/pgm/teams/TeamFactory.java @@ -77,6 +77,9 @@ public interface TeamFactory extends SluggedFeatureDefinition, Validatable, Feat return org.bukkit.scoreboard.Team.OptionStatus.ALWAYS; } + @Property(name="lives") + Optional getLives(); + MapDoc.Team getDocument(); @Override @@ -84,6 +87,9 @@ public interface TeamFactory extends SluggedFeatureDefinition, Validatable, Feat if(getMaxOverfill().isPresent() && getMaxOverfill().get() < getMaxPlayers()) { throw new InvalidXMLException("Max overfill cannot be less than max players"); } + if(getLives().isPresent() && getLives().get() <= 0) { + throw new InvalidXMLException("Lives must be at least 1"); + } } } diff --git a/Tourney/src/net/anxuiz/tourney/listener/TeamListener.java b/Tourney/src/net/anxuiz/tourney/listener/TeamListener.java index 6be49ac..b0c8b21 100644 --- a/Tourney/src/net/anxuiz/tourney/listener/TeamListener.java +++ b/Tourney/src/net/anxuiz/tourney/listener/TeamListener.java @@ -26,10 +26,11 @@ import tc.oc.api.docs.Entrant; import tc.oc.api.docs.Tournament; import tc.oc.commons.bukkit.event.UserLoginEvent; import tc.oc.commons.core.chat.Component; -import tc.oc.pgm.blitz.BlitzMatchModule; +import tc.oc.pgm.blitz.BlitzMatchModuleImpl; import tc.oc.pgm.channels.ChannelMatchModule; import tc.oc.pgm.events.MatchEndEvent; import tc.oc.pgm.events.MatchPlayerAddEvent; +import tc.oc.pgm.blitz.BlitzMatchModule; import tc.oc.pgm.match.Match; import tc.oc.pgm.match.MatchPlayer; import tc.oc.pgm.match.MatchState; @@ -120,7 +121,7 @@ public class TeamListener implements Listener { TourneyState state = event.getNewState(); if(tourney.getKDMSession() == null && state.equals(TourneyState.ENABLED_WAITING_FOR_READY)) { Match match = matchProvider.get(); - if(match.getMatchModule(ScoreMatchModule.class) != null || match.getMatchModule(BlitzMatchModule.class) != null) { + if(match.getMatchModule(ScoreMatchModule.class) != null || match.module(BlitzMatchModuleImpl.class).filter(BlitzMatchModule::activated).isPresent()) { tourney.createKDMSession(); } } diff --git a/Util/core/src/main/java/tc/oc/commons/core/formatting/StringUtils.java b/Util/core/src/main/java/tc/oc/commons/core/formatting/StringUtils.java index 594ca97..8ac183e 100644 --- a/Util/core/src/main/java/tc/oc/commons/core/formatting/StringUtils.java +++ b/Util/core/src/main/java/tc/oc/commons/core/formatting/StringUtils.java @@ -179,6 +179,10 @@ public class StringUtils { return builder.toString(); } + public static List complete(String prefix, Class enumClass) { + return complete(prefix, Stream.of(enumClass.getEnumConstants()).map(c -> c.name().toLowerCase().replace('_', ' '))); + } + public static List complete(String prefix, Collection options) { return complete(prefix, options.stream()); }