Add Job preference tests (#28625)
* Misc Job related changes * Add JobTest * A * Aa * Lets not confuse the yaml linter * fixes * a
This commit is contained in:
@@ -4,10 +4,12 @@ using Content.Client.Lobby;
|
|||||||
using Content.Client.RoundEnd;
|
using Content.Client.RoundEnd;
|
||||||
using Content.Shared.GameTicking;
|
using Content.Shared.GameTicking;
|
||||||
using Content.Shared.GameWindow;
|
using Content.Shared.GameWindow;
|
||||||
|
using Content.Shared.Roles;
|
||||||
using JetBrains.Annotations;
|
using JetBrains.Annotations;
|
||||||
using Robust.Client.Graphics;
|
using Robust.Client.Graphics;
|
||||||
using Robust.Client.State;
|
using Robust.Client.State;
|
||||||
using Robust.Client.UserInterface;
|
using Robust.Client.UserInterface;
|
||||||
|
using Robust.Shared.Prototypes;
|
||||||
|
|
||||||
namespace Content.Client.GameTicking.Managers
|
namespace Content.Client.GameTicking.Managers
|
||||||
{
|
{
|
||||||
@@ -17,10 +19,9 @@ namespace Content.Client.GameTicking.Managers
|
|||||||
[Dependency] private readonly IStateManager _stateManager = default!;
|
[Dependency] private readonly IStateManager _stateManager = default!;
|
||||||
[Dependency] private readonly IClientAdminManager _admin = default!;
|
[Dependency] private readonly IClientAdminManager _admin = default!;
|
||||||
[Dependency] private readonly IClyde _clyde = default!;
|
[Dependency] private readonly IClyde _clyde = default!;
|
||||||
[Dependency] private readonly SharedMapSystem _map = default!;
|
|
||||||
[Dependency] private readonly IUserInterfaceManager _userInterfaceManager = default!;
|
[Dependency] private readonly IUserInterfaceManager _userInterfaceManager = default!;
|
||||||
|
|
||||||
private Dictionary<NetEntity, Dictionary<string, uint?>> _jobsAvailable = new();
|
private Dictionary<NetEntity, Dictionary<ProtoId<JobPrototype>, int?>> _jobsAvailable = new();
|
||||||
private Dictionary<NetEntity, string> _stationNames = new();
|
private Dictionary<NetEntity, string> _stationNames = new();
|
||||||
|
|
||||||
[ViewVariables] public bool AreWeReady { get; private set; }
|
[ViewVariables] public bool AreWeReady { get; private set; }
|
||||||
@@ -32,13 +33,13 @@ namespace Content.Client.GameTicking.Managers
|
|||||||
[ViewVariables] public TimeSpan StartTime { get; private set; }
|
[ViewVariables] public TimeSpan StartTime { get; private set; }
|
||||||
[ViewVariables] public new bool Paused { get; private set; }
|
[ViewVariables] public new bool Paused { get; private set; }
|
||||||
|
|
||||||
[ViewVariables] public IReadOnlyDictionary<NetEntity, Dictionary<string, uint?>> JobsAvailable => _jobsAvailable;
|
[ViewVariables] public IReadOnlyDictionary<NetEntity, Dictionary<ProtoId<JobPrototype>, int?>> JobsAvailable => _jobsAvailable;
|
||||||
[ViewVariables] public IReadOnlyDictionary<NetEntity, string> StationNames => _stationNames;
|
[ViewVariables] public IReadOnlyDictionary<NetEntity, string> StationNames => _stationNames;
|
||||||
|
|
||||||
public event Action? InfoBlobUpdated;
|
public event Action? InfoBlobUpdated;
|
||||||
public event Action? LobbyStatusUpdated;
|
public event Action? LobbyStatusUpdated;
|
||||||
public event Action? LobbyLateJoinStatusUpdated;
|
public event Action? LobbyLateJoinStatusUpdated;
|
||||||
public event Action<IReadOnlyDictionary<NetEntity, Dictionary<string, uint?>>>? LobbyJobsAvailableUpdated;
|
public event Action<IReadOnlyDictionary<NetEntity, Dictionary<ProtoId<JobPrototype>, int?>>>? LobbyJobsAvailableUpdated;
|
||||||
|
|
||||||
public override void Initialize()
|
public override void Initialize()
|
||||||
{
|
{
|
||||||
@@ -69,7 +70,7 @@ namespace Content.Client.GameTicking.Managers
|
|||||||
// reading the console. E.g., logs like this one could leak the nuke station/grid:
|
// reading the console. E.g., logs like this one could leak the nuke station/grid:
|
||||||
// > Grid NT-Arrivals 1101 (122/n25896) changed parent. Old parent: map 10 (121/n25895). New parent: FTL (123/n26470)
|
// > Grid NT-Arrivals 1101 (122/n25896) changed parent. Old parent: map 10 (121/n25895). New parent: FTL (123/n26470)
|
||||||
#if !DEBUG
|
#if !DEBUG
|
||||||
_map.Log.Level = _admin.IsAdmin() ? LogLevel.Info : LogLevel.Warning;
|
EntityManager.System<SharedMapSystem>().Log.Level = _admin.IsAdmin() ? LogLevel.Info : LogLevel.Warning;
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -290,7 +290,7 @@ namespace Content.Client.LateJoin
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void JobsAvailableUpdated(IReadOnlyDictionary<NetEntity, Dictionary<string, uint?>> updatedJobs)
|
private void JobsAvailableUpdated(IReadOnlyDictionary<NetEntity, Dictionary<ProtoId<JobPrototype>, int?>> updatedJobs)
|
||||||
{
|
{
|
||||||
foreach (var stationEntries in updatedJobs)
|
foreach (var stationEntries in updatedJobs)
|
||||||
{
|
{
|
||||||
@@ -337,10 +337,10 @@ namespace Content.Client.LateJoin
|
|||||||
public Label JobLabel { get; }
|
public Label JobLabel { get; }
|
||||||
public string JobId { get; }
|
public string JobId { get; }
|
||||||
public string JobLocalisedName { get; }
|
public string JobLocalisedName { get; }
|
||||||
public uint? Amount { get; private set; }
|
public int? Amount { get; private set; }
|
||||||
private bool _initialised = false;
|
private bool _initialised = false;
|
||||||
|
|
||||||
public JobButton(Label jobLabel, string jobId, string jobLocalisedName, uint? amount)
|
public JobButton(Label jobLabel, ProtoId<JobPrototype> jobId, string jobLocalisedName, int? amount)
|
||||||
{
|
{
|
||||||
JobLabel = jobLabel;
|
JobLabel = jobLabel;
|
||||||
JobId = jobId;
|
JobId = jobId;
|
||||||
@@ -350,7 +350,7 @@ namespace Content.Client.LateJoin
|
|||||||
_initialised = true;
|
_initialised = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void RefreshLabel(uint? amount)
|
public void RefreshLabel(int? amount)
|
||||||
{
|
{
|
||||||
if (Amount == amount && _initialised)
|
if (Amount == amount && _initialised)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -302,7 +302,7 @@ public sealed class LobbyUIController : UIController, IOnStateEntered<LobbyState
|
|||||||
{
|
{
|
||||||
var highPriorityJob = profile.JobPriorities.FirstOrDefault(p => p.Value == JobPriority.High).Key;
|
var highPriorityJob = profile.JobPriorities.FirstOrDefault(p => p.Value == JobPriority.High).Key;
|
||||||
// ReSharper disable once NullCoalescingConditionIsAlwaysNotNullAccordingToAPIContract (what is resharper smoking?)
|
// ReSharper disable once NullCoalescingConditionIsAlwaysNotNullAccordingToAPIContract (what is resharper smoking?)
|
||||||
return _prototypeManager.Index<JobPrototype>(highPriorityJob ?? SharedGameTicker.FallbackOverflowJob);
|
return _prototypeManager.Index<JobPrototype>(highPriorityJob.Id ?? SharedGameTicker.FallbackOverflowJob);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void GiveDummyLoadout(EntityUid uid, RoleLoadout? roleLoadout)
|
public void GiveDummyLoadout(EntityUid uid, RoleLoadout? roleLoadout)
|
||||||
|
|||||||
@@ -53,9 +53,9 @@ public sealed partial class CharacterPickerButton : ContainerButton
|
|||||||
.LoadProfileEntity(humanoid, null, true);
|
.LoadProfileEntity(humanoid, null, true);
|
||||||
|
|
||||||
var highPriorityJob = humanoid.JobPriorities.SingleOrDefault(p => p.Value == JobPriority.High).Key;
|
var highPriorityJob = humanoid.JobPriorities.SingleOrDefault(p => p.Value == JobPriority.High).Key;
|
||||||
if (highPriorityJob != null)
|
if (highPriorityJob != default)
|
||||||
{
|
{
|
||||||
var jobName = prototypeManager.Index<JobPrototype>(highPriorityJob).LocalizedName;
|
var jobName = prototypeManager.Index(highPriorityJob).LocalizedName;
|
||||||
description = $"{description}\n{jobName}";
|
description = $"{description}\n{jobName}";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,7 +8,6 @@ using Content.Shared.Roles;
|
|||||||
using Robust.Shared.GameObjects;
|
using Robust.Shared.GameObjects;
|
||||||
using Robust.Shared.Map;
|
using Robust.Shared.Map;
|
||||||
using Robust.Shared.Network;
|
using Robust.Shared.Network;
|
||||||
using Robust.Shared.Player;
|
|
||||||
using Robust.Shared.Prototypes;
|
using Robust.Shared.Prototypes;
|
||||||
using Robust.UnitTesting;
|
using Robust.UnitTesting;
|
||||||
|
|
||||||
@@ -135,32 +134,73 @@ public sealed partial class TestPair
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Helper method for enabling or disabling a antag role
|
/// Set a user's antag preferences. Modified preferences are automatically reset at the end of the test.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public async Task SetAntagPref(ProtoId<AntagPrototype> id, bool value)
|
public async Task SetAntagPreference(ProtoId<AntagPrototype> id, bool value, NetUserId? user = null)
|
||||||
{
|
{
|
||||||
await SetAntagPref(Client.User!.Value, id, value);
|
user ??= Client.User!.Value;
|
||||||
|
if (user is not {} userId)
|
||||||
|
return;
|
||||||
|
|
||||||
|
var prefMan = Server.ResolveDependency<IServerPreferencesManager>();
|
||||||
|
var prefs = prefMan.GetPreferences(userId);
|
||||||
|
|
||||||
|
// Automatic preference resetting only resets slot 0.
|
||||||
|
Assert.That(prefs.SelectedCharacterIndex, Is.EqualTo(0));
|
||||||
|
|
||||||
|
var profile = (HumanoidCharacterProfile) prefs.Characters[0];
|
||||||
|
var newProfile = profile.WithAntagPreference(id, value);
|
||||||
|
_modifiedProfiles.Add(userId);
|
||||||
|
await Server.WaitPost(() => prefMan.SetProfile(userId, 0, newProfile).Wait());
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task SetAntagPref(NetUserId user, ProtoId<AntagPrototype> id, bool value)
|
/// <summary>
|
||||||
|
/// Set a user's job preferences. Modified preferences are automatically reset at the end of the test.
|
||||||
|
/// </summary>
|
||||||
|
public async Task SetJobPriority(ProtoId<JobPrototype> id, JobPriority value, NetUserId? user = null)
|
||||||
{
|
{
|
||||||
|
user ??= Client.User!.Value;
|
||||||
|
if (user is { } userId)
|
||||||
|
await SetJobPriorities(userId, (id, value));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc cref="SetJobPriority"/>
|
||||||
|
public async Task SetJobPriorities(params (ProtoId<JobPrototype>, JobPriority)[] priorities)
|
||||||
|
=> await SetJobPriorities(Client.User!.Value, priorities);
|
||||||
|
|
||||||
|
/// <inheritdoc cref="SetJobPriority"/>
|
||||||
|
public async Task SetJobPriorities(NetUserId user, params (ProtoId<JobPrototype>, JobPriority)[] priorities)
|
||||||
|
{
|
||||||
|
var highCount = priorities.Count(x => x.Item2 == JobPriority.High);
|
||||||
|
Assert.That(highCount, Is.LessThanOrEqualTo(1), "Cannot have more than one high priority job");
|
||||||
|
|
||||||
var prefMan = Server.ResolveDependency<IServerPreferencesManager>();
|
var prefMan = Server.ResolveDependency<IServerPreferencesManager>();
|
||||||
|
|
||||||
var prefs = prefMan.GetPreferences(user);
|
var prefs = prefMan.GetPreferences(user);
|
||||||
// what even is the point of ICharacterProfile if we always cast it to HumanoidCharacterProfile to make it usable?
|
var profile = (HumanoidCharacterProfile) prefs.Characters[0];
|
||||||
var profile = (HumanoidCharacterProfile) prefs.SelectedCharacter;
|
var dictionary = new Dictionary<ProtoId<JobPrototype>, JobPriority>(profile.JobPriorities);
|
||||||
|
|
||||||
Assert.That(profile.AntagPreferences.Contains(id), Is.EqualTo(!value));
|
// Automatic preference resetting only resets slot 0.
|
||||||
var newProfile = profile.WithAntagPreference(id, value);
|
Assert.That(prefs.SelectedCharacterIndex, Is.EqualTo(0));
|
||||||
|
|
||||||
await Server.WaitPost(() =>
|
if (highCount != 0)
|
||||||
{
|
{
|
||||||
prefMan.SetProfile(user, prefs.SelectedCharacterIndex, newProfile).Wait();
|
foreach (var (key, priority) in dictionary)
|
||||||
});
|
{
|
||||||
|
if (priority == JobPriority.High)
|
||||||
|
dictionary[key] = JobPriority.Medium;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// And why the fuck does it always create a new preference and profile object instead of just reusing them?
|
foreach (var (job, priority) in priorities)
|
||||||
var newPrefs = prefMan.GetPreferences(user);
|
{
|
||||||
var newProf = (HumanoidCharacterProfile) newPrefs.SelectedCharacter;
|
if (priority == JobPriority.Never)
|
||||||
Assert.That(newProf.AntagPreferences.Contains(id), Is.EqualTo(value));
|
dictionary.Remove(job);
|
||||||
|
else
|
||||||
|
dictionary[job] = priority;
|
||||||
|
}
|
||||||
|
|
||||||
|
var newProfile = profile.WithJobPriorities(dictionary);
|
||||||
|
_modifiedProfiles.Add(user);
|
||||||
|
await Server.WaitPost(() => prefMan.SetProfile(user, 0, newProfile).Wait());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,10 +2,12 @@
|
|||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using Content.Server.GameTicking;
|
using Content.Server.GameTicking;
|
||||||
|
using Content.Server.Preferences.Managers;
|
||||||
using Content.Shared.CCVar;
|
using Content.Shared.CCVar;
|
||||||
using Content.Shared.GameTicking;
|
using Content.Shared.GameTicking;
|
||||||
using Content.Shared.Mind;
|
using Content.Shared.Mind;
|
||||||
using Content.Shared.Mind.Components;
|
using Content.Shared.Mind.Components;
|
||||||
|
using Content.Shared.Preferences;
|
||||||
using Robust.Client;
|
using Robust.Client;
|
||||||
using Robust.Server.Player;
|
using Robust.Server.Player;
|
||||||
using Robust.Shared.Exceptions;
|
using Robust.Shared.Exceptions;
|
||||||
@@ -34,6 +36,9 @@ public sealed partial class TestPair : IAsyncDisposable
|
|||||||
|
|
||||||
private async Task OnCleanDispose()
|
private async Task OnCleanDispose()
|
||||||
{
|
{
|
||||||
|
await Server.WaitIdleAsync();
|
||||||
|
await Client.WaitIdleAsync();
|
||||||
|
await ResetModifiedPreferences();
|
||||||
await Server.RemoveAllDummySessions();
|
await Server.RemoveAllDummySessions();
|
||||||
|
|
||||||
if (TestMap != null)
|
if (TestMap != null)
|
||||||
@@ -81,6 +86,16 @@ public sealed partial class TestPair : IAsyncDisposable
|
|||||||
await _testOut.WriteLineAsync($"{nameof(CleanReturnAsync)}: PoolManager took {returnTime.TotalMilliseconds} ms to put pair {Id} back into the pool");
|
await _testOut.WriteLineAsync($"{nameof(CleanReturnAsync)}: PoolManager took {returnTime.TotalMilliseconds} ms to put pair {Id} back into the pool");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async Task ResetModifiedPreferences()
|
||||||
|
{
|
||||||
|
var prefMan = Server.ResolveDependency<IServerPreferencesManager>();
|
||||||
|
foreach (var user in _modifiedProfiles)
|
||||||
|
{
|
||||||
|
await Server.WaitPost(() => prefMan.SetProfile(user, 0, new HumanoidCharacterProfile()).Wait());
|
||||||
|
}
|
||||||
|
_modifiedProfiles.Clear();
|
||||||
|
}
|
||||||
|
|
||||||
public async ValueTask CleanReturnAsync()
|
public async ValueTask CleanReturnAsync()
|
||||||
{
|
{
|
||||||
if (State != PairState.InUse)
|
if (State != PairState.InUse)
|
||||||
|
|||||||
@@ -26,6 +26,8 @@ public sealed partial class TestPair
|
|||||||
public readonly List<string> TestHistory = new();
|
public readonly List<string> TestHistory = new();
|
||||||
public PoolSettings Settings = default!;
|
public PoolSettings Settings = default!;
|
||||||
public TestMapData? TestMap;
|
public TestMapData? TestMap;
|
||||||
|
private List<NetUserId> _modifiedProfiles = new();
|
||||||
|
|
||||||
public RobustIntegrationTest.ServerIntegrationInstance Server { get; private set; } = default!;
|
public RobustIntegrationTest.ServerIntegrationInstance Server { get; private set; } = default!;
|
||||||
public RobustIntegrationTest.ClientIntegrationInstance Client { get; private set; } = default!;
|
public RobustIntegrationTest.ClientIntegrationInstance Client { get; private set; } = default!;
|
||||||
|
|
||||||
@@ -37,9 +39,7 @@ public sealed partial class TestPair
|
|||||||
client = Client;
|
client = Client;
|
||||||
}
|
}
|
||||||
|
|
||||||
public ICommonSession? Player => Client.User == null
|
public ICommonSession? Player => Server.PlayerMan.SessionsDict.GetValueOrDefault(Client.User!.Value);
|
||||||
? null
|
|
||||||
: Server.PlayerMan.SessionsDict.GetValueOrDefault(Client.User.Value);
|
|
||||||
|
|
||||||
public ContentPlayerData? PlayerData => Player?.Data.ContentData();
|
public ContentPlayerData? PlayerData => Player?.Data.ContentData();
|
||||||
|
|
||||||
|
|||||||
@@ -28,6 +28,7 @@ public static partial class PoolManager
|
|||||||
(CCVars.EmergencyShuttleEnabled.Name, "false"),
|
(CCVars.EmergencyShuttleEnabled.Name, "false"),
|
||||||
(CCVars.ProcgenPreload.Name, "false"),
|
(CCVars.ProcgenPreload.Name, "false"),
|
||||||
(CCVars.WorldgenEnabled.Name, "false"),
|
(CCVars.WorldgenEnabled.Name, "false"),
|
||||||
|
(CCVars.GatewayGeneratorEnabled.Name, "false"),
|
||||||
(CVars.ReplayClientRecordingEnabled.Name, "false"),
|
(CVars.ReplayClientRecordingEnabled.Name, "false"),
|
||||||
(CVars.ReplayServerRecordingEnabled.Name, "false"),
|
(CVars.ReplayServerRecordingEnabled.Name, "false"),
|
||||||
(CCVars.GameDummyTicker.Name, "true"),
|
(CCVars.GameDummyTicker.Name, "true"),
|
||||||
|
|||||||
@@ -340,6 +340,7 @@ namespace Content.IntegrationTests.Tests
|
|||||||
"MapGrid",
|
"MapGrid",
|
||||||
"Broadphase",
|
"Broadphase",
|
||||||
"StationData", // errors when removed mid-round
|
"StationData", // errors when removed mid-round
|
||||||
|
"StationJobs",
|
||||||
"Actor", // We aren't testing actor components, those need their player session set.
|
"Actor", // We aren't testing actor components, those need their player session set.
|
||||||
"BlobFloorPlanBuilder", // Implodes if unconfigured.
|
"BlobFloorPlanBuilder", // Implodes if unconfigured.
|
||||||
"DebrisFeaturePlacerController", // Above.
|
"DebrisFeaturePlacerController", // Above.
|
||||||
|
|||||||
@@ -52,7 +52,7 @@ public sealed class AntagPreferenceTest
|
|||||||
Assert.That(pool.Count, Is.EqualTo(0));
|
Assert.That(pool.Count, Is.EqualTo(0));
|
||||||
|
|
||||||
// Opt into the traitor role.
|
// Opt into the traitor role.
|
||||||
await pair.SetAntagPref("Traitor", true);
|
await pair.SetAntagPreference("Traitor", true);
|
||||||
|
|
||||||
Assert.That(sys.IsSessionValid(rule, pair.Player, def), Is.True);
|
Assert.That(sys.IsSessionValid(rule, pair.Player, def), Is.True);
|
||||||
Assert.That(sys.IsEntityValid(client.AttachedEntity, def), Is.True);
|
Assert.That(sys.IsEntityValid(client.AttachedEntity, def), Is.True);
|
||||||
@@ -63,7 +63,7 @@ public sealed class AntagPreferenceTest
|
|||||||
Assert.That(sessions.Count, Is.EqualTo(1));
|
Assert.That(sessions.Count, Is.EqualTo(1));
|
||||||
|
|
||||||
// opt back out
|
// opt back out
|
||||||
await pair.SetAntagPref("Traitor", false);
|
await pair.SetAntagPreference("Traitor", false);
|
||||||
|
|
||||||
Assert.That(sys.IsSessionValid(rule, pair.Player, def), Is.True);
|
Assert.That(sys.IsSessionValid(rule, pair.Player, def), Is.True);
|
||||||
Assert.That(sys.IsEntityValid(client.AttachedEntity, def), Is.True);
|
Assert.That(sys.IsEntityValid(client.AttachedEntity, def), Is.True);
|
||||||
|
|||||||
@@ -62,8 +62,8 @@ public sealed class NukeOpsTest
|
|||||||
await pair.RunTicksSync(5);
|
await pair.RunTicksSync(5);
|
||||||
|
|
||||||
// Opt into the nukies role.
|
// Opt into the nukies role.
|
||||||
await pair.SetAntagPref("NukeopsCommander", true);
|
await pair.SetAntagPreference("NukeopsCommander", true);
|
||||||
await pair.SetAntagPref(dummies[1].UserId, "NukeopsMedic", true);
|
await pair.SetAntagPreference( "NukeopsMedic", true, dummies[1].UserId);
|
||||||
|
|
||||||
// Initially, the players have no attached entities
|
// Initially, the players have no attached entities
|
||||||
Assert.That(pair.Player?.AttachedEntity, Is.Null);
|
Assert.That(pair.Player?.AttachedEntity, Is.Null);
|
||||||
@@ -236,8 +236,6 @@ public sealed class NukeOpsTest
|
|||||||
}
|
}
|
||||||
|
|
||||||
ticker.SetGamePreset((GamePresetPrototype?)null);
|
ticker.SetGamePreset((GamePresetPrototype?)null);
|
||||||
await pair.SetAntagPref("NukeopsCommander", false);
|
|
||||||
await pair.SetAntagPref(dummies[1].UserId, "NukeopsMedic", false);
|
|
||||||
await pair.CleanReturnAsync();
|
await pair.CleanReturnAsync();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -245,22 +245,15 @@ namespace Content.IntegrationTests.Tests
|
|||||||
|
|
||||||
// Test all availableJobs have spawnPoints
|
// Test all availableJobs have spawnPoints
|
||||||
// This is done inside gamemap test because loading the map takes ages and we already have it.
|
// This is done inside gamemap test because loading the map takes ages and we already have it.
|
||||||
var jobList = entManager.GetComponent<StationJobsComponent>(station).RoundStartJobList
|
var comp = entManager.GetComponent<StationJobsComponent>(station);
|
||||||
.Where(x => x.Value != 0)
|
var jobs = new HashSet<ProtoId<JobPrototype>>(comp.SetupAvailableJobs.Keys);
|
||||||
.Select(x => x.Key);
|
|
||||||
var spawnPoints = entManager.EntityQuery<SpawnPointComponent>()
|
|
||||||
.Where(spawnpoint => spawnpoint.SpawnType == SpawnPointType.Job)
|
|
||||||
.Select(spawnpoint => spawnpoint.Job.ID)
|
|
||||||
.Distinct();
|
|
||||||
List<string> missingSpawnPoints = new();
|
|
||||||
foreach (var spawnpoint in jobList.Except(spawnPoints))
|
|
||||||
{
|
|
||||||
if (protoManager.Index<JobPrototype>(spawnpoint).SetPreference)
|
|
||||||
missingSpawnPoints.Add(spawnpoint);
|
|
||||||
}
|
|
||||||
|
|
||||||
Assert.That(missingSpawnPoints, Has.Count.EqualTo(0),
|
var spawnPoints = entManager.EntityQuery<SpawnPointComponent>()
|
||||||
$"There is no spawnpoint for {string.Join(", ", missingSpawnPoints)} on {mapProto}.");
|
.Where(x => x.SpawnType == SpawnPointType.Job)
|
||||||
|
.Select(x => x.Job!.Value);
|
||||||
|
|
||||||
|
jobs.ExceptWith(spawnPoints);
|
||||||
|
Assert.That(jobs, Is.Empty,$"There is no spawnpoints for {string.Join(", ", jobs)} on {mapProto}.");
|
||||||
}
|
}
|
||||||
|
|
||||||
try
|
try
|
||||||
|
|||||||
222
Content.IntegrationTests/Tests/Round/JobTest.cs
Normal file
222
Content.IntegrationTests/Tests/Round/JobTest.cs
Normal file
@@ -0,0 +1,222 @@
|
|||||||
|
#nullable enable
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using Content.IntegrationTests.Pair;
|
||||||
|
using Content.Server.GameTicking;
|
||||||
|
using Content.Server.Mind;
|
||||||
|
using Content.Server.Roles;
|
||||||
|
using Content.Shared.CCVar;
|
||||||
|
using Content.Shared.GameTicking;
|
||||||
|
using Content.Shared.Preferences;
|
||||||
|
using Content.Shared.Roles;
|
||||||
|
using Content.Shared.Roles.Jobs;
|
||||||
|
using Robust.Shared.Network;
|
||||||
|
using Robust.Shared.Prototypes;
|
||||||
|
|
||||||
|
namespace Content.IntegrationTests.Tests.Round;
|
||||||
|
|
||||||
|
[TestFixture]
|
||||||
|
public sealed class JobTest
|
||||||
|
{
|
||||||
|
private static ProtoId<JobPrototype> _passenger = "Passenger";
|
||||||
|
private static ProtoId<JobPrototype> _engineer = "StationEngineer";
|
||||||
|
private static ProtoId<JobPrototype> _captain = "Captain";
|
||||||
|
|
||||||
|
private static string _map = "JobTestMap";
|
||||||
|
|
||||||
|
[TestPrototypes]
|
||||||
|
public static string JobTestMap = @$"
|
||||||
|
- type: gameMap
|
||||||
|
id: {_map}
|
||||||
|
mapName: {_map}
|
||||||
|
mapPath: /Maps/Test/empty.yml
|
||||||
|
minPlayers: 0
|
||||||
|
stations:
|
||||||
|
Empty:
|
||||||
|
stationProto: StandardNanotrasenStation
|
||||||
|
components:
|
||||||
|
- type: StationNameSetup
|
||||||
|
mapNameTemplate: ""Empty""
|
||||||
|
- type: StationJobs
|
||||||
|
availableJobs:
|
||||||
|
{_passenger}: [ -1, -1 ]
|
||||||
|
{_engineer}: [ -1, -1 ]
|
||||||
|
{_captain}: [ 1, 1 ]
|
||||||
|
";
|
||||||
|
|
||||||
|
public void AssertJob(TestPair pair, ProtoId<JobPrototype> job, NetUserId? user = null, bool isAntag = false)
|
||||||
|
{
|
||||||
|
var jobSys = pair.Server.System<SharedJobSystem>();
|
||||||
|
var mindSys = pair.Server.System<MindSystem>();
|
||||||
|
var roleSys = pair.Server.System<RoleSystem>();
|
||||||
|
var ticker = pair.Server.System<GameTicker>();
|
||||||
|
|
||||||
|
user ??= pair.Client.User!.Value;
|
||||||
|
|
||||||
|
Assert.That(ticker.RunLevel, Is.EqualTo(GameRunLevel.InRound));
|
||||||
|
Assert.That(ticker.PlayerGameStatuses[user.Value], Is.EqualTo(PlayerGameStatus.JoinedGame));
|
||||||
|
|
||||||
|
var uid = pair.Server.PlayerMan.SessionsDict.GetValueOrDefault(user.Value)?.AttachedEntity;
|
||||||
|
Assert.That(pair.Server.EntMan.EntityExists(uid));
|
||||||
|
var mind = mindSys.GetMind(uid!.Value);
|
||||||
|
Assert.That(pair.Server.EntMan.EntityExists(mind));
|
||||||
|
Assert.That(jobSys.MindTryGetJobId(mind, out var actualJob));
|
||||||
|
Assert.That(actualJob, Is.EqualTo(job));
|
||||||
|
Assert.That(roleSys.MindIsAntagonist(mind), Is.EqualTo(isAntag));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Simple test that checks that starting the round spawns the player into the test map as a passenger.
|
||||||
|
/// </summary>
|
||||||
|
[Test]
|
||||||
|
public async Task StartRoundTest()
|
||||||
|
{
|
||||||
|
await using var pair = await PoolManager.GetServerClient(new PoolSettings
|
||||||
|
{
|
||||||
|
DummyTicker = false,
|
||||||
|
Connected = true,
|
||||||
|
InLobby = true
|
||||||
|
});
|
||||||
|
|
||||||
|
pair.Server.CfgMan.SetCVar(CCVars.GameMap, _map);
|
||||||
|
var ticker = pair.Server.System<GameTicker>();
|
||||||
|
|
||||||
|
// Initially in the lobby
|
||||||
|
Assert.That(ticker.RunLevel, Is.EqualTo(GameRunLevel.PreRoundLobby));
|
||||||
|
Assert.That(pair.Client.AttachedEntity, Is.Null);
|
||||||
|
Assert.That(ticker.PlayerGameStatuses[pair.Client.User!.Value], Is.EqualTo(PlayerGameStatus.NotReadyToPlay));
|
||||||
|
|
||||||
|
// Ready up and start the round
|
||||||
|
ticker.ToggleReadyAll(true);
|
||||||
|
Assert.That(ticker.PlayerGameStatuses[pair.Client.User!.Value], Is.EqualTo(PlayerGameStatus.ReadyToPlay));
|
||||||
|
await pair.Server.WaitPost(() => ticker.StartRound());
|
||||||
|
await pair.RunTicksSync(10);
|
||||||
|
|
||||||
|
AssertJob(pair, _passenger);
|
||||||
|
|
||||||
|
await pair.Server.WaitPost(() => ticker.RestartRound());
|
||||||
|
await pair.CleanReturnAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Check that job preferences are respected.
|
||||||
|
/// </summary>
|
||||||
|
[Test]
|
||||||
|
public async Task JobPreferenceTest()
|
||||||
|
{
|
||||||
|
await using var pair = await PoolManager.GetServerClient(new PoolSettings
|
||||||
|
{
|
||||||
|
DummyTicker = false,
|
||||||
|
Connected = true,
|
||||||
|
InLobby = true
|
||||||
|
});
|
||||||
|
|
||||||
|
pair.Server.CfgMan.SetCVar(CCVars.GameMap, _map);
|
||||||
|
var ticker = pair.Server.System<GameTicker>();
|
||||||
|
Assert.That(ticker.RunLevel, Is.EqualTo(GameRunLevel.PreRoundLobby));
|
||||||
|
Assert.That(pair.Client.AttachedEntity, Is.Null);
|
||||||
|
|
||||||
|
await pair.SetJobPriorities((_passenger, JobPriority.Medium), (_engineer, JobPriority.High));
|
||||||
|
ticker.ToggleReadyAll(true);
|
||||||
|
await pair.Server.WaitPost(() => ticker.StartRound());
|
||||||
|
await pair.RunTicksSync(10);
|
||||||
|
|
||||||
|
AssertJob(pair, _engineer);
|
||||||
|
|
||||||
|
await pair.Server.WaitPost(() => ticker.RestartRound());
|
||||||
|
Assert.That(ticker.RunLevel, Is.EqualTo(GameRunLevel.PreRoundLobby));
|
||||||
|
await pair.SetJobPriorities((_passenger, JobPriority.High), (_engineer, JobPriority.Medium));
|
||||||
|
ticker.ToggleReadyAll(true);
|
||||||
|
await pair.Server.WaitPost(() => ticker.StartRound());
|
||||||
|
await pair.RunTicksSync(10);
|
||||||
|
|
||||||
|
AssertJob(pair, _passenger);
|
||||||
|
|
||||||
|
await pair.Server.WaitPost(() => ticker.RestartRound());
|
||||||
|
await pair.CleanReturnAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Check high priority jobs (e.g., captain) are selected before other roles, even if it means a player does not
|
||||||
|
/// get their preferred job.
|
||||||
|
/// </summary>
|
||||||
|
[Test]
|
||||||
|
public async Task JobWeightTest()
|
||||||
|
{
|
||||||
|
await using var pair = await PoolManager.GetServerClient(new PoolSettings
|
||||||
|
{
|
||||||
|
DummyTicker = false,
|
||||||
|
Connected = true,
|
||||||
|
InLobby = true
|
||||||
|
});
|
||||||
|
|
||||||
|
pair.Server.CfgMan.SetCVar(CCVars.GameMap, _map);
|
||||||
|
var ticker = pair.Server.System<GameTicker>();
|
||||||
|
Assert.That(ticker.RunLevel, Is.EqualTo(GameRunLevel.PreRoundLobby));
|
||||||
|
Assert.That(pair.Client.AttachedEntity, Is.Null);
|
||||||
|
|
||||||
|
var captain = pair.Server.ProtoMan.Index(_captain);
|
||||||
|
var engineer = pair.Server.ProtoMan.Index(_engineer);
|
||||||
|
var passenger = pair.Server.ProtoMan.Index(_passenger);
|
||||||
|
Assert.That(captain.Weight, Is.GreaterThan(engineer.Weight));
|
||||||
|
Assert.That(engineer.Weight, Is.EqualTo(passenger.Weight));
|
||||||
|
|
||||||
|
await pair.SetJobPriorities((_passenger, JobPriority.Medium), (_engineer, JobPriority.High), (_captain, JobPriority.Low));
|
||||||
|
ticker.ToggleReadyAll(true);
|
||||||
|
await pair.Server.WaitPost(() => ticker.StartRound());
|
||||||
|
await pair.RunTicksSync(10);
|
||||||
|
|
||||||
|
AssertJob(pair, _captain);
|
||||||
|
|
||||||
|
await pair.Server.WaitPost(() => ticker.RestartRound());
|
||||||
|
await pair.CleanReturnAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Check that jobs are preferentially given to players that have marked those jobs as higher priority.
|
||||||
|
/// </summary>
|
||||||
|
[Test]
|
||||||
|
public async Task JobPriorityTest()
|
||||||
|
{
|
||||||
|
await using var pair = await PoolManager.GetServerClient(new PoolSettings
|
||||||
|
{
|
||||||
|
DummyTicker = false,
|
||||||
|
Connected = true,
|
||||||
|
InLobby = true
|
||||||
|
});
|
||||||
|
|
||||||
|
pair.Server.CfgMan.SetCVar(CCVars.GameMap, _map);
|
||||||
|
var ticker = pair.Server.System<GameTicker>();
|
||||||
|
Assert.That(ticker.RunLevel, Is.EqualTo(GameRunLevel.PreRoundLobby));
|
||||||
|
Assert.That(pair.Client.AttachedEntity, Is.Null);
|
||||||
|
|
||||||
|
await pair.Server.AddDummySessions(5);
|
||||||
|
await pair.RunTicksSync(5);
|
||||||
|
|
||||||
|
var engineers = pair.Server.PlayerMan.Sessions.Select(x => x.UserId).ToList();
|
||||||
|
var captain = engineers[3];
|
||||||
|
engineers.RemoveAt(3);
|
||||||
|
|
||||||
|
await pair.SetJobPriorities(captain, (_captain, JobPriority.High), (_engineer, JobPriority.Medium));
|
||||||
|
foreach (var engi in engineers)
|
||||||
|
{
|
||||||
|
await pair.SetJobPriorities(engi, (_captain, JobPriority.Medium), (_engineer, JobPriority.High));
|
||||||
|
}
|
||||||
|
|
||||||
|
ticker.ToggleReadyAll(true);
|
||||||
|
await pair.Server.WaitPost(() => ticker.StartRound());
|
||||||
|
await pair.RunTicksSync(10);
|
||||||
|
|
||||||
|
AssertJob(pair, _captain, captain);
|
||||||
|
Assert.Multiple(() =>
|
||||||
|
{
|
||||||
|
foreach (var engi in engineers)
|
||||||
|
{
|
||||||
|
AssertJob(pair, _engineer, engi);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
await pair.Server.WaitPost(() => ticker.RestartRound());
|
||||||
|
await pair.CleanReturnAsync();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -7,7 +7,6 @@ using Content.Shared.Preferences;
|
|||||||
using Content.Shared.Roles;
|
using Content.Shared.Roles;
|
||||||
using Robust.Shared.GameObjects;
|
using Robust.Shared.GameObjects;
|
||||||
using Robust.Shared.Log;
|
using Robust.Shared.Log;
|
||||||
using Robust.Shared.Map;
|
|
||||||
using Robust.Shared.Network;
|
using Robust.Shared.Network;
|
||||||
using Robust.Shared.Prototypes;
|
using Robust.Shared.Prototypes;
|
||||||
using Robust.Shared.Timing;
|
using Robust.Shared.Timing;
|
||||||
@@ -46,8 +45,6 @@ public sealed class StationJobsTest
|
|||||||
stationProto: StandardNanotrasenStation
|
stationProto: StandardNanotrasenStation
|
||||||
components:
|
components:
|
||||||
- type: StationJobs
|
- type: StationJobs
|
||||||
overflowJobs:
|
|
||||||
- Passenger
|
|
||||||
availableJobs:
|
availableJobs:
|
||||||
TMime: [0, -1]
|
TMime: [0, -1]
|
||||||
TAssistant: [-1, -1]
|
TAssistant: [-1, -1]
|
||||||
@@ -164,7 +161,6 @@ public sealed class StationJobsTest
|
|||||||
var server = pair.Server;
|
var server = pair.Server;
|
||||||
|
|
||||||
var prototypeManager = server.ResolveDependency<IPrototypeManager>();
|
var prototypeManager = server.ResolveDependency<IPrototypeManager>();
|
||||||
var mapManager = server.ResolveDependency<IMapManager>();
|
|
||||||
var fooStationProto = prototypeManager.Index<GameMapPrototype>("FooStation");
|
var fooStationProto = prototypeManager.Index<GameMapPrototype>("FooStation");
|
||||||
var entSysMan = server.ResolveDependency<IEntityManager>().EntitySysManager;
|
var entSysMan = server.ResolveDependency<IEntityManager>().EntitySysManager;
|
||||||
var stationJobs = entSysMan.GetEntitySystem<StationJobsSystem>();
|
var stationJobs = entSysMan.GetEntitySystem<StationJobsSystem>();
|
||||||
@@ -215,6 +211,8 @@ public sealed class StationJobsTest
|
|||||||
var server = pair.Server;
|
var server = pair.Server;
|
||||||
|
|
||||||
var prototypeManager = server.ResolveDependency<IPrototypeManager>();
|
var prototypeManager = server.ResolveDependency<IPrototypeManager>();
|
||||||
|
var compFact = server.ResolveDependency<IComponentFactory>();
|
||||||
|
var name = compFact.GetComponentName<StationJobsComponent>();
|
||||||
|
|
||||||
await server.WaitAssertion(() =>
|
await server.WaitAssertion(() =>
|
||||||
{
|
{
|
||||||
@@ -233,11 +231,14 @@ public sealed class StationJobsTest
|
|||||||
{
|
{
|
||||||
foreach (var (stationId, station) in gameMap.Stations)
|
foreach (var (stationId, station) in gameMap.Stations)
|
||||||
{
|
{
|
||||||
if (!station.StationComponentOverrides.TryGetComponent("StationJobs", out var comp))
|
if (!station.StationComponentOverrides.TryGetComponent(name, out var comp))
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
foreach (var (job, _) in ((StationJobsComponent) comp).SetupAvailableJobs)
|
foreach (var (job, array) in ((StationJobsComponent) comp).SetupAvailableJobs)
|
||||||
{
|
{
|
||||||
|
Assert.That(array.Length, Is.EqualTo(2));
|
||||||
|
Assert.That(array[0] is -1 or >= 0);
|
||||||
|
Assert.That(array[1] is -1 or >= 0);
|
||||||
Assert.That(invalidJobs, Does.Not.Contain(job), $"Station {stationId} contains job prototype {job} which cannot be present roundstart.");
|
Assert.That(invalidJobs, Does.Not.Contain(job), $"Station {stationId} contains job prototype {job} which cannot be present roundstart.");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ using Content.Shared.Humanoid.Markings;
|
|||||||
using Content.Shared.Preferences;
|
using Content.Shared.Preferences;
|
||||||
using Content.Shared.Preferences.Loadouts;
|
using Content.Shared.Preferences.Loadouts;
|
||||||
using Content.Shared.Roles;
|
using Content.Shared.Roles;
|
||||||
|
using Content.Shared.Traits;
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
using Robust.Shared.Enums;
|
using Robust.Shared.Enums;
|
||||||
using Robust.Shared.Network;
|
using Robust.Shared.Network;
|
||||||
@@ -183,9 +184,9 @@ namespace Content.Server.Database
|
|||||||
|
|
||||||
private static HumanoidCharacterProfile ConvertProfiles(Profile profile)
|
private static HumanoidCharacterProfile ConvertProfiles(Profile profile)
|
||||||
{
|
{
|
||||||
var jobs = profile.Jobs.ToDictionary(j => j.JobName, j => (JobPriority) j.Priority);
|
var jobs = profile.Jobs.ToDictionary(j => new ProtoId<JobPrototype>(j.JobName), j => (JobPriority) j.Priority);
|
||||||
var antags = profile.Antags.Select(a => a.AntagName);
|
var antags = profile.Antags.Select(a => new ProtoId<AntagPrototype>(a.AntagName));
|
||||||
var traits = profile.Traits.Select(t => t.TraitName);
|
var traits = profile.Traits.Select(t => new ProtoId<TraitPrototype>(t.TraitName));
|
||||||
|
|
||||||
var sex = Sex.Male;
|
var sex = Sex.Male;
|
||||||
if (Enum.TryParse<Sex>(profile.Sex, true, out var sexVal))
|
if (Enum.TryParse<Sex>(profile.Sex, true, out var sexVal))
|
||||||
|
|||||||
@@ -239,7 +239,7 @@ namespace Content.Server.GameTicking
|
|||||||
HumanoidCharacterProfile profile;
|
HumanoidCharacterProfile profile;
|
||||||
if (_prefsManager.TryGetCachedPreferences(userId, out var preferences))
|
if (_prefsManager.TryGetCachedPreferences(userId, out var preferences))
|
||||||
{
|
{
|
||||||
profile = (HumanoidCharacterProfile) preferences.GetProfile(preferences.SelectedCharacterIndex);
|
profile = (HumanoidCharacterProfile) preferences.SelectedCharacter;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -305,11 +305,7 @@ namespace Content.Server.Preferences.Managers
|
|||||||
return usernames
|
return usernames
|
||||||
.Select(p => (_cachedPlayerPrefs[p].Prefs, p))
|
.Select(p => (_cachedPlayerPrefs[p].Prefs, p))
|
||||||
.Where(p => p.Prefs != null)
|
.Where(p => p.Prefs != null)
|
||||||
.Select(p =>
|
.Select(p => new KeyValuePair<NetUserId, ICharacterProfile>(p.p, p.Prefs!.SelectedCharacter));
|
||||||
{
|
|
||||||
var idx = p.Prefs!.SelectedCharacterIndex;
|
|
||||||
return new KeyValuePair<NetUserId, ICharacterProfile>(p.p, p.Prefs!.GetProfile(idx));
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
internal static bool ShouldStorePrefs(LoginType loginType)
|
internal static bool ShouldStorePrefs(LoginType loginType)
|
||||||
|
|||||||
@@ -6,11 +6,8 @@ namespace Content.Server.Spawners.Components;
|
|||||||
[RegisterComponent]
|
[RegisterComponent]
|
||||||
public sealed partial class SpawnPointComponent : Component, ISpawnPoint
|
public sealed partial class SpawnPointComponent : Component, ISpawnPoint
|
||||||
{
|
{
|
||||||
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
|
|
||||||
|
|
||||||
[ViewVariables(VVAccess.ReadWrite)]
|
|
||||||
[DataField("job_id")]
|
[DataField("job_id")]
|
||||||
private string? _jobId;
|
public ProtoId<JobPrototype>? Job;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The type of spawn point
|
/// The type of spawn point
|
||||||
@@ -18,11 +15,9 @@ public sealed partial class SpawnPointComponent : Component, ISpawnPoint
|
|||||||
[DataField("spawn_type"), ViewVariables(VVAccess.ReadWrite)]
|
[DataField("spawn_type"), ViewVariables(VVAccess.ReadWrite)]
|
||||||
public SpawnPointType SpawnType { get; set; } = SpawnPointType.Unset;
|
public SpawnPointType SpawnType { get; set; } = SpawnPointType.Unset;
|
||||||
|
|
||||||
public JobPrototype? Job => string.IsNullOrEmpty(_jobId) ? null : _prototypeManager.Index<JobPrototype>(_jobId);
|
|
||||||
|
|
||||||
public override string ToString()
|
public override string ToString()
|
||||||
{
|
{
|
||||||
return $"{_jobId} {SpawnType}";
|
return $"{Job} {SpawnType}";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -39,7 +39,7 @@ public sealed class SpawnPointSystem : EntitySystem
|
|||||||
|
|
||||||
if (_gameTicker.RunLevel != GameRunLevel.InRound &&
|
if (_gameTicker.RunLevel != GameRunLevel.InRound &&
|
||||||
spawnPoint.SpawnType == SpawnPointType.Job &&
|
spawnPoint.SpawnType == SpawnPointType.Job &&
|
||||||
(args.Job == null || spawnPoint.Job?.ID == args.Job.Prototype))
|
(args.Job == null || spawnPoint.Job == args.Job.Prototype))
|
||||||
{
|
{
|
||||||
possiblePositions.Add(xform.Coordinates);
|
possiblePositions.Add(xform.Coordinates);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
using Content.Server.Station.Systems;
|
using System.Linq;
|
||||||
|
using Content.Server.Station.Systems;
|
||||||
using Content.Shared.Roles;
|
using Content.Shared.Roles;
|
||||||
using JetBrains.Annotations;
|
using JetBrains.Annotations;
|
||||||
using Robust.Shared.Network;
|
using Robust.Shared.Network;
|
||||||
@@ -14,25 +15,21 @@ namespace Content.Server.Station.Components;
|
|||||||
[RegisterComponent, Access(typeof(StationJobsSystem)), PublicAPI]
|
[RegisterComponent, Access(typeof(StationJobsSystem)), PublicAPI]
|
||||||
public sealed partial class StationJobsComponent : Component
|
public sealed partial class StationJobsComponent : Component
|
||||||
{
|
{
|
||||||
/// <summary>
|
|
||||||
/// Total *round-start* jobs at station start.
|
|
||||||
/// </summary>
|
|
||||||
[DataField("roundStartTotalJobs")] public int RoundStartTotalJobs;
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Total *mid-round* jobs at station start.
|
/// Total *mid-round* jobs at station start.
|
||||||
|
/// This is inferred automatically from <see cref="SetupAvailableJobs"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[DataField("midRoundTotalJobs")] public int MidRoundTotalJobs;
|
[ViewVariables] public int MidRoundTotalJobs;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Current total jobs.
|
/// Current total jobs.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[DataField("totalJobs")] public int TotalJobs;
|
[DataField] public int TotalJobs;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Station is running on extended access.
|
/// Station is running on extended access.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[DataField("extendedAccess")] public bool ExtendedAccess;
|
[DataField] public bool ExtendedAccess;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// If there are less than or equal this amount of players in the game at round start,
|
/// If there are less than or equal this amount of players in the game at round start,
|
||||||
@@ -41,7 +38,7 @@ public sealed partial class StationJobsComponent : Component
|
|||||||
/// <remarks>
|
/// <remarks>
|
||||||
/// Set to -1 to disable extended access.
|
/// Set to -1 to disable extended access.
|
||||||
/// </remarks>
|
/// </remarks>
|
||||||
[DataField("extendedAccessThreshold")]
|
[DataField]
|
||||||
public int ExtendedAccessThreshold { get; set; } = 15;
|
public int ExtendedAccessThreshold { get; set; } = 15;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -54,28 +51,20 @@ public sealed partial class StationJobsComponent : Component
|
|||||||
public float? PercentJobsRemaining => MidRoundTotalJobs > 0 ? TotalJobs / (float) MidRoundTotalJobs : null;
|
public float? PercentJobsRemaining => MidRoundTotalJobs > 0 ? TotalJobs / (float) MidRoundTotalJobs : null;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The current list of jobs.
|
/// The current list of jobs of available jobs. Null implies that is no limit.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <remarks>
|
/// <remarks>
|
||||||
/// This should not be mutated or used directly unless you really know what you're doing, go through StationJobsSystem.
|
/// This should not be mutated or used directly unless you really know what you're doing, go through StationJobsSystem.
|
||||||
/// </remarks>
|
/// </remarks>
|
||||||
[DataField("jobList", customTypeSerializer: typeof(PrototypeIdDictionarySerializer<uint?, JobPrototype>))]
|
[DataField]
|
||||||
public Dictionary<string, uint?> JobList = new();
|
public Dictionary<ProtoId<JobPrototype>, int?> JobList = new();
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// The round-start list of jobs.
|
|
||||||
/// </summary>
|
|
||||||
/// <remarks>
|
|
||||||
/// This should not be mutated, ever.
|
|
||||||
/// </remarks>
|
|
||||||
[DataField("roundStartJobList", customTypeSerializer: typeof(PrototypeIdDictionarySerializer<uint?, JobPrototype>))]
|
|
||||||
public Dictionary<string, uint?> RoundStartJobList = new();
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Overflow jobs that round-start can spawn infinitely many of.
|
/// Overflow jobs that round-start can spawn infinitely many of.
|
||||||
|
/// This is inferred automatically from <see cref="SetupAvailableJobs"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[DataField("overflowJobs", customTypeSerializer: typeof(PrototypeIdHashSetSerializer<JobPrototype>))]
|
[ViewVariables]
|
||||||
public HashSet<string> OverflowJobs = new();
|
public IReadOnlySet<ProtoId<JobPrototype>> OverflowJobs = default!;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// A dictionary relating a NetUserId to the jobs they have on station.
|
/// A dictionary relating a NetUserId to the jobs they have on station.
|
||||||
@@ -84,7 +73,10 @@ public sealed partial class StationJobsComponent : Component
|
|||||||
[DataField]
|
[DataField]
|
||||||
public Dictionary<NetUserId, List<ProtoId<JobPrototype>>> PlayerJobs = new();
|
public Dictionary<NetUserId, List<ProtoId<JobPrototype>>> PlayerJobs = new();
|
||||||
|
|
||||||
[DataField("availableJobs", required: true,
|
/// <summary>
|
||||||
customTypeSerializer: typeof(PrototypeIdDictionarySerializer<List<int?>, JobPrototype>))]
|
/// Mapping of jobs to an int[2] array that specifies jobs available at round start, and midround.
|
||||||
public Dictionary<string, List<int?>> SetupAvailableJobs = default!;
|
/// Negative values implies that there is no limit.
|
||||||
|
/// </summary>
|
||||||
|
[DataField("availableJobs", required: true)]
|
||||||
|
public Dictionary<ProtoId<JobPrototype>, int[]> SetupAvailableJobs = default!;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -52,23 +52,23 @@ public sealed partial class StationJobsSystem
|
|||||||
/// as there may end up being more round-start slots than available slots, which can cause weird behavior.
|
/// as there may end up being more round-start slots than available slots, which can cause weird behavior.
|
||||||
/// A warning to all who enter ye cursed lands: This function is long and mildly incomprehensible. Best used without touching.
|
/// A warning to all who enter ye cursed lands: This function is long and mildly incomprehensible. Best used without touching.
|
||||||
/// </remarks>
|
/// </remarks>
|
||||||
public Dictionary<NetUserId, (string?, EntityUid)> AssignJobs(Dictionary<NetUserId, HumanoidCharacterProfile> profiles, IReadOnlyList<EntityUid> stations, bool useRoundStartJobs = true)
|
public Dictionary<NetUserId, (ProtoId<JobPrototype>?, EntityUid)> AssignJobs(Dictionary<NetUserId, HumanoidCharacterProfile> profiles, IReadOnlyList<EntityUid> stations, bool useRoundStartJobs = true)
|
||||||
{
|
{
|
||||||
DebugTools.Assert(stations.Count > 0);
|
DebugTools.Assert(stations.Count > 0);
|
||||||
|
|
||||||
InitializeRoundStart();
|
InitializeRoundStart();
|
||||||
|
|
||||||
if (profiles.Count == 0)
|
if (profiles.Count == 0)
|
||||||
return new Dictionary<NetUserId, (string?, EntityUid)>();
|
return new();
|
||||||
|
|
||||||
// We need to modify this collection later, so make a copy of it.
|
// We need to modify this collection later, so make a copy of it.
|
||||||
profiles = profiles.ShallowClone();
|
profiles = profiles.ShallowClone();
|
||||||
|
|
||||||
// Player <-> (job, station)
|
// Player <-> (job, station)
|
||||||
var assigned = new Dictionary<NetUserId, (string?, EntityUid)>(profiles.Count);
|
var assigned = new Dictionary<NetUserId, (ProtoId<JobPrototype>?, EntityUid)>(profiles.Count);
|
||||||
|
|
||||||
// The jobs left on the stations. This collection is modified as jobs are assigned to track what's available.
|
// The jobs left on the stations. This collection is modified as jobs are assigned to track what's available.
|
||||||
var stationJobs = new Dictionary<EntityUid, Dictionary<string, uint?>>();
|
var stationJobs = new Dictionary<EntityUid, Dictionary<ProtoId<JobPrototype>, int?>>();
|
||||||
foreach (var station in stations)
|
foreach (var station in stations)
|
||||||
{
|
{
|
||||||
if (useRoundStartJobs)
|
if (useRoundStartJobs)
|
||||||
@@ -83,15 +83,15 @@ public sealed partial class StationJobsSystem
|
|||||||
|
|
||||||
|
|
||||||
// We reuse this collection. It tracks what jobs we're currently trying to select players for.
|
// We reuse this collection. It tracks what jobs we're currently trying to select players for.
|
||||||
var currentlySelectingJobs = new Dictionary<EntityUid, Dictionary<string, uint?>>(stations.Count);
|
var currentlySelectingJobs = new Dictionary<EntityUid, Dictionary<ProtoId<JobPrototype>, int?>>(stations.Count);
|
||||||
foreach (var station in stations)
|
foreach (var station in stations)
|
||||||
{
|
{
|
||||||
currentlySelectingJobs.Add(station, new Dictionary<string, uint?>());
|
currentlySelectingJobs.Add(station, new Dictionary<ProtoId<JobPrototype>, int?>());
|
||||||
}
|
}
|
||||||
|
|
||||||
// And these.
|
// And these.
|
||||||
// Tracks what players are available for a given job in the current iteration of selection.
|
// Tracks what players are available for a given job in the current iteration of selection.
|
||||||
var jobPlayerOptions = new Dictionary<string, HashSet<NetUserId>>();
|
var jobPlayerOptions = new Dictionary<ProtoId<JobPrototype>, HashSet<NetUserId>>();
|
||||||
// Tracks the total number of slots for the given stations in the current iteration of selection.
|
// Tracks the total number of slots for the given stations in the current iteration of selection.
|
||||||
var stationTotalSlots = new Dictionary<EntityUid, int>(stations.Count);
|
var stationTotalSlots = new Dictionary<EntityUid, int>(stations.Count);
|
||||||
// The share of the players each station gets in the current iteration of job selection.
|
// The share of the players each station gets in the current iteration of job selection.
|
||||||
@@ -112,7 +112,7 @@ public sealed partial class StationJobsSystem
|
|||||||
var optionsRemaining = 0;
|
var optionsRemaining = 0;
|
||||||
|
|
||||||
// Assigns a player to the given station, updating all the bookkeeping while at it.
|
// Assigns a player to the given station, updating all the bookkeeping while at it.
|
||||||
void AssignPlayer(NetUserId player, string job, EntityUid station)
|
void AssignPlayer(NetUserId player, ProtoId<JobPrototype> job, EntityUid station)
|
||||||
{
|
{
|
||||||
// Remove the player from all possible jobs as that's faster than actually checking what they have selected.
|
// Remove the player from all possible jobs as that's faster than actually checking what they have selected.
|
||||||
foreach (var (k, players) in jobPlayerOptions)
|
foreach (var (k, players) in jobPlayerOptions)
|
||||||
@@ -273,8 +273,11 @@ public sealed partial class StationJobsSystem
|
|||||||
/// <param name="allPlayersToAssign">All players that might need an overflow assigned.</param>
|
/// <param name="allPlayersToAssign">All players that might need an overflow assigned.</param>
|
||||||
/// <param name="profiles">Player character profiles.</param>
|
/// <param name="profiles">Player character profiles.</param>
|
||||||
/// <param name="stations">The stations to consider for spawn location.</param>
|
/// <param name="stations">The stations to consider for spawn location.</param>
|
||||||
public void AssignOverflowJobs(ref Dictionary<NetUserId, (string?, EntityUid)> assignedJobs,
|
public void AssignOverflowJobs(
|
||||||
IEnumerable<NetUserId> allPlayersToAssign, IReadOnlyDictionary<NetUserId, HumanoidCharacterProfile> profiles, IReadOnlyList<EntityUid> stations)
|
ref Dictionary<NetUserId, (ProtoId<JobPrototype>?, EntityUid)> assignedJobs,
|
||||||
|
IEnumerable<NetUserId> allPlayersToAssign,
|
||||||
|
IReadOnlyDictionary<NetUserId, HumanoidCharacterProfile> profiles,
|
||||||
|
IReadOnlyList<EntityUid> stations)
|
||||||
{
|
{
|
||||||
var givenStations = stations.ToList();
|
var givenStations = stations.ToList();
|
||||||
if (givenStations.Count == 0)
|
if (givenStations.Count == 0)
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ using System.Linq;
|
|||||||
using Content.Server.GameTicking;
|
using Content.Server.GameTicking;
|
||||||
using Content.Server.Station.Components;
|
using Content.Server.Station.Components;
|
||||||
using Content.Shared.CCVar;
|
using Content.Shared.CCVar;
|
||||||
|
using Content.Shared.FixedPoint;
|
||||||
using Content.Shared.GameTicking;
|
using Content.Shared.GameTicking;
|
||||||
using Content.Shared.Preferences;
|
using Content.Shared.Preferences;
|
||||||
using Content.Shared.Roles;
|
using Content.Shared.Roles;
|
||||||
@@ -31,12 +32,25 @@ public sealed partial class StationJobsSystem : EntitySystem
|
|||||||
public override void Initialize()
|
public override void Initialize()
|
||||||
{
|
{
|
||||||
SubscribeLocalEvent<StationInitializedEvent>(OnStationInitialized);
|
SubscribeLocalEvent<StationInitializedEvent>(OnStationInitialized);
|
||||||
|
SubscribeLocalEvent<StationJobsComponent, ComponentInit>(OnInit);
|
||||||
SubscribeLocalEvent<StationJobsComponent, StationRenamedEvent>(OnStationRenamed);
|
SubscribeLocalEvent<StationJobsComponent, StationRenamedEvent>(OnStationRenamed);
|
||||||
SubscribeLocalEvent<StationJobsComponent, ComponentShutdown>(OnStationDeletion);
|
SubscribeLocalEvent<StationJobsComponent, ComponentShutdown>(OnStationDeletion);
|
||||||
SubscribeLocalEvent<PlayerJoinedLobbyEvent>(OnPlayerJoinedLobby);
|
SubscribeLocalEvent<PlayerJoinedLobbyEvent>(OnPlayerJoinedLobby);
|
||||||
Subs.CVar(_configurationManager, CCVars.GameDisallowLateJoins, _ => UpdateJobsAvailable(), true);
|
Subs.CVar(_configurationManager, CCVars.GameDisallowLateJoins, _ => UpdateJobsAvailable(), true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void OnInit(Entity<StationJobsComponent> ent, ref ComponentInit args)
|
||||||
|
{
|
||||||
|
ent.Comp.MidRoundTotalJobs = ent.Comp.SetupAvailableJobs.Values
|
||||||
|
.Select(x => Math.Max(x[1], 0))
|
||||||
|
.Sum();
|
||||||
|
|
||||||
|
ent.Comp.OverflowJobs = ent.Comp.SetupAvailableJobs
|
||||||
|
.Where(x => x.Value[0] < 0)
|
||||||
|
.Select(x => x.Key)
|
||||||
|
.ToHashSet();
|
||||||
|
}
|
||||||
|
|
||||||
public override void Update(float _)
|
public override void Update(float _)
|
||||||
{
|
{
|
||||||
if (_availableJobsDirty)
|
if (_availableJobsDirty)
|
||||||
@@ -57,28 +71,11 @@ public sealed partial class StationJobsSystem : EntitySystem
|
|||||||
if (!TryComp<StationJobsComponent>(msg.Station, out var stationJobs))
|
if (!TryComp<StationJobsComponent>(msg.Station, out var stationJobs))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
var mapJobList = stationJobs.SetupAvailableJobs;
|
stationJobs.JobList = stationJobs.SetupAvailableJobs.ToDictionary(
|
||||||
|
x => x.Key,
|
||||||
|
x=> (int?)(x.Value[1] < 0 ? null : x.Value[1]));
|
||||||
|
|
||||||
stationJobs.RoundStartTotalJobs = mapJobList.Values.Where(x => x[0] is not null && x[0] > 0).Sum(x => x[0]!.Value);
|
stationJobs.TotalJobs = stationJobs.JobList.Values.Select(x => x ?? 0).Sum();
|
||||||
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 = stationJobs.OverflowJobs.ToHashSet();
|
|
||||||
|
|
||||||
UpdateJobsAvailable();
|
UpdateJobsAvailable();
|
||||||
}
|
}
|
||||||
@@ -141,7 +138,11 @@ public sealed partial class StationJobsSystem : EntitySystem
|
|||||||
/// <param name="stationJobs">Resolve pattern, station jobs component of the station.</param>
|
/// <param name="stationJobs">Resolve pattern, station jobs component of the station.</param>
|
||||||
/// <returns>Whether or not slot adjustment was a success.</returns>
|
/// <returns>Whether or not slot adjustment was a success.</returns>
|
||||||
/// <exception cref="ArgumentException">Thrown when the given station is not a station.</exception>
|
/// <exception cref="ArgumentException">Thrown when the given station is not a station.</exception>
|
||||||
public bool TryAdjustJobSlot(EntityUid station, string jobPrototypeId, int amount, bool createSlot = false, bool clamp = false,
|
public bool TryAdjustJobSlot(EntityUid station,
|
||||||
|
string jobPrototypeId,
|
||||||
|
int amount,
|
||||||
|
bool createSlot = false,
|
||||||
|
bool clamp = false,
|
||||||
StationJobsComponent? stationJobs = null)
|
StationJobsComponent? stationJobs = null)
|
||||||
{
|
{
|
||||||
if (!Resolve(station, ref stationJobs))
|
if (!Resolve(station, ref stationJobs))
|
||||||
@@ -156,7 +157,11 @@ public sealed partial class StationJobsSystem : EntitySystem
|
|||||||
// - Return false when you remove from a job that doesn't exist.
|
// - Return false when you remove from a job that doesn't exist.
|
||||||
// - Return false when you remove and exceed the number of slots available.
|
// - Return false when you remove and exceed the number of slots available.
|
||||||
// And additionally, if adding would add a job not previously on the manifest when createSlot is false, return false and do nothing.
|
// And additionally, if adding would add a job not previously on the manifest when createSlot is false, return false and do nothing.
|
||||||
switch (jobList.ContainsKey(jobPrototypeId))
|
|
||||||
|
if (amount == 0)
|
||||||
|
return true;
|
||||||
|
|
||||||
|
switch (jobList.TryGetValue(jobPrototypeId, out var available))
|
||||||
{
|
{
|
||||||
case false when amount < 0:
|
case false when amount < 0:
|
||||||
return false;
|
return false;
|
||||||
@@ -164,31 +169,20 @@ public sealed partial class StationJobsSystem : EntitySystem
|
|||||||
if (!createSlot)
|
if (!createSlot)
|
||||||
return false;
|
return false;
|
||||||
stationJobs.TotalJobs += amount;
|
stationJobs.TotalJobs += amount;
|
||||||
jobList[jobPrototypeId] = (uint?)amount;
|
jobList[jobPrototypeId] = amount;
|
||||||
UpdateJobsAvailable();
|
UpdateJobsAvailable();
|
||||||
return true;
|
return true;
|
||||||
case true:
|
case true:
|
||||||
// Job is unlimited so just say we adjusted it and do nothing.
|
// Job is unlimited so just say we adjusted it and do nothing.
|
||||||
if (jobList[jobPrototypeId] == null)
|
if (available is not {} avail)
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
// Would remove more jobs than we have available.
|
// Would remove more jobs than we have available.
|
||||||
if (amount < 0 && (jobList[jobPrototypeId] + amount < 0 && !clamp))
|
if (available + amount < 0 && !clamp)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
stationJobs.TotalJobs += amount;
|
jobList[jobPrototypeId] = Math.Max(avail + amount, 0);
|
||||||
|
stationJobs.TotalJobs = jobList.Values.Select(x => x ?? 0).Sum();
|
||||||
//C# type handling moment
|
|
||||||
if (amount > 0)
|
|
||||||
jobList[jobPrototypeId] += (uint)amount;
|
|
||||||
else
|
|
||||||
{
|
|
||||||
if ((int)jobList[jobPrototypeId]!.Value - Math.Abs(amount) <= 0)
|
|
||||||
jobList[jobPrototypeId] = 0;
|
|
||||||
else
|
|
||||||
jobList[jobPrototypeId] -= (uint) Math.Abs(amount);
|
|
||||||
}
|
|
||||||
|
|
||||||
UpdateJobsAvailable();
|
UpdateJobsAvailable();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -239,7 +233,10 @@ public sealed partial class StationJobsSystem : EntitySystem
|
|||||||
/// <param name="stationJobs">Resolve pattern, station jobs component of the station.</param>
|
/// <param name="stationJobs">Resolve pattern, station jobs component of the station.</param>
|
||||||
/// <returns>Whether or not setting the value succeeded.</returns>
|
/// <returns>Whether or not setting the value succeeded.</returns>
|
||||||
/// <exception cref="ArgumentException">Thrown when the given station is not a station.</exception>
|
/// <exception cref="ArgumentException">Thrown when the given station is not a station.</exception>
|
||||||
public bool TrySetJobSlot(EntityUid station, string jobPrototypeId, int amount, bool createSlot = false,
|
public bool TrySetJobSlot(EntityUid station,
|
||||||
|
string jobPrototypeId,
|
||||||
|
int amount,
|
||||||
|
bool createSlot = false,
|
||||||
StationJobsComponent? stationJobs = null)
|
StationJobsComponent? stationJobs = null)
|
||||||
{
|
{
|
||||||
if (!Resolve(station, ref stationJobs))
|
if (!Resolve(station, ref stationJobs))
|
||||||
@@ -255,13 +252,13 @@ public sealed partial class StationJobsSystem : EntitySystem
|
|||||||
if (!createSlot)
|
if (!createSlot)
|
||||||
return false;
|
return false;
|
||||||
stationJobs.TotalJobs += amount;
|
stationJobs.TotalJobs += amount;
|
||||||
jobList[jobPrototypeId] = (uint?)amount;
|
jobList[jobPrototypeId] = amount;
|
||||||
UpdateJobsAvailable();
|
UpdateJobsAvailable();
|
||||||
return true;
|
return true;
|
||||||
case true:
|
case true:
|
||||||
stationJobs.TotalJobs += amount - (int) (jobList[jobPrototypeId] ?? 0);
|
stationJobs.TotalJobs += amount - (jobList[jobPrototypeId] ?? 0);
|
||||||
|
|
||||||
jobList[jobPrototypeId] = (uint)amount;
|
jobList[jobPrototypeId] = amount;
|
||||||
UpdateJobsAvailable();
|
UpdateJobsAvailable();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -289,8 +286,8 @@ public sealed partial class StationJobsSystem : EntitySystem
|
|||||||
throw new ArgumentException("Tried to use a non-station entity as a station!", nameof(station));
|
throw new ArgumentException("Tried to use a non-station entity as a station!", nameof(station));
|
||||||
|
|
||||||
// Subtract out the job we're fixing to make have unlimited slots.
|
// Subtract out the job we're fixing to make have unlimited slots.
|
||||||
if (stationJobs.JobList.ContainsKey(jobPrototypeId) && stationJobs.JobList[jobPrototypeId] != null)
|
if (stationJobs.JobList.TryGetValue(jobPrototypeId, out var existing))
|
||||||
stationJobs.TotalJobs -= (int)stationJobs.JobList[jobPrototypeId]!.Value;
|
stationJobs.TotalJobs -= existing ?? 0;
|
||||||
|
|
||||||
stationJobs.JobList[jobPrototypeId] = null;
|
stationJobs.JobList[jobPrototypeId] = null;
|
||||||
|
|
||||||
@@ -319,8 +316,7 @@ public sealed partial class StationJobsSystem : EntitySystem
|
|||||||
if (!Resolve(station, ref stationJobs))
|
if (!Resolve(station, ref stationJobs))
|
||||||
throw new ArgumentException("Tried to use a non-station entity as a station!", nameof(station));
|
throw new ArgumentException("Tried to use a non-station entity as a station!", nameof(station));
|
||||||
|
|
||||||
var res = stationJobs.JobList.TryGetValue(jobPrototypeId, out var job) && job == null;
|
return stationJobs.JobList.TryGetValue(jobPrototypeId, out var job) && job == null;
|
||||||
return res;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc cref="TryGetJobSlot(Robust.Shared.GameObjects.EntityUid,string,out System.Nullable{uint},Content.Server.Station.Components.StationJobsComponent?)"/>
|
/// <inheritdoc cref="TryGetJobSlot(Robust.Shared.GameObjects.EntityUid,string,out System.Nullable{uint},Content.Server.Station.Components.StationJobsComponent?)"/>
|
||||||
@@ -328,7 +324,7 @@ public sealed partial class StationJobsSystem : EntitySystem
|
|||||||
/// <param name="job">Job to get slot info for.</param>
|
/// <param name="job">Job to get slot info for.</param>
|
||||||
/// <param name="slots">The number of slots remaining. Null if infinite.</param>
|
/// <param name="slots">The number of slots remaining. Null if infinite.</param>
|
||||||
/// <param name="stationJobs">Resolve pattern, station jobs component of the station.</param>
|
/// <param name="stationJobs">Resolve pattern, station jobs component of the station.</param>
|
||||||
public bool TryGetJobSlot(EntityUid station, JobPrototype job, out uint? slots, StationJobsComponent? stationJobs = null)
|
public bool TryGetJobSlot(EntityUid station, JobPrototype job, out int? slots, StationJobsComponent? stationJobs = null)
|
||||||
{
|
{
|
||||||
return TryGetJobSlot(station, job.ID, out slots, stationJobs);
|
return TryGetJobSlot(station, job.ID, out slots, stationJobs);
|
||||||
}
|
}
|
||||||
@@ -343,21 +339,12 @@ public sealed partial class StationJobsSystem : EntitySystem
|
|||||||
/// <returns>Whether or not the slot exists.</returns>
|
/// <returns>Whether or not the slot exists.</returns>
|
||||||
/// <exception cref="ArgumentException">Thrown when the given station is not a station.</exception>
|
/// <exception cref="ArgumentException">Thrown when the given station is not a station.</exception>
|
||||||
/// <remarks>slots will be null if the slot doesn't exist, as well, so make sure to check the return value.</remarks>
|
/// <remarks>slots will be null if the slot doesn't exist, as well, so make sure to check the return value.</remarks>
|
||||||
public bool TryGetJobSlot(EntityUid station, string jobPrototypeId, out uint? slots, StationJobsComponent? stationJobs = null)
|
public bool TryGetJobSlot(EntityUid station, string jobPrototypeId, out int? slots, StationJobsComponent? stationJobs = null)
|
||||||
{
|
{
|
||||||
if (!Resolve(station, ref stationJobs))
|
if (!Resolve(station, ref stationJobs))
|
||||||
throw new ArgumentException("Tried to use a non-station entity as a station!", nameof(station));
|
throw new ArgumentException("Tried to use a non-station entity as a station!", nameof(station));
|
||||||
|
|
||||||
if (stationJobs.JobList.TryGetValue(jobPrototypeId, out var job))
|
return stationJobs.JobList.TryGetValue(jobPrototypeId, out slots);
|
||||||
{
|
|
||||||
slots = job;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
else // Else if slot isn't present return null.
|
|
||||||
{
|
|
||||||
slots = null;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -367,12 +354,14 @@ public sealed partial class StationJobsSystem : EntitySystem
|
|||||||
/// <param name="stationJobs">Resolve pattern, station jobs component of the station.</param>
|
/// <param name="stationJobs">Resolve pattern, station jobs component of the station.</param>
|
||||||
/// <returns>Set containing all jobs available.</returns>
|
/// <returns>Set containing all jobs available.</returns>
|
||||||
/// <exception cref="ArgumentException">Thrown when the given station is not a station.</exception>
|
/// <exception cref="ArgumentException">Thrown when the given station is not a station.</exception>
|
||||||
public IReadOnlySet<string> GetAvailableJobs(EntityUid station, StationJobsComponent? stationJobs = null)
|
public IEnumerable<ProtoId<JobPrototype>> GetAvailableJobs(EntityUid station, StationJobsComponent? stationJobs = null)
|
||||||
{
|
{
|
||||||
if (!Resolve(station, ref stationJobs))
|
if (!Resolve(station, ref stationJobs))
|
||||||
throw new ArgumentException("Tried to use a non-station entity as a station!", nameof(station));
|
throw new ArgumentException("Tried to use a non-station entity as a station!", nameof(station));
|
||||||
|
|
||||||
return stationJobs.JobList.Where(x => x.Value != 0).Select(x => x.Key).ToHashSet();
|
return stationJobs.JobList
|
||||||
|
.Where(x => x.Value != 0)
|
||||||
|
.Select(x => x.Key);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -382,12 +371,12 @@ public sealed partial class StationJobsSystem : EntitySystem
|
|||||||
/// <param name="stationJobs">Resolve pattern, station jobs component of the station.</param>
|
/// <param name="stationJobs">Resolve pattern, station jobs component of the station.</param>
|
||||||
/// <returns>Set containing all overflow jobs available.</returns>
|
/// <returns>Set containing all overflow jobs available.</returns>
|
||||||
/// <exception cref="ArgumentException">Thrown when the given station is not a station.</exception>
|
/// <exception cref="ArgumentException">Thrown when the given station is not a station.</exception>
|
||||||
public IReadOnlySet<string> GetOverflowJobs(EntityUid station, StationJobsComponent? stationJobs = null)
|
public IReadOnlySet<ProtoId<JobPrototype>> GetOverflowJobs(EntityUid station, StationJobsComponent? stationJobs = null)
|
||||||
{
|
{
|
||||||
if (!Resolve(station, ref stationJobs))
|
if (!Resolve(station, ref stationJobs))
|
||||||
throw new ArgumentException("Tried to use a non-station entity as a station!", nameof(station));
|
throw new ArgumentException("Tried to use a non-station entity as a station!", nameof(station));
|
||||||
|
|
||||||
return stationJobs.OverflowJobs.ToHashSet();
|
return stationJobs.OverflowJobs;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -397,7 +386,7 @@ public sealed partial class StationJobsSystem : EntitySystem
|
|||||||
/// <param name="stationJobs">Resolve pattern, station jobs component of the station.</param>
|
/// <param name="stationJobs">Resolve pattern, station jobs component of the station.</param>
|
||||||
/// <returns>List of all jobs on the station.</returns>
|
/// <returns>List of all jobs on the station.</returns>
|
||||||
/// <exception cref="ArgumentException">Thrown when the given station is not a station.</exception>
|
/// <exception cref="ArgumentException">Thrown when the given station is not a station.</exception>
|
||||||
public IReadOnlyDictionary<string, uint?> GetJobs(EntityUid station, StationJobsComponent? stationJobs = null)
|
public IReadOnlyDictionary<ProtoId<JobPrototype>, int?> GetJobs(EntityUid station, StationJobsComponent? stationJobs = null)
|
||||||
{
|
{
|
||||||
if (!Resolve(station, ref stationJobs))
|
if (!Resolve(station, ref stationJobs))
|
||||||
throw new ArgumentException("Tried to use a non-station entity as a station!", nameof(station));
|
throw new ArgumentException("Tried to use a non-station entity as a station!", nameof(station));
|
||||||
@@ -412,12 +401,14 @@ public sealed partial class StationJobsSystem : EntitySystem
|
|||||||
/// <param name="stationJobs">Resolve pattern, station jobs component of the station.</param>
|
/// <param name="stationJobs">Resolve pattern, station jobs component of the station.</param>
|
||||||
/// <returns>List of all round-start jobs.</returns>
|
/// <returns>List of all round-start jobs.</returns>
|
||||||
/// <exception cref="ArgumentException">Thrown when the given station is not a station.</exception>
|
/// <exception cref="ArgumentException">Thrown when the given station is not a station.</exception>
|
||||||
public IReadOnlyDictionary<string, uint?> GetRoundStartJobs(EntityUid station, StationJobsComponent? stationJobs = null)
|
public Dictionary<ProtoId<JobPrototype>, int?> GetRoundStartJobs(EntityUid station, StationJobsComponent? stationJobs = null)
|
||||||
{
|
{
|
||||||
if (!Resolve(station, ref stationJobs))
|
if (!Resolve(station, ref stationJobs))
|
||||||
throw new ArgumentException("Tried to use a non-station entity as a station!", nameof(station));
|
throw new ArgumentException("Tried to use a non-station entity as a station!", nameof(station));
|
||||||
|
|
||||||
return stationJobs.RoundStartJobList;
|
return stationJobs.SetupAvailableJobs.ToDictionary(
|
||||||
|
x => x.Key,
|
||||||
|
x=> (int?)(x.Value[0] < 0 ? null : x.Value[0]));
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -428,13 +419,13 @@ public sealed partial class StationJobsSystem : EntitySystem
|
|||||||
/// <param name="pickOverflows">Whether or not to pick from the overflow list.</param>
|
/// <param name="pickOverflows">Whether or not to pick from the overflow list.</param>
|
||||||
/// <param name="disallowedJobs">A set of disallowed jobs, if any.</param>
|
/// <param name="disallowedJobs">A set of disallowed jobs, if any.</param>
|
||||||
/// <returns>The selected job, if any.</returns>
|
/// <returns>The selected job, if any.</returns>
|
||||||
public string? PickBestAvailableJobWithPriority(EntityUid station, IReadOnlyDictionary<string, JobPriority> jobPriorities, bool pickOverflows, IReadOnlySet<ProtoId<JobPrototype>>? disallowedJobs = null)
|
public ProtoId<JobPrototype>? PickBestAvailableJobWithPriority(EntityUid station, IReadOnlyDictionary<ProtoId<JobPrototype>, JobPriority> jobPriorities, bool pickOverflows, IReadOnlySet<ProtoId<JobPrototype>>? disallowedJobs = null)
|
||||||
{
|
{
|
||||||
if (station == EntityUid.Invalid)
|
if (station == EntityUid.Invalid)
|
||||||
return null;
|
return null;
|
||||||
|
|
||||||
var available = GetAvailableJobs(station);
|
var available = GetAvailableJobs(station);
|
||||||
bool TryPick(JobPriority priority, [NotNullWhen(true)] out string? jobId)
|
bool TryPick(JobPriority priority, [NotNullWhen(true)] out ProtoId<JobPrototype>? jobId)
|
||||||
{
|
{
|
||||||
var filtered = jobPriorities
|
var filtered = jobPriorities
|
||||||
.Where(p =>
|
.Where(p =>
|
||||||
@@ -474,7 +465,10 @@ public sealed partial class StationJobsSystem : EntitySystem
|
|||||||
return null;
|
return null;
|
||||||
|
|
||||||
var overflows = GetOverflowJobs(station);
|
var overflows = GetOverflowJobs(station);
|
||||||
return overflows.Count != 0 ? _random.Pick(overflows) : null;
|
if (overflows.Count == 0)
|
||||||
|
return null;
|
||||||
|
|
||||||
|
return _random.Pick(overflows);
|
||||||
}
|
}
|
||||||
|
|
||||||
#endregion Public API
|
#endregion Public API
|
||||||
@@ -483,7 +477,7 @@ public sealed partial class StationJobsSystem : EntitySystem
|
|||||||
|
|
||||||
private bool _availableJobsDirty;
|
private bool _availableJobsDirty;
|
||||||
|
|
||||||
private TickerJobsAvailableEvent _cachedAvailableJobs = new (new Dictionary<NetEntity, string>(), new Dictionary<NetEntity, Dictionary<string, uint?>>());
|
private TickerJobsAvailableEvent _cachedAvailableJobs = new(new(), new());
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Assembles an event from the current available-to-play jobs.
|
/// Assembles an event from the current available-to-play jobs.
|
||||||
@@ -494,9 +488,9 @@ public sealed partial class StationJobsSystem : EntitySystem
|
|||||||
{
|
{
|
||||||
// If late join is disallowed, return no available jobs.
|
// If late join is disallowed, return no available jobs.
|
||||||
if (_gameTicker.DisallowLateJoin)
|
if (_gameTicker.DisallowLateJoin)
|
||||||
return new TickerJobsAvailableEvent(new Dictionary<NetEntity, string>(), new Dictionary<NetEntity, Dictionary<string, uint?>>());
|
return new TickerJobsAvailableEvent(new(), new());
|
||||||
|
|
||||||
var jobs = new Dictionary<NetEntity, Dictionary<string, uint?>>();
|
var jobs = new Dictionary<NetEntity, Dictionary<ProtoId<JobPrototype>, int?>>();
|
||||||
var stationNames = new Dictionary<NetEntity, string>();
|
var stationNames = new Dictionary<NetEntity, string>();
|
||||||
|
|
||||||
var query = EntityQueryEnumerator<StationJobsComponent>();
|
var query = EntityQueryEnumerator<StationJobsComponent>();
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
using Content.Shared.Roles;
|
using Content.Shared.Roles;
|
||||||
using Robust.Shared.Network;
|
using Robust.Shared.Network;
|
||||||
|
using Robust.Shared.Prototypes;
|
||||||
using Robust.Shared.Replays;
|
using Robust.Shared.Replays;
|
||||||
using Robust.Shared.Serialization;
|
using Robust.Shared.Serialization;
|
||||||
using Robust.Shared.Serialization.Markdown.Mapping;
|
using Robust.Shared.Serialization.Markdown.Mapping;
|
||||||
@@ -128,19 +129,17 @@ namespace Content.Shared.GameTicking
|
|||||||
}
|
}
|
||||||
|
|
||||||
[Serializable, NetSerializable]
|
[Serializable, NetSerializable]
|
||||||
public sealed class TickerJobsAvailableEvent : EntityEventArgs
|
public sealed class TickerJobsAvailableEvent(
|
||||||
|
Dictionary<NetEntity, string> stationNames,
|
||||||
|
Dictionary<NetEntity, Dictionary<ProtoId<JobPrototype>, int?>> jobsAvailableByStation)
|
||||||
|
: EntityEventArgs
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The Status of the Player in the lobby (ready, observer, ...)
|
/// The Status of the Player in the lobby (ready, observer, ...)
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public Dictionary<NetEntity, Dictionary<string, uint?>> JobsAvailableByStation { get; }
|
public Dictionary<NetEntity, Dictionary<ProtoId<JobPrototype>, int?>> JobsAvailableByStation { get; } = jobsAvailableByStation;
|
||||||
public Dictionary<NetEntity, string> StationNames { get; }
|
|
||||||
|
|
||||||
public TickerJobsAvailableEvent(Dictionary<NetEntity, string> stationNames, Dictionary<NetEntity, Dictionary<string, uint?>> jobsAvailableByStation)
|
public Dictionary<NetEntity, string> StationNames { get; } = stationNames;
|
||||||
{
|
|
||||||
StationNames = stationNames;
|
|
||||||
JobsAvailableByStation = jobsAvailableByStation;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[Serializable, NetSerializable, DataDefinition]
|
[Serializable, NetSerializable, DataDefinition]
|
||||||
|
|||||||
@@ -35,7 +35,7 @@ namespace Content.Shared.Preferences
|
|||||||
/// Job preferences for initial spawn.
|
/// Job preferences for initial spawn.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[DataField]
|
[DataField]
|
||||||
private Dictionary<string, JobPriority> _jobPriorities = new()
|
private Dictionary<ProtoId<JobPrototype>, JobPriority> _jobPriorities = new()
|
||||||
{
|
{
|
||||||
{
|
{
|
||||||
SharedGameTicker.FallbackOverflowJob, JobPriority.High
|
SharedGameTicker.FallbackOverflowJob, JobPriority.High
|
||||||
@@ -46,13 +46,13 @@ namespace Content.Shared.Preferences
|
|||||||
/// Antags we have opted in to.
|
/// Antags we have opted in to.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[DataField]
|
[DataField]
|
||||||
private HashSet<string> _antagPreferences = new();
|
private HashSet<ProtoId<AntagPrototype>> _antagPreferences = new();
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Enabled traits.
|
/// Enabled traits.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[DataField]
|
[DataField]
|
||||||
private HashSet<string> _traitPreferences = new();
|
private HashSet<ProtoId<TraitPrototype>> _traitPreferences = new();
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// <see cref="_loadouts"/>
|
/// <see cref="_loadouts"/>
|
||||||
@@ -75,7 +75,7 @@ namespace Content.Shared.Preferences
|
|||||||
/// Associated <see cref="SpeciesPrototype"/> for this profile.
|
/// Associated <see cref="SpeciesPrototype"/> for this profile.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[DataField]
|
[DataField]
|
||||||
public string Species { get; set; } = SharedHumanoidAppearanceSystem.DefaultSpecies;
|
public ProtoId<SpeciesPrototype> Species { get; set; } = SharedHumanoidAppearanceSystem.DefaultSpecies;
|
||||||
|
|
||||||
[DataField]
|
[DataField]
|
||||||
public int Age { get; set; } = 18;
|
public int Age { get; set; } = 18;
|
||||||
@@ -106,17 +106,17 @@ namespace Content.Shared.Preferences
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// <see cref="_jobPriorities"/>
|
/// <see cref="_jobPriorities"/>
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public IReadOnlyDictionary<string, JobPriority> JobPriorities => _jobPriorities;
|
public IReadOnlyDictionary<ProtoId<JobPrototype>, JobPriority> JobPriorities => _jobPriorities;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// <see cref="_antagPreferences"/>
|
/// <see cref="_antagPreferences"/>
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public IReadOnlySet<string> AntagPreferences => _antagPreferences;
|
public IReadOnlySet<ProtoId<AntagPrototype>> AntagPreferences => _antagPreferences;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// <see cref="_traitPreferences"/>
|
/// <see cref="_traitPreferences"/>
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public IReadOnlySet<string> TraitPreferences => _traitPreferences;
|
public IReadOnlySet<ProtoId<TraitPrototype>> TraitPreferences => _traitPreferences;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// If we're unable to get one of our preferred jobs do we spawn as a fallback job or do we stay in lobby.
|
/// If we're unable to get one of our preferred jobs do we spawn as a fallback job or do we stay in lobby.
|
||||||
@@ -134,10 +134,10 @@ namespace Content.Shared.Preferences
|
|||||||
Gender gender,
|
Gender gender,
|
||||||
HumanoidCharacterAppearance appearance,
|
HumanoidCharacterAppearance appearance,
|
||||||
SpawnPriorityPreference spawnPriority,
|
SpawnPriorityPreference spawnPriority,
|
||||||
Dictionary<string, JobPriority> jobPriorities,
|
Dictionary<ProtoId<JobPrototype>, JobPriority> jobPriorities,
|
||||||
PreferenceUnavailableMode preferenceUnavailable,
|
PreferenceUnavailableMode preferenceUnavailable,
|
||||||
HashSet<string> antagPreferences,
|
HashSet<ProtoId<AntagPrototype>> antagPreferences,
|
||||||
HashSet<string> traitPreferences,
|
HashSet<ProtoId<TraitPrototype>> traitPreferences,
|
||||||
Dictionary<string, RoleLoadout> loadouts)
|
Dictionary<string, RoleLoadout> loadouts)
|
||||||
{
|
{
|
||||||
Name = name;
|
Name = name;
|
||||||
@@ -153,6 +153,20 @@ namespace Content.Shared.Preferences
|
|||||||
_antagPreferences = antagPreferences;
|
_antagPreferences = antagPreferences;
|
||||||
_traitPreferences = traitPreferences;
|
_traitPreferences = traitPreferences;
|
||||||
_loadouts = loadouts;
|
_loadouts = loadouts;
|
||||||
|
|
||||||
|
var hasHighPrority = false;
|
||||||
|
foreach (var (key, value) in _jobPriorities)
|
||||||
|
{
|
||||||
|
if (value == JobPriority.Never)
|
||||||
|
_jobPriorities.Remove(key);
|
||||||
|
else if (value != JobPriority.High)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
if (hasHighPrority)
|
||||||
|
_jobPriorities[key] = JobPriority.Medium;
|
||||||
|
|
||||||
|
hasHighPrority = true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>Copy constructor</summary>
|
/// <summary>Copy constructor</summary>
|
||||||
@@ -165,10 +179,10 @@ namespace Content.Shared.Preferences
|
|||||||
other.Gender,
|
other.Gender,
|
||||||
other.Appearance.Clone(),
|
other.Appearance.Clone(),
|
||||||
other.SpawnPriority,
|
other.SpawnPriority,
|
||||||
new Dictionary<string, JobPriority>(other.JobPriorities),
|
new Dictionary<ProtoId<JobPrototype>, JobPriority>(other.JobPriorities),
|
||||||
other.PreferenceUnavailable,
|
other.PreferenceUnavailable,
|
||||||
new HashSet<string>(other.AntagPreferences),
|
new HashSet<ProtoId<AntagPrototype>>(other.AntagPreferences),
|
||||||
new HashSet<string>(other.TraitPreferences),
|
new HashSet<ProtoId<TraitPrototype>>(other.TraitPreferences),
|
||||||
new Dictionary<string, RoleLoadout>(other.Loadouts))
|
new Dictionary<string, RoleLoadout>(other.Loadouts))
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
@@ -289,21 +303,48 @@ namespace Content.Shared.Preferences
|
|||||||
return new(this) { SpawnPriority = spawnPriority };
|
return new(this) { SpawnPriority = spawnPriority };
|
||||||
}
|
}
|
||||||
|
|
||||||
public HumanoidCharacterProfile WithJobPriorities(IEnumerable<KeyValuePair<string, JobPriority>> jobPriorities)
|
public HumanoidCharacterProfile WithJobPriorities(IEnumerable<KeyValuePair<ProtoId<JobPrototype>, JobPriority>> jobPriorities)
|
||||||
{
|
{
|
||||||
|
var dictionary = new Dictionary<ProtoId<JobPrototype>, JobPriority>(jobPriorities);
|
||||||
|
var hasHighPrority = false;
|
||||||
|
|
||||||
|
foreach (var (key, value) in dictionary)
|
||||||
|
{
|
||||||
|
if (value == JobPriority.Never)
|
||||||
|
dictionary.Remove(key);
|
||||||
|
else if (value != JobPriority.High)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
if (hasHighPrority)
|
||||||
|
dictionary[key] = JobPriority.Medium;
|
||||||
|
|
||||||
|
hasHighPrority = true;
|
||||||
|
}
|
||||||
|
|
||||||
return new(this)
|
return new(this)
|
||||||
{
|
{
|
||||||
_jobPriorities = new Dictionary<string, JobPriority>(jobPriorities),
|
_jobPriorities = dictionary
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
public HumanoidCharacterProfile WithJobPriority(string jobId, JobPriority priority)
|
public HumanoidCharacterProfile WithJobPriority(ProtoId<JobPrototype> jobId, JobPriority priority)
|
||||||
{
|
{
|
||||||
var dictionary = new Dictionary<string, JobPriority>(_jobPriorities);
|
var dictionary = new Dictionary<ProtoId<JobPrototype>, JobPriority>(_jobPriorities);
|
||||||
if (priority == JobPriority.Never)
|
if (priority == JobPriority.Never)
|
||||||
{
|
{
|
||||||
dictionary.Remove(jobId);
|
dictionary.Remove(jobId);
|
||||||
}
|
}
|
||||||
|
else if (priority == JobPriority.High)
|
||||||
|
{
|
||||||
|
// There can only ever be one high priority job.
|
||||||
|
foreach (var (job, value) in dictionary)
|
||||||
|
{
|
||||||
|
if (value == JobPriority.High)
|
||||||
|
dictionary[job] = JobPriority.Medium;
|
||||||
|
}
|
||||||
|
|
||||||
|
dictionary[jobId] = priority;
|
||||||
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
dictionary[jobId] = priority;
|
dictionary[jobId] = priority;
|
||||||
@@ -320,17 +361,17 @@ namespace Content.Shared.Preferences
|
|||||||
return new(this) { PreferenceUnavailable = mode };
|
return new(this) { PreferenceUnavailable = mode };
|
||||||
}
|
}
|
||||||
|
|
||||||
public HumanoidCharacterProfile WithAntagPreferences(IEnumerable<string> antagPreferences)
|
public HumanoidCharacterProfile WithAntagPreferences(IEnumerable<ProtoId<AntagPrototype>> antagPreferences)
|
||||||
{
|
{
|
||||||
return new(this)
|
return new(this)
|
||||||
{
|
{
|
||||||
_antagPreferences = new HashSet<string>(antagPreferences),
|
_antagPreferences = new (antagPreferences),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
public HumanoidCharacterProfile WithAntagPreference(string antagId, bool pref)
|
public HumanoidCharacterProfile WithAntagPreference(ProtoId<AntagPrototype> antagId, bool pref)
|
||||||
{
|
{
|
||||||
var list = new HashSet<string>(_antagPreferences);
|
var list = new HashSet<ProtoId<AntagPrototype>>(_antagPreferences);
|
||||||
if (pref)
|
if (pref)
|
||||||
{
|
{
|
||||||
list.Add(antagId);
|
list.Add(antagId);
|
||||||
@@ -346,16 +387,16 @@ namespace Content.Shared.Preferences
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
public HumanoidCharacterProfile WithTraitPreference(string traitId, string? categoryId, bool pref)
|
public HumanoidCharacterProfile WithTraitPreference(ProtoId<TraitPrototype> traitId, string? categoryId, bool pref)
|
||||||
{
|
{
|
||||||
var prototypeManager = IoCManager.Resolve<IPrototypeManager>();
|
var prototypeManager = IoCManager.Resolve<IPrototypeManager>();
|
||||||
var traitProto = prototypeManager.Index<TraitPrototype>(traitId);
|
var traitProto = prototypeManager.Index(traitId);
|
||||||
|
|
||||||
TraitCategoryPrototype? categoryProto = null;
|
TraitCategoryPrototype? categoryProto = null;
|
||||||
if (categoryId != null && categoryId != "default")
|
if (categoryId != null && categoryId != "default")
|
||||||
categoryProto = prototypeManager.Index<TraitCategoryPrototype>(categoryId);
|
categoryProto = prototypeManager.Index<TraitCategoryPrototype>(categoryId);
|
||||||
|
|
||||||
var list = new HashSet<string>(_traitPreferences);
|
var list = new HashSet<ProtoId<TraitPrototype>>(_traitPreferences);
|
||||||
|
|
||||||
if (pref)
|
if (pref)
|
||||||
{
|
{
|
||||||
@@ -372,7 +413,7 @@ namespace Content.Shared.Preferences
|
|||||||
var count = 0;
|
var count = 0;
|
||||||
foreach (var trait in list)
|
foreach (var trait in list)
|
||||||
{
|
{
|
||||||
var traitProtoTemp = prototypeManager.Index<TraitPrototype>(trait);
|
var traitProtoTemp = prototypeManager.Index(trait);
|
||||||
count += traitProtoTemp.Cost;
|
count += traitProtoTemp.Cost;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -514,7 +555,7 @@ namespace Content.Shared.Preferences
|
|||||||
_ => SpawnPriorityPreference.None // Invalid enum values.
|
_ => SpawnPriorityPreference.None // Invalid enum values.
|
||||||
};
|
};
|
||||||
|
|
||||||
var priorities = new Dictionary<string, JobPriority>(JobPriorities
|
var priorities = new Dictionary<ProtoId<JobPrototype>, JobPriority>(JobPriorities
|
||||||
.Where(p => prototypeManager.TryIndex<JobPrototype>(p.Key, out var job) && job.SetPreference && p.Value switch
|
.Where(p => prototypeManager.TryIndex<JobPrototype>(p.Key, out var job) && job.SetPreference && p.Value switch
|
||||||
{
|
{
|
||||||
JobPriority.Never => false, // Drop never since that's assumed default.
|
JobPriority.Never => false, // Drop never since that's assumed default.
|
||||||
@@ -524,6 +565,17 @@ namespace Content.Shared.Preferences
|
|||||||
_ => false
|
_ => false
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
var hasHighPrio = false;
|
||||||
|
foreach (var (key, value) in priorities)
|
||||||
|
{
|
||||||
|
if (value != JobPriority.High)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
if (hasHighPrio)
|
||||||
|
priorities[key] = JobPriority.Medium;
|
||||||
|
hasHighPrio = true;
|
||||||
|
}
|
||||||
|
|
||||||
var antags = AntagPreferences
|
var antags = AntagPreferences
|
||||||
.Where(id => prototypeManager.TryIndex<AntagPrototype>(id, out var antag) && antag.SetPreference)
|
.Where(id => prototypeManager.TryIndex<AntagPrototype>(id, out var antag) && antag.SetPreference)
|
||||||
.ToList();
|
.ToList();
|
||||||
|
|||||||
@@ -63,8 +63,8 @@ namespace Content.Shared.Roles
|
|||||||
public bool CanBeAntag { get; private set; } = true;
|
public bool CanBeAntag { get; private set; } = true;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Whether this job is a head.
|
/// The "weight" or importance of this job. If this number is large, the job system will assign this job
|
||||||
/// The job system will try to pick heads before other jobs on the same priority level.
|
/// before assigning other jobs.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[DataField("weight")]
|
[DataField("weight")]
|
||||||
public int Weight { get; private set; }
|
public int Weight { get; private set; }
|
||||||
|
|||||||
@@ -118,6 +118,18 @@ public abstract class SharedJobSystem : EntitySystem
|
|||||||
_prototypes.TryIndex(comp.Prototype, out prototype);
|
_prototypes.TryIndex(comp.Prototype, out prototype);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public bool MindTryGetJobId([NotNullWhen(true)] EntityUid? mindId, out ProtoId<JobPrototype>? job)
|
||||||
|
{
|
||||||
|
if (!TryComp(mindId, out JobComponent? comp))
|
||||||
|
{
|
||||||
|
job = null;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
job = comp.Prototype;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Tries to get the job name for this mind.
|
/// Tries to get the job name for this mind.
|
||||||
/// Returns unknown if not found.
|
/// Returns unknown if not found.
|
||||||
|
|||||||
@@ -10,7 +10,5 @@
|
|||||||
- type: StationNameSetup
|
- type: StationNameSetup
|
||||||
mapNameTemplate: "Meteor Arena"
|
mapNameTemplate: "Meteor Arena"
|
||||||
- type: StationJobs
|
- type: StationJobs
|
||||||
overflowJobs:
|
|
||||||
- Passenger
|
|
||||||
availableJobs:
|
availableJobs:
|
||||||
Passenger: [ -1, -1 ]
|
Passenger: [ -1, -1 ]
|
||||||
|
|||||||
@@ -14,8 +14,6 @@
|
|||||||
!type:NanotrasenNameGenerator
|
!type:NanotrasenNameGenerator
|
||||||
prefixCreator: 'R4' # R4407/Goon. GS isn't as cool sounding.
|
prefixCreator: 'R4' # R4407/Goon. GS isn't as cool sounding.
|
||||||
- type: StationJobs
|
- type: StationJobs
|
||||||
overflowJobs:
|
|
||||||
- Passenger
|
|
||||||
availableJobs:
|
availableJobs:
|
||||||
#service
|
#service
|
||||||
Captain: [ 1, 1 ]
|
Captain: [ 1, 1 ]
|
||||||
|
|||||||
@@ -16,8 +16,6 @@
|
|||||||
- type: StationEmergencyShuttle
|
- type: StationEmergencyShuttle
|
||||||
emergencyShuttlePath: /Maps/Shuttles/emergency_lox.yml
|
emergencyShuttlePath: /Maps/Shuttles/emergency_lox.yml
|
||||||
- type: StationJobs
|
- type: StationJobs
|
||||||
overflowJobs:
|
|
||||||
- Passenger
|
|
||||||
availableJobs:
|
availableJobs:
|
||||||
#service
|
#service
|
||||||
Captain: [ 1, 1 ]
|
Captain: [ 1, 1 ]
|
||||||
|
|||||||
@@ -15,8 +15,6 @@
|
|||||||
- type: StationEmergencyShuttle
|
- type: StationEmergencyShuttle
|
||||||
emergencyShuttlePath: /Maps/Shuttles/emergency_box.yml
|
emergencyShuttlePath: /Maps/Shuttles/emergency_box.yml
|
||||||
- type: StationJobs
|
- type: StationJobs
|
||||||
overflowJobs:
|
|
||||||
- Passenger
|
|
||||||
availableJobs:
|
availableJobs:
|
||||||
#service
|
#service
|
||||||
Captain: [ 1, 1 ]
|
Captain: [ 1, 1 ]
|
||||||
|
|||||||
@@ -16,8 +16,6 @@
|
|||||||
!type:NanotrasenNameGenerator
|
!type:NanotrasenNameGenerator
|
||||||
prefixCreator: '14'
|
prefixCreator: '14'
|
||||||
- type: StationJobs
|
- type: StationJobs
|
||||||
overflowJobs:
|
|
||||||
- Passenger
|
|
||||||
availableJobs:
|
availableJobs:
|
||||||
#service
|
#service
|
||||||
Captain: [ 1, 1 ]
|
Captain: [ 1, 1 ]
|
||||||
|
|||||||
@@ -18,8 +18,6 @@
|
|||||||
- type: StationCargoShuttle
|
- type: StationCargoShuttle
|
||||||
path: /Maps/Shuttles/cargo_core.yml
|
path: /Maps/Shuttles/cargo_core.yml
|
||||||
- type: StationJobs
|
- type: StationJobs
|
||||||
overflowJobs:
|
|
||||||
- Passenger
|
|
||||||
availableJobs:
|
availableJobs:
|
||||||
#service
|
#service
|
||||||
Bartender: [ 2, 2 ]
|
Bartender: [ 2, 2 ]
|
||||||
|
|||||||
@@ -10,8 +10,6 @@
|
|||||||
- type: StationNameSetup
|
- type: StationNameSetup
|
||||||
mapNameTemplate: "Empty"
|
mapNameTemplate: "Empty"
|
||||||
- type: StationJobs
|
- type: StationJobs
|
||||||
overflowJobs:
|
|
||||||
- Passenger
|
|
||||||
availableJobs:
|
availableJobs:
|
||||||
Passenger: [ -1, -1 ]
|
Passenger: [ -1, -1 ]
|
||||||
|
|
||||||
@@ -27,8 +25,6 @@
|
|||||||
- type: StationNameSetup
|
- type: StationNameSetup
|
||||||
mapNameTemplate: "Dev"
|
mapNameTemplate: "Dev"
|
||||||
- type: StationJobs
|
- type: StationJobs
|
||||||
overflowJobs:
|
|
||||||
- Captain
|
|
||||||
availableJobs:
|
availableJobs:
|
||||||
Captain: [ -1, -1 ]
|
Captain: [ -1, -1 ]
|
||||||
|
|
||||||
@@ -44,7 +40,5 @@
|
|||||||
- type: StationNameSetup
|
- type: StationNameSetup
|
||||||
mapNameTemplate: "TEG"
|
mapNameTemplate: "TEG"
|
||||||
- type: StationJobs
|
- type: StationJobs
|
||||||
overflowJobs:
|
|
||||||
- ChiefEngineer
|
|
||||||
availableJobs:
|
availableJobs:
|
||||||
ChiefEngineer: [ -1, -1 ]
|
ChiefEngineer: [ -1, -1 ]
|
||||||
|
|||||||
@@ -21,8 +21,6 @@
|
|||||||
- type: StationEmergencyShuttle
|
- type: StationEmergencyShuttle
|
||||||
emergencyShuttlePath: /Maps/Shuttles/emergency_transit.yml
|
emergencyShuttlePath: /Maps/Shuttles/emergency_transit.yml
|
||||||
- type: StationJobs
|
- type: StationJobs
|
||||||
overflowJobs:
|
|
||||||
- Passenger
|
|
||||||
availableJobs:
|
availableJobs:
|
||||||
#service
|
#service
|
||||||
Bartender: [ 1, 1 ]
|
Bartender: [ 1, 1 ]
|
||||||
|
|||||||
@@ -17,8 +17,6 @@
|
|||||||
- type: StationCargoShuttle
|
- type: StationCargoShuttle
|
||||||
path: /Maps/Shuttles/cargo_fland.yml
|
path: /Maps/Shuttles/cargo_fland.yml
|
||||||
- type: StationJobs
|
- type: StationJobs
|
||||||
overflowJobs:
|
|
||||||
- Passenger
|
|
||||||
availableJobs:
|
availableJobs:
|
||||||
#service
|
#service
|
||||||
Captain: [ 1, 1 ]
|
Captain: [ 1, 1 ]
|
||||||
|
|||||||
@@ -16,8 +16,6 @@
|
|||||||
- type: StationEmergencyShuttle
|
- type: StationEmergencyShuttle
|
||||||
emergencyShuttlePath: /Maps/Shuttles/emergency_rod.yml
|
emergencyShuttlePath: /Maps/Shuttles/emergency_rod.yml
|
||||||
- type: StationJobs
|
- type: StationJobs
|
||||||
overflowJobs:
|
|
||||||
- Passenger
|
|
||||||
availableJobs:
|
availableJobs:
|
||||||
#service
|
#service
|
||||||
Captain: [ 1, 1 ]
|
Captain: [ 1, 1 ]
|
||||||
|
|||||||
@@ -15,8 +15,6 @@
|
|||||||
- type: StationEmergencyShuttle
|
- type: StationEmergencyShuttle
|
||||||
emergencyShuttlePath: /Maps/Shuttles/emergency_meta.yml
|
emergencyShuttlePath: /Maps/Shuttles/emergency_meta.yml
|
||||||
- type: StationJobs
|
- type: StationJobs
|
||||||
overflowJobs:
|
|
||||||
- Passenger
|
|
||||||
availableJobs:
|
availableJobs:
|
||||||
#service
|
#service
|
||||||
Captain: [ 1, 1 ]
|
Captain: [ 1, 1 ]
|
||||||
|
|||||||
@@ -15,8 +15,6 @@
|
|||||||
- type: StationEmergencyShuttle
|
- type: StationEmergencyShuttle
|
||||||
emergencyShuttlePath: /Maps/Shuttles/emergency_delta.yml
|
emergencyShuttlePath: /Maps/Shuttles/emergency_delta.yml
|
||||||
- type: StationJobs
|
- type: StationJobs
|
||||||
overflowJobs:
|
|
||||||
- Passenger
|
|
||||||
availableJobs:
|
availableJobs:
|
||||||
#service
|
#service
|
||||||
Captain: [ 1, 1 ]
|
Captain: [ 1, 1 ]
|
||||||
|
|||||||
@@ -16,8 +16,6 @@
|
|||||||
- type: StationEmergencyShuttle
|
- type: StationEmergencyShuttle
|
||||||
emergencyShuttlePath: /Maps/Shuttles/emergency_omega.yml
|
emergencyShuttlePath: /Maps/Shuttles/emergency_omega.yml
|
||||||
- type: StationJobs
|
- type: StationJobs
|
||||||
overflowJobs:
|
|
||||||
- Passenger
|
|
||||||
availableJobs:
|
availableJobs:
|
||||||
#service
|
#service
|
||||||
Captain: [ 1, 1 ]
|
Captain: [ 1, 1 ]
|
||||||
|
|||||||
@@ -15,8 +15,6 @@
|
|||||||
- type: StationEmergencyShuttle
|
- type: StationEmergencyShuttle
|
||||||
emergencyShuttlePath: /Maps/Shuttles/emergency_courser.yml
|
emergencyShuttlePath: /Maps/Shuttles/emergency_courser.yml
|
||||||
- type: StationJobs
|
- type: StationJobs
|
||||||
overflowJobs:
|
|
||||||
- Passenger
|
|
||||||
availableJobs:
|
availableJobs:
|
||||||
#service
|
#service
|
||||||
Captain: [ 1, 1 ]
|
Captain: [ 1, 1 ]
|
||||||
|
|||||||
@@ -14,8 +14,6 @@
|
|||||||
!type:NanotrasenNameGenerator
|
!type:NanotrasenNameGenerator
|
||||||
prefixCreator: 'VG'
|
prefixCreator: 'VG'
|
||||||
- type: StationJobs
|
- type: StationJobs
|
||||||
overflowJobs:
|
|
||||||
- Passenger
|
|
||||||
availableJobs:
|
availableJobs:
|
||||||
#service
|
#service
|
||||||
Captain: [ 1, 1 ]
|
Captain: [ 1, 1 ]
|
||||||
|
|||||||
@@ -16,8 +16,6 @@
|
|||||||
- type: StationEmergencyShuttle
|
- type: StationEmergencyShuttle
|
||||||
emergencyShuttlePath: /Maps/Shuttles/emergency.yml
|
emergencyShuttlePath: /Maps/Shuttles/emergency.yml
|
||||||
- type: StationJobs
|
- type: StationJobs
|
||||||
overflowJobs:
|
|
||||||
- Passenger
|
|
||||||
availableJobs:
|
availableJobs:
|
||||||
Captain: [ 1, 1 ]
|
Captain: [ 1, 1 ]
|
||||||
HeadOfSecurity: [ 1, 1 ]
|
HeadOfSecurity: [ 1, 1 ]
|
||||||
|
|||||||
@@ -15,8 +15,6 @@
|
|||||||
!type:NanotrasenNameGenerator
|
!type:NanotrasenNameGenerator
|
||||||
prefixCreator: '14'
|
prefixCreator: '14'
|
||||||
- type: StationJobs
|
- type: StationJobs
|
||||||
overflowJobs:
|
|
||||||
- Passenger
|
|
||||||
availableJobs:
|
availableJobs:
|
||||||
#service
|
#service
|
||||||
Captain: [ 1, 1 ]
|
Captain: [ 1, 1 ]
|
||||||
|
|||||||
@@ -18,8 +18,6 @@
|
|||||||
- type: StationEmergencyShuttle
|
- type: StationEmergencyShuttle
|
||||||
emergencyShuttlePath: /Maps/Shuttles/emergency_omega.yml # To do - add railway station
|
emergencyShuttlePath: /Maps/Shuttles/emergency_omega.yml # To do - add railway station
|
||||||
- type: StationJobs
|
- type: StationJobs
|
||||||
overflowJobs:
|
|
||||||
- Passenger
|
|
||||||
availableJobs:
|
availableJobs:
|
||||||
#service
|
#service
|
||||||
Captain: [ 1, 1 ]
|
Captain: [ 1, 1 ]
|
||||||
|
|||||||
Reference in New Issue
Block a user