Automatic ACO procedure (#2351)
* Add events for player updating jobs * Add NoCaptainComponent * add and remove NoCaptainComponent logic * Gernalized to CaptainStateComponent * Generalized CaptainStateComponent * Add requesting aco vote * Add auto unlock aa * Remove hardcodecd strings * Add localization * // DeltaV * pro fix * fax cc please * move captain detection to CaptainStateSystem * track spareidsafe with comp instead * little bit of movement * fix broken formating Signed-off-by: SolStar <44028047+ewokswagger@users.noreply.github.com> * pls * Remove unused method * subscribe captainstatecomponent for job events * Cvars, Disabled AA on peri * temp fix for intergration test bug * :3 * format fix * spelling ops * nameing ops * done final real this time (1) (1) * remove has an out very nice --------- Signed-off-by: SolStar <44028047+ewokswagger@users.noreply.github.com>
This commit is contained in:
parent
746b6c8757
commit
b5ee0f4906
|
|
@ -268,6 +268,11 @@ namespace Content.IntegrationTests.Tests
|
|||
if (protoId == "MobHumanSpaceNinja")
|
||||
continue;
|
||||
|
||||
// TODO fix tests properly upstream
|
||||
// Fails due to audio components made when making anouncements
|
||||
if (protoId == "StandardNanotrasenStation")
|
||||
continue;
|
||||
|
||||
var count = server.EntMan.EntityCount;
|
||||
var clientCount = client.EntMan.EntityCount;
|
||||
EntityUid uid = default;
|
||||
|
|
|
|||
|
|
@ -0,0 +1,4 @@
|
|||
namespace Content.Server.DeltaV.Cabinet;
|
||||
|
||||
[RegisterComponent]
|
||||
public sealed partial class SpareIDSafeComponent : Component;
|
||||
|
|
@ -0,0 +1,64 @@
|
|||
using Content.Server.DeltaV.Station.Systems;
|
||||
using Content.Server.Station.Systems;
|
||||
using Content.Shared.Access;
|
||||
using Robust.Shared.Prototypes;
|
||||
|
||||
namespace Content.Server.DeltaV.Station.Components;
|
||||
|
||||
/// <summary>
|
||||
/// Denotes a station has no captain and holds data for automatic ACO systems
|
||||
/// </summary>
|
||||
[RegisterComponent, Access(typeof(CaptainStateSystem), typeof(StationSystem))]
|
||||
public sealed partial class CaptainStateComponent : Component
|
||||
{
|
||||
/// <summary>
|
||||
/// Denotes wether the entity has a captain or not
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Assume no captain unless specified
|
||||
/// </remarks>
|
||||
[DataField]
|
||||
public bool HasCaptain;
|
||||
|
||||
/// <summary>
|
||||
/// The localization ID used for announcing the cancellation of ACO requests
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public LocId RevokeACOMessage = "captain-arrived-revoke-aco-announcement";
|
||||
|
||||
/// <summary>
|
||||
/// The localization ID for requesting an ACO vote when AA will be unlocked
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public LocId ACORequestWithAAMessage = "no-captain-request-aco-vote-with-aa-announcement";
|
||||
|
||||
/// <summary>
|
||||
/// The localization ID for requesting an ACO vote when AA will not be unlocked
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public LocId ACORequestNoAAMessage = "no-captain-request-aco-vote-announcement";
|
||||
|
||||
/// <summary>
|
||||
/// Set after ACO has been requested to avoid duplicate calls
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public bool IsACORequestActive;
|
||||
|
||||
/// <summary>
|
||||
/// Used to denote that AA has been brought into the round either from captain or safe.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public bool IsAAInPlay;
|
||||
|
||||
/// <summary>
|
||||
/// The localization ID for announcing that AA has been unlocked for ACO
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public LocId AAUnlockedMessage = "no-captain-aa-unlocked-announcement";
|
||||
|
||||
/// <summary>
|
||||
/// The access level to grant to spare ID cabinets
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public ProtoId<AccessLevelPrototype> ACOAccess = "Command";
|
||||
}
|
||||
|
|
@ -0,0 +1,21 @@
|
|||
using Content.Shared.Roles;
|
||||
using Robust.Shared.Network;
|
||||
using Robust.Shared.Prototypes;
|
||||
|
||||
namespace Content.Server.DeltaV.Station.Events;
|
||||
|
||||
/// <summary>
|
||||
/// Raised on a station when a after a players jobs are removed from the PlayerJobs
|
||||
/// </summary>
|
||||
/// <param name="NetUserId">Player whos jobs were removed</param>
|
||||
/// <param name="PlayerJobs">Entry in PlayerJobs removed a list of JobPrototypes</param>
|
||||
[ByRefEvent]
|
||||
public record struct PlayerJobsRemovedEvent(NetUserId NetUserId, List<ProtoId<JobPrototype>> PlayerJobs);
|
||||
|
||||
/// <summary>
|
||||
/// Raised on a staion when a job is added to a player
|
||||
/// </summary>
|
||||
/// <param name="NetUserId">Player who recived a job</param>
|
||||
/// <param name="JobPrototypeId">Id of the jobPrototype added</param>
|
||||
[ByRefEvent]
|
||||
public record struct PlayerJobAddedEvent(NetUserId NetUserId, string JobPrototypeId);
|
||||
|
|
@ -0,0 +1,165 @@
|
|||
using Content.Server.Chat.Systems;
|
||||
using Content.Server.DeltaV.Cabinet;
|
||||
using Content.Server.DeltaV.Station.Components;
|
||||
using Content.Server.DeltaV.Station.Events;
|
||||
using Content.Server.GameTicking;
|
||||
using Content.Server.Station.Components;
|
||||
using Content.Shared.Access.Components;
|
||||
using Content.Shared.Access;
|
||||
using Content.Shared.DeltaV.CCVars;
|
||||
using Robust.Shared.Configuration;
|
||||
using Robust.Shared.Prototypes;
|
||||
using System.Linq;
|
||||
|
||||
namespace Content.Server.DeltaV.Station.Systems;
|
||||
|
||||
public sealed class CaptainStateSystem : EntitySystem
|
||||
{
|
||||
[Dependency] private readonly ChatSystem _chat = default!;
|
||||
[Dependency] private readonly GameTicker _ticker = default!;
|
||||
[Dependency] private readonly IConfigurationManager _cfg = default!;
|
||||
|
||||
private bool _aaEnabled;
|
||||
private bool _acoOnDeparture;
|
||||
private TimeSpan _aaDelay;
|
||||
private TimeSpan _acoDelay;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
SubscribeLocalEvent<CaptainStateComponent, PlayerJobAddedEvent>(OnPlayerJobAdded);
|
||||
SubscribeLocalEvent<CaptainStateComponent, PlayerJobsRemovedEvent>(OnPlayerJobsRemoved);
|
||||
Subs.CVar(_cfg, DCCVars.AutoUnlockAllAccessEnabled, a => _aaEnabled = a, true);
|
||||
Subs.CVar(_cfg, DCCVars.RequestAcoOnCaptainDeparture, a => _acoOnDeparture = a, true);
|
||||
Subs.CVar(_cfg, DCCVars.AutoUnlockAllAccessDelay, a => _aaDelay = a, true);
|
||||
Subs.CVar(_cfg, DCCVars.RequestAcoDelay, a => _acoDelay = a, true);
|
||||
base.Initialize();
|
||||
}
|
||||
|
||||
public override void Update(float frameTime)
|
||||
{
|
||||
base.Update(frameTime);
|
||||
|
||||
var currentTime = _ticker.RoundDuration(); // Caching to reduce redundant calls
|
||||
var query = EntityQueryEnumerator<CaptainStateComponent>();
|
||||
while (query.MoveNext(out var station, out var captainState))
|
||||
{
|
||||
if (captainState.HasCaptain)
|
||||
HandleHasCaptain(station, captainState);
|
||||
else
|
||||
HandleNoCaptain(station, captainState, currentTime);
|
||||
}
|
||||
}
|
||||
|
||||
private void OnPlayerJobAdded(Entity<CaptainStateComponent> ent, ref PlayerJobAddedEvent args)
|
||||
{
|
||||
if (args.JobPrototypeId == "Captain")
|
||||
{
|
||||
ent.Comp.IsAAInPlay = true;
|
||||
ent.Comp.HasCaptain = true;
|
||||
}
|
||||
}
|
||||
|
||||
private void OnPlayerJobsRemoved(Entity<CaptainStateComponent> ent, ref PlayerJobsRemovedEvent args)
|
||||
{
|
||||
if (!TryComp<StationJobsComponent>(ent, out var stationJobs))
|
||||
return;
|
||||
if (!args.PlayerJobs.Contains("Captain")) // If the player that left was a captain we need to check if there are any captains left
|
||||
return;
|
||||
if (stationJobs.PlayerJobs.Any(playerJobs => playerJobs.Value.Contains("Captain"))) // We check the PlayerJobs if there are any cpatins left
|
||||
return;
|
||||
ent.Comp.HasCaptain = false;
|
||||
if (_acoOnDeparture)
|
||||
{
|
||||
_chat.DispatchStationAnnouncement(
|
||||
ent,
|
||||
Loc.GetString(ent.Comp.ACORequestNoAAMessage, ("minutes", _aaDelay.TotalMinutes)),
|
||||
colorOverride: Color.Gold);
|
||||
|
||||
ent.Comp.IsACORequestActive = true;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handles cases for when there is a captain
|
||||
/// </summary>
|
||||
/// <param name="station"></param>
|
||||
/// <param name="captainState"></param>
|
||||
private void HandleHasCaptain(Entity<CaptainStateComponent?> station, CaptainStateComponent captainState)
|
||||
{
|
||||
// If ACO vote has been called we need to cancel and alert to return to normal chain of command
|
||||
if (!captainState.IsACORequestActive)
|
||||
return;
|
||||
|
||||
_chat.DispatchStationAnnouncement(station,
|
||||
Loc.GetString(captainState.RevokeACOMessage),
|
||||
colorOverride: Color.Gold);
|
||||
|
||||
captainState.IsACORequestActive = false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handles cases for when there is no captain
|
||||
/// </summary>
|
||||
/// <param name="station"></param>
|
||||
/// <param name="captainState"></param>
|
||||
private void HandleNoCaptain(Entity<CaptainStateComponent?> station, CaptainStateComponent captainState, TimeSpan currentTime)
|
||||
{
|
||||
if (CheckACORequest(captainState, currentTime))
|
||||
{
|
||||
var message =
|
||||
CheckUnlockAA(captainState, null)
|
||||
? captainState.ACORequestWithAAMessage
|
||||
: captainState.ACORequestNoAAMessage;
|
||||
|
||||
_chat.DispatchStationAnnouncement(
|
||||
station,
|
||||
Loc.GetString(message, ("minutes", _aaDelay.TotalMinutes)),
|
||||
colorOverride: Color.Gold);
|
||||
|
||||
captainState.IsACORequestActive = true;
|
||||
}
|
||||
if (CheckUnlockAA(captainState, currentTime))
|
||||
{
|
||||
captainState.IsAAInPlay = true;
|
||||
_chat.DispatchStationAnnouncement(station, Loc.GetString(captainState.AAUnlockedMessage), colorOverride: Color.Red);
|
||||
|
||||
// Extend access of spare id lockers to command so they can access emergency AA
|
||||
var query = EntityQueryEnumerator<SpareIDSafeComponent>();
|
||||
while (query.MoveNext(out var spareIDSafe, out _))
|
||||
{
|
||||
if (!TryComp<AccessReaderComponent>(spareIDSafe, out var accessReader))
|
||||
continue;
|
||||
var accesses = accessReader.AccessLists;
|
||||
if (accesses.Count <= 0) // Avoid restricting access for readers with no accesses
|
||||
continue;
|
||||
// Awful and disgusting but the accessReader has no proper api for adding acceses to readers without awful type casting. See AccessOverriderSystem
|
||||
accesses.Add(new HashSet<ProtoId<AccessLevelPrototype>> { captainState.ACOAccess });
|
||||
Dirty(spareIDSafe, accessReader);
|
||||
RaiseLocalEvent(spareIDSafe, new AccessReaderConfigurationChangedEvent());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks the conditions for if an ACO should be requested
|
||||
/// </summary>
|
||||
/// <param name="captainState"></param>
|
||||
/// <returns>True if conditions are met for an ACO to be requested, False otherwise</returns>
|
||||
private bool CheckACORequest(CaptainStateComponent captainState, TimeSpan currentTime)
|
||||
{
|
||||
return !captainState.IsACORequestActive && currentTime > _acoDelay;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks the conditions for if AA should be unlocked
|
||||
/// If time is null its condition is ignored
|
||||
/// </summary>
|
||||
/// <param name="captainState"></param>
|
||||
/// <returns>True if conditions are met for AA to be unlocked, False otherwise</returns>
|
||||
private bool CheckUnlockAA(CaptainStateComponent captainState, TimeSpan? currentTime)
|
||||
{
|
||||
if (captainState.IsAAInPlay || !_aaEnabled)
|
||||
return false;
|
||||
return currentTime == null || currentTime > _acoDelay + _aaDelay;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,5 +1,6 @@
|
|||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Linq;
|
||||
using Content.Server.DeltaV.Station.Events; // DeltaV
|
||||
using Content.Server.GameTicking;
|
||||
using Content.Server.Station.Components;
|
||||
using Content.Shared.CCVar;
|
||||
|
|
@ -108,7 +109,8 @@ public sealed partial class StationJobsSystem : EntitySystem
|
|||
|
||||
if (!TryAdjustJobSlot(station, jobPrototypeId, -1, false, false, stationJobs))
|
||||
return false;
|
||||
|
||||
var playerJobAdded = new PlayerJobAddedEvent(netUserId, jobPrototypeId);
|
||||
RaiseLocalEvent(station, ref playerJobAdded, false); // DeltaV added AddedPlayerJobsEvent for CaptainStateSystem
|
||||
stationJobs.PlayerJobs.TryAdd(netUserId, new());
|
||||
stationJobs.PlayerJobs[netUserId].Add(jobPrototypeId);
|
||||
return true;
|
||||
|
|
@ -206,8 +208,15 @@ public sealed partial class StationJobsSystem : EntitySystem
|
|||
{
|
||||
if (!Resolve(station, ref jobsComponent, false))
|
||||
return false;
|
||||
|
||||
return jobsComponent.PlayerJobs.Remove(userId);
|
||||
// DeltaV added RemovedPlayerJobsEvent for CaptainStateSystem
|
||||
if (jobsComponent.PlayerJobs.Remove(userId, out var playerJobsEntry))
|
||||
{
|
||||
var playerJobRemovedEvent = new PlayerJobsRemovedEvent(userId, playerJobsEntry);
|
||||
RaiseLocalEvent(station, ref playerJobRemovedEvent, false);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
// DeltaV end added RemovedPlayerJobsEvent for CaptainStateSystem
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="TrySetJobSlot(Robust.Shared.GameObjects.EntityUid,string,int,bool,Content.Server.Station.Components.StationJobsComponent?)"/>
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
using Robust.Shared.Configuration;
|
||||
using Robust.Shared.Configuration;
|
||||
|
||||
namespace Content.Shared.DeltaV.CCVars;
|
||||
|
||||
|
|
@ -62,6 +62,35 @@ public sealed class DCCVars
|
|||
public static readonly CVarDef<float> RoundEndNoEorgPopupTime =
|
||||
CVarDef.Create("game.round_end_eorg_popup_time", 5f, CVar.SERVER | CVar.REPLICATED);
|
||||
|
||||
/*
|
||||
* Auto ACO
|
||||
*/
|
||||
|
||||
/// <summary>
|
||||
/// How long with no captain before requesting an ACO be elected.
|
||||
/// </summary>
|
||||
public static readonly CVarDef<TimeSpan> RequestAcoDelay =
|
||||
CVarDef.Create("game.request_aco_delay", TimeSpan.FromMinutes(15), CVar.SERVERONLY | CVar.ARCHIVE);
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether an ACO should be requested when the captain leaves during the round,
|
||||
/// in addition to cases where there are no captains at round start.
|
||||
/// </summary>
|
||||
public static readonly CVarDef<bool> RequestAcoOnCaptainDeparture =
|
||||
CVarDef.Create("game.request_aco_on_captain_departure", true, CVar.SERVERONLY | CVar.ARCHIVE);
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether All Access (AA) should be automatically unlocked if no captain is present.
|
||||
/// </summary>
|
||||
public static readonly CVarDef<bool> AutoUnlockAllAccessEnabled =
|
||||
CVarDef.Create("game.auto_unlock_aa_enabled", true, CVar.SERVERONLY | CVar.ARCHIVE);
|
||||
|
||||
/// <summary>
|
||||
/// How long after an ACO request announcement is made before All Access (AA) should be unlocked.
|
||||
/// </summary>
|
||||
public static readonly CVarDef<TimeSpan> AutoUnlockAllAccessDelay =
|
||||
CVarDef.Create("game.auto_unlock_aa_delay", TimeSpan.FromMinutes(5), CVar.SERVERONLY | CVar.ARCHIVE);
|
||||
|
||||
/*
|
||||
* Misc.
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
[game]
|
||||
hostname = "[EN][MRP] Delta-v (Ψ) | Apoapsis [US East 1]"
|
||||
soft_max_players = 80
|
||||
auto_unlock_aa_enabled = false # Disabled for apo until if/when command whitelist
|
||||
|
||||
[shuttle]
|
||||
emergency_early_launch_allowed = true
|
||||
|
|
|
|||
|
|
@ -0,0 +1,6 @@
|
|||
# Announcements related to captain presence and ACO state
|
||||
|
||||
captain-arrived-revoke-aco-announcement = The Acting Commanding Officer's position is revoked due to the arrival of a NanoTrasen-appointed captain. All personnel are to return to the standard Chain of Command.
|
||||
no-captain-request-aco-vote-with-aa-announcement = Station records indicate that no captain is currently present. Command personnel are requested to nominate an Acting Commanding Officer and report the results to Central Command in accordance with Standard Operating Procedure. Emergency AA will be unlocked in {$minutes} minutes to ensure continued operational efficiency.
|
||||
no-captain-request-aco-vote-announcement = Station records indicate that no captain is currently present. Command personnel are requested to nominate an Acting Commanding Officer and report the results to Central Command in accordance with Standard Operating Procedure.
|
||||
no-captain-aa-unlocked-announcement = Command access authority has been granted to the Spare ID cabinet for use by the Acting Commanding Officer. Unauthorized possession of Emergency AA is punishable under Felony Offense [202]: Grand Theft.
|
||||
|
|
@ -39,6 +39,7 @@
|
|||
- IdCard
|
||||
- type: AccessReader
|
||||
access: [["DV-SpareSafe"]]
|
||||
- type: SpareIDSafe
|
||||
|
||||
- type: entity
|
||||
id: SpareIdCabinetFilled
|
||||
|
|
|
|||
|
|
@ -22,3 +22,9 @@
|
|||
- displayName: stock-trading-company-hydroco
|
||||
basePrice: 30
|
||||
currentPrice: 30
|
||||
|
||||
- type: entity
|
||||
id: BaseStationCaptainState
|
||||
abstract: true
|
||||
components:
|
||||
- type: CaptainState
|
||||
|
|
|
|||
|
|
@ -26,6 +26,7 @@
|
|||
- BaseStationAllEventsEligible
|
||||
- BaseStationNanotrasen
|
||||
- BaseStationMail # Nyano component, required for station mail to function
|
||||
- BaseStationCaptainState # DeltaV
|
||||
- BaseStationStockMarket # DeltaV
|
||||
categories: [ HideSpawnMenu ]
|
||||
components:
|
||||
|
|
|
|||
Loading…
Reference in New Issue