Extended access system (#8469)
* Extended access system Allows jobs to specify "extended" access levels, which will be granted if the round-start crew count is below a certain threshold. * Extended accesses for jobs * Spook
This commit is contained in:
committed by
GitHub
parent
c5982e0b10
commit
a4685bab4c
@@ -1,4 +1,7 @@
|
||||
using Content.Server.Access.Components;
|
||||
using Content.Server.GameTicking;
|
||||
using Content.Server.Station.Components;
|
||||
using Content.Server.Station.Systems;
|
||||
using Content.Shared.Access.Systems;
|
||||
using Content.Shared.Roles;
|
||||
using Robust.Shared.Prototypes;
|
||||
@@ -10,16 +13,50 @@ namespace Content.Server.Access.Systems
|
||||
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
|
||||
[Dependency] private readonly IdCardSystem _cardSystem = default!;
|
||||
[Dependency] private readonly AccessSystem _accessSystem = default!;
|
||||
[Dependency] private readonly StationSystem _stationSystem = default!;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
SubscribeLocalEvent<PresetIdCardComponent, MapInitEvent>(OnMapInit);
|
||||
|
||||
SubscribeLocalEvent<RulePlayerJobsAssignedEvent>(PlayerJobsAssigned);
|
||||
}
|
||||
|
||||
private void PlayerJobsAssigned(RulePlayerJobsAssignedEvent ev)
|
||||
{
|
||||
// Go over all ID cards and make sure they're correctly configured for extended access.
|
||||
|
||||
foreach (var card in EntityQuery<PresetIdCardComponent>())
|
||||
{
|
||||
var station = _stationSystem.GetOwningStation(card.Owner);
|
||||
|
||||
// If we're not on an extended access station, the ID is already configured correctly from MapInit.
|
||||
if (station == null || !Comp<StationJobsComponent>(station.Value).ExtendedAccess)
|
||||
return;
|
||||
|
||||
SetupIdAccess(card.Owner, card, true);
|
||||
}
|
||||
}
|
||||
|
||||
private void OnMapInit(EntityUid uid, PresetIdCardComponent id, MapInitEvent args)
|
||||
{
|
||||
if (id.JobName == null) return;
|
||||
// If a preset ID card is spawned on a station at setup time,
|
||||
// the station may not exist,
|
||||
// or may not yet know whether it is on extended access (players not spawned yet).
|
||||
// PlayerJobsAssigned makes sure extended access is configured correctly in that case.
|
||||
|
||||
var station = _stationSystem.GetOwningStation(id.Owner);
|
||||
var extended = false;
|
||||
if (station != null)
|
||||
extended = Comp<StationJobsComponent>(station.Value).ExtendedAccess;
|
||||
|
||||
SetupIdAccess(uid, id, extended);
|
||||
}
|
||||
|
||||
private void SetupIdAccess(EntityUid uid, PresetIdCardComponent id, bool extended)
|
||||
{
|
||||
if (id.JobName == null)
|
||||
return;
|
||||
|
||||
if (!_prototypeManager.TryIndex(id.JobName, out JobPrototype? job))
|
||||
{
|
||||
@@ -27,9 +64,7 @@ namespace Content.Server.Access.Systems
|
||||
return;
|
||||
}
|
||||
|
||||
// set access for access component
|
||||
_accessSystem.TrySetTags(uid, job.Access);
|
||||
_accessSystem.TryAddGroups(uid, job.AccessGroups);
|
||||
_accessSystem.SetAccessToJob(uid, job, extended);
|
||||
|
||||
// and also change job title on a card id
|
||||
_cardSystem.TryChangeJobTitle(uid, job.Name);
|
||||
|
||||
@@ -6,6 +6,7 @@ using Content.Server.Players;
|
||||
using Content.Server.Roles;
|
||||
using Content.Server.Spawners.Components;
|
||||
using Content.Server.Speech.Components;
|
||||
using Content.Server.Station.Components;
|
||||
using Content.Shared.Database;
|
||||
using Content.Shared.GameTicking;
|
||||
using Content.Shared.Ghost;
|
||||
@@ -59,6 +60,15 @@ namespace Content.Server.GameTicking
|
||||
|
||||
_stationJobs.AssignOverflowJobs(ref assignedJobs, playerNetIds, profiles, _stationSystem.Stations.ToList());
|
||||
|
||||
// Calculate extended access for stations.
|
||||
var stationJobCounts = _stationSystem.Stations.ToDictionary(e => e, _ => 0);
|
||||
foreach (var (_, (_, station)) in assignedJobs)
|
||||
{
|
||||
stationJobCounts[station] += 1;
|
||||
}
|
||||
|
||||
_stationJobs.CalcExtendedAccess(stationJobCounts);
|
||||
|
||||
// Spawn everybody in!
|
||||
foreach (var (player, (job, station)) in assignedJobs)
|
||||
{
|
||||
@@ -173,6 +183,14 @@ namespace Content.Server.GameTicking
|
||||
else
|
||||
_adminLogSystem.Add(LogType.RoundStartJoin, LogImpact.Medium, $"Player {player.Name} joined as {character.Name:characterName} on station {Name(station):stationName} with {ToPrettyString(mob):entity} as a {job.Name:jobName}.");
|
||||
|
||||
// Make sure they're aware of extended access.
|
||||
if (Comp<StationJobsComponent>(station).ExtendedAccess
|
||||
&& (jobPrototype.ExtendedAccess.Count > 0
|
||||
|| jobPrototype.ExtendedAccessGroups.Count > 0))
|
||||
{
|
||||
_chatManager.DispatchServerMessage(player, Loc.GetString("job-greet-crew-shortages"));
|
||||
}
|
||||
|
||||
// We raise this event directed to the mob, but also broadcast it so game rules can do something now.
|
||||
var aev = new PlayerSpawnCompleteEvent(mob, player, jobId, lateJoin, station, character);
|
||||
RaiseLocalEvent(mob, aev);
|
||||
|
||||
@@ -31,14 +31,22 @@ public sealed class SpawnPointSystem : EntitySystem
|
||||
|
||||
if (_gameTicker.RunLevel == GameRunLevel.InRound && spawnPoint.SpawnType == SpawnPointType.LateJoin)
|
||||
{
|
||||
args.SpawnResult = _stationSpawning.SpawnPlayerMob(xform.Coordinates, args.Job,
|
||||
args.HumanoidCharacterProfile);
|
||||
args.SpawnResult = _stationSpawning.SpawnPlayerMob(
|
||||
xform.Coordinates,
|
||||
args.Job,
|
||||
args.HumanoidCharacterProfile,
|
||||
args.Station);
|
||||
|
||||
return;
|
||||
}
|
||||
else if (_gameTicker.RunLevel != GameRunLevel.InRound && spawnPoint.SpawnType == SpawnPointType.Job && (args.Job == null || spawnPoint.Job?.ID == args.Job.Prototype.ID))
|
||||
{
|
||||
args.SpawnResult = _stationSpawning.SpawnPlayerMob(xform.Coordinates, args.Job,
|
||||
args.HumanoidCharacterProfile);
|
||||
args.SpawnResult = _stationSpawning.SpawnPlayerMob(
|
||||
xform.Coordinates,
|
||||
args.Job,
|
||||
args.HumanoidCharacterProfile,
|
||||
args.Station);
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -48,7 +56,12 @@ public sealed class SpawnPointSystem : EntitySystem
|
||||
foreach (var spawnPoint in points)
|
||||
{
|
||||
var xform = Transform(spawnPoint.Owner);
|
||||
args.SpawnResult = _stationSpawning.SpawnPlayerMob(xform.Coordinates, args.Job, args.HumanoidCharacterProfile);
|
||||
args.SpawnResult = _stationSpawning.SpawnPlayerMob(
|
||||
xform.Coordinates,
|
||||
args.Job,
|
||||
args.HumanoidCharacterProfile,
|
||||
args.Station);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -27,6 +27,11 @@ public sealed class StationJobsComponent : Component
|
||||
/// </summary>
|
||||
[DataField("totalJobs")] public int TotalJobs;
|
||||
|
||||
/// <summary>
|
||||
/// Station is running on extended access.
|
||||
/// </summary>
|
||||
[DataField("extendedAccess")] public bool ExtendedAccess;
|
||||
|
||||
/// <summary>
|
||||
/// The percentage of jobs remaining.
|
||||
/// </summary>
|
||||
|
||||
@@ -25,4 +25,14 @@ public sealed partial class StationConfig
|
||||
/// job name: [round-start, mid-round]
|
||||
/// </summary>
|
||||
public IReadOnlyDictionary<string, List<int?>> AvailableJobs => _availableJobs;
|
||||
|
||||
/// <summary>
|
||||
/// If there are less than or equal this amount of players in the game at round start,
|
||||
/// people get extended access levels from job prototypes.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Set to -1 to disable extended access.
|
||||
/// </remarks>
|
||||
[DataField("extendedAccessThreshold")]
|
||||
public int ExtendedAccessThreshold { get; set; } = 15;
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using System.Linq;
|
||||
using Content.Server.Administration.Managers;
|
||||
using Content.Server.Station.Components;
|
||||
using Content.Shared.Preferences;
|
||||
using Content.Shared.Roles;
|
||||
using Robust.Shared.Network;
|
||||
@@ -304,6 +305,24 @@ public sealed partial class StationJobsSystem
|
||||
}
|
||||
}
|
||||
|
||||
public void CalcExtendedAccess(Dictionary<EntityUid, int> jobsCount)
|
||||
{
|
||||
// Calculate whether stations need to be on extended access or not.
|
||||
foreach (var (station, count) in jobsCount)
|
||||
{
|
||||
var jobs = Comp<StationJobsComponent>(station);
|
||||
var data = Comp<StationDataComponent>(station);
|
||||
|
||||
var thresh = data.StationConfig?.ExtendedAccessThreshold ?? -1;
|
||||
|
||||
jobs.ExtendedAccess = count <= thresh;
|
||||
|
||||
Logger.DebugS(
|
||||
"station", "Station {Station} on extended access: {ExtendedAccess}",
|
||||
Name(station), jobs.ExtendedAccess);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets all jobs that the input players have that match the given weight and priority.
|
||||
/// </summary>
|
||||
|
||||
@@ -7,6 +7,7 @@ using Content.Server.PDA;
|
||||
using Content.Server.Roles;
|
||||
using Content.Server.Station.Components;
|
||||
using Content.Shared.Access.Components;
|
||||
using Content.Shared.Access.Systems;
|
||||
using Content.Shared.CCVar;
|
||||
using Content.Shared.Inventory;
|
||||
using Content.Shared.PDA;
|
||||
@@ -35,6 +36,7 @@ public sealed class StationSpawningSystem : EntitySystem
|
||||
[Dependency] private readonly IdCardSystem _cardSystem = default!;
|
||||
[Dependency] private readonly InventorySystem _inventorySystem = default!;
|
||||
[Dependency] private readonly PDASystem _pdaSystem = default!;
|
||||
[Dependency] private readonly AccessSystem _accessSystem = default!;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override void Initialize()
|
||||
@@ -82,8 +84,13 @@ public sealed class StationSpawningSystem : EntitySystem
|
||||
/// <param name="coordinates">Coordinates to spawn the character at.</param>
|
||||
/// <param name="job">Job to assign to the character, if any.</param>
|
||||
/// <param name="profile">Appearance profile to use for the character.</param>
|
||||
/// <param name="station">The station this player is being spawned on.</param>
|
||||
/// <returns>The spawned entity</returns>
|
||||
public EntityUid SpawnPlayerMob(EntityCoordinates coordinates, Job? job, HumanoidCharacterProfile? profile)
|
||||
public EntityUid SpawnPlayerMob(
|
||||
EntityCoordinates coordinates,
|
||||
Job? job,
|
||||
HumanoidCharacterProfile? profile,
|
||||
EntityUid? station)
|
||||
{
|
||||
var entity = EntityManager.SpawnEntity(
|
||||
_prototypeManager.Index<SpeciesPrototype>(profile?.Species ?? SpeciesManager.DefaultSpecies).Prototype,
|
||||
@@ -94,7 +101,7 @@ public sealed class StationSpawningSystem : EntitySystem
|
||||
var startingGear = _prototypeManager.Index<StartingGearPrototype>(job.StartingGear);
|
||||
EquipStartingGear(entity, startingGear, profile);
|
||||
if (profile != null)
|
||||
EquipIdCard(entity, profile.Name, job.Prototype);
|
||||
EquipIdCard(entity, profile.Name, job.Prototype, station);
|
||||
}
|
||||
|
||||
if (profile != null)
|
||||
@@ -154,7 +161,8 @@ public sealed class StationSpawningSystem : EntitySystem
|
||||
/// <param name="entity">Entity to load out.</param>
|
||||
/// <param name="characterName">Character name to use for the ID.</param>
|
||||
/// <param name="jobPrototype">Job prototype to use for the PDA and ID.</param>
|
||||
public void EquipIdCard(EntityUid entity, string characterName, JobPrototype jobPrototype)
|
||||
/// <param name="station">The station this player is being spawned on.</param>
|
||||
public void EquipIdCard(EntityUid entity, string characterName, JobPrototype jobPrototype, EntityUid? station)
|
||||
{
|
||||
if (!_inventorySystem.TryGetSlotEntity(entity, "id", out var idUid))
|
||||
return;
|
||||
@@ -163,12 +171,19 @@ public sealed class StationSpawningSystem : EntitySystem
|
||||
return;
|
||||
|
||||
var card = pdaComponent.ContainedID;
|
||||
_cardSystem.TryChangeFullName(card.Owner, characterName, card);
|
||||
_cardSystem.TryChangeJobTitle(card.Owner, jobPrototype.Name, card);
|
||||
var cardId = card.Owner;
|
||||
_cardSystem.TryChangeFullName(cardId, characterName, card);
|
||||
_cardSystem.TryChangeJobTitle(cardId, jobPrototype.Name, card);
|
||||
|
||||
var extendedAccess = false;
|
||||
if (station != null)
|
||||
{
|
||||
var data = Comp<StationJobsComponent>(station.Value);
|
||||
extendedAccess = data.ExtendedAccess;
|
||||
}
|
||||
|
||||
_accessSystem.SetAccessToJob(cardId, jobPrototype, extendedAccess);
|
||||
|
||||
var access = EntityManager.GetComponent<AccessComponent>(card.Owner);
|
||||
var accessTags = access.Tags;
|
||||
accessTags.UnionWith(jobPrototype.Access);
|
||||
_pdaSystem.SetOwner(pdaComponent, characterName);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using Content.Shared.Access.Components;
|
||||
using Content.Shared.Roles;
|
||||
using Robust.Shared.Prototypes;
|
||||
|
||||
namespace Content.Shared.Access.Systems
|
||||
@@ -56,5 +57,33 @@ namespace Content.Shared.Access.Systems
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Set the access on an <see cref="AccessComponent"/> to the access for a specific job.
|
||||
/// </summary>
|
||||
/// <param name="uid">The ID of the entity with the access component.</param>
|
||||
/// <param name="prototype">The job prototype to use access from.</param>
|
||||
/// <param name="extended">Whether to apply extended job access.</param>
|
||||
/// <param name="access">The access component.</param>
|
||||
public void SetAccessToJob(
|
||||
EntityUid uid,
|
||||
JobPrototype prototype,
|
||||
bool extended,
|
||||
AccessComponent? access = null)
|
||||
{
|
||||
if (!Resolve(uid, ref access))
|
||||
return;
|
||||
|
||||
access.Tags.Clear();
|
||||
access.Tags.UnionWith(prototype.Access);
|
||||
|
||||
TryAddGroups(uid, prototype.AccessGroups, access);
|
||||
|
||||
if (extended)
|
||||
{
|
||||
access.Tags.UnionWith(prototype.ExtendedAccess);
|
||||
TryAddGroups(uid, prototype.ExtendedAccessGroups, access);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -61,5 +61,11 @@ namespace Content.Shared.Roles
|
||||
|
||||
[DataField("accessGroups", customTypeSerializer: typeof(PrototypeIdListSerializer<AccessGroupPrototype>))]
|
||||
public IReadOnlyCollection<string> AccessGroups { get; } = Array.Empty<string>();
|
||||
|
||||
[DataField("extendedAccess", customTypeSerializer: typeof(PrototypeIdListSerializer<AccessLevelPrototype>))]
|
||||
public IReadOnlyCollection<string> ExtendedAccess { get; } = Array.Empty<string>();
|
||||
|
||||
[DataField("extendedAccessGroups", customTypeSerializer: typeof(PrototypeIdListSerializer<AccessGroupPrototype>))]
|
||||
public IReadOnlyCollection<string> ExtendedAccessGroups { get; } = Array.Empty<string>();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,3 +3,4 @@ job-greet-important-disconnect-admin-notify = You are playing a job that is impo
|
||||
job-greet-supervisors-warning = As the {$jobName} you answer directly to {$supervisors}. Special circumstances may change this.
|
||||
job-greet-join-notify-crew = { CAPITALIZE($jobName)} {$characterName} on deck!
|
||||
job-greet-join-notify-crew-announcer = Station
|
||||
job-greet-crew-shortages = As this station was initially staffed with a skeleton crew, additional access has been added to your ID card.
|
||||
|
||||
@@ -10,6 +10,9 @@
|
||||
- Service
|
||||
- Maintenance
|
||||
- Bar
|
||||
extendedAccess:
|
||||
- Kitchen
|
||||
- Hydroponics
|
||||
|
||||
- type: startingGear
|
||||
id: BartenderGear
|
||||
|
||||
@@ -10,6 +10,9 @@
|
||||
- Service
|
||||
- Maintenance
|
||||
- Hydroponics
|
||||
extendedAccess:
|
||||
- Kitchen
|
||||
- Bar
|
||||
|
||||
- type: startingGear
|
||||
id: BotanistGear
|
||||
|
||||
@@ -10,6 +10,9 @@
|
||||
- Service
|
||||
- Maintenance
|
||||
- Kitchen
|
||||
extendedAccess:
|
||||
- Hydroponics
|
||||
- Bar
|
||||
|
||||
- type: startingGear
|
||||
id: ChefGear
|
||||
|
||||
@@ -10,6 +10,8 @@
|
||||
- Maintenance
|
||||
- Engineering
|
||||
- External
|
||||
extendedAccess:
|
||||
- Atmospherics
|
||||
|
||||
- type: startingGear
|
||||
id: StationEngineerGear
|
||||
|
||||
@@ -9,6 +9,8 @@
|
||||
access:
|
||||
- Medical
|
||||
- Maintenance
|
||||
extendedAccess:
|
||||
- Chemistry
|
||||
|
||||
- type: startingGear
|
||||
id: DoctorGear
|
||||
|
||||
Reference in New Issue
Block a user