using System.Diagnostics; using System.Diagnostics.CodeAnalysis; 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.Network; using Robust.Shared.Player; using Robust.Shared.Random; using Robust.Shared.Utility; namespace Content.Server.GameTicking { // This code is responsible for the assigning & picking of jobs. public sealed partial class GameTicker { [ViewVariables] private readonly List _manifest = new(); [ViewVariables] private readonly Dictionary _spawnedPositions = new(); private Dictionary AssignJobs(List availablePlayers, Dictionary profiles) { var assigned = new Dictionary(); List<(IPlayerSession, List)> GetPlayersJobCandidates(bool heads, JobPriority i) { return availablePlayers.Select(player => { var profile = profiles[player.UserId]; var roleBans = _roleBanManager.GetJobBans(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; }) .Where(p => roleBans != null && !roleBans.Contains(p.Key)) .Select(j => j.Key) .ToList(); return (player, availableJobs); }) .Where(p => p.availableJobs.Count != 0) .ToList(); } void ProcessJobs(bool heads, Dictionary 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; } availablePositions[picked] -= 1; assigned.Add(candidate, (picked, id)); break; } } availablePlayers.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; } private string? PickBestAvailableJob(IPlayerSession playerSession, HumanoidCharacterProfile profile, StationId station) { if (station == StationId.Invalid) return null; var available = _stationSystem.StationInfo[station].JobList; bool TryPick(JobPriority priority, [NotNullWhen(true)] out string? jobId) { var roleBans = _roleBanManager.GetJobBans(playerSession.UserId); var filtered = profile.JobPriorities .Where(p => p.Value == priority) .Where(p => roleBans != null && !roleBans.Contains(p.Key)) .Select(p => p.Key) .ToList(); while (filtered.Count != 0) { jobId = _robustRandom.Pick(filtered); if (available.GetValueOrDefault(jobId, 0) > 0) { return true; } filtered.Remove(jobId); } jobId = default; return false; } if (TryPick(JobPriority.High, out var picked)) { return picked; } if (TryPick(JobPriority.Medium, out picked)) { return picked; } if (TryPick(JobPriority.Low, out picked)) { return picked; } var overflows = _stationSystem.StationInfo[station].MapPrototype.OverflowJobs.Clone().ToList(); return overflows.Count != 0 ? _robustRandom.Pick(overflows) : null; } [Conditional("DEBUG")] private void InitializeJobController() { // Verify that the overflow role exists and has the correct name. var role = _prototypeManager.Index(FallbackOverflowJob); DebugTools.Assert(role.Name == Loc.GetString(FallbackOverflowJobName), "Overflow role does not have the correct name!"); } private void AddSpawnedPosition(string jobId) { _spawnedPositions[jobId] = _spawnedPositions.GetValueOrDefault(jobId, 0) + 1; } private TickerJobsAvailableEvent GetJobsAvailable() { // If late join is disallowed, return no available jobs. if (DisallowLateJoin) return new TickerJobsAvailableEvent(new Dictionary(), new Dictionary>()); var jobs = new Dictionary>(); var stationNames = new Dictionary(); 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); } public void UpdateJobsAvailable() { RaiseNetworkEvent(GetJobsAvailable(), Filter.Empty().AddPlayers(_playersInLobby.Keys)); } } }