NPCBlackboard.cs 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355
  1. using System.Collections;
  2. using System.Diagnostics.CodeAnalysis;
  3. using Content.Server.Interaction;
  4. using Content.Shared.Access.Systems;
  5. using Content.Shared.ActionBlocker;
  6. using Content.Shared.Hands.Components;
  7. using Content.Shared.Interaction;
  8. using Content.Shared.Inventory;
  9. using JetBrains.Annotations;
  10. using Robust.Shared.Utility;
  11. namespace Content.Server.NPC;
  12. [DataDefinition]
  13. public sealed partial class NPCBlackboard : IEnumerable<KeyValuePair<string, object>>
  14. {
  15. /// <summary>
  16. /// Global defaults for NPCs
  17. /// </summary>
  18. private static readonly Dictionary<string, object> BlackboardDefaults = new()
  19. {
  20. {"BufferRange", 10f},
  21. {"FollowCloseRange", 3f},
  22. {"FollowRange", 7f},
  23. {"FleeRange", 12f},
  24. {"IdleRange", 7f},
  25. {"InteractRange", SharedInteractionSystem.InteractionRange},
  26. {"MaximumIdleTime", 7f},
  27. {MedibotInjectRange, 4f},
  28. {MeleeMissChance, 0.3f},
  29. {"MeleeRange", 1f},
  30. {"PreadatorAttackRange", 7f},
  31. {"PredatorWarnRange", 10f},
  32. {"MinimumIdleTime", 2f},
  33. {"MovementRangeClose", 0.2f},
  34. {"MovementRange", 1.5f},
  35. {"RangedRange", 10f},
  36. {"RotateSpeed", float.MaxValue},
  37. {"VisionRadius", 10f},
  38. {"AggroVisionRadius", 10f},
  39. };
  40. /// <summary>
  41. /// The specific blackboard for this NPC.
  42. /// </summary>
  43. private readonly Dictionary<string, object> _blackboard = new();
  44. /// <summary>
  45. /// Should we allow setting values on the blackboard. This is true when we are planning.
  46. /// <remarks>
  47. /// The effects get stored separately so they can potentially be re-applied during execution.
  48. /// </remarks>
  49. /// </summary>
  50. public bool ReadOnly = false;
  51. public void Clear()
  52. {
  53. _blackboard.Clear();
  54. }
  55. public NPCBlackboard ShallowClone()
  56. {
  57. var dict = new NPCBlackboard();
  58. foreach (var item in _blackboard)
  59. {
  60. dict.SetValue(item.Key, item.Value);
  61. }
  62. return dict;
  63. }
  64. [Pure]
  65. public bool ContainsKey(string key)
  66. {
  67. return _blackboard.ContainsKey(key);
  68. }
  69. /// <summary>
  70. /// Get the blackboard data for a particular key.
  71. /// </summary>
  72. [Pure]
  73. public T GetValue<T>(string key)
  74. {
  75. return (T)_blackboard[key];
  76. }
  77. /// <summary>
  78. /// Tries to get the blackboard data for a particular key. Returns default if not found
  79. /// </summary>
  80. [Pure]
  81. public T? GetValueOrDefault<T>(string key, IEntityManager entManager)
  82. {
  83. if (_blackboard.TryGetValue(key, out var value))
  84. {
  85. return (T)value;
  86. }
  87. if (TryGetEntityDefault(key, out value, entManager))
  88. {
  89. return (T)value;
  90. }
  91. if (BlackboardDefaults.TryGetValue(key, out value))
  92. {
  93. return (T)value;
  94. }
  95. return default;
  96. }
  97. /// <summary>
  98. /// Tries to get the blackboard data for a particular key.
  99. /// </summary>
  100. public bool TryGetValue<T>(string key, [NotNullWhen(true)] out T? value, IEntityManager entManager)
  101. {
  102. if (_blackboard.TryGetValue(key, out var data))
  103. {
  104. value = (T)data;
  105. return true;
  106. }
  107. if (TryGetEntityDefault(key, out data, entManager))
  108. {
  109. value = (T)data;
  110. return true;
  111. }
  112. if (BlackboardDefaults.TryGetValue(key, out data))
  113. {
  114. value = (T)data;
  115. return true;
  116. }
  117. value = default;
  118. return false;
  119. }
  120. public void SetValue(string key, object value)
  121. {
  122. if (ReadOnly)
  123. {
  124. AssertReadonly();
  125. return;
  126. }
  127. _blackboard[key] = value;
  128. }
  129. private void AssertReadonly()
  130. {
  131. DebugTools.Assert(false, $"Tried to write to an NPC blackboard that is readonly!");
  132. }
  133. private bool TryGetEntityDefault(string key, [NotNullWhen(true)] out object? value, IEntityManager entManager)
  134. {
  135. value = default;
  136. EntityUid owner;
  137. switch (key)
  138. {
  139. case Access:
  140. {
  141. if (!TryGetValue(Owner, out owner, entManager))
  142. {
  143. return false;
  144. }
  145. var access = entManager.EntitySysManager.GetEntitySystem<AccessReaderSystem>();
  146. value = access.FindAccessTags(owner);
  147. return true;
  148. }
  149. case ActiveHand:
  150. {
  151. if (!TryGetValue(Owner, out owner, entManager) ||
  152. !entManager.TryGetComponent<HandsComponent>(owner, out var hands) ||
  153. hands.ActiveHand == null)
  154. {
  155. return false;
  156. }
  157. value = hands.ActiveHand;
  158. return true;
  159. }
  160. case ActiveHandFree:
  161. {
  162. if (!TryGetValue(Owner, out owner, entManager) ||
  163. !entManager.TryGetComponent<HandsComponent>(owner, out var hands) ||
  164. hands.ActiveHand == null)
  165. {
  166. return false;
  167. }
  168. value = hands.ActiveHand.IsEmpty;
  169. return true;
  170. }
  171. case CanMove:
  172. {
  173. if (!TryGetValue(Owner, out owner, entManager))
  174. {
  175. return false;
  176. }
  177. var blocker = entManager.EntitySysManager.GetEntitySystem<ActionBlockerSystem>();
  178. value = blocker.CanMove(owner);
  179. return true;
  180. }
  181. case FreeHands:
  182. {
  183. if (!TryGetValue(Owner, out owner, entManager) ||
  184. !entManager.TryGetComponent<HandsComponent>(owner, out var hands) ||
  185. hands.ActiveHand == null)
  186. {
  187. return false;
  188. }
  189. var handos = new List<string>();
  190. foreach (var (id, hand) in hands.Hands)
  191. {
  192. if (!hand.IsEmpty)
  193. continue;
  194. handos.Add(id);
  195. }
  196. value = handos;
  197. return true;
  198. }
  199. case Inventory:
  200. {
  201. if (!TryGetValue(Owner, out owner, entManager) ||
  202. !entManager.TryGetComponent<HandsComponent>(owner, out var hands) ||
  203. hands.ActiveHand == null)
  204. {
  205. return false;
  206. }
  207. var handos = new List<string>();
  208. foreach (var (id, hand) in hands.Hands)
  209. {
  210. if (!hand.IsEmpty)
  211. continue;
  212. handos.Add(id);
  213. }
  214. value = handos;
  215. return true;
  216. }
  217. case OwnerCoordinates:
  218. {
  219. if (!TryGetValue(Owner, out owner, entManager))
  220. {
  221. return false;
  222. }
  223. if (entManager.TryGetComponent<TransformComponent>(owner, out var xform))
  224. {
  225. value = xform.Coordinates;
  226. return true;
  227. }
  228. return false;
  229. }
  230. default:
  231. return false;
  232. }
  233. }
  234. public bool Remove<T>(string key)
  235. {
  236. DebugTools.Assert(!_blackboard.ContainsKey(key) || _blackboard[key] is T);
  237. return _blackboard.Remove(key);
  238. }
  239. public string GetVisionRadiusKey(IEntityManager entMan)
  240. {
  241. return TryGetValue<EntityUid>("Target", out _, entMan)
  242. ? AggroVisionRadius
  243. : VisionRadius;
  244. }
  245. // I Ummd and Ahhd about using strings vs enums and decided on tags because
  246. // if a fork wants to do their own thing they don't need to touch the enum.
  247. /*
  248. * Constants to make development easier
  249. */
  250. public const string Access = "Access";
  251. public const string ActiveHand = "ActiveHand";
  252. public const string ActiveHandFree = "ActiveHandFree";
  253. public const string CanMove = "CanMove";
  254. public const string FreeHands = "FreeHands";
  255. public const string FollowTarget = "FollowTarget";
  256. public const string Inventory = "Inventory";
  257. public const string MedibotInjectRange = "MedibotInjectRange";
  258. public const string MeleeMissChance = "MeleeMissChance";
  259. public const string Owner = "Owner";
  260. public const string OwnerCoordinates = "OwnerCoordinates";
  261. public const string MovementTarget = "MovementTarget";
  262. /// <summary>
  263. /// Can the NPC click open entities such as doors.
  264. /// </summary>
  265. public const string NavInteract = "NavInteract";
  266. /// <summary>
  267. /// Can the NPC pry open doors for steering.
  268. /// </summary>
  269. public const string NavPry = "NavPry";
  270. /// <summary>
  271. /// Can the NPC smash obstacles for steering.
  272. /// </summary>
  273. public const string NavSmash = "NavSmash";
  274. /// <summary>
  275. /// Can the NPC climb obstacles for steering.
  276. /// </summary>
  277. public const string NavClimb = "NavClimb";
  278. /// <summary>
  279. /// Default key storage for a movement pathfind.
  280. /// </summary>
  281. public const string PathfindKey = "MovementPathfind";
  282. public const string RotateSpeed = "RotateSpeed";
  283. public const string UtilityTarget = "UtilityTarget";
  284. private const string VisionRadius = "VisionRadius";
  285. private const string AggroVisionRadius = "AggroVisionRadius";
  286. /// <summary>
  287. /// A configurable "order" enum that can be given to an NPC from an external source.
  288. /// </summary>
  289. public const string CurrentOrders = "CurrentOrders";
  290. /// <summary>
  291. /// A configurable target that's ordered by external sources.
  292. /// </summary>
  293. public const string CurrentOrderedTarget = "CurrentOrderedTarget";
  294. public IEnumerator<KeyValuePair<string, object>> GetEnumerator()
  295. {
  296. return _blackboard.GetEnumerator();
  297. }
  298. IEnumerator IEnumerable.GetEnumerator()
  299. {
  300. return GetEnumerator();
  301. }
  302. }