1
0

SharedSprayPainterSystem.cs 6.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195
  1. using Content.Shared.Administration.Logs;
  2. using Content.Shared.Database;
  3. using Content.Shared.DoAfter;
  4. using Content.Shared.Doors.Components;
  5. using Content.Shared.Interaction;
  6. using Content.Shared.Popups;
  7. using Content.Shared.SprayPainter.Components;
  8. using Content.Shared.SprayPainter.Prototypes;
  9. using Robust.Shared.Audio.Systems;
  10. using Robust.Shared.Prototypes;
  11. using System.Linq;
  12. namespace Content.Shared.SprayPainter;
  13. /// <summary>
  14. /// System for painting airlocks using a spray painter.
  15. /// Pipes are handled serverside since AtmosPipeColorSystem is server only.
  16. /// </summary>
  17. public abstract class SharedSprayPainterSystem : EntitySystem
  18. {
  19. [Dependency] protected readonly IPrototypeManager Proto = default!;
  20. [Dependency] private readonly ISharedAdminLogManager _adminLogger = default!;
  21. [Dependency] protected readonly SharedAppearanceSystem Appearance = default!;
  22. [Dependency] protected readonly SharedAudioSystem Audio = default!;
  23. [Dependency] protected readonly SharedDoAfterSystem DoAfter = default!;
  24. [Dependency] private readonly SharedPopupSystem _popup = default!;
  25. public List<AirlockStyle> Styles { get; private set; } = new();
  26. public List<AirlockGroupPrototype> Groups { get; private set; } = new();
  27. [ValidatePrototypeId<AirlockDepartmentsPrototype>]
  28. private const string Departments = "Departments";
  29. public override void Initialize()
  30. {
  31. base.Initialize();
  32. CacheStyles();
  33. SubscribeLocalEvent<SprayPainterComponent, MapInitEvent>(OnMapInit);
  34. SubscribeLocalEvent<SprayPainterComponent, SprayPainterDoorDoAfterEvent>(OnDoorDoAfter);
  35. Subs.BuiEvents<SprayPainterComponent>(SprayPainterUiKey.Key, subs =>
  36. {
  37. subs.Event<SprayPainterSpritePickedMessage>(OnSpritePicked);
  38. subs.Event<SprayPainterColorPickedMessage>(OnColorPicked);
  39. });
  40. SubscribeLocalEvent<PaintableAirlockComponent, InteractUsingEvent>(OnAirlockInteract);
  41. SubscribeLocalEvent<PrototypesReloadedEventArgs>(OnPrototypesReloaded);
  42. }
  43. private void OnMapInit(Entity<SprayPainterComponent> ent, ref MapInitEvent args)
  44. {
  45. if (ent.Comp.ColorPalette.Count == 0)
  46. return;
  47. SetColor(ent, ent.Comp.ColorPalette.First().Key);
  48. }
  49. private void OnDoorDoAfter(Entity<SprayPainterComponent> ent, ref SprayPainterDoorDoAfterEvent args)
  50. {
  51. if (args.Handled || args.Cancelled)
  52. return;
  53. if (args.Args.Target is not {} target)
  54. return;
  55. if (!TryComp<PaintableAirlockComponent>(target, out var airlock))
  56. return;
  57. airlock.Department = args.Department;
  58. Dirty(target, airlock);
  59. Audio.PlayPredicted(ent.Comp.SpraySound, ent, args.Args.User);
  60. Appearance.SetData(target, DoorVisuals.BaseRSI, args.Sprite);
  61. _adminLogger.Add(LogType.Action, LogImpact.Low, $"{ToPrettyString(args.Args.User):user} painted {ToPrettyString(args.Args.Target.Value):target}");
  62. args.Handled = true;
  63. }
  64. #region UI messages
  65. private void OnColorPicked(Entity<SprayPainterComponent> ent, ref SprayPainterColorPickedMessage args)
  66. {
  67. SetColor(ent, args.Key);
  68. }
  69. private void OnSpritePicked(Entity<SprayPainterComponent> ent, ref SprayPainterSpritePickedMessage args)
  70. {
  71. if (args.Index >= Styles.Count)
  72. return;
  73. ent.Comp.Index = args.Index;
  74. Dirty(ent, ent.Comp);
  75. }
  76. private void SetColor(Entity<SprayPainterComponent> ent, string? paletteKey)
  77. {
  78. if (paletteKey == null || paletteKey == ent.Comp.PickedColor)
  79. return;
  80. if (!ent.Comp.ColorPalette.ContainsKey(paletteKey))
  81. return;
  82. ent.Comp.PickedColor = paletteKey;
  83. Dirty(ent, ent.Comp);
  84. }
  85. #endregion
  86. private void OnAirlockInteract(Entity<PaintableAirlockComponent> ent, ref InteractUsingEvent args)
  87. {
  88. if (args.Handled)
  89. return;
  90. if (!TryComp<SprayPainterComponent>(args.Used, out var painter))
  91. return;
  92. var group = Proto.Index<AirlockGroupPrototype>(ent.Comp.Group);
  93. var style = Styles[painter.Index];
  94. if (!group.StylePaths.TryGetValue(style.Name, out var sprite))
  95. {
  96. string msg = Loc.GetString("spray-painter-style-not-available");
  97. _popup.PopupClient(msg, args.User, args.User);
  98. return;
  99. }
  100. var doAfterEventArgs = new DoAfterArgs(EntityManager, args.User, painter.AirlockSprayTime, new SprayPainterDoorDoAfterEvent(sprite, style.Department), args.Used, target: ent, used: args.Used)
  101. {
  102. BreakOnMove = true,
  103. BreakOnDamage = true,
  104. NeedHand = true,
  105. };
  106. if (!DoAfter.TryStartDoAfter(doAfterEventArgs, out var id))
  107. return;
  108. args.Handled = true;
  109. // Log the attempt
  110. _adminLogger.Add(LogType.Action, LogImpact.Low, $"{ToPrettyString(args.User):user} is painting {ToPrettyString(ent):target} to '{style.Name}' at {Transform(ent).Coordinates:targetlocation}");
  111. }
  112. #region Style caching
  113. private void OnPrototypesReloaded(PrototypesReloadedEventArgs args)
  114. {
  115. if (!args.WasModified<AirlockGroupPrototype>() && !args.WasModified<AirlockDepartmentsPrototype>())
  116. return;
  117. Styles.Clear();
  118. Groups.Clear();
  119. CacheStyles();
  120. // style index might be invalid now so check them all
  121. var max = Styles.Count - 1;
  122. var query = AllEntityQuery<SprayPainterComponent>();
  123. while (query.MoveNext(out var uid, out var comp))
  124. {
  125. if (comp.Index > max)
  126. {
  127. comp.Index = max;
  128. Dirty(uid, comp);
  129. }
  130. }
  131. }
  132. protected virtual void CacheStyles()
  133. {
  134. // collect every style's name
  135. var names = new SortedSet<string>();
  136. foreach (var group in Proto.EnumeratePrototypes<AirlockGroupPrototype>())
  137. {
  138. Groups.Add(group);
  139. foreach (var style in group.StylePaths.Keys)
  140. {
  141. names.Add(style);
  142. }
  143. }
  144. // get their department ids too for the final style list
  145. var departments = Proto.Index<AirlockDepartmentsPrototype>(Departments);
  146. Styles.Capacity = names.Count;
  147. foreach (var name in names)
  148. {
  149. departments.Departments.TryGetValue(name, out var department);
  150. Styles.Add(new AirlockStyle(name, department));
  151. }
  152. }
  153. #endregion
  154. }
  155. public record struct AirlockStyle(string Name, string? Department);