Refactor how jobs are handed out (#5422)
* Completely refactor how job spawning works * Remove remains of old system. * Squash the final bug, cleanup. * Attempt to fix tests * Adjusts packed's round-start crew roster, re-enables a bunch of old roles. Also adds the Central Command Official as a proper role. * pretty up ui * refactor StationSystem into the correct folder & namespace. * remove a log, make sure the lobby gets updated if a new map is spontaneously added. * re-add accidentally removed log * We do a little logging * we do a little resolving * we do a little documenting * Renamed OverflowJob to FallbackOverflowJob Allows stations to configure their own roundstart overflow job list. * narrator: it did not compile * oops * support having no overflow jobs * filescope for consistency * small fixes * Bumps a few role counts for Packed, namely engis * log moment * E * Update Resources/Prototypes/Entities/Objects/Misc/identification_cards.yml Co-authored-by: Leon Friedrich <60421075+ElectroJr@users.noreply.github.com> * Update Content.Server/Maps/GameMapPrototype.cs Co-authored-by: Leon Friedrich <60421075+ElectroJr@users.noreply.github.com> * factored job logic, cleanup. * e * Address reviews * Remove the concept of a "default" grid. It has no future in our new multi-station world * why was clickable using that in the first place * fix bad evil bug that almost slipped through also adds chemist * rms obsolete things from chemist * Adds a sanity fallback * address reviews * adds ability to set name * fuck * cleanup joingame
This commit is contained in:
@@ -1,4 +1,3 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
@@ -6,6 +5,7 @@ using System.Linq;
|
||||
using Content.Shared.GameTicking;
|
||||
using Content.Shared.Preferences;
|
||||
using Content.Shared.Roles;
|
||||
using Content.Shared.Station;
|
||||
using Robust.Server.Player;
|
||||
using Robust.Shared.Localization;
|
||||
using Robust.Shared.Network;
|
||||
@@ -25,131 +25,90 @@ namespace Content.Server.GameTicking
|
||||
[ViewVariables]
|
||||
private readonly Dictionary<string, int> _spawnedPositions = new();
|
||||
|
||||
private Dictionary<IPlayerSession, string> AssignJobs(List<IPlayerSession> available,
|
||||
private Dictionary<IPlayerSession, (string, StationId)> AssignJobs(List<IPlayerSession> available,
|
||||
Dictionary<NetUserId, HumanoidCharacterProfile> profiles)
|
||||
{
|
||||
// Calculate positions available round-start for each job.
|
||||
var availablePositions = GetBasePositions(true);
|
||||
var assigned = new Dictionary<IPlayerSession, (string, StationId)>();
|
||||
|
||||
// Output dictionary of assigned jobs.
|
||||
var assigned = new Dictionary<IPlayerSession, string>();
|
||||
|
||||
// Go over each priority level top to bottom.
|
||||
for (var i = JobPriority.High; i > JobPriority.Never; i--)
|
||||
List<(IPlayerSession, List<string>)> GetPlayersJobCandidates(bool heads, JobPriority i)
|
||||
{
|
||||
void ProcessJobs(bool heads)
|
||||
{
|
||||
// Get all candidates for this priority & heads combo.
|
||||
// That is all people with at LEAST one job at this priority & heads level,
|
||||
// and the jobs they have selected here.
|
||||
var candidates = available
|
||||
.Select(player =>
|
||||
{
|
||||
var profile = profiles[player.UserId];
|
||||
|
||||
var availableJobs = profile.JobPriorities
|
||||
.Where(j =>
|
||||
{
|
||||
var (jobId, priority) = j;
|
||||
if (!_prototypeManager.TryIndex(jobId, out JobPrototype? job))
|
||||
{
|
||||
// Job doesn't exist, probably old data?
|
||||
return false;
|
||||
}
|
||||
if (job.IsHead != heads)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return priority == i;
|
||||
})
|
||||
.Select(j => j.Key)
|
||||
.ToList();
|
||||
|
||||
return (player, availableJobs);
|
||||
})
|
||||
.Where(p => p.availableJobs.Count != 0)
|
||||
.ToList();
|
||||
|
||||
_robustRandom.Shuffle(candidates);
|
||||
|
||||
foreach (var (candidate, jobs) in candidates)
|
||||
return available.Select(player =>
|
||||
{
|
||||
while (jobs.Count != 0)
|
||||
{
|
||||
var picked = _robustRandom.Pick(jobs);
|
||||
var profile = profiles[player.UserId];
|
||||
|
||||
var openPositions = availablePositions.GetValueOrDefault(picked, 0);
|
||||
if (openPositions == 0)
|
||||
var availableJobs = profile.JobPriorities
|
||||
.Where(j =>
|
||||
{
|
||||
jobs.Remove(picked);
|
||||
continue;
|
||||
}
|
||||
var (jobId, priority) = j;
|
||||
if (!_prototypeManager.TryIndex(jobId, out JobPrototype? job))
|
||||
{
|
||||
// Job doesn't exist, probably old data?
|
||||
return false;
|
||||
}
|
||||
|
||||
availablePositions[picked] -= 1;
|
||||
assigned.Add(candidate, picked);
|
||||
break;
|
||||
if (job.IsHead != heads)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return priority == i;
|
||||
})
|
||||
.Select(j => j.Key)
|
||||
.ToList();
|
||||
|
||||
return (player, availableJobs);
|
||||
})
|
||||
.Where(p => p.availableJobs.Count != 0)
|
||||
.ToList();
|
||||
}
|
||||
|
||||
void ProcessJobs(bool heads, Dictionary<string, int> availablePositions, StationId id, JobPriority i)
|
||||
{
|
||||
var candidates = GetPlayersJobCandidates(heads, i);
|
||||
|
||||
foreach (var (candidate, jobs) in candidates)
|
||||
{
|
||||
while (jobs.Count != 0)
|
||||
{
|
||||
var picked = _robustRandom.Pick(jobs);
|
||||
|
||||
var openPositions = availablePositions.GetValueOrDefault(picked, 0);
|
||||
if (openPositions == 0)
|
||||
{
|
||||
jobs.Remove(picked);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
available.RemoveAll(a => assigned.ContainsKey(a));
|
||||
availablePositions[picked] -= 1;
|
||||
assigned.Add(candidate, (picked, id));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Process heads FIRST.
|
||||
// This means that if you have head and non-head roles on the same priority level,
|
||||
// you will always get picked as head.
|
||||
// Unless of course somebody beats you to those head roles.
|
||||
ProcessJobs(true);
|
||||
ProcessJobs(false);
|
||||
available.RemoveAll(a => assigned.ContainsKey(a));
|
||||
}
|
||||
|
||||
// Current strategy is to fill each station one by one.
|
||||
foreach (var (id, station) in _stationSystem.StationInfo)
|
||||
{
|
||||
// Get the ROUND-START job list.
|
||||
var availablePositions = station.MapPrototype.AvailableJobs.ToDictionary(x => x.Key, x => x.Value[0]);
|
||||
|
||||
for (var i = JobPriority.High; i > JobPriority.Never; i--)
|
||||
{
|
||||
// Process jobs possible for heads...
|
||||
ProcessJobs(true, availablePositions, id, i);
|
||||
// and then jobs that are not heads.
|
||||
ProcessJobs(false, availablePositions, id, i);
|
||||
}
|
||||
}
|
||||
|
||||
return assigned;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the available positions for all jobs, *not* accounting for the current crew manifest.
|
||||
/// </summary>
|
||||
private Dictionary<string, int> GetBasePositions(bool roundStart)
|
||||
private string PickBestAvailableJob(HumanoidCharacterProfile profile, StationId station)
|
||||
{
|
||||
var availablePositions = _prototypeManager
|
||||
.EnumeratePrototypes<JobPrototype>()
|
||||
// -1 is treated as infinite slots.
|
||||
.ToDictionary(job => job.ID, job =>
|
||||
{
|
||||
if (job.SpawnPositions < 0)
|
||||
{
|
||||
return int.MaxValue;
|
||||
}
|
||||
|
||||
if (roundStart)
|
||||
{
|
||||
return job.SpawnPositions;
|
||||
}
|
||||
|
||||
return job.TotalPositions;
|
||||
});
|
||||
|
||||
return availablePositions;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the remaining available job positions in the current round.
|
||||
/// </summary>
|
||||
public Dictionary<string, int> GetAvailablePositions()
|
||||
{
|
||||
var basePositions = GetBasePositions(false);
|
||||
|
||||
foreach (var (jobId, count) in _spawnedPositions)
|
||||
{
|
||||
basePositions[jobId] = Math.Max(0, basePositions[jobId] - count);
|
||||
}
|
||||
|
||||
return basePositions;
|
||||
}
|
||||
|
||||
private string PickBestAvailableJob(HumanoidCharacterProfile profile)
|
||||
{
|
||||
var available = GetAvailablePositions();
|
||||
var available = _stationSystem.StationInfo[station].JobList;
|
||||
|
||||
bool TryPick(JobPriority priority, [NotNullWhen(true)] out string? jobId)
|
||||
{
|
||||
@@ -188,18 +147,17 @@ namespace Content.Server.GameTicking
|
||||
return picked;
|
||||
}
|
||||
|
||||
return OverflowJob;
|
||||
var overflows = _stationSystem.StationInfo[station].MapPrototype.OverflowJobs.Clone().ToList();
|
||||
return _robustRandom.Pick(overflows);
|
||||
}
|
||||
|
||||
[Conditional("DEBUG")]
|
||||
private void InitializeJobController()
|
||||
{
|
||||
// Verify that the overflow role exists and has the correct name.
|
||||
var role = _prototypeManager.Index<JobPrototype>(OverflowJob);
|
||||
DebugTools.Assert(role.Name == Loc.GetString(OverflowJobName),
|
||||
var role = _prototypeManager.Index<JobPrototype>(FallbackOverflowJob);
|
||||
DebugTools.Assert(role.Name == Loc.GetString(FallbackOverflowJobName),
|
||||
"Overflow role does not have the correct name!");
|
||||
|
||||
DebugTools.Assert(role.SpawnPositions < 0, "Overflow role must have infinite spawn positions!");
|
||||
}
|
||||
|
||||
private void AddSpawnedPosition(string jobId)
|
||||
@@ -211,17 +169,21 @@ namespace Content.Server.GameTicking
|
||||
{
|
||||
// If late join is disallowed, return no available jobs.
|
||||
if (DisallowLateJoin)
|
||||
return new TickerJobsAvailableEvent(Array.Empty<string>());
|
||||
return new TickerJobsAvailableEvent(new Dictionary<StationId, string>(), new Dictionary<StationId, Dictionary<string, int>>());
|
||||
|
||||
var jobs = GetAvailablePositions()
|
||||
.Where(e => e.Value > 0)
|
||||
.Select(e => e.Key)
|
||||
.ToArray();
|
||||
var jobs = new Dictionary<StationId, Dictionary<string, int>>();
|
||||
var stationNames = new Dictionary<StationId, string>();
|
||||
|
||||
return new TickerJobsAvailableEvent(jobs);
|
||||
foreach (var (id, station) in _stationSystem.StationInfo)
|
||||
{
|
||||
var list = station.JobList.ToDictionary(x => x.Key, x => x.Value);
|
||||
jobs.Add(id, list);
|
||||
stationNames.Add(id, station.Name);
|
||||
}
|
||||
return new TickerJobsAvailableEvent(stationNames, jobs);
|
||||
}
|
||||
|
||||
private void UpdateJobsAvailable()
|
||||
public void UpdateJobsAvailable()
|
||||
{
|
||||
RaiseNetworkEvent(GetJobsAvailable(), Filter.Empty().AddPlayers(_playersInLobby.Keys));
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user