1
0

SharedDecalSystem.cs 5.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151
  1. using System.Collections;
  2. using System.Diagnostics.CodeAnalysis;
  3. using System.Numerics;
  4. using Robust.Shared.GameStates;
  5. using Robust.Shared.Map;
  6. using Robust.Shared.Prototypes;
  7. using Robust.Shared.Serialization;
  8. using static Content.Shared.Decals.DecalGridComponent;
  9. namespace Content.Shared.Decals
  10. {
  11. public abstract class SharedDecalSystem : EntitySystem
  12. {
  13. [Dependency] protected readonly IPrototypeManager PrototypeManager = default!;
  14. [Dependency] protected readonly IMapManager MapManager = default!;
  15. protected bool PvsEnabled;
  16. // Note that this constant is effectively baked into all map files, because of how they save the grid decal component.
  17. // So if this ever needs changing, the maps need converting.
  18. public const int ChunkSize = 32;
  19. public static Vector2i GetChunkIndices(Vector2 coordinates) => new ((int) Math.Floor(coordinates.X / ChunkSize), (int) Math.Floor(coordinates.Y / ChunkSize));
  20. public override void Initialize()
  21. {
  22. base.Initialize();
  23. SubscribeLocalEvent<GridInitializeEvent>(OnGridInitialize);
  24. SubscribeLocalEvent<DecalGridComponent, ComponentStartup>(OnCompStartup);
  25. SubscribeLocalEvent<DecalGridComponent, ComponentGetState>(OnGetState);
  26. }
  27. private void OnGetState(EntityUid uid, DecalGridComponent component, ref ComponentGetState args)
  28. {
  29. if (PvsEnabled && !args.ReplayState)
  30. return;
  31. // Should this be a full component state or a delta-state?
  32. if (args.FromTick <= component.CreationTick || args.FromTick <= component.ForceTick)
  33. {
  34. args.State = new DecalGridState(component.ChunkCollection.ChunkCollection);
  35. return;
  36. }
  37. var data = new Dictionary<Vector2i, DecalChunk>();
  38. foreach (var (index, chunk) in component.ChunkCollection.ChunkCollection)
  39. {
  40. if (chunk.LastModified >= args.FromTick)
  41. data[index] = chunk;
  42. }
  43. args.State = new DecalGridDeltaState(data, new(component.ChunkCollection.ChunkCollection.Keys));
  44. }
  45. private void OnGridInitialize(GridInitializeEvent msg)
  46. {
  47. EnsureComp<DecalGridComponent>(msg.EntityUid);
  48. }
  49. private void OnCompStartup(EntityUid uid, DecalGridComponent component, ComponentStartup args)
  50. {
  51. foreach (var (indices, decals) in component.ChunkCollection.ChunkCollection)
  52. {
  53. foreach (var decalUid in decals.Decals.Keys)
  54. {
  55. component.DecalIndex[decalUid] = indices;
  56. }
  57. }
  58. // This **shouldn't** be required, but just in case we ever get entity prototypes that have decal grids, we
  59. // need to ensure that we send an initial full state to players.
  60. Dirty(uid, component);
  61. }
  62. protected Dictionary<Vector2i, DecalChunk>? ChunkCollection(EntityUid gridEuid, DecalGridComponent? comp = null)
  63. {
  64. if (!Resolve(gridEuid, ref comp))
  65. return null;
  66. return comp.ChunkCollection.ChunkCollection;
  67. }
  68. protected virtual void DirtyChunk(EntityUid id, Vector2i chunkIndices, DecalChunk chunk) {}
  69. // internal, so that client/predicted code doesn't accidentally remove decals. There is a public server-side function.
  70. protected bool RemoveDecalInternal(EntityUid gridId, uint decalId, [NotNullWhen(true)] out Decal? removed, DecalGridComponent? component = null)
  71. {
  72. removed = null;
  73. if (!Resolve(gridId, ref component))
  74. return false;
  75. if (!component.DecalIndex.Remove(decalId, out var indices)
  76. || !component.ChunkCollection.ChunkCollection.TryGetValue(indices, out var chunk)
  77. || !chunk.Decals.Remove(decalId, out removed))
  78. {
  79. return false;
  80. }
  81. if (chunk.Decals.Count == 0)
  82. component.ChunkCollection.ChunkCollection.Remove(indices);
  83. DirtyChunk(gridId, indices, chunk);
  84. OnDecalRemoved(gridId, decalId, component, indices, chunk);
  85. return true;
  86. }
  87. protected virtual void OnDecalRemoved(EntityUid gridId, uint decalId, DecalGridComponent component, Vector2i indices, DecalChunk chunk)
  88. {
  89. // used by client-side overlay code
  90. }
  91. public virtual HashSet<(uint Index, Decal Decal)> GetDecalsInRange(EntityUid gridId, Vector2 position, float distance = 0.75f, Func<Decal, bool>? validDelegate = null)
  92. {
  93. // NOOP on client atm.
  94. return new HashSet<(uint Index, Decal Decal)>();
  95. }
  96. public virtual bool RemoveDecal(EntityUid gridId, uint decalId, DecalGridComponent? component = null)
  97. {
  98. // NOOP on client atm.
  99. return true;
  100. }
  101. }
  102. /// <summary>
  103. /// Sent by clients to request that a decal is placed on the server.
  104. /// </summary>
  105. [Serializable, NetSerializable]
  106. public sealed class RequestDecalPlacementEvent : EntityEventArgs
  107. {
  108. public Decal Decal;
  109. public NetCoordinates Coordinates;
  110. public RequestDecalPlacementEvent(Decal decal, NetCoordinates coordinates)
  111. {
  112. Decal = decal;
  113. Coordinates = coordinates;
  114. }
  115. }
  116. [Serializable, NetSerializable]
  117. public sealed class RequestDecalRemovalEvent : EntityEventArgs
  118. {
  119. public NetCoordinates Coordinates;
  120. public RequestDecalRemovalEvent(NetCoordinates coordinates)
  121. {
  122. Coordinates = coordinates;
  123. }
  124. }
  125. }