1
0

SharedMortarSystem.cs 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447
  1. using Content.Shared._RMC14.Extensions;
  2. using Content.Shared.Administration.Logs;
  3. using Content.Shared.Construction.Components;
  4. using Content.Shared.Coordinates;
  5. using Content.Shared.Damage;
  6. using Content.Shared.Destructible;
  7. using Content.Shared.DoAfter;
  8. using Content.Shared.Examine;
  9. using Content.Shared.Interaction;
  10. using Content.Shared.Interaction.Events;
  11. using Content.Shared.Popups;
  12. using Content.Shared.UserInterface;
  13. using Content.Shared.Explosion.EntitySystems;
  14. using Robust.Shared.Audio.Systems;
  15. using Robust.Shared.Containers;
  16. using Robust.Shared.Map;
  17. using Robust.Shared.Network;
  18. using Robust.Shared.Physics.Systems;
  19. using Robust.Shared.Player;
  20. using Robust.Shared.Random;
  21. using Robust.Shared.Timing;
  22. namespace Content.Shared._RMC14.Mortar;
  23. public abstract class SharedMortarSystem : EntitySystem
  24. {
  25. [Dependency] private readonly ISharedAdminLogManager _adminLogs = default!;
  26. [Dependency] private readonly SharedAppearanceSystem _appearance = default!;
  27. [Dependency] private readonly SharedAudioSystem _audio = default!;
  28. [Dependency] private readonly SharedContainerSystem _container = default!;
  29. [Dependency] private readonly SharedDoAfterSystem _doAfter = default!;
  30. [Dependency] private readonly FixtureSystem _fixture = default!;
  31. [Dependency] private readonly MetaDataSystem _metaData = default!;
  32. [Dependency] private readonly INetManager _net = default!;
  33. [Dependency] private readonly SharedPhysicsSystem _physics = default!;
  34. [Dependency] private readonly SharedPopupSystem _popup = default!;
  35. [Dependency] private readonly ISharedPlayerManager _player = default!;
  36. [Dependency] private readonly IRobustRandom _random = default!;
  37. [Dependency] private readonly IGameTiming _timing = default!;
  38. [Dependency] private readonly SharedTransformSystem _transform = default!;
  39. [Dependency] private readonly SharedUserInterfaceSystem _ui = default!;
  40. [Dependency] private readonly SharedExplosionSystem _explosion = default!;
  41. private EntityQuery<TransformComponent> _transformQuery;
  42. public override void Initialize()
  43. {
  44. _transformQuery = GetEntityQuery<TransformComponent>();
  45. SubscribeLocalEvent<MortarComponent, UseInHandEvent>(OnMortarUseInHand, before: [typeof(ActivatableUISystem)]);
  46. SubscribeLocalEvent<MortarComponent, DeployMortarDoAfterEvent>(OnMortarDeployDoAfter);
  47. SubscribeLocalEvent<MortarComponent, TargetMortarDoAfterEvent>(OnMortarTargetDoAfter);
  48. SubscribeLocalEvent<MortarComponent, DialMortarDoAfterEvent>(OnMortarDialDoAfter);
  49. SubscribeLocalEvent<MortarComponent, InteractUsingEvent>(OnMortarInteractUsing);
  50. SubscribeLocalEvent<MortarComponent, LoadMortarShellDoAfterEvent>(OnMortarLoadDoAfter);
  51. SubscribeLocalEvent<MortarComponent, UnanchorAttemptEvent>(OnMortarUnanchorAttempt);
  52. SubscribeLocalEvent<MortarComponent, AnchorStateChangedEvent>(OnMortarAnchorStateChanged);
  53. SubscribeLocalEvent<MortarComponent, ExaminedEvent>(OnMortarExamined);
  54. SubscribeLocalEvent<MortarComponent, ActivatableUIOpenAttemptEvent>(OnMortarActivatableUIOpenAttempt);
  55. SubscribeLocalEvent<MortarComponent, CombatModeShouldHandInteractEvent>(OnMortarShouldInteract);
  56. SubscribeLocalEvent<MortarComponent, DestructionEventArgs>(OnMortarDestruction);
  57. SubscribeLocalEvent<MortarComponent, BeforeDamageChangedEvent>(OnMortarBeforeDamageChanged);
  58. Subs.BuiEvents<MortarComponent>(MortarUiKey.Key,
  59. subs =>
  60. {
  61. subs.Event<MortarTargetBuiMsg>(OnMortarTargetBui);
  62. subs.Event<MortarDialBuiMsg>(OnMortarDialBui);
  63. });
  64. }
  65. private void OnMortarBeforeDamageChanged(Entity<MortarComponent> ent, ref BeforeDamageChangedEvent args)
  66. {
  67. if (!ent.Comp.Deployed) // cannot destroy in item form
  68. args.Cancelled = true;
  69. }
  70. private void OnMortarDestruction(Entity<MortarComponent> mortar, ref DestructionEventArgs args)
  71. {
  72. if (!mortar.Comp.Deployed || _net.IsClient)
  73. return;
  74. SpawnAtPosition(mortar.Comp.Drop, mortar.Owner.ToCoordinates());
  75. }
  76. private void OnMortarUseInHand(Entity<MortarComponent> mortar, ref UseInHandEvent args)
  77. {
  78. args.Handled = true;
  79. DeployMortar(mortar, args.User);
  80. }
  81. private void OnMortarDeployDoAfter(Entity<MortarComponent> mortar, ref DeployMortarDoAfterEvent args)
  82. {
  83. var user = args.User;
  84. if (args.Cancelled || args.Handled)
  85. return;
  86. args.Handled = true;
  87. if (mortar.Comp.Deployed)
  88. return;
  89. if (!CanDeployPopup(mortar, user))
  90. return;
  91. mortar.Comp.Deployed = true;
  92. Dirty(mortar);
  93. if (_fixture.GetFixtureOrNull(mortar, mortar.Comp.FixtureId) is { } fixture)
  94. _physics.SetHard(mortar, fixture, true);
  95. _appearance.SetData(mortar, MortarVisualLayers.State, MortarVisuals.Deployed);
  96. var xform = Transform(mortar);
  97. var coordinates = _transform.GetMoverCoordinates(mortar, xform);
  98. var rotation = Transform(user).LocalRotation.GetCardinalDir().ToAngle();
  99. _transform.SetCoordinates(mortar, xform, coordinates, rotation);
  100. _transform.AnchorEntity((mortar, xform));
  101. _audio.PlayPredicted(mortar.Comp.DeploySound, mortar, user);
  102. }
  103. private void OnMortarTargetDoAfter(Entity<MortarComponent> mortar, ref TargetMortarDoAfterEvent args)
  104. {
  105. if (args.Cancelled || args.Handled)
  106. return;
  107. args.Handled = true;
  108. var user = args.User;
  109. var selfMsg = Loc.GetString("rmc-mortar-target-finish-self", ("mortar", mortar));
  110. var othersMsg = Loc.GetString("rmc-mortar-target-finish-others", ("user", user), ("mortar", mortar));
  111. _popup.PopupPredicted(selfMsg, othersMsg, user, user);
  112. if (_net.IsClient)
  113. return;
  114. var target = args.Vector;
  115. var position = _transform.GetMapCoordinates(mortar).Position;
  116. var offset = target;
  117. //if (_rmcPlanet.TryGetOffset(_transform.GetMapCoordinates(mortar.Owner), out var planetOffset))
  118. // offset -= planetOffset;
  119. // RMC uses a system to offset based on set values per map
  120. mortar.Comp.Target = target;
  121. var tilesPer = mortar.Comp.TilesPerOffset;
  122. var xOffset = (int) Math.Floor(Math.Abs(offset.X - position.X) / tilesPer);
  123. var yOffset = (int) Math.Floor(Math.Abs(offset.Y - position.Y) / tilesPer);
  124. mortar.Comp.Offset = (_random.Next(-xOffset, xOffset + 1), _random.Next(-yOffset, yOffset + 1));
  125. Dirty(mortar);
  126. }
  127. private void OnMortarDialDoAfter(Entity<MortarComponent> mortar, ref DialMortarDoAfterEvent args)
  128. {
  129. if (args.Cancelled || args.Handled)
  130. return;
  131. args.Handled = true;
  132. mortar.Comp.Dial = args.Vector;
  133. Dirty(mortar);
  134. var user = args.User;
  135. var selfMsg = Loc.GetString("rmc-mortar-dial-finish-self", ("mortar", mortar));
  136. var othersMsg = Loc.GetString("rmc-mortar-dial-finish-others", ("user", user), ("mortar", mortar));
  137. _popup.PopupPredicted(selfMsg, othersMsg, user, user);
  138. }
  139. private void OnMortarInteractUsing(Entity<MortarComponent> mortar, ref InteractUsingEvent args)
  140. {
  141. var shellId = args.Used;
  142. if (!TryComp(shellId, out MortarShellComponent? shell))
  143. return;
  144. args.Handled = true;
  145. var user = args.User;
  146. if (!CanLoadPopup(mortar, (shellId, shell), user, out _, out _))
  147. return;
  148. var ev = new LoadMortarShellDoAfterEvent();
  149. var doAfter = new DoAfterArgs(EntityManager, user, shell.LoadDelay, ev, mortar, mortar, shellId)
  150. {
  151. BreakOnMove = true,
  152. BreakOnHandChange = true,
  153. //ForceVisible = true,
  154. };
  155. if (_doAfter.TryStartDoAfter(doAfter))
  156. {
  157. var selfMsg = Loc.GetString("rmc-mortar-shell-load-start-self", ("mortar", mortar), ("shell", shellId));
  158. var othersMsg = Loc.GetString("rmc-mortar-shell-load-start-others",
  159. ("user", user),
  160. ("mortar", mortar),
  161. ("shell", shellId));
  162. _popup.PopupPredicted(selfMsg, othersMsg, mortar, user);
  163. _audio.PlayPredicted(mortar.Comp.ReloadSound, mortar, user);
  164. }
  165. }
  166. private void OnMortarLoadDoAfter(Entity<MortarComponent> mortar, ref LoadMortarShellDoAfterEvent args)
  167. {
  168. var user = args.User;
  169. if (args.Cancelled || args.Handled || args.Used is not { } shellId)
  170. return;
  171. args.Handled = true;
  172. if (_net.IsClient)
  173. return;
  174. if (!TryComp(shellId, out MortarShellComponent? shell))
  175. return;
  176. if (!mortar.Comp.Deployed)
  177. return;
  178. if (HasComp<ActiveMortarShellComponent>(shellId))
  179. return;
  180. if (!CanLoadPopup(mortar, (shellId, shell), user, out var travelTime, out var coordinates))
  181. return;
  182. var container = _container.EnsureContainer<Container>(mortar, mortar.Comp.ContainerId);
  183. if (!_container.Insert(shellId, container))
  184. return;
  185. var time = _timing.CurTime;
  186. mortar.Comp.LastFiredAt = time;
  187. var active = new ActiveMortarShellComponent
  188. {
  189. Coordinates = _transform.ToCoordinates(coordinates),
  190. WarnAt = time + travelTime,
  191. ImpactWarnAt = time + travelTime + shell.ImpactWarningDelay,
  192. LandAt = time + travelTime + shell.ImpactDelay,
  193. };
  194. AddComp(shellId, active, true);
  195. var selfMsg = Loc.GetString("rmc-mortar-shell-load-finish-self", ("mortar", mortar), ("shell", shellId));
  196. var othersMsg = Loc.GetString("rmc-mortar-shell-load-finish-others", ("user", user), ("mortar", mortar), ("shell", shellId));
  197. _popup.PopupPredicted(selfMsg, othersMsg, user, user);
  198. othersMsg = Loc.GetString("rmc-mortar-shell-fire", ("mortar", mortar));
  199. _popup.PopupEntity(othersMsg, mortar, PopupType.MediumCaution);
  200. var filter = Filter.Pvs(mortar);
  201. _audio.PlayPvs(mortar.Comp.FireSound, mortar);
  202. var ev = new MortarFiredEvent(GetNetEntity(mortar));
  203. if (_net.IsServer)
  204. RaiseNetworkEvent(ev, filter);
  205. }
  206. private void OnMortarUnanchorAttempt(Entity<MortarComponent> mortar, ref UnanchorAttemptEvent args)
  207. {
  208. if (args.Cancelled)
  209. return;
  210. }
  211. private void OnMortarAnchorStateChanged(Entity<MortarComponent> mortar, ref AnchorStateChangedEvent args)
  212. {
  213. if (args.Anchored)
  214. return;
  215. mortar.Comp.Deployed = false;
  216. Dirty(mortar);
  217. if (_fixture.GetFixtureOrNull(mortar, mortar.Comp.FixtureId) is { } fixture)
  218. _physics.SetHard(mortar, fixture, false);
  219. _appearance.SetData(mortar, MortarVisualLayers.State, MortarVisuals.Item);
  220. }
  221. private void OnMortarExamined(Entity<MortarComponent> ent, ref ExaminedEvent args)
  222. {
  223. using (args.PushGroup(nameof(MortarComponent)))
  224. {
  225. args.PushMarkup(Loc.GetString("rmc-mortar-less-accurate-with-range"));
  226. }
  227. }
  228. private void OnMortarActivatableUIOpenAttempt(Entity<MortarComponent> ent, ref ActivatableUIOpenAttemptEvent args)
  229. {
  230. if (args.Cancelled)
  231. return;
  232. if (!ent.Comp.Deployed)
  233. args.Cancel();
  234. }
  235. private void OnMortarShouldInteract(Entity<MortarComponent> ent, ref CombatModeShouldHandInteractEvent args)
  236. {
  237. args.Cancelled = true;
  238. }
  239. private void OnMortarTargetBui(Entity<MortarComponent> mortar, ref MortarTargetBuiMsg args)
  240. {
  241. args.Target.X.Cap(mortar.Comp.MaxTarget);
  242. args.Target.Y.Cap(mortar.Comp.MaxTarget);
  243. var user = args.Actor;
  244. var ev = new TargetMortarDoAfterEvent(args.Target);
  245. var doAfter = new DoAfterArgs(EntityManager, user, mortar.Comp.TargetDelay, ev, mortar)
  246. {
  247. BreakOnMove = true,
  248. };
  249. if (_doAfter.TryStartDoAfter(doAfter))
  250. {
  251. var selfMsg = Loc.GetString("rmc-mortar-target-start-self", ("mortar", mortar));
  252. var othersMsg = Loc.GetString("rmc-mortar-target-start-others", ("user", user), ("mortar", mortar));
  253. _popup.PopupPredicted(selfMsg, othersMsg, user, user);
  254. }
  255. }
  256. private void OnMortarDialBui(Entity<MortarComponent> mortar, ref MortarDialBuiMsg args)
  257. {
  258. args.Target.X.Cap(mortar.Comp.MaxDial);
  259. args.Target.Y.Cap(mortar.Comp.MaxDial);
  260. var user = args.Actor;
  261. var ev = new DialMortarDoAfterEvent(args.Target);
  262. var doAfter = new DoAfterArgs(EntityManager, user, mortar.Comp.TargetDelay, ev, mortar)
  263. {
  264. BreakOnMove = true,
  265. };
  266. if (_doAfter.TryStartDoAfter(doAfter))
  267. {
  268. var selfMsg = Loc.GetString("rmc-mortar-dial-start-self", ("mortar", mortar));
  269. var othersMsg = Loc.GetString("rmc-mortar-dial-start-others", ("user", user), ("mortar", mortar));
  270. _popup.PopupPredicted(selfMsg, othersMsg, user, user);
  271. }
  272. }
  273. private void DeployMortar(Entity<MortarComponent> mortar, EntityUid user)
  274. {
  275. if (mortar.Comp.Deployed)
  276. return;
  277. if (!CanDeployPopup(mortar, user))
  278. return;
  279. var ev = new DeployMortarDoAfterEvent();
  280. var args = new DoAfterArgs(EntityManager, user, mortar.Comp.DeployDelay, ev, mortar)
  281. {
  282. BreakOnMove = true,
  283. BreakOnHandChange = true,
  284. };
  285. if (_doAfter.TryStartDoAfter(args))
  286. _popup.PopupClient(Loc.GetString("rmc-mortar-deploy-start", ("mortar", mortar)), user, user);
  287. }
  288. private bool CanDeployPopup(Entity<MortarComponent> mortar, EntityUid user)
  289. {
  290. //if (!_area.CanMortarPlacement(user.ToCoordinates()))
  291. //{
  292. // _popup.PopupClient(Loc.GetString("rmc-mortar-covered", ("mortar", mortar)), user, user, PopupType.SmallCaution);
  293. // return false;
  294. //}
  295. // Need a system to check for a roof
  296. return true;
  297. }
  298. protected virtual bool CanLoadPopup(
  299. Entity<MortarComponent> mortar,
  300. Entity<MortarShellComponent> shell,
  301. EntityUid user,
  302. out TimeSpan travelTime,
  303. out MapCoordinates coordinates)
  304. {
  305. travelTime = default;
  306. coordinates = default;
  307. return false;
  308. }
  309. public void PopupWarning(MapCoordinates coordinates, float range, LocId warning, LocId warningAbove)
  310. {
  311. foreach (var session in _player.NetworkedSessions)
  312. {
  313. if (session.AttachedEntity is not { } recipient ||
  314. !_transformQuery.TryComp(recipient, out var xform) ||
  315. xform.MapID != coordinates.MapId)
  316. {
  317. continue;
  318. }
  319. var sessionCoordinates = _transform.GetMapCoordinates(xform);
  320. var distanceVec = (coordinates.Position - sessionCoordinates.Position);
  321. var distance = distanceVec.Length();
  322. if (distance > range)
  323. continue;
  324. var direction = distanceVec.GetDir().ToString().ToUpperInvariant();
  325. var msg = distance < 1
  326. ? Loc.GetString(warningAbove)
  327. : Loc.GetString(warning, ("direction", direction));
  328. _popup.PopupEntity(msg, recipient, recipient, PopupType.LargeCaution);
  329. }
  330. }
  331. public override void Update(float frameTime)
  332. {
  333. if (_net.IsClient)
  334. return;
  335. var time = _timing.CurTime;
  336. var shells = EntityQueryEnumerator<ActiveMortarShellComponent>();
  337. while (shells.MoveNext(out var uid, out var active))
  338. {
  339. if (!active.Warned && time >= active.WarnAt)
  340. {
  341. active.Warned = true;
  342. var coordinates = _transform.ToMapCoordinates(active.Coordinates);
  343. //PopupWarning(coordinates,
  344. // active.WarnRange,
  345. // "rmc-mortar-shell-warning",
  346. // "rmc-mortar-shell-warning-above");
  347. _audio.PlayPvs(active.WarnSound, active.Coordinates);
  348. }
  349. if (!active.ImpactWarned && time >= active.ImpactWarnAt)
  350. {
  351. active.ImpactWarned = true;
  352. var coordinates = _transform.ToMapCoordinates(active.Coordinates);
  353. //PopupWarning(coordinates,
  354. // active.WarnRange,
  355. // "rmc-mortar-shell-impact-warning",
  356. // "rmc-mortar-shell-impact-warning-above");
  357. }
  358. if (time >= active.LandAt)
  359. {
  360. _transform.SetCoordinates(uid, active.Coordinates);
  361. var ev = new MortarShellLandEvent(active.Coordinates);
  362. RaiseLocalEvent(uid, ref ev);
  363. _explosion.TriggerExplosive(uid);
  364. }
  365. }
  366. }
  367. }