Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Automatic ACO procedure #2351

Merged
merged 32 commits into from
Dec 9, 2024
Merged
Show file tree
Hide file tree
Changes from 12 commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
f698f49
Add events for player updating jobs
ewokswagger Nov 29, 2024
5d59364
Add NoCaptainComponent
ewokswagger Nov 29, 2024
a9d9bd2
add and remove NoCaptainComponent logic
ewokswagger Nov 29, 2024
d8fa64c
Gernalized to CaptainStateComponent
ewokswagger Nov 29, 2024
e2c93dc
Generalized CaptainStateComponent
ewokswagger Nov 29, 2024
a6b971c
Add requesting aco vote
ewokswagger Nov 30, 2024
ddbd3d3
Add auto unlock aa
ewokswagger Dec 2, 2024
4ba9d96
Remove hardcodecd strings
ewokswagger Dec 2, 2024
a4e0166
Add localization
ewokswagger Dec 2, 2024
fbe2781
// DeltaV
ewokswagger Dec 3, 2024
bda59b0
pro fix
ewokswagger Dec 3, 2024
3c28a4e
fax cc please
ewokswagger Dec 3, 2024
6f20132
move captain detection to CaptainStateSystem
ewokswagger Dec 3, 2024
97f0225
track spareidsafe with comp instead
ewokswagger Dec 3, 2024
05f4020
little bit of movement
ewokswagger Dec 5, 2024
ef3edaa
Merge branch 'master' into auto-aco
ewokswagger Dec 5, 2024
5204be1
fix broken formating
ewokswagger Dec 5, 2024
63c8267
pls
ewokswagger Dec 5, 2024
5504827
Merge branch 'auto-aco' of https://github.com/ewokswagger/Delta-v int…
ewokswagger Dec 5, 2024
21c05cf
Merge branch 'master' into auto-aco
ewokswagger Dec 5, 2024
144dded
Remove unused method
ewokswagger Dec 5, 2024
fc70f54
subscribe captainstatecomponent for job events
ewokswagger Dec 5, 2024
cba4776
Merge branch 'master' into auto-aco
ewokswagger Dec 5, 2024
5516ea5
Cvars, Disabled AA on peri
ewokswagger Dec 8, 2024
4f19a45
temp fix for intergration test bug
ewokswagger Dec 8, 2024
84cae22
Merge branch 'auto-aco' of https://github.com/ewokswagger/Delta-v int…
ewokswagger Dec 8, 2024
903de99
:3
ewokswagger Dec 8, 2024
6584cfc
format fix
ewokswagger Dec 9, 2024
1d35c10
spelling ops
ewokswagger Dec 9, 2024
3102804
nameing ops
ewokswagger Dec 9, 2024
5f5503b
done final real this time (1) (1)
ewokswagger Dec 9, 2024
e938cf4
remove has an out very nice
ewokswagger Dec 9, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
using Content.Server.DeltaV.Station.Systems;
using Content.Server.Station.Systems;
using Content.Shared.Access;
using Robust.Shared.Prototypes;
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom;
ewokswagger marked this conversation as resolved.
Show resolved Hide resolved

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)), AutoGenerateComponentPause]
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>
/// Holds the round time of the last time a captain was present if one is not present currently
/// </summary>
[DataField("TimeSinceCaptain", customTypeSerializer: typeof(TimeOffsetSerializer)), AutoPausedField]
ewokswagger marked this conversation as resolved.
Show resolved Hide resolved
public TimeSpan TimeSinceCaptain = TimeSpan.Zero;

/// <summary>
/// How long with no captain before an ACO is requested
/// </summary>
[DataField]
public TimeSpan ACORequestDelay = TimeSpan.FromMinutes(15);

/// <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 should be unlocked after the delay
/// </summary>
[DataField]
public bool UnlockAA = true;

/// <summary>
/// How long after ACO is requested the spare id cabinet will be unlocked if applicable
/// </summary>
[DataField]
public TimeSpan UnlockAADelay = TimeSpan.FromMinutes(5);

/// <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 used to identify spare ID cabinets
/// </summary>
[DataField]
public List<ProtoId<AccessLevelPrototype>> EmergencyAAAccess = new() { "DV-SpareSafe" };

/// <summary>
/// The access level to grant to spare ID cabinets
/// </summary>
[DataField]
public List<ProtoId<AccessLevelPrototype>> ACOAccess = new() { "Command" };
}
21 changes: 21 additions & 0 deletions Content.Server/DeltaV/Station/Events/PlayerJobEvents.cs
Original file line number Diff line number Diff line change
@@ -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);
86 changes: 86 additions & 0 deletions Content.Server/DeltaV/Station/Systems/CaptainStateSystem.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
using Content.Server.Chat.Systems;
using Content.Server.DeltaV.Station.Components;
using Content.Server.GameTicking;
using Content.Server.Station.Systems;
using Content.Shared.Access.Systems;

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 IEntityManager _entity = default!;
ewokswagger marked this conversation as resolved.
Show resolved Hide resolved

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 == true)
ewokswagger marked this conversation as resolved.
Show resolved Hide resolved
HandleHasCaptain(station, captainState);
else
HandleNoCaptain(station, captainState, currentTime);
}
}

/// <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 == false)
ewokswagger marked this conversation as resolved.
Show resolved Hide resolved
return;
_chat.DispatchStationAnnouncement(station, Loc.GetString(captainState.RevokeACOMessage), colorOverride: Color.Gold);
ewokswagger marked this conversation as resolved.
Show resolved Hide resolved
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 = captainState.UnlockAA ? captainState.ACORequestWithAAMessage : captainState.ACORequestNoAAMessage;
_chat.DispatchStationAnnouncement(station, Loc.GetString(message, ("minutes", captainState.UnlockAADelay.TotalMinutes)), colorOverride: Color.Gold);
captainState.IsACORequestActive = true;
}
if (CheckUnlockAA(captainState, currentTime))
{
captainState.UnlockAA = false; // Once unlocked don't unlock again
_chat.DispatchStationAnnouncement(station, Loc.GetString(captainState.AAUnlockedMessage), colorOverride: Color.Red);
ewokswagger marked this conversation as resolved.
Show resolved Hide resolved

// Extend access of spare id lockers to command so they can access emergency AA
_entity.System<AccessReaderSystem>().ExpandAccessForAllReaders(captainState.EmergencyAAAccess, captainState.ACOAccess);
ewokswagger marked this conversation as resolved.
Show resolved Hide resolved
}
deltanedas marked this conversation as resolved.
Show resolved Hide resolved
}

/// <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 > captainState.TimeSinceCaptain + captainState.ACORequestDelay;
}

/// <summary>
/// Checks the conditions for if AA should be unlocked
/// </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)
{
return captainState.UnlockAA && currentTime > captainState.TimeSinceCaptain + captainState.ACORequestDelay + captainState.UnlockAADelay;
}
}
16 changes: 13 additions & 3 deletions Content.Server/Station/Systems/StationJobsSystem.cs
Original file line number Diff line number Diff line change
@@ -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;
Expand Down Expand Up @@ -108,7 +109,8 @@ public bool TryAssignJob(EntityUid station, string jobPrototypeId, NetUserId net

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;
Expand Down Expand Up @@ -206,8 +208,16 @@ public bool TryRemovePlayerJobs(EntityUid station,
{
if (!Resolve(station, ref jobsComponent, false))
return false;

return jobsComponent.PlayerJobs.Remove(userId);
// DeltaV added RemovedPlayerJobsEvent for CaptainStateSystem
if (jobsComponent.PlayerJobs.TryGetValue(userId, out var playerJobsEntry))
{
jobsComponent.PlayerJobs.Remove(userId);
ewokswagger marked this conversation as resolved.
Show resolved Hide resolved
var playerJobRemovedEvent = new PlayerJobsRemovedEvent(userId, playerJobsEntry);
RaiseLocalEvent(station, ref playerJobRemovedEvent, false);
return true;
}
return false;
// DeltaV end added RemovedPlayerJobsEvent for CaptainStateSystem
ewokswagger marked this conversation as resolved.
Show resolved Hide resolved
}

/// <inheritdoc cref="TrySetJobSlot(Robust.Shared.GameObjects.EntityUid,string,int,bool,Content.Server.Station.Components.StationJobsComponent?)"/>
Expand Down
38 changes: 38 additions & 0 deletions Content.Server/Station/Systems/StationSystem.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
using System.Linq;
using Content.Server.Chat.Systems;
using Content.Server.DeltaV.Station.Components; // DeltaV
using Content.Server.DeltaV.Station.Events; // DeltaV
using Content.Server.GameTicking;
using Content.Server.Station.Components;
using Content.Server.Station.Events;
Expand Down Expand Up @@ -34,6 +36,7 @@ public sealed class StationSystem : EntitySystem
[Dependency] private readonly SharedTransformSystem _transform = default!;
[Dependency] private readonly MetaDataSystem _metaData = default!;
[Dependency] private readonly MapSystem _map = default!;
[Dependency] public readonly GameTicker GameTicker = default!; // DeltaV
ewokswagger marked this conversation as resolved.
Show resolved Hide resolved

private ISawmill _sawmill = default!;

Expand All @@ -48,6 +51,8 @@ public override void Initialize()
SubscribeLocalEvent<StationDataComponent, ComponentShutdown>(OnStationDeleted);
SubscribeLocalEvent<StationMemberComponent, ComponentShutdown>(OnStationGridDeleted);
SubscribeLocalEvent<StationMemberComponent, PostGridSplitEvent>(OnStationSplitEvent);
SubscribeLocalEvent<StationJobsComponent, PlayerJobAddedEvent>(OnPlayerJobAdded); // DeltaV
SubscribeLocalEvent<StationJobsComponent, PlayerJobsRemovedEvent>(OnPlayerJobsRemoved); // DeltaV

_player.PlayerStatusChanged += OnPlayerStatusChanged;
}
Expand Down Expand Up @@ -148,6 +153,39 @@ private void OnRoundEnd(GameRunLevelChangedEvent eventArgs)
}
}

// DeltaV Handle PlayerJob Events
public void OnPlayerJobAdded(Entity<StationJobsComponent> station, ref PlayerJobAddedEvent args)
{
if (args.JobPrototypeId == "Captain")
{
CaptainStateComponent? captainStateComponent = null;
if (Resolve(station, ref captainStateComponent, false))
captainStateComponent.HasCaptain = true;
}
}
ewokswagger marked this conversation as resolved.
Show resolved Hide resolved
public void OnPlayerJobsRemoved(EntityUid station, StationJobsComponent stationJobs, ref PlayerJobsRemovedEvent args)
{
if (args.PlayerJobs == null)
return;

// If the player that left was the only captain then there is no captain
if (args.PlayerJobs.Contains("Captain"))
{
if (stationJobs.PlayerJobs.Count == 0 || !stationJobs.PlayerJobs.Any(playerJobs => playerJobs.Value.Contains("Captain"))) // Expensive but only ran if a cap leaves so fine
{
CaptainStateComponent? captainStateComponent = null;
if (Resolve(station, ref captainStateComponent, false))
{
captainStateComponent.HasCaptain = false;
captainStateComponent.TimeSinceCaptain = GameTicker.RoundDuration();
captainStateComponent.UnlockAA = false; // Captain has already brought AA in the round and should have resolved staffing issues already.
captainStateComponent.ACORequestDelay = TimeSpan.Zero; // Expedite the voting process due to midround and captain equipment being in play.
}
}
}
}
ewokswagger marked this conversation as resolved.
Show resolved Hide resolved
// DeltaV End handle PlayerJob Events

#endregion Event handlers

/// <summary>
Expand Down
28 changes: 28 additions & 0 deletions Content.Shared/Access/Systems/AccessReaderSystem.cs
Original file line number Diff line number Diff line change
Expand Up @@ -328,6 +328,34 @@ public void SetAccesses(EntityUid uid, AccessReaderComponent component, List<Pro
RaiseLocalEvent(uid, new AccessReaderConfigurationChangedEvent());
}

// DeltaV Expanding access logic
/// <summary>
/// Extends access of readers accepted with the existing accesses to the new accesses.
/// </summary>
/// <param name="existingAcesss">The existing access levels used to identify matching readers.</param>
/// <param name="newAccess">The new access levels to add.</param>
/// <returns>List of affected Entities</returns>
/// <remarks>Access Readers with no access restrictions are unaffected to preserve thier status.</remarks>
public List<EntityUid> ExpandAccessForAllReaders(ICollection<ProtoId<AccessLevelPrototype>> existingAcesss, List<ProtoId<AccessLevelPrototype>> newAccess)
{
List<EntityUid> affected = new();
var query = EntityQueryEnumerator<AccessReaderComponent>();
while (query.MoveNext(out var uid, out var accessReader))
{
if (accessReader.AccessLists.Count == 0) // We need to exclude access readers with no restrictions or else all hallway doors will suddenly need the new access
continue;
if (!AreAccessTagsAllowed(existingAcesss, accessReader))
continue;
affected.Add(uid);
foreach (var access in newAccess)
accessReader.AccessLists.Add(new HashSet<ProtoId<AccessLevelPrototype>>() { access });
Dirty(uid, accessReader);
RaiseLocalEvent(uid, new AccessReaderConfigurationChangedEvent());
}
return affected;
}
// DeltaV End expanding access logic
ewokswagger marked this conversation as resolved.
Show resolved Hide resolved

public bool FindAccessItemsInventory(EntityUid uid, out HashSet<EntityUid> items)
{
items = new();
Expand Down
6 changes: 6 additions & 0 deletions Resources/Locale/en-US/deltav/job/captain-state.ftl
Original file line number Diff line number Diff line change
@@ -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.
deltanedas marked this conversation as resolved.
Show resolved Hide resolved
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.
3 changes: 2 additions & 1 deletion Resources/Prototypes/Entities/Stations/nanotrasen.yml
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
- type: entity
- type: entity
abstract: true
id: BaseStationNanotrasen
components:
Expand Down Expand Up @@ -29,6 +29,7 @@
- BaseStationStockMarket # DeltaV
categories: [ HideSpawnMenu ]
components:
- type: CaptainState # DeltaV
- type: Transform

- type: entity
Expand Down
Loading