1
0

RCDSystem.cs 24 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647
  1. using Content.Shared.Administration.Logs;
  2. using Content.Shared.Charges.Components;
  3. using Content.Shared.Charges.Systems;
  4. using Content.Shared.Construction;
  5. using Content.Shared.Database;
  6. using Content.Shared.DoAfter;
  7. using Content.Shared.Examine;
  8. using Content.Shared.Hands.Components;
  9. using Content.Shared.Interaction;
  10. using Content.Shared.Maps;
  11. using Content.Shared.Physics;
  12. using Content.Shared.Popups;
  13. using Content.Shared.RCD.Components;
  14. using Content.Shared.Tag;
  15. using Content.Shared.Tiles;
  16. using Robust.Shared.Audio.Systems;
  17. using Robust.Shared.Map;
  18. using Robust.Shared.Map.Components;
  19. using Robust.Shared.Network;
  20. using Robust.Shared.Physics;
  21. using Robust.Shared.Physics.Collision.Shapes;
  22. using Robust.Shared.Physics.Dynamics;
  23. using Robust.Shared.Prototypes;
  24. using Robust.Shared.Serialization;
  25. using Robust.Shared.Timing;
  26. using System.Diagnostics.CodeAnalysis;
  27. using System.Linq;
  28. namespace Content.Shared.RCD.Systems;
  29. [Virtual]
  30. public class RCDSystem : EntitySystem
  31. {
  32. [Dependency] private readonly IGameTiming _timing = default!;
  33. [Dependency] private readonly INetManager _net = default!;
  34. [Dependency] private readonly ISharedAdminLogManager _adminLogger = default!;
  35. [Dependency] private readonly ITileDefinitionManager _tileDefMan = default!;
  36. [Dependency] private readonly FloorTileSystem _floors = default!;
  37. [Dependency] private readonly SharedAudioSystem _audio = default!;
  38. [Dependency] private readonly SharedChargesSystem _charges = default!;
  39. [Dependency] private readonly SharedDoAfterSystem _doAfter = default!;
  40. [Dependency] private readonly SharedInteractionSystem _interaction = default!;
  41. [Dependency] private readonly SharedPopupSystem _popup = default!;
  42. [Dependency] private readonly TurfSystem _turf = default!;
  43. [Dependency] private readonly EntityLookupSystem _lookup = default!;
  44. [Dependency] private readonly IPrototypeManager _protoManager = default!;
  45. [Dependency] private readonly SharedMapSystem _mapSystem = default!;
  46. [Dependency] private readonly TagSystem _tags = default!;
  47. private readonly int _instantConstructionDelay = 0;
  48. private readonly EntProtoId _instantConstructionFx = "EffectRCDConstruct0";
  49. private readonly ProtoId<RCDPrototype> _deconstructTileProto = "DeconstructTile";
  50. private readonly ProtoId<RCDPrototype> _deconstructLatticeProto = "DeconstructLattice";
  51. private HashSet<EntityUid> _intersectingEntities = new();
  52. public override void Initialize()
  53. {
  54. base.Initialize();
  55. SubscribeLocalEvent<RCDComponent, MapInitEvent>(OnMapInit);
  56. SubscribeLocalEvent<RCDComponent, ExaminedEvent>(OnExamine);
  57. SubscribeLocalEvent<RCDComponent, AfterInteractEvent>(OnAfterInteract);
  58. SubscribeLocalEvent<RCDComponent, RCDDoAfterEvent>(OnDoAfter);
  59. SubscribeLocalEvent<RCDComponent, DoAfterAttemptEvent<RCDDoAfterEvent>>(OnDoAfterAttempt);
  60. SubscribeLocalEvent<RCDComponent, RCDSystemMessage>(OnRCDSystemMessage);
  61. SubscribeNetworkEvent<RCDConstructionGhostRotationEvent>(OnRCDconstructionGhostRotationEvent);
  62. }
  63. #region Event handling
  64. private void OnMapInit(EntityUid uid, RCDComponent component, MapInitEvent args)
  65. {
  66. // On init, set the RCD to its first available recipe
  67. if (component.AvailablePrototypes.Any())
  68. {
  69. component.ProtoId = component.AvailablePrototypes.First();
  70. UpdateCachedPrototype(uid, component);
  71. Dirty(uid, component);
  72. return;
  73. }
  74. // The RCD has no valid recipes somehow? Get rid of it
  75. QueueDel(uid);
  76. }
  77. private void OnRCDSystemMessage(EntityUid uid, RCDComponent component, RCDSystemMessage args)
  78. {
  79. // Exit if the RCD doesn't actually know the supplied prototype
  80. if (!component.AvailablePrototypes.Contains(args.ProtoId))
  81. return;
  82. if (!_protoManager.HasIndex(args.ProtoId))
  83. return;
  84. // Set the current RCD prototype to the one supplied
  85. component.ProtoId = args.ProtoId;
  86. UpdateCachedPrototype(uid, component);
  87. Dirty(uid, component);
  88. }
  89. private void OnExamine(EntityUid uid, RCDComponent component, ExaminedEvent args)
  90. {
  91. if (!args.IsInDetailsRange)
  92. return;
  93. // Update cached prototype if required
  94. UpdateCachedPrototype(uid, component);
  95. var msg = Loc.GetString("rcd-component-examine-mode-details", ("mode", Loc.GetString(component.CachedPrototype.SetName)));
  96. if (component.CachedPrototype.Mode == RcdMode.ConstructTile || component.CachedPrototype.Mode == RcdMode.ConstructObject)
  97. {
  98. var name = Loc.GetString(component.CachedPrototype.SetName);
  99. if (component.CachedPrototype.Prototype != null &&
  100. _protoManager.TryIndex(component.CachedPrototype.Prototype, out var proto))
  101. name = proto.Name;
  102. msg = Loc.GetString("rcd-component-examine-build-details", ("name", name));
  103. }
  104. args.PushMarkup(msg);
  105. }
  106. private void OnAfterInteract(EntityUid uid, RCDComponent component, AfterInteractEvent args)
  107. {
  108. if (args.Handled || !args.CanReach)
  109. return;
  110. var user = args.User;
  111. var location = args.ClickLocation;
  112. // Initial validity checks
  113. if (!location.IsValid(EntityManager))
  114. return;
  115. if (!TryGetMapGridData(location, out var mapGridData))
  116. {
  117. _popup.PopupClient(Loc.GetString("rcd-component-no-valid-grid"), uid, user);
  118. return;
  119. }
  120. if (!IsRCDOperationStillValid(uid, component, mapGridData.Value, args.Target, args.User))
  121. return;
  122. if (!_net.IsServer)
  123. return;
  124. // Get the starting cost, delay, and effect from the prototype
  125. var cost = component.CachedPrototype.Cost;
  126. var delay = component.CachedPrototype.Delay;
  127. var effectPrototype = component.CachedPrototype.Effect;
  128. #region: Operation modifiers
  129. // Deconstruction modifiers
  130. switch (component.CachedPrototype.Mode)
  131. {
  132. case RcdMode.Deconstruct:
  133. // Deconstructing an object
  134. if (args.Target != null)
  135. {
  136. if (TryComp<RCDDeconstructableComponent>(args.Target, out var destructible))
  137. {
  138. cost = destructible.Cost;
  139. delay = destructible.Delay;
  140. effectPrototype = destructible.Effect;
  141. }
  142. }
  143. // Deconstructing a tile
  144. else
  145. {
  146. var deconstructedTile = _mapSystem.GetTileRef(mapGridData.Value.GridUid, mapGridData.Value.Component, mapGridData.Value.Location);
  147. var protoName = !deconstructedTile.IsSpace() ? _deconstructTileProto : _deconstructLatticeProto;
  148. if (_protoManager.TryIndex(protoName, out var deconProto))
  149. {
  150. cost = deconProto.Cost;
  151. delay = deconProto.Delay;
  152. effectPrototype = deconProto.Effect;
  153. }
  154. }
  155. break;
  156. case RcdMode.ConstructTile:
  157. // If replacing a tile, make the construction instant
  158. var contructedTile = _mapSystem.GetTileRef(mapGridData.Value.GridUid, mapGridData.Value.Component, mapGridData.Value.Location);
  159. if (!contructedTile.Tile.IsEmpty)
  160. {
  161. delay = _instantConstructionDelay;
  162. effectPrototype = _instantConstructionFx;
  163. }
  164. break;
  165. }
  166. #endregion
  167. // Try to start the do after
  168. var effect = Spawn(effectPrototype, mapGridData.Value.Location);
  169. var ev = new RCDDoAfterEvent(GetNetCoordinates(mapGridData.Value.Location), component.ConstructionDirection, component.ProtoId, cost, EntityManager.GetNetEntity(effect));
  170. var doAfterArgs = new DoAfterArgs(EntityManager, user, delay, ev, uid, target: args.Target, used: uid)
  171. {
  172. BreakOnDamage = true,
  173. BreakOnHandChange = true,
  174. BreakOnMove = true,
  175. AttemptFrequency = AttemptFrequency.EveryTick,
  176. CancelDuplicate = false,
  177. BlockDuplicate = false
  178. };
  179. args.Handled = true;
  180. if (!_doAfter.TryStartDoAfter(doAfterArgs))
  181. QueueDel(effect);
  182. }
  183. private void OnDoAfterAttempt(EntityUid uid, RCDComponent component, DoAfterAttemptEvent<RCDDoAfterEvent> args)
  184. {
  185. if (args.Event?.DoAfter?.Args == null)
  186. return;
  187. // Exit if the RCD prototype has changed
  188. if (component.ProtoId != args.Event.StartingProtoId)
  189. {
  190. args.Cancel();
  191. return;
  192. }
  193. // Ensure the RCD operation is still valid
  194. var location = GetCoordinates(args.Event.Location);
  195. if (!TryGetMapGridData(location, out var mapGridData))
  196. {
  197. args.Cancel();
  198. return;
  199. }
  200. if (!IsRCDOperationStillValid(uid, component, mapGridData.Value, args.Event.Target, args.Event.User))
  201. args.Cancel();
  202. }
  203. private void OnDoAfter(EntityUid uid, RCDComponent component, RCDDoAfterEvent args)
  204. {
  205. if (args.Cancelled && _net.IsServer)
  206. QueueDel(EntityManager.GetEntity(args.Effect));
  207. if (args.Handled || args.Cancelled || !_timing.IsFirstTimePredicted)
  208. return;
  209. args.Handled = true;
  210. var location = GetCoordinates(args.Location);
  211. if (!TryGetMapGridData(location, out var mapGridData))
  212. return;
  213. // Ensure the RCD operation is still valid
  214. if (!IsRCDOperationStillValid(uid, component, mapGridData.Value, args.Target, args.User))
  215. return;
  216. // Finalize the operation
  217. FinalizeRCDOperation(uid, component, mapGridData.Value, args.Direction, args.Target, args.User);
  218. // Play audio and consume charges
  219. _audio.PlayPredicted(component.SuccessSound, uid, args.User);
  220. _charges.UseCharges(uid, args.Cost);
  221. }
  222. private void OnRCDconstructionGhostRotationEvent(RCDConstructionGhostRotationEvent ev, EntitySessionEventArgs session)
  223. {
  224. var uid = GetEntity(ev.NetEntity);
  225. // Determine if player that send the message is carrying the specified RCD in their active hand
  226. if (session.SenderSession.AttachedEntity == null)
  227. return;
  228. if (!TryComp<HandsComponent>(session.SenderSession.AttachedEntity, out var hands) ||
  229. uid != hands.ActiveHand?.HeldEntity)
  230. return;
  231. if (!TryComp<RCDComponent>(uid, out var rcd))
  232. return;
  233. // Update the construction direction
  234. rcd.ConstructionDirection = ev.Direction;
  235. Dirty(uid, rcd);
  236. }
  237. #endregion
  238. #region Entity construction/deconstruction rule checks
  239. public bool IsRCDOperationStillValid(EntityUid uid, RCDComponent component, MapGridData mapGridData, EntityUid? target, EntityUid user, bool popMsgs = true)
  240. {
  241. // Update cached prototype if required
  242. UpdateCachedPrototype(uid, component);
  243. // Check that the RCD has enough ammo to get the job done
  244. TryComp<LimitedChargesComponent>(uid, out var charges);
  245. // Both of these were messages were suppose to be predicted, but HasInsufficientCharges wasn't being checked on the client for some reason?
  246. if (_charges.IsEmpty(uid, charges))
  247. {
  248. if (popMsgs)
  249. _popup.PopupClient(Loc.GetString("rcd-component-no-ammo-message"), uid, user);
  250. return false;
  251. }
  252. if (_charges.HasInsufficientCharges(uid, component.CachedPrototype.Cost, charges))
  253. {
  254. if (popMsgs)
  255. _popup.PopupClient(Loc.GetString("rcd-component-insufficient-ammo-message"), uid, user);
  256. return false;
  257. }
  258. // Exit if the target / target location is obstructed
  259. var unobstructed = (target == null)
  260. ? _interaction.InRangeUnobstructed(user, _mapSystem.GridTileToWorld(mapGridData.GridUid, mapGridData.Component, mapGridData.Position), popup: popMsgs)
  261. : _interaction.InRangeUnobstructed(user, target.Value, popup: popMsgs);
  262. if (!unobstructed)
  263. return false;
  264. // Return whether the operation location is valid
  265. switch (component.CachedPrototype.Mode)
  266. {
  267. case RcdMode.ConstructTile: return IsConstructionLocationValid(uid, component, mapGridData, user, popMsgs);
  268. case RcdMode.ConstructObject: return IsConstructionLocationValid(uid, component, mapGridData, user, popMsgs);
  269. case RcdMode.Deconstruct: return IsDeconstructionStillValid(uid, component, mapGridData, target, user, popMsgs);
  270. }
  271. return false;
  272. }
  273. private bool IsConstructionLocationValid(EntityUid uid, RCDComponent component, MapGridData mapGridData, EntityUid user, bool popMsgs = true)
  274. {
  275. // Check rule: Must build on empty tile
  276. if (component.CachedPrototype.ConstructionRules.Contains(RcdConstructionRule.MustBuildOnEmptyTile) && !mapGridData.Tile.Tile.IsEmpty)
  277. {
  278. if (popMsgs)
  279. _popup.PopupClient(Loc.GetString("rcd-component-must-build-on-empty-tile-message"), uid, user);
  280. return false;
  281. }
  282. // Check rule: Must build on non-empty tile
  283. if (!component.CachedPrototype.ConstructionRules.Contains(RcdConstructionRule.CanBuildOnEmptyTile) && mapGridData.Tile.Tile.IsEmpty)
  284. {
  285. if (popMsgs)
  286. _popup.PopupClient(Loc.GetString("rcd-component-cannot-build-on-empty-tile-message"), uid, user);
  287. return false;
  288. }
  289. // Check rule: Must place on subfloor
  290. if (component.CachedPrototype.ConstructionRules.Contains(RcdConstructionRule.MustBuildOnSubfloor) && !mapGridData.Tile.Tile.GetContentTileDefinition().IsSubFloor)
  291. {
  292. if (popMsgs)
  293. _popup.PopupClient(Loc.GetString("rcd-component-must-build-on-subfloor-message"), uid, user);
  294. return false;
  295. }
  296. // Tile specific rules
  297. if (component.CachedPrototype.Mode == RcdMode.ConstructTile)
  298. {
  299. // Check rule: Tile placement is valid
  300. if (!_floors.CanPlaceTile(mapGridData.GridUid, mapGridData.Component, out var reason))
  301. {
  302. if (popMsgs)
  303. _popup.PopupClient(reason, uid, user);
  304. return false;
  305. }
  306. // Check rule: Tiles can't be identical
  307. if (mapGridData.Tile.Tile.GetContentTileDefinition().ID == component.CachedPrototype.Prototype)
  308. {
  309. if (popMsgs)
  310. _popup.PopupClient(Loc.GetString("rcd-component-cannot-build-identical-tile"), uid, user);
  311. return false;
  312. }
  313. // Ensure that all construction rules shared between tiles and object are checked before exiting here
  314. return true;
  315. }
  316. // Entity specific rules
  317. // Check rule: The tile is unoccupied
  318. var isWindow = component.CachedPrototype.ConstructionRules.Contains(RcdConstructionRule.IsWindow);
  319. var isCatwalk = component.CachedPrototype.ConstructionRules.Contains(RcdConstructionRule.IsCatwalk);
  320. _intersectingEntities.Clear();
  321. _lookup.GetLocalEntitiesIntersecting(mapGridData.GridUid, mapGridData.Position, _intersectingEntities, -0.05f, LookupFlags.Uncontained);
  322. foreach (var ent in _intersectingEntities)
  323. {
  324. if (isWindow && HasComp<SharedCanBuildWindowOnTopComponent>(ent))
  325. continue;
  326. if (isCatwalk && _tags.HasTag(ent, "Catwalk"))
  327. {
  328. if (popMsgs)
  329. _popup.PopupClient(Loc.GetString("rcd-component-cannot-build-on-occupied-tile-message"), uid, user);
  330. return false;
  331. }
  332. if (component.CachedPrototype.CollisionMask != CollisionGroup.None && TryComp<FixturesComponent>(ent, out var fixtures))
  333. {
  334. foreach (var fixture in fixtures.Fixtures.Values)
  335. {
  336. // Continue if no collision is possible
  337. if (!fixture.Hard || fixture.CollisionLayer <= 0 || (fixture.CollisionLayer & (int) component.CachedPrototype.CollisionMask) == 0)
  338. continue;
  339. // Continue if our custom collision bounds are not intersected
  340. if (component.CachedPrototype.CollisionPolygon != null &&
  341. !DoesCustomBoundsIntersectWithFixture(component.CachedPrototype.CollisionPolygon, component.ConstructionTransform, ent, fixture))
  342. continue;
  343. // Collision was detected
  344. if (popMsgs)
  345. _popup.PopupClient(Loc.GetString("rcd-component-cannot-build-on-occupied-tile-message"), uid, user);
  346. return false;
  347. }
  348. }
  349. }
  350. return true;
  351. }
  352. private bool IsDeconstructionStillValid(EntityUid uid, RCDComponent component, MapGridData mapGridData, EntityUid? target, EntityUid user, bool popMsgs = true)
  353. {
  354. // Attempt to deconstruct a floor tile
  355. if (target == null)
  356. {
  357. // The tile is empty
  358. if (mapGridData.Tile.Tile.IsEmpty)
  359. {
  360. if (popMsgs)
  361. _popup.PopupClient(Loc.GetString("rcd-component-nothing-to-deconstruct-message"), uid, user);
  362. return false;
  363. }
  364. // The tile has a structure sitting on it
  365. if (_turf.IsTileBlocked(mapGridData.Tile, CollisionGroup.MobMask))
  366. {
  367. if (popMsgs)
  368. _popup.PopupClient(Loc.GetString("rcd-component-tile-obstructed-message"), uid, user);
  369. return false;
  370. }
  371. // The tile cannot be destroyed
  372. var tileDef = (ContentTileDefinition) _tileDefMan[mapGridData.Tile.Tile.TypeId];
  373. if (tileDef.Indestructible)
  374. {
  375. if (popMsgs)
  376. _popup.PopupClient(Loc.GetString("rcd-component-tile-indestructible-message"), uid, user);
  377. return false;
  378. }
  379. }
  380. // Attempt to deconstruct an object
  381. else
  382. {
  383. // The object is not in the whitelist
  384. if (!TryComp<RCDDeconstructableComponent>(target, out var deconstructible) || !deconstructible.Deconstructable)
  385. {
  386. if (popMsgs)
  387. _popup.PopupClient(Loc.GetString("rcd-component-deconstruct-target-not-on-whitelist-message"), uid, user);
  388. return false;
  389. }
  390. }
  391. return true;
  392. }
  393. #endregion
  394. #region Entity construction/deconstruction
  395. private void FinalizeRCDOperation(EntityUid uid, RCDComponent component, MapGridData mapGridData, Direction direction, EntityUid? target, EntityUid user)
  396. {
  397. if (!_net.IsServer)
  398. return;
  399. if (component.CachedPrototype.Prototype == null)
  400. return;
  401. switch (component.CachedPrototype.Mode)
  402. {
  403. case RcdMode.ConstructTile:
  404. _mapSystem.SetTile(mapGridData.GridUid, mapGridData.Component, mapGridData.Position, new Tile(_tileDefMan[component.CachedPrototype.Prototype].TileId));
  405. _adminLogger.Add(LogType.RCD, LogImpact.High, $"{ToPrettyString(user):user} used RCD to set grid: {mapGridData.GridUid} {mapGridData.Position} to {component.CachedPrototype.Prototype}");
  406. break;
  407. case RcdMode.ConstructObject:
  408. var ent = Spawn(component.CachedPrototype.Prototype, _mapSystem.GridTileToLocal(mapGridData.GridUid, mapGridData.Component, mapGridData.Position));
  409. switch (component.CachedPrototype.Rotation)
  410. {
  411. case RcdRotation.Fixed:
  412. Transform(ent).LocalRotation = Angle.Zero;
  413. break;
  414. case RcdRotation.Camera:
  415. Transform(ent).LocalRotation = Transform(uid).LocalRotation;
  416. break;
  417. case RcdRotation.User:
  418. Transform(ent).LocalRotation = direction.ToAngle();
  419. break;
  420. }
  421. _adminLogger.Add(LogType.RCD, LogImpact.High, $"{ToPrettyString(user):user} used RCD to spawn {ToPrettyString(ent)} at {mapGridData.Position} on grid {mapGridData.GridUid}");
  422. break;
  423. case RcdMode.Deconstruct:
  424. if (target == null)
  425. {
  426. // Deconstruct tile (either converts the tile to lattice, or removes lattice)
  427. var tile = (mapGridData.Tile.Tile.GetContentTileDefinition().ID != "Lattice") ? new Tile(_tileDefMan["Lattice"].TileId) : Tile.Empty;
  428. _mapSystem.SetTile(mapGridData.GridUid, mapGridData.Component, mapGridData.Position, tile);
  429. _adminLogger.Add(LogType.RCD, LogImpact.High, $"{ToPrettyString(user):user} used RCD to set grid: {mapGridData.GridUid} tile: {mapGridData.Position} open to space");
  430. }
  431. else
  432. {
  433. // Deconstruct object
  434. _adminLogger.Add(LogType.RCD, LogImpact.High, $"{ToPrettyString(user):user} used RCD to delete {ToPrettyString(target):target}");
  435. QueueDel(target);
  436. }
  437. break;
  438. }
  439. }
  440. #endregion
  441. #region Utility functions
  442. public bool TryGetMapGridData(EntityCoordinates location, [NotNullWhen(true)] out MapGridData? mapGridData)
  443. {
  444. mapGridData = null;
  445. var gridUid = location.GetGridUid(EntityManager);
  446. if (!TryComp<MapGridComponent>(gridUid, out var mapGrid))
  447. {
  448. location = location.AlignWithClosestGridTile(1.75f, EntityManager);
  449. gridUid = location.GetGridUid(EntityManager);
  450. // Check if we got a grid ID the second time round
  451. if (!TryComp(gridUid, out mapGrid))
  452. return false;
  453. }
  454. var tile = _mapSystem.GetTileRef(gridUid.Value, mapGrid, location);
  455. var position = _mapSystem.TileIndicesFor(gridUid.Value, mapGrid, location);
  456. mapGridData = new MapGridData(gridUid.Value, mapGrid, location, tile, position);
  457. return true;
  458. }
  459. private bool DoesCustomBoundsIntersectWithFixture(PolygonShape boundingPolygon, Transform boundingTransform, EntityUid fixtureOwner, Fixture fixture)
  460. {
  461. var entXformComp = Transform(fixtureOwner);
  462. var entXform = new Transform(new(), entXformComp.LocalRotation);
  463. return boundingPolygon.ComputeAABB(boundingTransform, 0).Intersects(fixture.Shape.ComputeAABB(entXform, 0));
  464. }
  465. public void UpdateCachedPrototype(EntityUid uid, RCDComponent component)
  466. {
  467. if (component.ProtoId.Id != component.CachedPrototype?.Prototype)
  468. component.CachedPrototype = _protoManager.Index(component.ProtoId);
  469. }
  470. #endregion
  471. }
  472. public struct MapGridData
  473. {
  474. public EntityUid GridUid;
  475. public MapGridComponent Component;
  476. public EntityCoordinates Location;
  477. public TileRef Tile;
  478. public Vector2i Position;
  479. public MapGridData(EntityUid gridUid, MapGridComponent component, EntityCoordinates location, TileRef tile, Vector2i position)
  480. {
  481. GridUid = gridUid;
  482. Component = component;
  483. Location = location;
  484. Tile = tile;
  485. Position = position;
  486. }
  487. }
  488. [Serializable, NetSerializable]
  489. public sealed partial class RCDDoAfterEvent : DoAfterEvent
  490. {
  491. [DataField(required: true)]
  492. public NetCoordinates Location { get; private set; } = default!;
  493. [DataField]
  494. public Direction Direction { get; private set; } = default!;
  495. [DataField]
  496. public ProtoId<RCDPrototype> StartingProtoId { get; private set; } = default!;
  497. [DataField]
  498. public int Cost { get; private set; } = 1;
  499. [DataField("fx")]
  500. public NetEntity? Effect { get; private set; } = null;
  501. private RCDDoAfterEvent() { }
  502. public RCDDoAfterEvent(NetCoordinates location, Direction direction, ProtoId<RCDPrototype> startingProtoId, int cost, NetEntity? effect = null)
  503. {
  504. Location = location;
  505. Direction = direction;
  506. StartingProtoId = startingProtoId;
  507. Cost = cost;
  508. Effect = effect;
  509. }
  510. public override DoAfterEvent Clone() => this;
  511. }