Blitz overhaul
This commit is contained in:
parent
91fb5b3fe6
commit
88a250b2bb
|
@ -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 extends Enum<E>> E getEnum(CommandContext args, CommandSender sender, int index, Class<E> type) throws CommandException {
|
||||
return getEnum(args, sender, index, type, null);
|
||||
}
|
||||
|
||||
public static <E extends Enum<E>> E getEnum(CommandContext args, CommandSender sender, int index, Class<E> type, E def) throws CommandException {
|
||||
return getEnum(args.getString(index, null), sender, type, def);
|
||||
}
|
||||
|
||||
public static <E extends Enum<E>> E getEnum(String text, CommandSender sender, Class<E> 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);
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -234,12 +234,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}
|
||||
|
||||
|
|
|
@ -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<EventRuleModule>(){});
|
||||
|
@ -68,7 +69,6 @@ public class MapModulesManifest extends HybridManifest {
|
|||
install(new StaticMethodMapModuleFactory<ModifyBowProjectileModule>(){});
|
||||
install(new StaticMethodMapModuleFactory<MobsModule>(){});
|
||||
install(new StaticMethodMapModuleFactory<HungerModule>(){});
|
||||
install(new StaticMethodMapModuleFactory<BlitzModule>(){});
|
||||
install(new StaticMethodMapModuleFactory<KillRewardModule>(){});
|
||||
install(new StaticMethodMapModuleFactory<GhostSquadronModule>(){});
|
||||
install(new StaticMethodMapModuleFactory<RageModule>(){});
|
||||
|
|
|
@ -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());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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<MutationMatchModule> mutations;
|
||||
private final Optional<GoalMatchModule> goals;
|
||||
private final Optional<BlitzMatchModule> blitz;
|
||||
private final BlitzMatchModule blitz;
|
||||
private final Optional<JoinMatchModule> join;
|
||||
|
||||
@Inject MatchDocument(ServerDoc.Identity localServer, MapDoc map, Match match, VictoryMatchModule victory, Optional<MutationMatchModule> mutations, Optional<GoalMatchModule> goals, Optional<BlitzMatchModule> blitz, Optional<JoinMatchModule> join) {
|
||||
@Inject MatchDocument(ServerDoc.Identity localServer, MapDoc map, Match match, VictoryMatchModule victory, Optional<MutationMatchModule> mutations, Optional<GoalMatchModule> goals, BlitzMatchModule blitz, Optional<JoinMatchModule> 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() {
|
||||
|
|
|
@ -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<MatchDoc> 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<MatchDoc> matchService, MatchDoc matchDocument) {
|
||||
@Inject MatchPublishingMatchModule(Match match, MatchManager mm, MinecraftService minecraftService, UpdateService<MatchDoc> 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(); }
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
|
@ -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<BlitzMatchModuleImpl>(){});
|
||||
}
|
||||
|
||||
}
|
|
@ -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<UUID> 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> 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> lives(Competitor competitor);
|
||||
|
||||
}
|
||||
|
|
|
@ -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<TeamMatchModule> teams;
|
||||
private BlitzProperties properties;
|
||||
|
||||
private final Set<Lives> lives = new HashSet<>();
|
||||
private final Set<PlayerId> eliminated = new HashSet<>();
|
||||
private boolean activated = false;
|
||||
private int competitors = 0;
|
||||
|
||||
@Inject BlitzMatchModuleImpl(Match match, World world, JoinMatchModule join, VictoryMatchModule victory, Optional<TeamMatchModule> 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 = 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> lives(MatchPlayer player) {
|
||||
return lives.stream()
|
||||
.filter(lives -> lives.applicableTo(player.getPlayerId()))
|
||||
.findFirst();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<Lives> 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);
|
||||
}
|
||||
|
||||
}
|
|
@ -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");
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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<BlitzMatchModule> {
|
||||
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<MapDoc.Gamemode> 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<BlitzModule> {
|
||||
|
||||
/**
|
||||
* 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<BlitzProperties> propertiesProvider;
|
||||
|
||||
// ---------------------
|
||||
// ---- XML Parsing ----
|
||||
// ---------------------
|
||||
|
||||
public static BlitzModule parse(MapModuleContext context, Logger logger, Document doc) throws InvalidXMLException {
|
||||
List<Element> 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);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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<BlitzProperties> {
|
||||
|
||||
private final FilterParser filters;
|
||||
private final Provider<List<TeamFactory>> factories;
|
||||
|
||||
@Inject private BlitzParser(FilterParser filters, Provider<List<TeamFactory>> factories) {
|
||||
this.filters = filters;
|
||||
this.factories = factories;
|
||||
}
|
||||
|
||||
@Override
|
||||
public BlitzProperties parseElement(Element element) throws InvalidXMLException {
|
||||
boolean broadcast = true;
|
||||
int global = -1;
|
||||
|
||||
Map<Filter, Integer> individuals = new HashMap<>();
|
||||
Map<TeamFactory, Integer> 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();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -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<TeamFactory, Integer> teams;
|
||||
public final Map<Filter, Integer> individuals;
|
||||
public final Lives.Type type;
|
||||
public final boolean broadcast;
|
||||
|
||||
private final boolean multi;
|
||||
private final boolean empty;
|
||||
|
||||
public BlitzProperties(Map<TeamFactory, Integer> teams, Map<Filter, Integer> 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<Filter, Integer> individuals, boolean broadcast) {
|
||||
return new BlitzProperties(new HashMap<>(), individuals, Lives.Type.INDIVIDUAL, broadcast);
|
||||
}
|
||||
|
||||
public static BlitzProperties teams(Map<TeamFactory, Integer> 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;
|
||||
}
|
||||
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
|
||||
}
|
|
@ -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<PlayerId, Integer> 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;
|
||||
}
|
||||
}
|
|
@ -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 }
|
||||
|
||||
}
|
|
@ -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<PlayerId, Integer> 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
|
||||
);
|
||||
}
|
||||
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
|
@ -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());
|
||||
}
|
||||
|
||||
}
|
|
@ -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));
|
||||
}
|
||||
|
||||
}
|
|
@ -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());
|
||||
}
|
||||
|
||||
}
|
|
@ -110,6 +110,10 @@ public abstract class CountdownContext implements Listener {
|
|||
return countdowns().anyMatch(test);
|
||||
}
|
||||
|
||||
public boolean anyRunning(Class<? extends Countdown> countdownClass) {
|
||||
return anyRunning(countdown -> countdown.getClass().equals(countdownClass));
|
||||
}
|
||||
|
||||
public void cancelAll() {
|
||||
cancelAll(false);
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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());
|
||||
|
|
|
@ -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<Kit> 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));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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<MatchPlayer, Integer> 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);
|
||||
}
|
||||
|
|
|
@ -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),
|
||||
|
|
|
@ -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<Integer> 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();
|
||||
}
|
||||
|
||||
}
|
|
@ -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<MatchPlayer> picking = new HashSet<>();
|
||||
|
||||
@Inject PickerMatchModule(ComponentRenderContext renderer, JoinMatchModule jmm, Optional<TeamModule> teamModule, Optional<ClassModule> classModule, Optional<BlitzModule> blitzModule) {
|
||||
@Inject PickerMatchModule(ComponentRenderContext renderer, JoinMatchModule jmm, BlitzMatchModule bmm, Optional<TeamModule> teamModule, Optional<ClassModule> 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.
|
||||
|
|
|
@ -47,7 +47,7 @@ public class RageModule implements MapModule, MatchModuleFactory<RageMatchModule
|
|||
public static RageModule parse(MapModuleContext context, Logger logger, Document doc) {
|
||||
|
||||
if(doc.getRootElement().getChild("rage") != null) {
|
||||
return new RageModule(context.hasModule(BlitzModule.class));
|
||||
return new RageModule(context.module(BlitzModule.class).filter(BlitzModule::active).isPresent());
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
|
|
|
@ -18,11 +18,11 @@ import org.jdom2.Document;
|
|||
import org.jdom2.Element;
|
||||
import tc.oc.api.docs.virtual.MapDoc;
|
||||
import tc.oc.commons.core.util.Optionals;
|
||||
import tc.oc.pgm.blitz.BlitzModule;
|
||||
import tc.oc.pgm.filters.Filter;
|
||||
import tc.oc.pgm.filters.matcher.StaticFilter;
|
||||
import tc.oc.pgm.filters.parser.FilterParser;
|
||||
import tc.oc.pgm.goals.GoalModule;
|
||||
import tc.oc.pgm.blitz.BlitzModule;
|
||||
import tc.oc.pgm.map.MapModule;
|
||||
import tc.oc.pgm.map.MapModuleContext;
|
||||
import tc.oc.pgm.map.ProtoVersions;
|
||||
|
@ -36,7 +36,7 @@ import tc.oc.pgm.utils.XMLUtils;
|
|||
import tc.oc.pgm.xml.InvalidXMLException;
|
||||
import tc.oc.pgm.xml.Node;
|
||||
|
||||
@ModuleDescription(name = "Score", follows = {GoalModule.class, BlitzModule.class })
|
||||
@ModuleDescription(name = "Score", follows = { GoalModule.class, BlitzModule.class })
|
||||
public class ScoreModule implements MapModule, MatchModuleFactory<ScoreMatchModule> {
|
||||
|
||||
private final ScoreConfig config;
|
||||
|
|
|
@ -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<MonumentWoolFactory> 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<Competitor> competitorsWithGoals = new HashSet<>();
|
||||
|
@ -367,7 +381,7 @@ public class SidebarMatchModule extends MatchModule implements Listener {
|
|||
List<String> 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;
|
||||
|
|
|
@ -77,6 +77,9 @@ public interface TeamFactory extends SluggedFeatureDefinition, Validatable, Feat
|
|||
return org.bukkit.scoreboard.Team.OptionStatus.ALWAYS;
|
||||
}
|
||||
|
||||
@Property(name="lives")
|
||||
Optional<Integer> 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");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -179,6 +179,10 @@ public class StringUtils {
|
|||
return builder.toString();
|
||||
}
|
||||
|
||||
public static List<String> complete(String prefix, Class<? extends Enum> enumClass) {
|
||||
return complete(prefix, Stream.of(enumClass.getEnumConstants()).map(c -> c.name().toLowerCase().replace('_', ' ')));
|
||||
}
|
||||
|
||||
public static List<String> complete(String prefix, Collection<String> options) {
|
||||
return complete(prefix, options.stream());
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue