1
0

MarkingsSet.cs 26 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838
  1. using System.Collections;
  2. using System.Diagnostics.CodeAnalysis;
  3. using System.Linq;
  4. using Content.Shared.Humanoid.Prototypes;
  5. using Robust.Shared.Prototypes;
  6. using Robust.Shared.Serialization;
  7. using Robust.Shared.Utility;
  8. namespace Content.Shared.Humanoid.Markings;
  9. // the better version of MarkingsSet
  10. // This one should ensure that a set is valid. Dependency retrieval is
  11. // probably not a good idea, and any dependency references should last
  12. // only for the length of a call, and not the lifetime of the set itself.
  13. //
  14. // Compared to MarkingsSet, this should allow for server-side authority.
  15. // Instead of sending the set over, we can instead just send the dictionary
  16. // and build the set from there. We can also just send a list and rebuild
  17. // the set without validating points (we're assuming that the server
  18. /// <summary>
  19. /// Marking set. For humanoid markings.
  20. /// </summary>
  21. /// <remarks>
  22. /// This is serializable for the admin panel that sets markings on demand for a player.
  23. /// Most APIs that accept a set of markings usually use a List of type Marking instead.
  24. /// </remarks>
  25. [DataDefinition]
  26. [Serializable, NetSerializable]
  27. public sealed partial class MarkingSet
  28. {
  29. /// <summary>
  30. /// Every single marking in this set.
  31. /// </summary>
  32. /// <remarks>
  33. /// The original version of MarkingSet preserved ordering across all
  34. /// markings - this one should instead preserve ordering across all
  35. /// categories, but not marking categories themselves. This is because
  36. /// the layers that markings appear in are guaranteed to be in the correct
  37. /// order. This is here to make lookups slightly faster, even if the n of
  38. /// a marking set is relatively small, and to encapsulate another important
  39. /// feature of markings, which is the limit of markings you can put on a
  40. /// humanoid.
  41. /// </remarks>
  42. [DataField("markings")]
  43. public Dictionary<MarkingCategories, List<Marking>> Markings = new();
  44. /// <summary>
  45. /// Marking points for each category.
  46. /// </summary>
  47. [DataField("points")]
  48. public Dictionary<MarkingCategories, MarkingPoints> Points = new();
  49. public MarkingSet()
  50. {}
  51. /// <summary>
  52. /// Construct a MarkingSet using a list of markings, and a points
  53. /// dictionary. This will set up the points dictionary, and
  54. /// process the list, truncating if necessary. Markings that
  55. /// do not exist as a prototype will be removed.
  56. /// </summary>
  57. /// <param name="markings">The lists of markings to use.</param>
  58. /// <param name="pointsPrototype">The ID of the points dictionary prototype.</param>
  59. public MarkingSet(List<Marking> markings, string pointsPrototype, MarkingManager? markingManager = null, IPrototypeManager? prototypeManager = null)
  60. {
  61. IoCManager.Resolve(ref markingManager, ref prototypeManager);
  62. if (!prototypeManager.TryIndex(pointsPrototype, out MarkingPointsPrototype? points))
  63. {
  64. return;
  65. }
  66. Points = MarkingPoints.CloneMarkingPointDictionary(points.Points);
  67. foreach (var marking in markings)
  68. {
  69. if (!markingManager.TryGetMarking(marking, out var prototype))
  70. {
  71. continue;
  72. }
  73. AddBack(prototype.MarkingCategory, marking);
  74. }
  75. }
  76. /// <summary>
  77. /// Construct a MarkingSet using a dictionary of markings,
  78. /// without point validation. This will still validate every
  79. /// marking, to ensure that it can be placed into the set.
  80. /// </summary>
  81. /// <param name="markings">The list of markings to use.</param>
  82. public MarkingSet(List<Marking> markings, MarkingManager? markingManager = null)
  83. {
  84. IoCManager.Resolve(ref markingManager);
  85. foreach (var marking in markings)
  86. {
  87. if (!markingManager.TryGetMarking(marking, out var prototype))
  88. {
  89. continue;
  90. }
  91. AddBack(prototype.MarkingCategory, marking);
  92. }
  93. }
  94. /// <summary>
  95. /// Construct a MarkingSet only with a points dictionary.
  96. /// </summary>
  97. /// <param name="pointsPrototype">The ID of the points dictionary prototype.</param>
  98. public MarkingSet(string pointsPrototype, MarkingManager? markingManager = null, IPrototypeManager? prototypeManager = null)
  99. {
  100. IoCManager.Resolve(ref markingManager, ref prototypeManager);
  101. if (!prototypeManager.TryIndex(pointsPrototype, out MarkingPointsPrototype? points))
  102. {
  103. return;
  104. }
  105. Points = MarkingPoints.CloneMarkingPointDictionary(points.Points);
  106. }
  107. /// <summary>
  108. /// Construct a MarkingSet by deep cloning another set.
  109. /// </summary>
  110. /// <param name="other">The other marking set.</param>
  111. public MarkingSet(MarkingSet other)
  112. {
  113. foreach (var (key, list) in other.Markings)
  114. {
  115. foreach (var marking in list)
  116. {
  117. AddBack(key, new(marking));
  118. }
  119. }
  120. Points = MarkingPoints.CloneMarkingPointDictionary(other.Points);
  121. }
  122. /// <summary>
  123. /// Filters and colors markings based on species and it's restrictions in the marking's prototype from this marking set.
  124. /// </summary>
  125. /// <param name="species">The species to filter.</param>
  126. /// <param name="skinColor">The skin color for recoloring (i.e. slimes). Use null if you want only filter markings</param>
  127. /// <param name="markingManager">Marking manager.</param>
  128. /// <param name="prototypeManager">Prototype manager.</param>
  129. public void EnsureSpecies(string species, Color? skinColor, MarkingManager? markingManager = null, IPrototypeManager? prototypeManager = null)
  130. {
  131. IoCManager.Resolve(ref markingManager);
  132. IoCManager.Resolve(ref prototypeManager);
  133. var toRemove = new List<(MarkingCategories category, string id)>();
  134. var speciesProto = prototypeManager.Index<SpeciesPrototype>(species);
  135. var onlyWhitelisted = prototypeManager.Index<MarkingPointsPrototype>(speciesProto.MarkingPoints).OnlyWhitelisted;
  136. foreach (var (category, list) in Markings)
  137. {
  138. foreach (var marking in list)
  139. {
  140. if (!markingManager.TryGetMarking(marking, out var prototype))
  141. {
  142. toRemove.Add((category, marking.MarkingId));
  143. continue;
  144. }
  145. if (onlyWhitelisted && prototype.SpeciesRestrictions == null)
  146. {
  147. toRemove.Add((category, marking.MarkingId));
  148. }
  149. if (prototype.SpeciesRestrictions != null
  150. && !prototype.SpeciesRestrictions.Contains(species))
  151. {
  152. toRemove.Add((category, marking.MarkingId));
  153. }
  154. }
  155. }
  156. foreach (var remove in toRemove)
  157. {
  158. Remove(remove.category, remove.id);
  159. }
  160. // Re-color left markings them into skin color if needed (i.e. for slimes)
  161. if (skinColor != null)
  162. {
  163. foreach (var (category, list) in Markings)
  164. {
  165. foreach (var marking in list)
  166. {
  167. if (markingManager.TryGetMarking(marking, out var prototype) &&
  168. markingManager.MustMatchSkin(species, prototype.BodyPart, out var alpha, prototypeManager))
  169. {
  170. marking.SetColor(skinColor.Value.WithAlpha(alpha));
  171. }
  172. }
  173. }
  174. }
  175. }
  176. /// <summary>
  177. /// Filters markings based on sex and it's restrictions in the marking's prototype from this marking set.
  178. /// </summary>
  179. /// <param name="sex">The species to filter.</param>
  180. /// <param name="markingManager">Marking manager.</param>
  181. public void EnsureSexes(Sex sex, MarkingManager? markingManager = null)
  182. {
  183. IoCManager.Resolve(ref markingManager);
  184. var toRemove = new List<(MarkingCategories category, string id)>();
  185. foreach (var (category, list) in Markings)
  186. {
  187. foreach (var marking in list)
  188. {
  189. if (!markingManager.TryGetMarking(marking, out var prototype))
  190. {
  191. toRemove.Add((category, marking.MarkingId));
  192. continue;
  193. }
  194. if (prototype.SexRestriction != null && prototype.SexRestriction != sex)
  195. {
  196. toRemove.Add((category, marking.MarkingId));
  197. }
  198. }
  199. }
  200. foreach (var remove in toRemove)
  201. {
  202. Remove(remove.category, remove.id);
  203. }
  204. }
  205. /// <summary>
  206. /// Ensures that all markings in this set are valid.
  207. /// </summary>
  208. /// <param name="markingManager">Marking manager.</param>
  209. public void EnsureValid(MarkingManager? markingManager = null)
  210. {
  211. IoCManager.Resolve(ref markingManager);
  212. var toRemove = new List<int>();
  213. foreach (var (category, list) in Markings)
  214. {
  215. for (var i = 0; i < list.Count; i++)
  216. {
  217. if (!markingManager.TryGetMarking(list[i], out var marking))
  218. {
  219. toRemove.Add(i);
  220. continue;
  221. }
  222. if (marking.Sprites.Count != list[i].MarkingColors.Count)
  223. {
  224. list[i] = new Marking(marking.ID, marking.Sprites.Count);
  225. }
  226. }
  227. foreach (var i in toRemove)
  228. {
  229. Remove(category, i);
  230. }
  231. }
  232. }
  233. /// <summary>
  234. /// Ensures that the default markings as defined by the marking point set in this marking set are applied.
  235. /// </summary>
  236. /// <param name="skinColor">Skin color for marking coloring.</param>
  237. /// <param name="eyeColor">Eye color for marking coloring.</param>
  238. /// <param name="hairColor">Hair color for marking coloring.</param>
  239. /// <param name="markingManager">Marking manager.</param>
  240. public void EnsureDefault(Color? skinColor = null, Color? eyeColor = null, MarkingManager? markingManager = null)
  241. {
  242. IoCManager.Resolve(ref markingManager);
  243. foreach (var (category, points) in Points)
  244. {
  245. if (points.Points <= 0 || points.DefaultMarkings.Count <= 0)
  246. {
  247. continue;
  248. }
  249. var index = 0;
  250. while (points.Points > 0 || index < points.DefaultMarkings.Count)
  251. {
  252. if (markingManager.Markings.TryGetValue(points.DefaultMarkings[index], out var prototype))
  253. {
  254. var colors = MarkingColoring.GetMarkingLayerColors(
  255. prototype,
  256. skinColor,
  257. eyeColor,
  258. this
  259. );
  260. var marking = new Marking(points.DefaultMarkings[index], colors);
  261. AddBack(category, marking);
  262. }
  263. index++;
  264. }
  265. }
  266. }
  267. /// <summary>
  268. /// How many points are left in this marking set's category
  269. /// </summary>
  270. /// <param name="category">The category to check</param>
  271. /// <returns>A number equal or greater than zero if the category exists, -1 otherwise.</returns>
  272. public int PointsLeft(MarkingCategories category)
  273. {
  274. if (!Points.TryGetValue(category, out var points))
  275. {
  276. return -1;
  277. }
  278. return points.Points;
  279. }
  280. /// <summary>
  281. /// Add a marking to the front of the category's list of markings.
  282. /// </summary>
  283. /// <param name="category">Category to add the marking to.</param>
  284. /// <param name="marking">The marking instance in question.</param>
  285. public void AddFront(MarkingCategories category, Marking marking)
  286. {
  287. if (!marking.Forced && Points.TryGetValue(category, out var points))
  288. {
  289. if (points.Points <= 0)
  290. {
  291. return;
  292. }
  293. points.Points--;
  294. }
  295. if (!Markings.TryGetValue(category, out var markings))
  296. {
  297. markings = new();
  298. Markings[category] = markings;
  299. }
  300. markings.Insert(0, marking);
  301. }
  302. /// <summary>
  303. /// Add a marking to the back of the category's list of markings.
  304. /// </summary>
  305. /// <param name="category"></param>
  306. /// <param name="marking"></param>
  307. public void AddBack(MarkingCategories category, Marking marking)
  308. {
  309. if (!marking.Forced && Points.TryGetValue(category, out var points))
  310. {
  311. if (points.Points <= 0)
  312. {
  313. return;
  314. }
  315. points.Points--;
  316. }
  317. if (!Markings.TryGetValue(category, out var markings))
  318. {
  319. markings = new();
  320. Markings[category] = markings;
  321. }
  322. markings.Add(marking);
  323. }
  324. /// <summary>
  325. /// Adds a category to this marking set.
  326. /// </summary>
  327. /// <param name="category"></param>
  328. /// <returns></returns>
  329. public List<Marking> AddCategory(MarkingCategories category)
  330. {
  331. var markings = new List<Marking>();
  332. Markings.Add(category, markings);
  333. return markings;
  334. }
  335. /// <summary>
  336. /// Replace a marking at a given index in a marking category with another marking.
  337. /// </summary>
  338. /// <param name="category">The category to replace the marking in.</param>
  339. /// <param name="index">The index of the marking.</param>
  340. /// <param name="marking">The marking to insert.</param>
  341. public void Replace(MarkingCategories category, int index, Marking marking)
  342. {
  343. if (index < 0 || !Markings.TryGetValue(category, out var markings)
  344. || index >= markings.Count)
  345. {
  346. return;
  347. }
  348. markings[index] = marking;
  349. }
  350. /// <summary>
  351. /// Remove a marking by category and ID.
  352. /// </summary>
  353. /// <param name="category">The category that contains the marking.</param>
  354. /// <param name="id">The marking's ID.</param>
  355. /// <returns>True if removed, false otherwise.</returns>
  356. public bool Remove(MarkingCategories category, string id)
  357. {
  358. if (!Markings.TryGetValue(category, out var markings))
  359. {
  360. return false;
  361. }
  362. for (var i = 0; i < markings.Count; i++)
  363. {
  364. if (markings[i].MarkingId != id)
  365. {
  366. continue;
  367. }
  368. if (!markings[i].Forced && Points.TryGetValue(category, out var points))
  369. {
  370. points.Points++;
  371. }
  372. markings.RemoveAt(i);
  373. return true;
  374. }
  375. return false;
  376. }
  377. /// <summary>
  378. /// Remove a marking by category and index.
  379. /// </summary>
  380. /// <param name="category">The category that contains the marking.</param>
  381. /// <param name="idx">The marking's index.</param>
  382. /// <returns>True if removed, false otherwise.</returns>
  383. public void Remove(MarkingCategories category, int idx)
  384. {
  385. if (!Markings.TryGetValue(category, out var markings))
  386. {
  387. return;
  388. }
  389. if (idx < 0 || idx >= markings.Count)
  390. {
  391. return;
  392. }
  393. if (!markings[idx].Forced && Points.TryGetValue(category, out var points))
  394. {
  395. points.Points++;
  396. }
  397. markings.RemoveAt(idx);
  398. }
  399. /// <summary>
  400. /// Remove an entire category from this marking set.
  401. /// </summary>
  402. /// <param name="category">The category to remove.</param>
  403. /// <returns>True if removed, false otherwise.</returns>
  404. public bool RemoveCategory(MarkingCategories category)
  405. {
  406. if (!Markings.TryGetValue(category, out var markings))
  407. {
  408. return false;
  409. }
  410. if (Points.TryGetValue(category, out var points))
  411. {
  412. foreach (var marking in markings)
  413. {
  414. if (marking.Forced)
  415. {
  416. continue;
  417. }
  418. points.Points++;
  419. }
  420. }
  421. Markings.Remove(category);
  422. return true;
  423. }
  424. /// <summary>
  425. /// Clears all markings from this marking set.
  426. /// </summary>
  427. public void Clear()
  428. {
  429. foreach (var category in Enum.GetValues<MarkingCategories>())
  430. {
  431. RemoveCategory(category);
  432. }
  433. }
  434. /// <summary>
  435. /// Attempt to find the index of a marking in a category by ID.
  436. /// </summary>
  437. /// <param name="category">The category to search in.</param>
  438. /// <param name="id">The ID to search for.</param>
  439. /// <returns>The index of the marking, otherwise a negative number.</returns>
  440. public int FindIndexOf(MarkingCategories category, string id)
  441. {
  442. if (!Markings.TryGetValue(category, out var markings))
  443. {
  444. return -1;
  445. }
  446. return markings.FindIndex(m => m.MarkingId == id);
  447. }
  448. /// <summary>
  449. /// Tries to get an entire category from this marking set.
  450. /// </summary>
  451. /// <param name="category">The category to fetch.</param>
  452. /// <param name="markings">A read only list of the all markings in that category.</param>
  453. /// <returns>True if successful, false otherwise.</returns>
  454. public bool TryGetCategory(MarkingCategories category, [NotNullWhen(true)] out IReadOnlyList<Marking>? markings)
  455. {
  456. markings = null;
  457. if (Markings.TryGetValue(category, out var list))
  458. {
  459. markings = list;
  460. return true;
  461. }
  462. return false;
  463. }
  464. /// <summary>
  465. /// Tries to get a marking from this marking set, by category.
  466. /// </summary>
  467. /// <param name="category">The category to search in.</param>
  468. /// <param name="id">The ID to search for.</param>
  469. /// <param name="marking">The marking, if it was retrieved.</param>
  470. /// <returns>True if successful, false otherwise.</returns>
  471. public bool TryGetMarking(MarkingCategories category, string id, [NotNullWhen(true)] out Marking? marking)
  472. {
  473. marking = null;
  474. if (!Markings.TryGetValue(category, out var markings))
  475. {
  476. return false;
  477. }
  478. foreach (var m in markings)
  479. {
  480. if (m.MarkingId == id)
  481. {
  482. marking = m;
  483. return true;
  484. }
  485. }
  486. return false;
  487. }
  488. /// <summary>
  489. /// Shifts a marking's rank towards the front of the list
  490. /// </summary>
  491. /// <param name="category">The category to shift in.</param>
  492. /// <param name="idx">Index of the marking.</param>
  493. public void ShiftRankUp(MarkingCategories category, int idx)
  494. {
  495. if (!Markings.TryGetValue(category, out var markings))
  496. {
  497. return;
  498. }
  499. if (idx < 0 || idx >= markings.Count || idx - 1 < 0)
  500. {
  501. return;
  502. }
  503. (markings[idx - 1], markings[idx]) = (markings[idx], markings[idx - 1]);
  504. }
  505. /// <summary>
  506. /// Shifts a marking's rank upwards from the end of the list
  507. /// </summary>
  508. /// <param name="category">The category to shift in.</param>
  509. /// <param name="idx">Index of the marking from the end</param>
  510. public void ShiftRankUpFromEnd(MarkingCategories category, int idx)
  511. {
  512. if (!Markings.TryGetValue(category, out var markings))
  513. {
  514. return;
  515. }
  516. ShiftRankUp(category, markings.Count - idx - 1);
  517. }
  518. /// <summary>
  519. /// Shifts a marking's rank towards the end of the list
  520. /// </summary>
  521. /// <param name="category">The category to shift in.</param>
  522. /// <param name="idx">Index of the marking.</param>
  523. public void ShiftRankDown(MarkingCategories category, int idx)
  524. {
  525. if (!Markings.TryGetValue(category, out var markings))
  526. {
  527. return;
  528. }
  529. if (idx < 0 || idx >= markings.Count || idx + 1 >= markings.Count)
  530. {
  531. return;
  532. }
  533. (markings[idx + 1], markings[idx]) = (markings[idx], markings[idx + 1]);
  534. }
  535. /// <summary>
  536. /// Shifts a marking's rank downwards from the end of the list
  537. /// </summary>
  538. /// <param name="category">The category to shift in.</param>
  539. /// <param name="idx">Index of the marking from the end</param>
  540. public void ShiftRankDownFromEnd(MarkingCategories category, int idx)
  541. {
  542. if (!Markings.TryGetValue(category, out var markings))
  543. {
  544. return;
  545. }
  546. ShiftRankDown(category, markings.Count - idx - 1);
  547. }
  548. /// <summary>
  549. /// Gets all markings in this set as an enumerator. Lists will be organized, but categories may be in any order.
  550. /// </summary>
  551. /// <returns>An enumerator of <see cref="Marking"/>s.</returns>
  552. public ForwardMarkingEnumerator GetForwardEnumerator()
  553. {
  554. var markings = new List<Marking>();
  555. foreach (var (_, list) in Markings)
  556. {
  557. markings.AddRange(list);
  558. }
  559. return new ForwardMarkingEnumerator(markings);
  560. }
  561. /// <summary>
  562. /// Gets an enumerator of markings in this set, but only for one category.
  563. /// </summary>
  564. /// <param name="category">The category to fetch.</param>
  565. /// <returns>An enumerator of <see cref="Marking"/>s in that category.</returns>
  566. public ForwardMarkingEnumerator GetForwardEnumerator(MarkingCategories category)
  567. {
  568. var markings = new List<Marking>();
  569. if (Markings.TryGetValue(category, out var listing))
  570. {
  571. markings = new(listing);
  572. }
  573. return new ForwardMarkingEnumerator(markings);
  574. }
  575. /// <summary>
  576. /// Gets all markings in this set as an enumerator, but in reverse order. Lists will be in reverse order, but categories may be in any order.
  577. /// </summary>
  578. /// <returns>An enumerator of <see cref="Marking"/>s in reverse.</returns>
  579. public ReverseMarkingEnumerator GetReverseEnumerator()
  580. {
  581. var markings = new List<Marking>();
  582. foreach (var (_, list) in Markings)
  583. {
  584. markings.AddRange(list);
  585. }
  586. return new ReverseMarkingEnumerator(markings);
  587. }
  588. /// <summary>
  589. /// Gets an enumerator of markings in this set in reverse order, but only for one category.
  590. /// </summary>
  591. /// <param name="category">The category to fetch.</param>
  592. /// <returns>An enumerator of <see cref="Marking"/>s in that category, in reverse order.</returns>
  593. public ReverseMarkingEnumerator GetReverseEnumerator(MarkingCategories category)
  594. {
  595. var markings = new List<Marking>();
  596. if (Markings.TryGetValue(category, out var listing))
  597. {
  598. markings = new(listing);
  599. }
  600. return new ReverseMarkingEnumerator(markings);
  601. }
  602. public bool CategoryEquals(MarkingCategories category, MarkingSet other)
  603. {
  604. if (!Markings.TryGetValue(category, out var markings)
  605. || !other.Markings.TryGetValue(category, out var markingsOther))
  606. {
  607. return false;
  608. }
  609. return markings.SequenceEqual(markingsOther);
  610. }
  611. public bool Equals(MarkingSet other)
  612. {
  613. foreach (var (category, _) in Markings)
  614. {
  615. if (!CategoryEquals(category, other))
  616. {
  617. return false;
  618. }
  619. }
  620. return true;
  621. }
  622. /// <summary>
  623. /// Gets a difference of marking categories between two marking sets
  624. /// </summary>
  625. /// <param name="other">The other marking set.</param>
  626. /// <returns>Enumerator of marking categories that were different between the two.</returns>
  627. public IEnumerable<MarkingCategories> CategoryDifference(MarkingSet other)
  628. {
  629. foreach (var (category, _) in Markings)
  630. {
  631. if (!CategoryEquals(category, other))
  632. {
  633. yield return category;
  634. }
  635. }
  636. }
  637. }
  638. public sealed class ForwardMarkingEnumerator : IEnumerable<Marking>
  639. {
  640. private List<Marking> _markings;
  641. public ForwardMarkingEnumerator(List<Marking> markings)
  642. {
  643. _markings = markings;
  644. }
  645. public IEnumerator<Marking> GetEnumerator()
  646. {
  647. return new MarkingsEnumerator(_markings, false);
  648. }
  649. IEnumerator IEnumerable.GetEnumerator()
  650. {
  651. return GetEnumerator();
  652. }
  653. }
  654. public sealed class ReverseMarkingEnumerator : IEnumerable<Marking>
  655. {
  656. private List<Marking> _markings;
  657. public ReverseMarkingEnumerator(List<Marking> markings)
  658. {
  659. _markings = markings;
  660. }
  661. public IEnumerator<Marking> GetEnumerator()
  662. {
  663. return new MarkingsEnumerator(_markings, true);
  664. }
  665. IEnumerator IEnumerable.GetEnumerator()
  666. {
  667. return GetEnumerator();
  668. }
  669. }
  670. public sealed class MarkingsEnumerator : IEnumerator<Marking>
  671. {
  672. private List<Marking> _markings;
  673. private bool _reverse;
  674. int position;
  675. public MarkingsEnumerator(List<Marking> markings, bool reverse)
  676. {
  677. _markings = markings;
  678. _reverse = reverse;
  679. if (_reverse)
  680. {
  681. position = _markings.Count;
  682. }
  683. else
  684. {
  685. position = -1;
  686. }
  687. }
  688. public bool MoveNext()
  689. {
  690. if (_reverse)
  691. {
  692. position--;
  693. return (position >= 0);
  694. }
  695. else
  696. {
  697. position++;
  698. return (position < _markings.Count);
  699. }
  700. }
  701. public void Reset()
  702. {
  703. if (_reverse)
  704. {
  705. position = _markings.Count;
  706. }
  707. else
  708. {
  709. position = -1;
  710. }
  711. }
  712. public void Dispose()
  713. {}
  714. object IEnumerator.Current
  715. {
  716. get => _markings[position];
  717. }
  718. public Marking Current
  719. {
  720. get => _markings[position];
  721. }
  722. }