diff --git a/Content.Server/GameTicking/Rules/AllCaptainsRuleSystem.cs b/Content.Server/GameTicking/Rules/AllCaptainsRuleSystem.cs new file mode 100644 index 0000000000..6d755afb72 --- /dev/null +++ b/Content.Server/GameTicking/Rules/AllCaptainsRuleSystem.cs @@ -0,0 +1,73 @@ +using System.Linq; +using Content.Server.GameTicking.Presets; +using Content.Server.GameTicking.Rules.Configurations; +using Content.Server.Station.Components; +using Content.Shared.Random; +using Content.Shared.Random.Helpers; +using Robust.Shared.Map; +using Robust.Shared.Prototypes; +using Robust.Shared.Random; +using Robust.Shared.Configuration; +using Content.Shared.CCVar; + +namespace Content.Server.GameTicking.Rules; + +public sealed class AllCaptainsRuleSystem : GameRuleSystem +{ + [Dependency] private readonly IPrototypeManager _prototypeManager = default!; + [Dependency] private readonly IRobustRandom _random = default!; + [Dependency] private readonly GameTicker _ticker = default!; + [Dependency] private readonly IConfigurationManager _cfg = default!; + + public override string Prototype => "AllCaptains"; + + private EntityUid holdJobs; // dunno if there should only be one reference like this because I'm a ss14 noob but hey it's only gotta work one day :P + + public override void Added() + { + } + + public override void Started() + { + // temporarily disable role timers -- super hacky way workaround for client to be aware that role timers aren't required + // without having to set up some kind of replication + _cfg.SetCVar(CCVars.GameRoleTimers, false); + } + + public override void Ended() + { + _cfg.SetCVar(CCVars.GameRoleTimers, true); + } + + public StationJobsComponent GetJobs(EntityUid station) + { + if (!holdJobs.IsValid() || !HasComp(holdJobs)) // this doesn't check station parameter since all captains mode is the same for all stations. + { + holdJobs = Spawn(null, new EntityCoordinates(station, Vector2.Zero)); + var stationJobs = AddComp(holdJobs); + + // Create captains-only specific job list + var mapJobList = new Dictionary> {{"Captain", new List{int.MaxValue, int.MaxValue}}}; + + stationJobs.RoundStartTotalJobs = mapJobList.Values.Where(x => x[0] is not null && x[0] > 0).Sum(x => x[0]!.Value); + stationJobs.MidRoundTotalJobs = mapJobList.Values.Where(x => x[1] is not null && x[1] > 0).Sum(x => x[1]!.Value); + stationJobs.TotalJobs = stationJobs.MidRoundTotalJobs; + stationJobs.JobList = mapJobList.ToDictionary(x => x.Key, x => + { + if (x.Value[1] <= -1) + return null; + return (uint?) x.Value[1]; + }); + stationJobs.RoundStartJobList = mapJobList.ToDictionary(x => x.Key, x => + { + if (x.Value[0] <= -1) + return null; + return (uint?) x.Value[0]; + }); + stationJobs.OverflowJobs = new HashSet{"Captain"}; //stationData.StationConfig.OverflowJobs.ToHashSet(); + } + + return Comp(holdJobs); + } + +} diff --git a/Content.Server/GameTicking/Rules/TraitorRuleSystem.cs b/Content.Server/GameTicking/Rules/TraitorRuleSystem.cs index e005357f1f..02969d6852 100644 --- a/Content.Server/GameTicking/Rules/TraitorRuleSystem.cs +++ b/Content.Server/GameTicking/Rules/TraitorRuleSystem.cs @@ -36,6 +36,7 @@ public sealed class TraitorRuleSystem : GameRuleSystem [Dependency] private readonly MobStateSystem _mobStateSystem = default!; [Dependency] private readonly UplinkSystem _uplink = default!; [Dependency] private readonly SharedAudioSystem _audioSystem = default!; + [Dependency] private readonly AllCaptainsRuleSystem _allCaptainsRule = default!; private ISawmill _sawmill = default!; @@ -178,7 +179,8 @@ public sealed class TraitorRuleSystem : GameRuleSystem foreach (var player in candidates.Keys) { // Role prevents antag. - if (!(player.Data.ContentData()?.Mind?.AllRoles.All(role => role is not Job { CanBeAntag: false }) ?? false)) + if (!(_allCaptainsRule != null && _allCaptainsRule.RuleStarted) && // all captains mode lets some of the captains be traitors :3 + !(player.Data.ContentData()?.Mind?.AllRoles.All(role => role is not Job { CanBeAntag: false }) ?? false)) { continue; } @@ -296,7 +298,8 @@ public sealed class TraitorRuleSystem : GameRuleSystem if (ev.JobId == null || !_prototypeManager.TryIndex(ev.JobId, out var job)) return; - if (!job.CanBeAntag) + if (!job.CanBeAntag && + !(_allCaptainsRule != null && _allCaptainsRule.RuleStarted)) // all captains mode lets some of the captains be traitors :3 return; // Before the announcement is made, late-joiners are considered the same as players who readied. diff --git a/Content.Server/Players/PlayTimeTracking/PlayTimeTrackingSystem.cs b/Content.Server/Players/PlayTimeTracking/PlayTimeTrackingSystem.cs index 1ff9eeee49..a3c3b7d3c7 100644 --- a/Content.Server/Players/PlayTimeTracking/PlayTimeTrackingSystem.cs +++ b/Content.Server/Players/PlayTimeTracking/PlayTimeTrackingSystem.cs @@ -2,6 +2,7 @@ using System.Linq; using Content.Server.Afk; using Content.Server.Afk.Events; using Content.Server.GameTicking; +using Content.Server.GameTicking.Rules; using Content.Server.Roles; using Content.Shared.CCVar; using Content.Shared.GameTicking; @@ -28,6 +29,7 @@ public sealed class PlayTimeTrackingSystem : EntitySystem [Dependency] private readonly IPrototypeManager _prototypes = default!; [Dependency] private readonly IConfigurationManager _cfg = default!; [Dependency] private readonly PlayTimeTrackingManager _tracking = default!; + [Dependency] private readonly AllCaptainsRuleSystem _allCaptainsRule = default!; public override void Initialize() { @@ -159,7 +161,8 @@ public sealed class PlayTimeTrackingSystem : EntitySystem { if (!_prototypes.TryIndex(role, out var job) || job.Requirements == null || - !_cfg.GetCVar(CCVars.GameRoleTimers)) + !_cfg.GetCVar(CCVars.GameRoleTimers) || + (_allCaptainsRule != null && _allCaptainsRule.RuleStarted)) return true; var playTimes = _tracking.GetTrackerTimes(player); @@ -170,7 +173,7 @@ public sealed class PlayTimeTrackingSystem : EntitySystem public HashSet GetDisallowedJobs(IPlayerSession player) { var roles = new HashSet(); - if (!_cfg.GetCVar(CCVars.GameRoleTimers)) + if (!_cfg.GetCVar(CCVars.GameRoleTimers) || (_allCaptainsRule != null && _allCaptainsRule.RuleStarted)) return roles; var playTimes = _tracking.GetTrackerTimes(player); @@ -197,7 +200,7 @@ public sealed class PlayTimeTrackingSystem : EntitySystem public void RemoveDisallowedJobs(NetUserId userId, ref List jobs) { - if (!_cfg.GetCVar(CCVars.GameRoleTimers)) + if (!_cfg.GetCVar(CCVars.GameRoleTimers) || (_allCaptainsRule != null && _allCaptainsRule.RuleStarted)) return; var player = _playerManager.GetSessionByUserId(userId); diff --git a/Content.Server/Station/Components/StationJobsComponent.cs b/Content.Server/Station/Components/StationJobsComponent.cs index 42295fcca7..100fcc1aef 100644 --- a/Content.Server/Station/Components/StationJobsComponent.cs +++ b/Content.Server/Station/Components/StationJobsComponent.cs @@ -1,4 +1,5 @@ -using Content.Server.Station.Systems; +using Content.Server.GameTicking.Rules; +using Content.Server.Station.Systems; using Content.Shared.Roles; using JetBrains.Annotations; using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype.Dictionary; @@ -9,7 +10,7 @@ namespace Content.Server.Station.Components; /// /// Stores information about a station's job selection. /// -[RegisterComponent, Access(typeof(StationJobsSystem)), PublicAPI] +[RegisterComponent, Access(typeof(StationJobsSystem), typeof(AllCaptainsRuleSystem)), PublicAPI] public sealed class StationJobsComponent : Component { /// diff --git a/Content.Server/Station/Systems/StationJobsSystem.cs b/Content.Server/Station/Systems/StationJobsSystem.cs index 268d50739d..12f8611f43 100644 --- a/Content.Server/Station/Systems/StationJobsSystem.cs +++ b/Content.Server/Station/Systems/StationJobsSystem.cs @@ -1,6 +1,7 @@ using System.Diagnostics.CodeAnalysis; using System.Linq; using Content.Server.GameTicking; +using Content.Server.GameTicking.Rules; using Content.Server.Station.Components; using Content.Shared.CCVar; using Content.Shared.GameTicking; @@ -25,6 +26,7 @@ public sealed partial class StationJobsSystem : EntitySystem [Dependency] private readonly GameTicker _gameTicker = default!; [Dependency] private readonly StationSystem _stationSystem = default!; [Dependency] private readonly IPlayerManager _playerManager = default!; + [Dependency] private readonly AllCaptainsRuleSystem _allCaptainsRule = default!; /// public override void Initialize() @@ -33,6 +35,8 @@ public sealed partial class StationJobsSystem : EntitySystem SubscribeLocalEvent(OnStationRenamed); SubscribeLocalEvent(OnStationDeletion); SubscribeLocalEvent(OnPlayerJoinedLobby); + SubscribeLocalEvent(OnGameRuleStarted); + SubscribeLocalEvent(OnGameRuleEnded); _configurationManager.OnValueChanged(CCVars.GameDisallowLateJoins, _ => UpdateJobsAvailable(), true); } @@ -136,6 +140,9 @@ public sealed partial class StationJobsSystem : EntitySystem var jobList = stationJobs.JobList; + if (_allCaptainsRule != null && _allCaptainsRule.RuleStarted) + jobList = _allCaptainsRule.GetJobs(station).JobList; + // This should: // - Return true when zero slots are added/removed. // - Return true when you add. @@ -214,6 +221,11 @@ public sealed partial class StationJobsSystem : EntitySystem var jobList = stationJobs.JobList; + // If all captains mode, override job list with the allcaptains job list -- prevents modifying the "real" job list + // in case mode changes later. + if (_allCaptainsRule != null && _allCaptainsRule.RuleStarted) + jobList = _allCaptainsRule.GetJobs(station).JobList; + switch (jobList.ContainsKey(jobPrototypeId)) { case false: @@ -313,7 +325,11 @@ public sealed partial class StationJobsSystem : EntitySystem if (!Resolve(station, ref stationJobs)) throw new ArgumentException("Tried to use a non-station entity as a station!", nameof(station)); - if (stationJobs.JobList.TryGetValue(jobPrototypeId, out var job)) + var jobList = stationJobs.JobList; + if (_allCaptainsRule != null && _allCaptainsRule.RuleStarted) + jobList = _allCaptainsRule.GetJobs(station).JobList; + + if (jobList.TryGetValue(jobPrototypeId, out var job)) { slots = job; return true; @@ -337,6 +353,9 @@ public sealed partial class StationJobsSystem : EntitySystem if (!Resolve(station, ref stationJobs)) throw new ArgumentException("Tried to use a non-station entity as a station!", nameof(station)); + if (_allCaptainsRule != null && _allCaptainsRule.RuleStarted) + return _allCaptainsRule.GetJobs(station).JobList.Where(x => x.Value != 0).Select(x => x.Key).ToHashSet(); + return stationJobs.JobList.Where(x => x.Value != 0).Select(x => x.Key).ToHashSet(); } @@ -352,6 +371,9 @@ public sealed partial class StationJobsSystem : EntitySystem if (!Resolve(station, ref stationJobs)) throw new ArgumentException("Tried to use a non-station entity as a station!", nameof(station)); + if (_allCaptainsRule != null && _allCaptainsRule.RuleStarted) + return _allCaptainsRule.GetJobs(station).OverflowJobs.ToHashSet(); + return stationJobs.OverflowJobs.ToHashSet(); } @@ -367,6 +389,9 @@ public sealed partial class StationJobsSystem : EntitySystem if (!Resolve(station, ref stationJobs)) throw new ArgumentException("Tried to use a non-station entity as a station!", nameof(station)); + if (_allCaptainsRule != null && _allCaptainsRule.RuleStarted) + return _allCaptainsRule.GetJobs(station).JobList; + return stationJobs.JobList; } @@ -382,6 +407,9 @@ public sealed partial class StationJobsSystem : EntitySystem if (!Resolve(station, ref stationJobs)) throw new ArgumentException("Tried to use a non-station entity as a station!", nameof(station)); + if (_allCaptainsRule != null && _allCaptainsRule.RuleStarted) + return _allCaptainsRule.GetJobs(station).RoundStartJobList; + return stationJobs.RoundStartJobList; } @@ -467,6 +495,8 @@ public sealed partial class StationJobsSystem : EntitySystem foreach (var station in _stationSystem.Stations) { var list = Comp(station).JobList.ToDictionary(x => x.Key, x => x.Value); + if (_allCaptainsRule != null && _allCaptainsRule.RuleStarted) + list = _allCaptainsRule.GetJobs(station).JobList.ToDictionary(x => x.Key, x => x.Value); jobs.Add(station, list); stationNames.Add(station, Name(station)); } @@ -491,5 +521,17 @@ public sealed partial class StationJobsSystem : EntitySystem UpdateJobsAvailable(); } + private void OnGameRuleStarted(GameRuleStartedEvent msg) + { + if (msg.Rule.ID == "AllCaptains") + UpdateJobsAvailable(); + } + + private void OnGameRuleEnded(GameRuleEndedEvent msg) + { + if (msg.Rule.ID == "AllCaptains") + UpdateJobsAvailable(); + } + #endregion } diff --git a/Resources/Prototypes/GameRules/roundstart.yml b/Resources/Prototypes/GameRules/roundstart.yml index 5a62ca2706..31849e1b59 100644 --- a/Resources/Prototypes/GameRules/roundstart.yml +++ b/Resources/Prototypes/GameRules/roundstart.yml @@ -1,3 +1,9 @@ +- type: gameRule + id: AllCaptains + config: + !type:GenericGameRuleConfiguration + id: AllCaptains + - type: gameRule id: DeathMatch config: diff --git a/Resources/Prototypes/game_presets.yml b/Resources/Prototypes/game_presets.yml index f56452f20b..f73bafae7b 100644 --- a/Resources/Prototypes/game_presets.yml +++ b/Resources/Prototypes/game_presets.yml @@ -119,3 +119,23 @@ rules: - Pirates - BasicStationEventScheduler + +- type: gamePreset + id: OopsAllCaptains + alias: + - captains + - captain + - onlycaptains + - OnlyCaptains + - oopsonlycaptains + - oopsallcaptains + - allcaptains + - AllCaptains + name: "Oops! All Captains." + description: "Look at you. You're the captain now." + showInVote: true + rules: + - AllCaptains + - Traitor + - BasicStationEventScheduler + diff --git a/Resources/Prototypes/secret_weights.yml b/Resources/Prototypes/secret_weights.yml index 9c392a1549..d9378a6dcf 100644 --- a/Resources/Prototypes/secret_weights.yml +++ b/Resources/Prototypes/secret_weights.yml @@ -5,3 +5,4 @@ Nukeops: 0.25 Traitor: 0.75 Zombie: 0.05 + OopsAllCaptains: 0.20