StationJobsSystem.cs 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527
  1. using System.Diagnostics.CodeAnalysis;
  2. using System.Linq;
  3. using Content.Server.GameTicking;
  4. using Content.Server.Station.Components;
  5. using Content.Shared.CCVar;
  6. using Content.Shared.FixedPoint;
  7. using Content.Shared.GameTicking;
  8. using Content.Shared.Preferences;
  9. using Content.Shared.Roles;
  10. using JetBrains.Annotations;
  11. using Robust.Server.Player;
  12. using Robust.Shared.Configuration;
  13. using Robust.Shared.Network;
  14. using Robust.Shared.Player;
  15. using Robust.Shared.Prototypes;
  16. using Robust.Shared.Random;
  17. namespace Content.Server.Station.Systems;
  18. /// <summary>
  19. /// Manages job slots for stations.
  20. /// </summary>
  21. [PublicAPI]
  22. public sealed partial class StationJobsSystem : EntitySystem
  23. {
  24. [Dependency] private readonly IConfigurationManager _configurationManager = default!;
  25. [Dependency] private readonly IPlayerManager _player = default!;
  26. [Dependency] private readonly IRobustRandom _random = default!;
  27. [Dependency] private readonly GameTicker _gameTicker = default!;
  28. /// <inheritdoc/>
  29. public override void Initialize()
  30. {
  31. SubscribeLocalEvent<StationInitializedEvent>(OnStationInitialized);
  32. SubscribeLocalEvent<StationJobsComponent, ComponentInit>(OnInit);
  33. SubscribeLocalEvent<StationJobsComponent, StationRenamedEvent>(OnStationRenamed);
  34. SubscribeLocalEvent<StationJobsComponent, ComponentShutdown>(OnStationDeletion);
  35. SubscribeLocalEvent<PlayerJoinedLobbyEvent>(OnPlayerJoinedLobby);
  36. Subs.CVar(_configurationManager, CCVars.GameDisallowLateJoins, _ => UpdateJobsAvailable(), true);
  37. }
  38. private void OnInit(Entity<StationJobsComponent> ent, ref ComponentInit args)
  39. {
  40. ent.Comp.MidRoundTotalJobs = ent.Comp.SetupAvailableJobs.Values
  41. .Select(x => Math.Max(x[1], 0))
  42. .Sum();
  43. ent.Comp.OverflowJobs = ent.Comp.SetupAvailableJobs
  44. .Where(x => x.Value[0] < 0)
  45. .Select(x => x.Key)
  46. .ToHashSet();
  47. }
  48. public override void Update(float _)
  49. {
  50. if (_availableJobsDirty)
  51. {
  52. _cachedAvailableJobs = GenerateJobsAvailableEvent();
  53. RaiseNetworkEvent(_cachedAvailableJobs, Filter.Empty().AddPlayers(_player.Sessions));
  54. _availableJobsDirty = false;
  55. }
  56. }
  57. private void OnStationDeletion(EntityUid uid, StationJobsComponent component, ComponentShutdown args)
  58. {
  59. UpdateJobsAvailable(); // we no longer exist so the jobs list is changed.
  60. }
  61. private void OnStationInitialized(StationInitializedEvent msg)
  62. {
  63. if (!TryComp<StationJobsComponent>(msg.Station, out var stationJobs))
  64. return;
  65. stationJobs.JobList = stationJobs.SetupAvailableJobs.ToDictionary(
  66. x => x.Key,
  67. x=> (int?)(x.Value[1] < 0 ? null : x.Value[1]));
  68. stationJobs.TotalJobs = stationJobs.JobList.Values.Select(x => x ?? 0).Sum();
  69. UpdateJobsAvailable();
  70. }
  71. #region Public API
  72. /// <inheritdoc cref="TryAssignJob(Robust.Shared.GameObjects.EntityUid,string,NetUserId,Content.Server.Station.Components.StationJobsComponent?)"/>
  73. /// <param name="station">Station to assign a job on.</param>
  74. /// <param name="job">Job to assign.</param>
  75. /// <param name="netUserId">The net user ID of the player we're assigning this job to.</param>
  76. /// <param name="stationJobs">Resolve pattern, station jobs component of the station.</param>
  77. public bool TryAssignJob(EntityUid station, JobPrototype job, NetUserId netUserId, StationJobsComponent? stationJobs = null)
  78. {
  79. return TryAssignJob(station, job.ID, netUserId, stationJobs);
  80. }
  81. /// <summary>
  82. /// Attempts to assign the given job once. (essentially, it decrements the slot if possible).
  83. /// </summary>
  84. /// <param name="station">Station to assign a job on.</param>
  85. /// <param name="jobPrototypeId">Job prototype ID to assign.</param>
  86. /// <param name="netUserId">The net user ID of the player we're assigning this job to.</param>
  87. /// <param name="stationJobs">Resolve pattern, station jobs component of the station.</param>
  88. /// <returns>Whether or not assignment was a success.</returns>
  89. /// <exception cref="ArgumentException">Thrown when the given station is not a station.</exception>
  90. public bool TryAssignJob(EntityUid station, string jobPrototypeId, NetUserId netUserId, StationJobsComponent? stationJobs = null)
  91. {
  92. if (!Resolve(station, ref stationJobs, false))
  93. return false;
  94. if (!TryAdjustJobSlot(station, jobPrototypeId, -1, false, false, stationJobs))
  95. return false;
  96. stationJobs.PlayerJobs.TryAdd(netUserId, new());
  97. stationJobs.PlayerJobs[netUserId].Add(jobPrototypeId);
  98. return true;
  99. }
  100. /// <inheritdoc cref="TryAdjustJobSlot(Robust.Shared.GameObjects.EntityUid,string,int,bool,bool,Content.Server.Station.Components.StationJobsComponent?)"/>
  101. /// <param name="station">Station to adjust the job slot on.</param>
  102. /// <param name="job">Job to adjust.</param>
  103. /// <param name="amount">Amount to adjust by.</param>
  104. /// <param name="createSlot">Whether or not it should create the slot if it doesn't exist.</param>
  105. /// <param name="clamp">Whether or not to clamp to zero if you'd remove more jobs than are available.</param>
  106. /// <param name="stationJobs">Resolve pattern, station jobs component of the station.</param>
  107. public bool TryAdjustJobSlot(EntityUid station, JobPrototype job, int amount, bool createSlot = false, bool clamp = false,
  108. StationJobsComponent? stationJobs = null)
  109. {
  110. return TryAdjustJobSlot(station, job.ID, amount, createSlot, clamp, stationJobs);
  111. }
  112. /// <summary>
  113. /// Attempts to adjust the given job slot by the amount provided.
  114. /// </summary>
  115. /// <param name="station">Station to adjust the job slot on.</param>
  116. /// <param name="jobPrototypeId">Job prototype ID to adjust.</param>
  117. /// <param name="amount">Amount to adjust by.</param>
  118. /// <param name="createSlot">Whether or not it should create the slot if it doesn't exist.</param>
  119. /// <param name="clamp">Whether or not to clamp to zero if you'd remove more jobs than are available.</param>
  120. /// <param name="stationJobs">Resolve pattern, station jobs component of the station.</param>
  121. /// <returns>Whether or not slot adjustment was a success.</returns>
  122. /// <exception cref="ArgumentException">Thrown when the given station is not a station.</exception>
  123. public bool TryAdjustJobSlot(EntityUid station,
  124. string jobPrototypeId,
  125. int amount,
  126. bool createSlot = false,
  127. bool clamp = false,
  128. StationJobsComponent? stationJobs = null)
  129. {
  130. if (!Resolve(station, ref stationJobs))
  131. throw new ArgumentException("Tried to use a non-station entity as a station!", nameof(station));
  132. var jobList = stationJobs.JobList;
  133. // This should:
  134. // - Return true when zero slots are added/removed.
  135. // - Return true when you add.
  136. // - Return true when you remove and do not exceed the number of slot available.
  137. // - Return false when you remove from a job that doesn't exist.
  138. // - Return false when you remove and exceed the number of slots available.
  139. // And additionally, if adding would add a job not previously on the manifest when createSlot is false, return false and do nothing.
  140. if (amount == 0)
  141. return true;
  142. switch (jobList.TryGetValue(jobPrototypeId, out var available))
  143. {
  144. case false when amount < 0:
  145. return false;
  146. case false:
  147. if (!createSlot)
  148. return false;
  149. stationJobs.TotalJobs += amount;
  150. jobList[jobPrototypeId] = amount;
  151. UpdateJobsAvailable();
  152. return true;
  153. case true:
  154. // Job is unlimited so just say we adjusted it and do nothing.
  155. if (available is not {} avail)
  156. return true;
  157. // Would remove more jobs than we have available.
  158. if (available + amount < 0 && !clamp)
  159. return false;
  160. jobList[jobPrototypeId] = Math.Max(avail + amount, 0);
  161. stationJobs.TotalJobs = jobList.Values.Select(x => x ?? 0).Sum();
  162. UpdateJobsAvailable();
  163. return true;
  164. }
  165. }
  166. public bool TryGetPlayerJobs(EntityUid station,
  167. NetUserId userId,
  168. [NotNullWhen(true)] out List<ProtoId<JobPrototype>>? jobs,
  169. StationJobsComponent? jobsComponent = null)
  170. {
  171. jobs = null;
  172. if (!Resolve(station, ref jobsComponent, false))
  173. return false;
  174. return jobsComponent.PlayerJobs.TryGetValue(userId, out jobs);
  175. }
  176. public bool TryRemovePlayerJobs(EntityUid station,
  177. NetUserId userId,
  178. StationJobsComponent? jobsComponent = null)
  179. {
  180. if (!Resolve(station, ref jobsComponent, false))
  181. return false;
  182. return jobsComponent.PlayerJobs.Remove(userId);
  183. }
  184. /// <inheritdoc cref="TrySetJobSlot(Robust.Shared.GameObjects.EntityUid,string,int,bool,Content.Server.Station.Components.StationJobsComponent?)"/>
  185. /// <param name="station">Station to adjust the job slot on.</param>
  186. /// <param name="jobPrototype">Job prototype to adjust.</param>
  187. /// <param name="amount">Amount to set to.</param>
  188. /// <param name="createSlot">Whether or not it should create the slot if it doesn't exist.</param>
  189. /// <param name="stationJobs">Resolve pattern, station jobs component of the station.</param>
  190. /// <returns></returns>
  191. public bool TrySetJobSlot(EntityUid station, JobPrototype jobPrototype, int amount, bool createSlot = false,
  192. StationJobsComponent? stationJobs = null)
  193. {
  194. return TrySetJobSlot(station, jobPrototype.ID, amount, createSlot, stationJobs);
  195. }
  196. /// <summary>
  197. /// Attempts to set the given job slot to the amount provided.
  198. /// </summary>
  199. /// <param name="station">Station to adjust the job slot on.</param>
  200. /// <param name="jobPrototypeId">Job prototype ID to adjust.</param>
  201. /// <param name="amount">Amount to set to.</param>
  202. /// <param name="createSlot">Whether or not it should create the slot if it doesn't exist.</param>
  203. /// <param name="stationJobs">Resolve pattern, station jobs component of the station.</param>
  204. /// <returns>Whether or not setting the value succeeded.</returns>
  205. /// <exception cref="ArgumentException">Thrown when the given station is not a station.</exception>
  206. public bool TrySetJobSlot(EntityUid station,
  207. string jobPrototypeId,
  208. int amount,
  209. bool createSlot = false,
  210. StationJobsComponent? stationJobs = null)
  211. {
  212. if (!Resolve(station, ref stationJobs))
  213. throw new ArgumentException("Tried to use a non-station entity as a station!", nameof(station));
  214. if (amount < 0)
  215. throw new ArgumentException("Tried to set a job to have a negative number of slots!", nameof(amount));
  216. var jobList = stationJobs.JobList;
  217. switch (jobList.ContainsKey(jobPrototypeId))
  218. {
  219. case false:
  220. if (!createSlot)
  221. return false;
  222. stationJobs.TotalJobs += amount;
  223. jobList[jobPrototypeId] = amount;
  224. UpdateJobsAvailable();
  225. return true;
  226. case true:
  227. stationJobs.TotalJobs += amount - (jobList[jobPrototypeId] ?? 0);
  228. jobList[jobPrototypeId] = amount;
  229. UpdateJobsAvailable();
  230. return true;
  231. }
  232. }
  233. /// <inheritdoc cref="MakeJobUnlimited(Robust.Shared.GameObjects.EntityUid,string,Content.Server.Station.Components.StationJobsComponent?)"/>
  234. /// <param name="station">Station to make a job unlimited on.</param>
  235. /// <param name="job">Job to make unlimited.</param>
  236. /// <param name="stationJobs">Resolve pattern, station jobs component of the station.</param>
  237. public void MakeJobUnlimited(EntityUid station, JobPrototype job, StationJobsComponent? stationJobs = null)
  238. {
  239. MakeJobUnlimited(station, job.ID, stationJobs);
  240. }
  241. /// <summary>
  242. /// Makes the given job have unlimited slots.
  243. /// </summary>
  244. /// <param name="station">Station to make a job unlimited on.</param>
  245. /// <param name="jobPrototypeId">Job prototype ID to make unlimited.</param>
  246. /// <param name="stationJobs">Resolve pattern, station jobs component of the station.</param>
  247. /// <exception cref="ArgumentException">Thrown when the given station is not a station.</exception>
  248. public void MakeJobUnlimited(EntityUid station, string jobPrototypeId, StationJobsComponent? stationJobs = null)
  249. {
  250. if (!Resolve(station, ref stationJobs))
  251. throw new ArgumentException("Tried to use a non-station entity as a station!", nameof(station));
  252. // Subtract out the job we're fixing to make have unlimited slots.
  253. if (stationJobs.JobList.TryGetValue(jobPrototypeId, out var existing))
  254. stationJobs.TotalJobs -= existing ?? 0;
  255. stationJobs.JobList[jobPrototypeId] = null;
  256. UpdateJobsAvailable();
  257. }
  258. /// <inheritdoc cref="IsJobUnlimited(Robust.Shared.GameObjects.EntityUid,string,Content.Server.Station.Components.StationJobsComponent?)"/>
  259. /// <param name="station">Station to check.</param>
  260. /// <param name="job">Job to check.</param>
  261. /// <param name="stationJobs">Resolve pattern, station jobs component of the station.</param>
  262. public bool IsJobUnlimited(EntityUid station, JobPrototype job, StationJobsComponent? stationJobs = null)
  263. {
  264. return IsJobUnlimited(station, job.ID, stationJobs);
  265. }
  266. /// <summary>
  267. /// Checks if the given job is unlimited.
  268. /// </summary>
  269. /// <param name="station">Station to check.</param>
  270. /// <param name="jobPrototypeId">Job prototype ID to check.</param>
  271. /// <param name="stationJobs">Resolve pattern, station jobs component of the station.</param>
  272. /// <returns>Returns if the given slot is unlimited.</returns>
  273. /// <exception cref="ArgumentException">Thrown when the given station is not a station.</exception>
  274. public bool IsJobUnlimited(EntityUid station, string jobPrototypeId, StationJobsComponent? stationJobs = null)
  275. {
  276. if (!Resolve(station, ref stationJobs))
  277. throw new ArgumentException("Tried to use a non-station entity as a station!", nameof(station));
  278. return stationJobs.JobList.TryGetValue(jobPrototypeId, out var job) && job == null;
  279. }
  280. /// <inheritdoc cref="TryGetJobSlot(Robust.Shared.GameObjects.EntityUid,string,out System.Nullable{uint},Content.Server.Station.Components.StationJobsComponent?)"/>
  281. /// <param name="station">Station to get slot info from.</param>
  282. /// <param name="job">Job to get slot info for.</param>
  283. /// <param name="slots">The number of slots remaining. Null if infinite.</param>
  284. /// <param name="stationJobs">Resolve pattern, station jobs component of the station.</param>
  285. public bool TryGetJobSlot(EntityUid station, JobPrototype job, out int? slots, StationJobsComponent? stationJobs = null)
  286. {
  287. return TryGetJobSlot(station, job.ID, out slots, stationJobs);
  288. }
  289. /// <summary>
  290. /// Returns information about the given job slot.
  291. /// </summary>
  292. /// <param name="station">Station to get slot info from.</param>
  293. /// <param name="jobPrototypeId">Job prototype ID to get slot info for.</param>
  294. /// <param name="slots">The number of slots remaining. Null if infinite.</param>
  295. /// <param name="stationJobs">Resolve pattern, station jobs component of the station.</param>
  296. /// <returns>Whether or not the slot exists.</returns>
  297. /// <exception cref="ArgumentException">Thrown when the given station is not a station.</exception>
  298. /// <remarks>slots will be null if the slot doesn't exist, as well, so make sure to check the return value.</remarks>
  299. public bool TryGetJobSlot(EntityUid station, string jobPrototypeId, out int? slots, StationJobsComponent? stationJobs = null)
  300. {
  301. if (!Resolve(station, ref stationJobs))
  302. throw new ArgumentException("Tried to use a non-station entity as a station!", nameof(station));
  303. return stationJobs.JobList.TryGetValue(jobPrototypeId, out slots);
  304. }
  305. /// <summary>
  306. /// Returns all jobs available on the station.
  307. /// </summary>
  308. /// <param name="station">Station to get jobs for</param>
  309. /// <param name="stationJobs">Resolve pattern, station jobs component of the station.</param>
  310. /// <returns>Set containing all jobs available.</returns>
  311. /// <exception cref="ArgumentException">Thrown when the given station is not a station.</exception>
  312. public IEnumerable<ProtoId<JobPrototype>> GetAvailableJobs(EntityUid station, StationJobsComponent? stationJobs = null)
  313. {
  314. if (!Resolve(station, ref stationJobs))
  315. throw new ArgumentException("Tried to use a non-station entity as a station!", nameof(station));
  316. return stationJobs.JobList
  317. .Where(x => x.Value != 0)
  318. .Select(x => x.Key);
  319. }
  320. /// <summary>
  321. /// Returns all overflow jobs available on the station.
  322. /// </summary>
  323. /// <param name="station">Station to get jobs for</param>
  324. /// <param name="stationJobs">Resolve pattern, station jobs component of the station.</param>
  325. /// <returns>Set containing all overflow jobs available.</returns>
  326. /// <exception cref="ArgumentException">Thrown when the given station is not a station.</exception>
  327. public IReadOnlySet<ProtoId<JobPrototype>> GetOverflowJobs(EntityUid station, StationJobsComponent? stationJobs = null)
  328. {
  329. if (!Resolve(station, ref stationJobs))
  330. throw new ArgumentException("Tried to use a non-station entity as a station!", nameof(station));
  331. return stationJobs.OverflowJobs;
  332. }
  333. /// <summary>
  334. /// Returns a readonly dictionary of all jobs and their slot info.
  335. /// </summary>
  336. /// <param name="station">Station to get jobs for</param>
  337. /// <param name="stationJobs">Resolve pattern, station jobs component of the station.</param>
  338. /// <returns>List of all jobs on the station.</returns>
  339. /// <exception cref="ArgumentException">Thrown when the given station is not a station.</exception>
  340. public IReadOnlyDictionary<ProtoId<JobPrototype>, int?> GetJobs(EntityUid station, StationJobsComponent? stationJobs = null)
  341. {
  342. if (!Resolve(station, ref stationJobs))
  343. throw new ArgumentException("Tried to use a non-station entity as a station!", nameof(station));
  344. return stationJobs.JobList;
  345. }
  346. /// <summary>
  347. /// Returns a readonly dictionary of all round-start jobs and their slot info.
  348. /// </summary>
  349. /// <param name="station">Station to get jobs for</param>
  350. /// <param name="stationJobs">Resolve pattern, station jobs component of the station.</param>
  351. /// <returns>List of all round-start jobs.</returns>
  352. /// <exception cref="ArgumentException">Thrown when the given station is not a station.</exception>
  353. public Dictionary<ProtoId<JobPrototype>, int?> GetRoundStartJobs(EntityUid station, StationJobsComponent? stationJobs = null)
  354. {
  355. if (!Resolve(station, ref stationJobs))
  356. throw new ArgumentException("Tried to use a non-station entity as a station!", nameof(station));
  357. return stationJobs.SetupAvailableJobs.ToDictionary(
  358. x => x.Key,
  359. x=> (int?)(x.Value[0] < 0 ? null : x.Value[0]));
  360. }
  361. /// <summary>
  362. /// Looks at the given priority list, and picks the best available job (optionally with the given exclusions)
  363. /// </summary>
  364. /// <param name="station">Station to pick from.</param>
  365. /// <param name="jobPriorities">The priority list to use for selecting a job.</param>
  366. /// <param name="pickOverflows">Whether or not to pick from the overflow list.</param>
  367. /// <param name="disallowedJobs">A set of disallowed jobs, if any.</param>
  368. /// <returns>The selected job, if any.</returns>
  369. public ProtoId<JobPrototype>? PickBestAvailableJobWithPriority(EntityUid station, IReadOnlyDictionary<ProtoId<JobPrototype>, JobPriority> jobPriorities, bool pickOverflows, IReadOnlySet<ProtoId<JobPrototype>>? disallowedJobs = null)
  370. {
  371. if (station == EntityUid.Invalid)
  372. return null;
  373. var available = GetAvailableJobs(station);
  374. bool TryPick(JobPriority priority, [NotNullWhen(true)] out ProtoId<JobPrototype>? jobId)
  375. {
  376. var filtered = jobPriorities
  377. .Where(p =>
  378. p.Value == priority
  379. && disallowedJobs != null
  380. && !disallowedJobs.Contains(p.Key)
  381. && available.Contains(p.Key))
  382. .Select(p => p.Key)
  383. .ToList();
  384. if (filtered.Count != 0)
  385. {
  386. jobId = _random.Pick(filtered);
  387. return true;
  388. }
  389. jobId = default;
  390. return false;
  391. }
  392. if (TryPick(JobPriority.High, out var picked))
  393. {
  394. return picked;
  395. }
  396. if (TryPick(JobPriority.Medium, out picked))
  397. {
  398. return picked;
  399. }
  400. if (TryPick(JobPriority.Low, out picked))
  401. {
  402. return picked;
  403. }
  404. if (!pickOverflows)
  405. return null;
  406. var overflows = GetOverflowJobs(station);
  407. if (overflows.Count == 0)
  408. return null;
  409. return _random.Pick(overflows);
  410. }
  411. #endregion Public API
  412. #region Latejoin job management
  413. private bool _availableJobsDirty;
  414. private TickerJobsAvailableEvent _cachedAvailableJobs = new(new(), new());
  415. /// <summary>
  416. /// Assembles an event from the current available-to-play jobs.
  417. /// This is moderately expensive to construct.
  418. /// </summary>
  419. /// <returns>The event.</returns>
  420. private TickerJobsAvailableEvent GenerateJobsAvailableEvent()
  421. {
  422. // If late join is disallowed, return no available jobs.
  423. if (_gameTicker.DisallowLateJoin)
  424. return new TickerJobsAvailableEvent(new(), new());
  425. var jobs = new Dictionary<NetEntity, Dictionary<ProtoId<JobPrototype>, int?>>();
  426. var stationNames = new Dictionary<NetEntity, string>();
  427. var query = EntityQueryEnumerator<StationJobsComponent>();
  428. while (query.MoveNext(out var station, out var comp))
  429. {
  430. var netStation = GetNetEntity(station);
  431. var list = comp.JobList.ToDictionary(x => x.Key, x => x.Value);
  432. jobs.Add(netStation, list);
  433. stationNames.Add(netStation, Name(station));
  434. }
  435. return new TickerJobsAvailableEvent(stationNames, jobs);
  436. }
  437. /// <summary>
  438. /// Updates the cached available jobs. Moderately expensive.
  439. /// </summary>
  440. private void UpdateJobsAvailable()
  441. {
  442. _availableJobsDirty = true;
  443. }
  444. private void OnPlayerJoinedLobby(PlayerJoinedLobbyEvent ev)
  445. {
  446. RaiseNetworkEvent(_cachedAvailableJobs, ev.PlayerSession.Channel);
  447. }
  448. private void OnStationRenamed(EntityUid uid, StationJobsComponent component, StationRenamedEvent args)
  449. {
  450. UpdateJobsAvailable();
  451. }
  452. #endregion
  453. }