1
0

SharpSystem.cs 7.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199
  1. using Content.Server.Body.Systems;
  2. using Content.Server.Kitchen.Components;
  3. using Content.Server.Nutrition.EntitySystems;
  4. using Content.Shared.Body.Components;
  5. using Content.Shared.Administration.Logs;
  6. using Content.Shared.Database;
  7. using Content.Shared.Interaction;
  8. using Content.Shared.Nutrition.Components;
  9. using Content.Shared.Popups;
  10. using Content.Shared.Storage;
  11. using Content.Shared.Verbs;
  12. using Content.Shared.Destructible;
  13. using Content.Shared.DoAfter;
  14. using Content.Shared.Hands.Components;
  15. using Content.Shared.Kitchen;
  16. using Content.Shared.Mobs.Components;
  17. using Content.Shared.Mobs.Systems;
  18. using Robust.Server.Containers;
  19. using Robust.Server.GameObjects;
  20. using Robust.Shared.Random;
  21. using Robust.Shared.Utility;
  22. namespace Content.Server.Kitchen.EntitySystems;
  23. public sealed class SharpSystem : EntitySystem
  24. {
  25. [Dependency] private readonly BodySystem _bodySystem = default!;
  26. [Dependency] private readonly SharedDestructibleSystem _destructibleSystem = default!;
  27. [Dependency] private readonly SharedDoAfterSystem _doAfterSystem = default!;
  28. [Dependency] private readonly SharedPopupSystem _popupSystem = default!;
  29. [Dependency] private readonly ContainerSystem _containerSystem = default!;
  30. [Dependency] private readonly MobStateSystem _mobStateSystem = default!;
  31. [Dependency] private readonly TransformSystem _transform = default!;
  32. [Dependency] private readonly IRobustRandom _robustRandom = default!;
  33. [Dependency] private readonly ISharedAdminLogManager _adminLogger = default!;
  34. public override void Initialize()
  35. {
  36. base.Initialize();
  37. SubscribeLocalEvent<SharpComponent, AfterInteractEvent>(OnAfterInteract, before: [typeof(UtensilSystem)]);
  38. SubscribeLocalEvent<SharpComponent, SharpDoAfterEvent>(OnDoAfter);
  39. SubscribeLocalEvent<ButcherableComponent, GetVerbsEvent<InteractionVerb>>(OnGetInteractionVerbs);
  40. }
  41. private void OnAfterInteract(EntityUid uid, SharpComponent component, AfterInteractEvent args)
  42. {
  43. if (args.Handled || args.Target is null || !args.CanReach)
  44. return;
  45. if (TryStartButcherDoafter(uid, args.Target.Value, args.User))
  46. args.Handled = true;
  47. }
  48. private bool TryStartButcherDoafter(EntityUid knife, EntityUid target, EntityUid user)
  49. {
  50. if (!TryComp<ButcherableComponent>(target, out var butcher))
  51. return false;
  52. if (!TryComp<SharpComponent>(knife, out var sharp))
  53. return false;
  54. if (TryComp<MobStateComponent>(target, out var mobState) && !_mobStateSystem.IsDead(target, mobState))
  55. return false;
  56. if (butcher.Type != ButcheringType.Knife && target != user)
  57. {
  58. _popupSystem.PopupEntity(Loc.GetString("butcherable-different-tool", ("target", target)), knife, user);
  59. return false;
  60. }
  61. if (!sharp.Butchering.Add(target))
  62. return false;
  63. // if the user isn't the entity with the sharp component,
  64. // they will need to be holding something with their hands, so we set needHand to true
  65. // so that the doafter can be interrupted if they drop the item in their hands
  66. var needHand = user != knife;
  67. var doAfter =
  68. new DoAfterArgs(EntityManager, user, sharp.ButcherDelayModifier * butcher.ButcherDelay, new SharpDoAfterEvent(), knife, target: target, used: knife)
  69. {
  70. BreakOnDamage = true,
  71. BreakOnMove = true,
  72. NeedHand = needHand,
  73. };
  74. _doAfterSystem.TryStartDoAfter(doAfter);
  75. return true;
  76. }
  77. private void OnDoAfter(EntityUid uid, SharpComponent component, DoAfterEvent args)
  78. {
  79. if (args.Handled || !TryComp<ButcherableComponent>(args.Args.Target, out var butcher))
  80. return;
  81. if (args.Cancelled)
  82. {
  83. component.Butchering.Remove(args.Args.Target.Value);
  84. return;
  85. }
  86. component.Butchering.Remove(args.Args.Target.Value);
  87. if (_containerSystem.IsEntityInContainer(args.Args.Target.Value))
  88. {
  89. args.Handled = true;
  90. return;
  91. }
  92. var spawnEntities = EntitySpawnCollection.GetSpawns(butcher.SpawnedEntities, _robustRandom);
  93. var coords = _transform.GetMapCoordinates(args.Args.Target.Value);
  94. EntityUid popupEnt = default!;
  95. foreach (var proto in spawnEntities)
  96. {
  97. // distribute the spawned items randomly in a small radius around the origin
  98. popupEnt = Spawn(proto, coords.Offset(_robustRandom.NextVector2(0.25f)));
  99. }
  100. var hasBody = TryComp<BodyComponent>(args.Args.Target.Value, out var body);
  101. // only show a big popup when butchering living things.
  102. var popupType = PopupType.Small;
  103. if (hasBody)
  104. popupType = PopupType.LargeCaution;
  105. _popupSystem.PopupEntity(Loc.GetString("butcherable-knife-butchered-success", ("target", args.Args.Target.Value), ("knife", uid)),
  106. popupEnt, args.Args.User, popupType);
  107. if (hasBody)
  108. _bodySystem.GibBody(args.Args.Target.Value, body: body);
  109. _destructibleSystem.DestroyEntity(args.Args.Target.Value);
  110. args.Handled = true;
  111. _adminLogger.Add(LogType.Gib,
  112. $"{EntityManager.ToPrettyString(args.User):user} " +
  113. $"has butchered {EntityManager.ToPrettyString(args.Target):target} " +
  114. $"with {EntityManager.ToPrettyString(args.Used):knife}");
  115. }
  116. private void OnGetInteractionVerbs(EntityUid uid, ButcherableComponent component, GetVerbsEvent<InteractionVerb> args)
  117. {
  118. if (component.Type != ButcheringType.Knife || !args.CanAccess || !args.CanInteract)
  119. return;
  120. // if the user has no hands, don't show them the verb if they have no SharpComponent either
  121. if (!TryComp<SharpComponent>(args.User, out var userSharpComp) && args.Hands == null)
  122. return;
  123. var disabled = false;
  124. string? message = null;
  125. // if the user has hands
  126. // and the item they're holding doesn't have the SharpComponent
  127. // disable the verb
  128. if (!TryComp<SharpComponent>(args.Using, out var usingSharpComp) && args.Hands != null)
  129. {
  130. disabled = true;
  131. message = Loc.GetString("butcherable-need-knife",
  132. ("target", uid));
  133. }
  134. else if (_containerSystem.IsEntityInContainer(uid))
  135. {
  136. disabled = true;
  137. message = Loc.GetString("butcherable-not-in-container",
  138. ("target", uid));
  139. }
  140. else if (TryComp<MobStateComponent>(uid, out var state) && !_mobStateSystem.IsDead(uid, state))
  141. {
  142. disabled = true;
  143. message = Loc.GetString("butcherable-mob-isnt-dead");
  144. }
  145. // set the object doing the butchering to the item in the user's hands or to the user themselves
  146. // if either has the SharpComponent
  147. EntityUid sharpObject = default;
  148. if (usingSharpComp != null)
  149. sharpObject = args.Using!.Value;
  150. else if (userSharpComp != null)
  151. sharpObject = args.User;
  152. InteractionVerb verb = new()
  153. {
  154. Act = () =>
  155. {
  156. if (!disabled)
  157. TryStartButcherDoafter(sharpObject, args.Target, args.User);
  158. },
  159. Message = message,
  160. Disabled = disabled,
  161. Icon = new SpriteSpecifier.Texture(new ("/Textures/Interface/VerbIcons/cutlery.svg.192dpi.png")),
  162. Text = Loc.GetString("butcherable-verb-name"),
  163. };
  164. args.Verbs.Add(verb);
  165. }
  166. }