1
0

ActionsSystem.cs 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356
  1. using System.IO;
  2. using System.Linq;
  3. using Content.Shared.Actions;
  4. using JetBrains.Annotations;
  5. using Robust.Client.Player;
  6. using Robust.Shared.ContentPack;
  7. using Robust.Shared.GameStates;
  8. using Robust.Shared.Input.Binding;
  9. using Robust.Shared.Player;
  10. using Robust.Shared.Serialization.Manager;
  11. using Robust.Shared.Serialization.Markdown;
  12. using Robust.Shared.Serialization.Markdown.Mapping;
  13. using Robust.Shared.Serialization.Markdown.Sequence;
  14. using Robust.Shared.Serialization.Markdown.Value;
  15. using Robust.Shared.Utility;
  16. using YamlDotNet.RepresentationModel;
  17. namespace Content.Client.Actions
  18. {
  19. [UsedImplicitly]
  20. public sealed class ActionsSystem : SharedActionsSystem
  21. {
  22. public delegate void OnActionReplaced(EntityUid actionId);
  23. [Dependency] private readonly IPlayerManager _playerManager = default!;
  24. [Dependency] private readonly IResourceManager _resources = default!;
  25. [Dependency] private readonly ISerializationManager _serialization = default!;
  26. [Dependency] private readonly MetaDataSystem _metaData = default!;
  27. public event Action<EntityUid>? OnActionAdded;
  28. public event Action<EntityUid>? OnActionRemoved;
  29. public event Action? ActionsUpdated;
  30. public event Action<ActionsComponent>? LinkActions;
  31. public event Action? UnlinkActions;
  32. public event Action? ClearAssignments;
  33. public event Action<List<SlotAssignment>>? AssignSlot;
  34. private readonly List<EntityUid> _removed = new();
  35. private readonly List<(EntityUid, BaseActionComponent?)> _added = new();
  36. public override void Initialize()
  37. {
  38. base.Initialize();
  39. SubscribeLocalEvent<ActionsComponent, LocalPlayerAttachedEvent>(OnPlayerAttached);
  40. SubscribeLocalEvent<ActionsComponent, LocalPlayerDetachedEvent>(OnPlayerDetached);
  41. SubscribeLocalEvent<ActionsComponent, ComponentHandleState>(HandleComponentState);
  42. SubscribeLocalEvent<InstantActionComponent, ComponentHandleState>(OnInstantHandleState);
  43. SubscribeLocalEvent<EntityTargetActionComponent, ComponentHandleState>(OnEntityTargetHandleState);
  44. SubscribeLocalEvent<WorldTargetActionComponent, ComponentHandleState>(OnWorldTargetHandleState);
  45. SubscribeLocalEvent<EntityWorldTargetActionComponent, ComponentHandleState>(OnEntityWorldTargetHandleState);
  46. }
  47. public override void FrameUpdate(float frameTime)
  48. {
  49. base.FrameUpdate(frameTime);
  50. var worldActionQuery = EntityQueryEnumerator<WorldTargetActionComponent>();
  51. while (worldActionQuery.MoveNext(out var uid, out var action))
  52. {
  53. UpdateAction(uid, action);
  54. }
  55. var instantActionQuery = EntityQueryEnumerator<InstantActionComponent>();
  56. while (instantActionQuery.MoveNext(out var uid, out var action))
  57. {
  58. UpdateAction(uid, action);
  59. }
  60. var entityActionQuery = EntityQueryEnumerator<EntityTargetActionComponent>();
  61. while (entityActionQuery.MoveNext(out var uid, out var action))
  62. {
  63. UpdateAction(uid, action);
  64. }
  65. }
  66. private void OnInstantHandleState(EntityUid uid, InstantActionComponent component, ref ComponentHandleState args)
  67. {
  68. if (args.Current is not InstantActionComponentState state)
  69. return;
  70. BaseHandleState<InstantActionComponent>(uid, component, state);
  71. }
  72. private void OnEntityTargetHandleState(EntityUid uid, EntityTargetActionComponent component, ref ComponentHandleState args)
  73. {
  74. if (args.Current is not EntityTargetActionComponentState state)
  75. return;
  76. component.Whitelist = state.Whitelist;
  77. component.Blacklist = state.Blacklist;
  78. component.CanTargetSelf = state.CanTargetSelf;
  79. BaseHandleState<EntityTargetActionComponent>(uid, component, state);
  80. }
  81. private void OnWorldTargetHandleState(EntityUid uid, WorldTargetActionComponent component, ref ComponentHandleState args)
  82. {
  83. if (args.Current is not WorldTargetActionComponentState state)
  84. return;
  85. BaseHandleState<WorldTargetActionComponent>(uid, component, state);
  86. }
  87. private void OnEntityWorldTargetHandleState(EntityUid uid,
  88. EntityWorldTargetActionComponent component,
  89. ref ComponentHandleState args)
  90. {
  91. if (args.Current is not EntityWorldTargetActionComponentState state)
  92. return;
  93. component.Whitelist = state.Whitelist;
  94. component.CanTargetSelf = state.CanTargetSelf;
  95. BaseHandleState<EntityWorldTargetActionComponent>(uid, component, state);
  96. }
  97. private void BaseHandleState<T>(EntityUid uid, BaseActionComponent component, BaseActionComponentState state) where T : BaseActionComponent
  98. {
  99. // TODO ACTIONS use auto comp states
  100. component.Icon = state.Icon;
  101. component.IconOn = state.IconOn;
  102. component.IconColor = state.IconColor;
  103. component.OriginalIconColor = state.OriginalIconColor;
  104. component.DisabledIconColor = state.DisabledIconColor;
  105. component.Keywords.Clear();
  106. component.Keywords.UnionWith(state.Keywords);
  107. component.Enabled = state.Enabled;
  108. component.Toggled = state.Toggled;
  109. component.Cooldown = state.Cooldown;
  110. component.UseDelay = state.UseDelay;
  111. component.Charges = state.Charges;
  112. component.MaxCharges = state.MaxCharges;
  113. component.RenewCharges = state.RenewCharges;
  114. component.Container = EnsureEntity<T>(state.Container, uid);
  115. component.EntityIcon = EnsureEntity<T>(state.EntityIcon, uid);
  116. component.CheckCanInteract = state.CheckCanInteract;
  117. component.CheckConsciousness = state.CheckConsciousness;
  118. component.ClientExclusive = state.ClientExclusive;
  119. component.Priority = state.Priority;
  120. component.AttachedEntity = EnsureEntity<T>(state.AttachedEntity, uid);
  121. component.RaiseOnUser = state.RaiseOnUser;
  122. component.RaiseOnAction = state.RaiseOnAction;
  123. component.AutoPopulate = state.AutoPopulate;
  124. component.Temporary = state.Temporary;
  125. component.ItemIconStyle = state.ItemIconStyle;
  126. component.Sound = state.Sound;
  127. UpdateAction(uid, component);
  128. }
  129. public override void UpdateAction(EntityUid? actionId, BaseActionComponent? action = null)
  130. {
  131. if (!ResolveActionData(actionId, ref action))
  132. return;
  133. action.IconColor = action.Charges < 1 ? action.DisabledIconColor : action.OriginalIconColor;
  134. base.UpdateAction(actionId, action);
  135. if (_playerManager.LocalEntity != action.AttachedEntity)
  136. return;
  137. ActionsUpdated?.Invoke();
  138. }
  139. private void HandleComponentState(EntityUid uid, ActionsComponent component, ref ComponentHandleState args)
  140. {
  141. if (args.Current is not ActionsComponentState state)
  142. return;
  143. _added.Clear();
  144. _removed.Clear();
  145. var stateEnts = EnsureEntitySet<ActionsComponent>(state.Actions, uid);
  146. foreach (var act in component.Actions)
  147. {
  148. if (!stateEnts.Contains(act) && !IsClientSide(act))
  149. _removed.Add(act);
  150. }
  151. component.Actions.ExceptWith(_removed);
  152. foreach (var actionId in stateEnts)
  153. {
  154. if (!actionId.IsValid())
  155. continue;
  156. if (!component.Actions.Add(actionId))
  157. continue;
  158. TryGetActionData(actionId, out var action);
  159. _added.Add((actionId, action));
  160. }
  161. if (_playerManager.LocalEntity != uid)
  162. return;
  163. foreach (var action in _removed)
  164. {
  165. OnActionRemoved?.Invoke(action);
  166. }
  167. _added.Sort(ActionComparer);
  168. foreach (var action in _added)
  169. {
  170. OnActionAdded?.Invoke(action.Item1);
  171. }
  172. ActionsUpdated?.Invoke();
  173. }
  174. public static int ActionComparer((EntityUid, BaseActionComponent?) a, (EntityUid, BaseActionComponent?) b)
  175. {
  176. var priorityA = a.Item2?.Priority ?? 0;
  177. var priorityB = b.Item2?.Priority ?? 0;
  178. if (priorityA != priorityB)
  179. return priorityA - priorityB;
  180. priorityA = a.Item2?.Container?.Id ?? 0;
  181. priorityB = b.Item2?.Container?.Id ?? 0;
  182. return priorityA - priorityB;
  183. }
  184. protected override void ActionAdded(EntityUid performer, EntityUid actionId, ActionsComponent comp,
  185. BaseActionComponent action)
  186. {
  187. if (_playerManager.LocalEntity != performer)
  188. return;
  189. OnActionAdded?.Invoke(actionId);
  190. }
  191. protected override void ActionRemoved(EntityUid performer, EntityUid actionId, ActionsComponent comp, BaseActionComponent action)
  192. {
  193. if (_playerManager.LocalEntity != performer)
  194. return;
  195. OnActionRemoved?.Invoke(actionId);
  196. }
  197. public IEnumerable<(EntityUid Id, BaseActionComponent Comp)> GetClientActions()
  198. {
  199. if (_playerManager.LocalEntity is not { } user)
  200. return Enumerable.Empty<(EntityUid, BaseActionComponent)>();
  201. return GetActions(user);
  202. }
  203. private void OnPlayerAttached(EntityUid uid, ActionsComponent component, LocalPlayerAttachedEvent args)
  204. {
  205. LinkAllActions(component);
  206. }
  207. private void OnPlayerDetached(EntityUid uid, ActionsComponent component, LocalPlayerDetachedEvent? args = null)
  208. {
  209. UnlinkAllActions();
  210. }
  211. public void UnlinkAllActions()
  212. {
  213. UnlinkActions?.Invoke();
  214. }
  215. public void LinkAllActions(ActionsComponent? actions = null)
  216. {
  217. if (_playerManager.LocalEntity is not { } user ||
  218. !Resolve(user, ref actions, false))
  219. {
  220. return;
  221. }
  222. LinkActions?.Invoke(actions);
  223. }
  224. public override void Shutdown()
  225. {
  226. base.Shutdown();
  227. CommandBinds.Unregister<ActionsSystem>();
  228. }
  229. public void TriggerAction(EntityUid actionId, BaseActionComponent action)
  230. {
  231. if (_playerManager.LocalEntity is not { } user ||
  232. !TryComp(user, out ActionsComponent? actions))
  233. {
  234. return;
  235. }
  236. if (action is not InstantActionComponent instantAction)
  237. return;
  238. if (action.ClientExclusive)
  239. {
  240. PerformAction(user, actions, actionId, instantAction, instantAction.Event, GameTiming.CurTime);
  241. }
  242. else
  243. {
  244. var request = new RequestPerformActionEvent(GetNetEntity(actionId));
  245. EntityManager.RaisePredictiveEvent(request);
  246. }
  247. }
  248. /// <summary>
  249. /// Load actions and their toolbar assignments from a file.
  250. /// </summary>
  251. public void LoadActionAssignments(string path, bool userData)
  252. {
  253. if (_playerManager.LocalEntity is not { } user)
  254. return;
  255. var file = new ResPath(path).ToRootedPath();
  256. TextReader reader = userData
  257. ? _resources.UserData.OpenText(file)
  258. : _resources.ContentFileReadText(file);
  259. var yamlStream = new YamlStream();
  260. yamlStream.Load(reader);
  261. if (yamlStream.Documents[0].RootNode.ToDataNode() is not SequenceDataNode sequence)
  262. return;
  263. ClearAssignments?.Invoke();
  264. var assignments = new List<SlotAssignment>();
  265. foreach (var entry in sequence.Sequence)
  266. {
  267. if (entry is not MappingDataNode map)
  268. continue;
  269. if (!map.TryGet("action", out var actionNode))
  270. continue;
  271. var action = _serialization.Read<BaseActionComponent>(actionNode, notNullableOverride: true);
  272. var actionId = Spawn();
  273. AddComp(actionId, action);
  274. AddActionDirect(user, actionId);
  275. if (map.TryGet<ValueDataNode>("name", out var nameNode))
  276. _metaData.SetEntityName(actionId, nameNode.Value);
  277. if (!map.TryGet("assignments", out var assignmentNode))
  278. continue;
  279. var nodeAssignments = _serialization.Read<List<(byte Hotbar, byte Slot)>>(assignmentNode, notNullableOverride: true);
  280. foreach (var index in nodeAssignments)
  281. {
  282. var assignment = new SlotAssignment(index.Hotbar, index.Slot, actionId);
  283. assignments.Add(assignment);
  284. }
  285. }
  286. AssignSlot?.Invoke(assignments);
  287. }
  288. public record struct SlotAssignment(byte Hotbar, byte Slot, EntityUid ActionId);
  289. }
  290. }