diff --git a/Content.IntegrationTests/Tests/GameRules/FailAndStartPresetTest.cs b/Content.IntegrationTests/Tests/GameRules/FailAndStartPresetTest.cs index 3109df890a7d..abfd64aa4f09 100644 --- a/Content.IntegrationTests/Tests/GameRules/FailAndStartPresetTest.cs +++ b/Content.IntegrationTests/Tests/GameRules/FailAndStartPresetTest.cs @@ -140,9 +140,10 @@ private void OnRoundStartAttempt(RoundStartAttemptEvent args) while (query.MoveNext(out _, out _, out var gameRule)) { var minPlayers = gameRule.MinPlayers; - if (!gameRule.CancelPresetOnTooFewPlayers) + var maxPlayers = gameRule.MaxPlayers; + if (!gameRule.CancelPresetOnTooFewPlayers && !gameRule.CancelPresetOnTooManyPlayers) continue; - if (args.Players.Length >= minPlayers) + if (args.Players.Length >= minPlayers && args.Players.Length <= maxPlayers) continue; args.Cancel(); diff --git a/Content.Server/GameTicking/Rules/GameRuleSystem.cs b/Content.Server/GameTicking/Rules/GameRuleSystem.cs index cb5b11754952..452ffb0e2129 100644 --- a/Content.Server/GameTicking/Rules/GameRuleSystem.cs +++ b/Content.Server/GameTicking/Rules/GameRuleSystem.cs @@ -38,10 +38,11 @@ private void OnStartAttempt(RoundStartAttemptEvent args) while (query.MoveNext(out var uid, out _, out var gameRule)) { var minPlayers = gameRule.MinPlayers; - if (args.Players.Length >= minPlayers) + var maxPlayers = gameRule.MaxPlayers; + if (args.Players.Length >= minPlayers && args.Players.Length <= maxPlayers) continue; - if (gameRule.CancelPresetOnTooFewPlayers) + if (args.Players.Length < minPlayers && gameRule.CancelPresetOnTooFewPlayers) { ChatManager.SendAdminAnnouncement(Loc.GetString("preset-not-enough-ready-players", ("readyPlayersCount", args.Players.Length), @@ -49,6 +50,14 @@ private void OnStartAttempt(RoundStartAttemptEvent args) ("presetName", ToPrettyString(uid)))); args.Cancel(); } + else if (args.Players.Length > maxPlayers && gameRule.CancelPresetOnTooManyPlayers) + { + ChatManager.SendAdminAnnouncement(Loc.GetString("preset-too-many-ready-players", + ("readyPlayersCount", args.Players.Length), + ("maximumPlayers", maxPlayers), + ("presetName", ToPrettyString(uid)))); + args.Cancel(); + } else { ForceEndSelf(uid, gameRule); diff --git a/Content.Server/GameTicking/Rules/SecretRuleSystem.cs b/Content.Server/GameTicking/Rules/SecretRuleSystem.cs index 5f42888e5e2b..5a88f0e83b52 100644 --- a/Content.Server/GameTicking/Rules/SecretRuleSystem.cs +++ b/Content.Server/GameTicking/Rules/SecretRuleSystem.cs @@ -176,6 +176,9 @@ private bool CanPick([NotNullWhen(true)] GamePresetPrototype? selected, int play if (ruleComp.MinPlayers > players && ruleComp.CancelPresetOnTooFewPlayers) return false; + + if (ruleComp.MaxPlayers < players && ruleComp.CancelPresetOnTooManyPlayers) + return false; } if (_nextRoundAllowed.ContainsKey(selected.ID) && _nextRoundAllowed[selected.ID] > _ticker.RoundId) diff --git a/Content.Shared/GameTicking/Components/GameRuleComponent.cs b/Content.Shared/GameTicking/Components/GameRuleComponent.cs index 87a5822d4747..9c7a67f0337c 100644 --- a/Content.Shared/GameTicking/Components/GameRuleComponent.cs +++ b/Content.Shared/GameTicking/Components/GameRuleComponent.cs @@ -23,6 +23,12 @@ public sealed partial class GameRuleComponent : Component [DataField] public int MinPlayers; + /// + /// The maximum amount of players supported by this game rule. + /// + [DataField] + public int MaxPlayers = 9999; // cheap hack + /// /// If true, this rule not having enough players will cancel the preset selection. /// If false, it will simply not run silently. @@ -30,6 +36,13 @@ public sealed partial class GameRuleComponent : Component [DataField] public bool CancelPresetOnTooFewPlayers = true; + /// + /// If true, this rule having too many players will cancel the preset selection. + /// If false, it will simply not run silently. + /// + [DataField] + public bool CancelPresetOnTooManyPlayers = false; + /// /// A delay for when the rule the is started and when the starting logic actually runs. /// diff --git a/Resources/Locale/en-US/_Impstation/game-ticking/game-presets/preset-extended-lowpop.ftl b/Resources/Locale/en-US/_Impstation/game-ticking/game-presets/preset-extended-lowpop.ftl new file mode 100644 index 000000000000..26b47d0b0252 --- /dev/null +++ b/Resources/Locale/en-US/_Impstation/game-ticking/game-presets/preset-extended-lowpop.ftl @@ -0,0 +1,2 @@ +extended-lowpop-title = Extended (Lowpop) +extended-lowpop-description = A calm experience. diff --git a/Resources/Locale/en-US/_Impstation/game-ticking/game-presets/preset-survival-lowpop.ftl b/Resources/Locale/en-US/_Impstation/game-ticking/game-presets/preset-survival-lowpop.ftl new file mode 100644 index 000000000000..3ca9e611c8b2 --- /dev/null +++ b/Resources/Locale/en-US/_Impstation/game-ticking/game-presets/preset-survival-lowpop.ftl @@ -0,0 +1,2 @@ +survival-lowpop-title = Survival (Lowpop) +survival-lowpop-description = No internal threats, but how long can the station survive increasingly chaotic and frequent events? diff --git a/Resources/Locale/en-US/_Impstation/game-ticking/game-presets/preset-thief-lowpop.ftl b/Resources/Locale/en-US/_Impstation/game-ticking/game-presets/preset-thief-lowpop.ftl new file mode 100644 index 000000000000..77dea8e16c86 --- /dev/null +++ b/Resources/Locale/en-US/_Impstation/game-ticking/game-presets/preset-thief-lowpop.ftl @@ -0,0 +1,2 @@ +thief-lowpop-title = Thief (Lowpop) +thief-lowpop-description = Watch your pockets. diff --git a/Resources/Locale/en-US/_Impstation/game-ticking/game-presets/preset-traitor-lowpop.ftl b/Resources/Locale/en-US/_Impstation/game-ticking/game-presets/preset-traitor-lowpop.ftl new file mode 100644 index 000000000000..f9931870540d --- /dev/null +++ b/Resources/Locale/en-US/_Impstation/game-ticking/game-presets/preset-traitor-lowpop.ftl @@ -0,0 +1,2 @@ +traitor-lowpop-title = Traitor (Lowpop) +traitor-lowpop-description = There are traitors among us... diff --git a/Resources/Locale/en-US/_Impstation/game-ticking/game-ticker.ftl b/Resources/Locale/en-US/_Impstation/game-ticking/game-ticker.ftl new file mode 100644 index 000000000000..711ff94ccda6 --- /dev/null +++ b/Resources/Locale/en-US/_Impstation/game-ticking/game-ticker.ftl @@ -0,0 +1 @@ +preset-too-many-ready-players = Can't start {$presetName}. Requires at most {$maximumPlayers} players but we have {$readyPlayersCount}. \ No newline at end of file diff --git a/Resources/Prototypes/GameRules/events.yml b/Resources/Prototypes/GameRules/events.yml index 76f583b1365d..757abd2a34eb 100644 --- a/Resources/Prototypes/GameRules/events.yml +++ b/Resources/Prototypes/GameRules/events.yml @@ -41,6 +41,8 @@ id: SleeperlessAntagEventsTable table: !type:AllSelector # we need to pass a list of rules, since rules have further restrictions to consider via StationEventComp children: + - !type:NestedSelector # DeltaV + tableId: BasicAntagEventsTableDeltaV - id: ClosetSkeleton - id: DragonSpawn - id: KingRatMigration diff --git a/Resources/Prototypes/_Goobstation/game_presets.yml b/Resources/Prototypes/_Goobstation/game_presets.yml index 20b6a0d16b26..8f9938419245 100644 --- a/Resources/Prototypes/_Goobstation/game_presets.yml +++ b/Resources/Prototypes/_Goobstation/game_presets.yml @@ -15,6 +15,7 @@ - MeteorSwarmScheduler - SpaceTrafficControlEventScheduler - BasicRoundstartVariation + - MidpopLimiter - type: gamePreset id: Secretling diff --git a/Resources/Prototypes/_Impstation/GameRules/events.yml b/Resources/Prototypes/_Impstation/GameRules/events.yml index f3464cfe7d85..1c5a01ee0e9b 100644 --- a/Resources/Prototypes/_Impstation/GameRules/events.yml +++ b/Resources/Prototypes/_Impstation/GameRules/events.yml @@ -1,3 +1,17 @@ +- type: entityTable + id: LowpopAntagEventsTable + table: !type:AllSelector # we need to pass a list of rules, since rules have further restrictions to consider via StationEventComp + children: + - !type:NestedSelector # DeltaV + tableId: BasicAntagEventsTableDeltaV + - id: ClosetSkeleton + - id: DragonSpawn + - id: KingRatMigration + - id: NinjaSpawn + - id: RevenantSpawn + - id: GoblinStowawaysEvent # imp + - id: GoblinCastawaysEvent # imp + - type: entity id: ChangelingAwakening parent: BaseGameRule diff --git a/Resources/Prototypes/_Impstation/GameRules/roundstart.yml b/Resources/Prototypes/_Impstation/GameRules/roundstart.yml index efe7876974d3..2e09aa450a96 100644 --- a/Resources/Prototypes/_Impstation/GameRules/roundstart.yml +++ b/Resources/Prototypes/_Impstation/GameRules/roundstart.yml @@ -98,3 +98,53 @@ forceAllPossible: true mindRoles: - MindRoleTraitor + +# population limiters + +- type: entity + parent: BaseGameRule + id: MidpopLimiter + components: + - type: GameRule + minPlayers: 20 + +- type: entity + parent: BaseGameRule + id: LowpopLimiter + components: + - type: GameRule + maxPlayers: 19 + cancelPresetOnTooManyPlayers: true + +# event schedulers + +- type: entityTable + id: LowpopGameRulesTable + table: !type:AllSelector # we need to pass a list of rules, since rules have further restrictions to consider via StationEventComp + children: + - !type:NestedSelector + tableId: BasicCalmEventsTable + - !type:NestedSelector + tableId: LowpopAntagEventsTable + - !type:NestedSelector + tableId: CargoGiftsTable + - !type:NestedSelector + tableId: CalmPestEventsTable + - !type:NestedSelector + tableId: SpicyPestEventsTable + +- type: entity + id: LowpopStationEventScheduler + parent: BaseGameRule + components: + - type: BasicStationEventScheduler + scheduledGameRules: !type:NestedSelector + tableId: LowpopGameRulesTable + +- type: entity + id: LowpopRampingStationEventScheduler + parent: BaseGameRule + components: + - type: RampingStationEventScheduler + scheduledGameRules: !type:NestedSelector + tableId: LowpopGameRulesTable diff --git a/Resources/Prototypes/_Impstation/game_presets.yml b/Resources/Prototypes/_Impstation/game_presets.yml index 19624453d3ed..f4ed58ced98f 100644 --- a/Resources/Prototypes/_Impstation/game_presets.yml +++ b/Resources/Prototypes/_Impstation/game_presets.yml @@ -19,3 +19,65 @@ - SpaceTrafficControlEventScheduler - SpaceTrafficControlFriendlyEventScheduler - BasicRoundstartVariation + +- type: gamePreset + id: TraitorLowpop + alias: + - LowpopTraitor + name: traitor-lowpop-title + description: traitor-lowpop-description + showInVote: false + rules: + - Traitor + - SubGamemodesRule + - LowpopStationEventScheduler + - MeteorSwarmScheduler + - SpaceTrafficControlEventScheduler + - BasicRoundstartVariation + - LowpopLimiter + +- type: gamePreset + id: ThiefLowpop + alias: + - LowpopThief + name: thief-lowpop-title + description: thief-lowpop-description + showInVote: false + rules: + - Thief + - LowpopStationEventScheduler + - MeteorSwarmScheduler + - SpaceTrafficControlEventScheduler + - BasicRoundstartVariation + - LowpopLimiter + +- type: gamePreset + id: SurvivalLowpop + alias: + - LowpopSurvival + name: survival-lowpop-title + description: survival-lowpop-description + showInVote: false + cooldown: 2 + rules: + - MeteorSwarmScheduler + - LowpopRampingStationEventScheduler + - SpaceTrafficControlEventScheduler + - SpaceTrafficControlFriendlyEventScheduler + - BasicRoundstartVariation + - LowpopLimiter + +- type: gamePreset + id: ExtendedLowpop + alias: + - LowpopExtended + name: extended-lowpop-title + description: extended-lowpop-description + showInVote: false + cooldown: 1 # zzz + rules: + - LowpopStationEventScheduler + - MeteorSwarmScheduler + - SpaceTrafficControlEventScheduler + - BasicRoundstartVariation + - LowpopLimiter diff --git a/Resources/Prototypes/game_presets.yml b/Resources/Prototypes/game_presets.yml index 65b82f63e81b..9d99a7cc5c06 100644 --- a/Resources/Prototypes/game_presets.yml +++ b/Resources/Prototypes/game_presets.yml @@ -12,6 +12,7 @@ - SpaceTrafficControlEventScheduler - SpaceTrafficControlFriendlyEventScheduler - BasicRoundstartVariation + - MidpopLimiter - type: gamePreset id: KesslerSyndrome @@ -28,6 +29,7 @@ - RampingStationEventScheduler - SpaceTrafficControlEventScheduler - BasicRoundstartVariation + - MidpopLimiter - type: gamePreset id: AllAtOnce @@ -159,6 +161,7 @@ - MeteorSwarmScheduler - SpaceTrafficControlEventScheduler - BasicRoundstartVariation + - MidpopLimiter - type: gamePreset id: SpyVsSpy @@ -175,6 +178,7 @@ - MeteorSwarmScheduler - SpaceTrafficControlEventScheduler - BasicRoundstartVariation + - MidpopLimiter - type: gamePreset id: SpyVsSpy3TC @@ -245,6 +249,7 @@ - MeteorSwarmScheduler - SpaceTrafficControlEventScheduler - BasicRoundstartVariation + - MidpopLimiter - type: gamePreset id: Revolutionary @@ -262,6 +267,7 @@ - MeteorSwarmScheduler - SpaceTrafficControlEventScheduler - BasicRoundstartVariation + - MidpopLimiter - type: gamePreset id: Zombie @@ -281,6 +287,7 @@ - MeteorSwarmScheduler - SpaceTrafficControlEventScheduler - BasicRoundstartVariation + - MidpopLimiter - type: gamePreset id: Zombieteors @@ -298,3 +305,4 @@ - KesslerSyndromeScheduler - SpaceTrafficControlEventScheduler - BasicRoundstartVariation + - MidpopLimiter diff --git a/Resources/Prototypes/secret_weights.yml b/Resources/Prototypes/secret_weights.yml index 7fdb74292edb..09d70ef09f06 100644 --- a/Resources/Prototypes/secret_weights.yml +++ b/Resources/Prototypes/secret_weights.yml @@ -1,6 +1,7 @@ - type: weightedRandom id: Secret weights: + # regular gamemodes, gamemodes with 20 population requirement or more Nukeops: 0.15 Traitor: 0.50 SpyVsSpy: 0.03 @@ -10,3 +11,9 @@ Survival: 0.10 KesslerSyndrome: 0.01 Revolutionary: 0.03 + + # lowpop gamemodes, gamemodes with 19 population requirement or below + TraitorLowpop: 0.50 + ThiefLowpop: 0.25 + SurvivalLowpop: 0.20 + ExtendedLowpop: 0.05