1
0

SalvageSystem.Magnet.cs 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465
  1. using System.Linq;
  2. using System.Numerics;
  3. using System.Threading.Tasks;
  4. using Content.Server.Salvage.Magnet;
  5. using Content.Shared.Mobs.Components;
  6. using Content.Shared.Procedural;
  7. using Content.Shared.Radio;
  8. using Content.Shared.Salvage.Magnet;
  9. using Robust.Shared.Exceptions;
  10. using Robust.Shared.Map;
  11. namespace Content.Server.Salvage;
  12. public sealed partial class SalvageSystem
  13. {
  14. [Dependency] private readonly IRuntimeLog _runtimeLog = default!;
  15. [ValidatePrototypeId<RadioChannelPrototype>]
  16. private const string MagnetChannel = "Common";
  17. private EntityQuery<SalvageMobRestrictionsComponent> _salvMobQuery;
  18. private EntityQuery<MobStateComponent> _mobStateQuery;
  19. private List<(Entity<TransformComponent> Entity, EntityUid MapUid, Vector2 LocalPosition)> _detachEnts = new();
  20. private void InitializeMagnet()
  21. {
  22. _salvMobQuery = GetEntityQuery<SalvageMobRestrictionsComponent>();
  23. _mobStateQuery = GetEntityQuery<MobStateComponent>();
  24. SubscribeLocalEvent<SalvageMagnetDataComponent, MapInitEvent>(OnMagnetDataMapInit);
  25. SubscribeLocalEvent<SalvageMagnetTargetComponent, GridSplitEvent>(OnMagnetTargetSplit);
  26. SubscribeLocalEvent<SalvageMagnetComponent, MagnetClaimOfferEvent>(OnMagnetClaim);
  27. SubscribeLocalEvent<SalvageMagnetComponent, ComponentStartup>(OnMagnetStartup);
  28. SubscribeLocalEvent<SalvageMagnetComponent, AnchorStateChangedEvent>(OnMagnetAnchored);
  29. }
  30. private void OnMagnetClaim(EntityUid uid, SalvageMagnetComponent component, ref MagnetClaimOfferEvent args)
  31. {
  32. var station = _station.GetOwningStation(uid);
  33. if (!TryComp(station, out SalvageMagnetDataComponent? dataComp) ||
  34. dataComp.EndTime != null)
  35. {
  36. return;
  37. }
  38. var index = args.Index;
  39. async void TryTakeMagnetOffer()
  40. {
  41. try
  42. {
  43. await TakeMagnetOffer((station.Value, dataComp), index, (uid, component));
  44. }
  45. catch (Exception e)
  46. {
  47. _runtimeLog.LogException(e, $"{nameof(SalvageSystem)}.{nameof(TakeMagnetOffer)}");
  48. }
  49. }
  50. TryTakeMagnetOffer();
  51. }
  52. private void OnMagnetStartup(EntityUid uid, SalvageMagnetComponent component, ComponentStartup args)
  53. {
  54. UpdateMagnetUI((uid, component), Transform(uid));
  55. }
  56. private void OnMagnetAnchored(EntityUid uid, SalvageMagnetComponent component, ref AnchorStateChangedEvent args)
  57. {
  58. if (!args.Anchored)
  59. return;
  60. UpdateMagnetUI((uid, component), args.Transform);
  61. }
  62. private void OnMagnetDataMapInit(EntityUid uid, SalvageMagnetDataComponent component, ref MapInitEvent args)
  63. {
  64. CreateMagnetOffers((uid, component));
  65. }
  66. private void OnMagnetTargetSplit(EntityUid uid, SalvageMagnetTargetComponent component, ref GridSplitEvent args)
  67. {
  68. // Don't think I'm not onto you people splitting to make new grids.
  69. if (TryComp(component.DataTarget, out SalvageMagnetDataComponent? dataComp))
  70. {
  71. foreach (var gridUid in args.NewGrids)
  72. {
  73. dataComp.ActiveEntities?.Add(gridUid);
  74. }
  75. }
  76. }
  77. private void UpdateMagnet()
  78. {
  79. var dataQuery = EntityQueryEnumerator<SalvageMagnetDataComponent>();
  80. var curTime = _timing.CurTime;
  81. while (dataQuery.MoveNext(out var uid, out var magnetData))
  82. {
  83. // Magnet currently active.
  84. if (magnetData.EndTime != null)
  85. {
  86. if (magnetData.EndTime.Value < curTime)
  87. {
  88. EndMagnet((uid, magnetData));
  89. }
  90. else if (!magnetData.Announced && (magnetData.EndTime.Value - curTime).TotalSeconds < 31)
  91. {
  92. var magnet = GetMagnet((uid, magnetData));
  93. if (magnet != null)
  94. {
  95. Report(magnet.Value.Owner, MagnetChannel,
  96. "salvage-system-announcement-losing",
  97. ("timeLeft", (magnetData.EndTime.Value - curTime).Seconds));
  98. }
  99. magnetData.Announced = true;
  100. }
  101. }
  102. if (magnetData.NextOffer < curTime)
  103. {
  104. CreateMagnetOffers((uid, magnetData));
  105. }
  106. }
  107. }
  108. /// <summary>
  109. /// Ends the magnet attachment and deletes the relevant grids.
  110. /// </summary>
  111. private void EndMagnet(Entity<SalvageMagnetDataComponent> data)
  112. {
  113. if (data.Comp.ActiveEntities != null)
  114. {
  115. // Handle mobrestrictions getting deleted
  116. var query = AllEntityQuery<SalvageMobRestrictionsComponent>();
  117. while (query.MoveNext(out var salvUid, out var salvMob))
  118. {
  119. if (data.Comp.ActiveEntities.Contains(salvMob.LinkedEntity))
  120. {
  121. QueueDel(salvUid);
  122. }
  123. }
  124. // Uhh yeah don't delete mobs or whatever
  125. var mobQuery = AllEntityQuery<MobStateComponent, TransformComponent>();
  126. _detachEnts.Clear();
  127. while (mobQuery.MoveNext(out var mobUid, out _, out var xform))
  128. {
  129. if (xform.GridUid == null || !data.Comp.ActiveEntities.Contains(xform.GridUid.Value) || xform.MapUid == null)
  130. continue;
  131. if (_salvMobQuery.HasComp(mobUid))
  132. continue;
  133. bool CheckParents(EntityUid uid)
  134. {
  135. do
  136. {
  137. uid = _transform.GetParentUid(uid);
  138. if (_mobStateQuery.HasComp(uid))
  139. return true;
  140. }
  141. while (uid != xform.GridUid && uid != EntityUid.Invalid);
  142. return false;
  143. }
  144. if (CheckParents(mobUid))
  145. continue;
  146. // Can't parent directly to map as it runs grid traversal.
  147. _detachEnts.Add(((mobUid, xform), xform.MapUid.Value, _transform.GetWorldPosition(xform)));
  148. _transform.DetachEntity(mobUid, xform);
  149. }
  150. // Go and cleanup the active ents.
  151. foreach (var ent in data.Comp.ActiveEntities)
  152. {
  153. Del(ent);
  154. }
  155. foreach (var entity in _detachEnts)
  156. {
  157. _transform.SetCoordinates(entity.Entity.Owner, new EntityCoordinates(entity.MapUid, entity.LocalPosition));
  158. }
  159. data.Comp.ActiveEntities = null;
  160. }
  161. data.Comp.EndTime = null;
  162. UpdateMagnetUIs(data);
  163. }
  164. private void CreateMagnetOffers(Entity<SalvageMagnetDataComponent> data)
  165. {
  166. data.Comp.Offered.Clear();
  167. for (var i = 0; i < data.Comp.OfferCount; i++)
  168. {
  169. var seed = _random.Next();
  170. // Fuck with the seed to mix wrecks and asteroids.
  171. seed = (int)(seed / 10f) * 10;
  172. if (i >= data.Comp.OfferCount / 2)
  173. {
  174. seed++;
  175. }
  176. data.Comp.Offered.Add(seed);
  177. }
  178. data.Comp.NextOffer = _timing.CurTime + data.Comp.OfferCooldown;
  179. UpdateMagnetUIs(data);
  180. }
  181. // Just need something to announce.
  182. private Entity<SalvageMagnetComponent>? GetMagnet(Entity<SalvageMagnetDataComponent> data)
  183. {
  184. var query = AllEntityQuery<SalvageMagnetComponent, TransformComponent>();
  185. while (query.MoveNext(out var magnetUid, out var magnet, out var xform))
  186. {
  187. var stationUid = _station.GetOwningStation(magnetUid, xform);
  188. if (stationUid != data.Owner)
  189. continue;
  190. return (magnetUid, magnet);
  191. }
  192. return null;
  193. }
  194. private void UpdateMagnetUI(Entity<SalvageMagnetComponent> entity, TransformComponent xform)
  195. {
  196. var station = _station.GetOwningStation(entity, xform);
  197. if (!TryComp(station, out SalvageMagnetDataComponent? dataComp))
  198. return;
  199. _ui.SetUiState(entity.Owner, SalvageMagnetUiKey.Key,
  200. new SalvageMagnetBoundUserInterfaceState(dataComp.Offered)
  201. {
  202. Cooldown = dataComp.OfferCooldown,
  203. Duration = dataComp.ActiveTime,
  204. EndTime = dataComp.EndTime,
  205. NextOffer = dataComp.NextOffer,
  206. ActiveSeed = dataComp.ActiveSeed,
  207. });
  208. }
  209. private void UpdateMagnetUIs(Entity<SalvageMagnetDataComponent> data)
  210. {
  211. var query = AllEntityQuery<SalvageMagnetComponent, TransformComponent>();
  212. while (query.MoveNext(out var magnetUid, out var magnet, out var xform))
  213. {
  214. var station = _station.GetOwningStation(magnetUid, xform);
  215. if (station != data.Owner)
  216. continue;
  217. _ui.SetUiState(magnetUid, SalvageMagnetUiKey.Key,
  218. new SalvageMagnetBoundUserInterfaceState(data.Comp.Offered)
  219. {
  220. Cooldown = data.Comp.OfferCooldown,
  221. Duration = data.Comp.ActiveTime,
  222. EndTime = data.Comp.EndTime,
  223. NextOffer = data.Comp.NextOffer,
  224. ActiveSeed = data.Comp.ActiveSeed,
  225. });
  226. }
  227. }
  228. private async Task TakeMagnetOffer(Entity<SalvageMagnetDataComponent> data, int index, Entity<SalvageMagnetComponent> magnet)
  229. {
  230. var seed = data.Comp.Offered[index];
  231. var offering = GetSalvageOffering(seed);
  232. var salvMap = _mapSystem.CreateMap();
  233. var salvMapXform = Transform(salvMap);
  234. // Set values while awaiting asteroid dungeon if relevant so we can't double-take offers.
  235. data.Comp.ActiveSeed = seed;
  236. data.Comp.EndTime = _timing.CurTime + data.Comp.ActiveTime;
  237. data.Comp.NextOffer = data.Comp.EndTime.Value;
  238. UpdateMagnetUIs(data);
  239. switch (offering)
  240. {
  241. case AsteroidOffering asteroid:
  242. var grid = _mapManager.CreateGridEntity(salvMap);
  243. await _dungeon.GenerateDungeonAsync(asteroid.DungeonConfig, grid.Owner, grid.Comp, Vector2i.Zero, seed);
  244. break;
  245. case DebrisOffering debris:
  246. var debrisProto = _prototypeManager.Index<DungeonConfigPrototype>(debris.Id);
  247. var debrisGrid = _mapManager.CreateGridEntity(salvMap);
  248. await _dungeon.GenerateDungeonAsync(debrisProto, debrisGrid.Owner, debrisGrid.Comp, Vector2i.Zero, seed);
  249. break;
  250. case SalvageOffering wreck:
  251. var salvageProto = wreck.SalvageMap;
  252. if (!_loader.TryLoadGrid(salvMapXform.MapID, salvageProto.MapPath, out _))
  253. {
  254. Report(magnet, MagnetChannel, "salvage-system-announcement-spawn-debris-disintegrated");
  255. _mapSystem.DeleteMap(salvMapXform.MapID);
  256. return;
  257. }
  258. break;
  259. default:
  260. throw new ArgumentOutOfRangeException();
  261. }
  262. Box2? bounds = null;
  263. if (salvMapXform.ChildCount == 0)
  264. {
  265. Report(magnet.Owner, MagnetChannel, "salvage-system-announcement-spawn-no-debris-available");
  266. return;
  267. }
  268. var mapChildren = salvMapXform.ChildEnumerator;
  269. while (mapChildren.MoveNext(out var mapChild))
  270. {
  271. // If something went awry in dungen.
  272. if (!_gridQuery.TryGetComponent(mapChild, out var childGrid))
  273. continue;
  274. var childAABB = _transform.GetWorldMatrix(mapChild).TransformBox(childGrid.LocalAABB);
  275. bounds = bounds?.Union(childAABB) ?? childAABB;
  276. // Update mass scanner names as relevant.
  277. if (offering is AsteroidOffering or DebrisOffering)
  278. {
  279. _metaData.SetEntityName(mapChild, Loc.GetString("salvage-asteroid-name"));
  280. _gravity.EnableGravity(mapChild);
  281. }
  282. }
  283. var magnetXform = _xformQuery.GetComponent(magnet.Owner);
  284. var magnetGridUid = magnetXform.GridUid;
  285. var attachedBounds = new Box2Rotated();
  286. var mapId = MapId.Nullspace;
  287. Angle worldAngle;
  288. if (magnetGridUid != null)
  289. {
  290. var magnetGridXform = _xformQuery.GetComponent(magnetGridUid.Value);
  291. var (gridPos, gridRot) = _transform.GetWorldPositionRotation(magnetGridXform);
  292. var gridAABB = _gridQuery.GetComponent(magnetGridUid.Value).LocalAABB;
  293. attachedBounds = new Box2Rotated(gridAABB.Translated(gridPos), gridRot, gridPos);
  294. worldAngle = (gridRot + magnetXform.LocalRotation) - MathF.PI / 2;
  295. mapId = magnetGridXform.MapID;
  296. }
  297. else
  298. {
  299. worldAngle = _random.NextAngle();
  300. }
  301. if (!TryGetSalvagePlacementLocation(magnet, mapId, attachedBounds, bounds!.Value, worldAngle, out var spawnLocation, out var spawnAngle))
  302. {
  303. Report(magnet.Owner, MagnetChannel, "salvage-system-announcement-spawn-no-debris-available");
  304. _mapSystem.DeleteMap(salvMapXform.MapID);
  305. return;
  306. }
  307. // I have no idea if we want to return on failure or not
  308. // but I assume trying to set the parent with a null value wouldn't have worked out anyways
  309. if (!_mapSystem.TryGetMap(spawnLocation.MapId, out var spawnUid))
  310. return;
  311. data.Comp.ActiveEntities = null;
  312. mapChildren = salvMapXform.ChildEnumerator;
  313. // It worked, move it into position and cleanup values.
  314. while (mapChildren.MoveNext(out var mapChild))
  315. {
  316. var salvXForm = _xformQuery.GetComponent(mapChild);
  317. var localPos = salvXForm.LocalPosition;
  318. _transform.SetParent(mapChild, salvXForm, spawnUid.Value);
  319. _transform.SetWorldPositionRotation(mapChild, spawnLocation.Position + localPos, spawnAngle, salvXForm);
  320. data.Comp.ActiveEntities ??= new List<EntityUid>();
  321. data.Comp.ActiveEntities?.Add(mapChild);
  322. // Handle mob restrictions
  323. var children = salvXForm.ChildEnumerator;
  324. while (children.MoveNext(out var child))
  325. {
  326. if (!_salvMobQuery.TryGetComponent(child, out var salvMob))
  327. continue;
  328. salvMob.LinkedEntity = mapChild;
  329. }
  330. }
  331. Report(magnet.Owner, MagnetChannel, "salvage-system-announcement-arrived", ("timeLeft", data.Comp.ActiveTime.TotalSeconds));
  332. _mapSystem.DeleteMap(salvMapXform.MapID);
  333. data.Comp.Announced = false;
  334. var active = new SalvageMagnetActivatedEvent()
  335. {
  336. Magnet = magnet,
  337. };
  338. RaiseLocalEvent(ref active);
  339. }
  340. private bool TryGetSalvagePlacementLocation(Entity<SalvageMagnetComponent> magnet, MapId mapId, Box2Rotated attachedBounds, Box2 bounds, Angle worldAngle, out MapCoordinates coords, out Angle angle)
  341. {
  342. var attachedAABB = attachedBounds.CalcBoundingBox();
  343. var magnetPos = _transform.GetWorldPosition(magnet) + worldAngle.ToVec() * bounds.MaxDimension;
  344. var origin = attachedAABB.ClosestPoint(magnetPos);
  345. var fraction = 0.50f;
  346. // Thanks 20kdc
  347. for (var i = 0; i < 20; i++)
  348. {
  349. var randomPos = origin +
  350. worldAngle.ToVec() * (magnet.Comp.MagnetSpawnDistance * fraction) +
  351. (worldAngle + Math.PI / 2).ToVec() * _random.NextFloat(-magnet.Comp.LateralOffset, magnet.Comp.LateralOffset);
  352. var finalCoords = new MapCoordinates(randomPos, mapId);
  353. angle = _random.NextAngle();
  354. var box2 = Box2.CenteredAround(finalCoords.Position, bounds.Size);
  355. var box2Rot = new Box2Rotated(box2, angle, finalCoords.Position);
  356. // This doesn't stop it from spawning on top of random things in space
  357. // Might be better like this, ghosts could stop it before
  358. if (_mapManager.FindGridsIntersecting(finalCoords.MapId, box2Rot).Any())
  359. {
  360. // Bump it further and further just in case.
  361. fraction += 0.1f;
  362. continue;
  363. }
  364. coords = finalCoords;
  365. return true;
  366. }
  367. angle = Angle.Zero;
  368. coords = MapCoordinates.Nullspace;
  369. return false;
  370. }
  371. }
  372. [ByRefEvent]
  373. public record struct SalvageMagnetActivatedEvent
  374. {
  375. public EntityUid Magnet;
  376. }