ArtifactSystem.cs 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299
  1. using System.Diagnostics.CodeAnalysis;
  2. using System.Linq;
  3. using Content.Server.Cargo.Systems;
  4. using Content.Server.GameTicking;
  5. using Content.Server.Power.EntitySystems;
  6. using Content.Server.Xenoarchaeology.Equipment.Components;
  7. using Content.Server.Xenoarchaeology.XenoArtifacts.Events;
  8. using Content.Server.Xenoarchaeology.XenoArtifacts.Triggers.Components;
  9. using Content.Shared.CCVar;
  10. using Content.Shared.Xenoarchaeology.XenoArtifacts;
  11. using JetBrains.Annotations;
  12. using Robust.Shared.Audio;
  13. using Robust.Shared.Audio.Systems;
  14. using Robust.Shared.Configuration;
  15. using Robust.Shared.Prototypes;
  16. using Robust.Shared.Random;
  17. using Robust.Shared.Serialization.Manager;
  18. using Robust.Shared.Timing;
  19. namespace Content.Server.Xenoarchaeology.XenoArtifacts;
  20. public sealed partial class ArtifactSystem : EntitySystem
  21. {
  22. [Dependency] private readonly IComponentFactory _componentFactory = default!;
  23. [Dependency] private readonly IGameTiming _gameTiming = default!;
  24. [Dependency] private readonly IPrototypeManager _prototype = default!;
  25. [Dependency] private readonly IRobustRandom _random = default!;
  26. [Dependency] private readonly ISerializationManager _serialization = default!;
  27. [Dependency] private readonly SharedAudioSystem _audio = default!;
  28. public override void Initialize()
  29. {
  30. base.Initialize();
  31. SubscribeLocalEvent<ArtifactComponent, PriceCalculationEvent>(GetPrice);
  32. InitializeCommands();
  33. InitializeActions();
  34. }
  35. /// <summary>
  36. /// Calculates the price of an artifact based on
  37. /// how many nodes have been unlocked/triggered
  38. /// </summary>
  39. /// <remarks>
  40. /// General balancing (for fully unlocked artifacts):
  41. /// Simple (1-2 Nodes): 1-2K
  42. /// Medium (5-8 Nodes): 6-7K
  43. /// Complex (7-12 Nodes): 10-11K
  44. /// </remarks>
  45. private void GetPrice(EntityUid uid, ArtifactComponent component, ref PriceCalculationEvent args)
  46. {
  47. args.Price += (GetResearchPointValue(uid, component) + component.ConsumedPoints) * component.PriceMultiplier;
  48. }
  49. /// <summary>
  50. /// Calculates how many research points the artifact is worth
  51. /// </summary>
  52. /// <remarks>
  53. /// General balancing (for fully unlocked artifacts):
  54. /// Simple (1-2 Nodes): ~10K
  55. /// Medium (5-8 Nodes): ~30-40K
  56. /// Complex (7-12 Nodes): ~60-80K
  57. ///
  58. /// Simple artifacts should be enough to unlock a few techs.
  59. /// Medium should get you partway through a tree.
  60. /// Complex should get you through a full tree and then some.
  61. /// </remarks>
  62. public int GetResearchPointValue(EntityUid uid, ArtifactComponent? component = null, bool getMaxPrice = false)
  63. {
  64. if (!Resolve(uid, ref component))
  65. return 0;
  66. var sumValue = component.NodeTree.Sum(n => GetNodePointValue(n, component, getMaxPrice));
  67. var fullyExploredBonus = component.NodeTree.All(x => x.Triggered) || getMaxPrice ? 1.25f : 1;
  68. return (int) (sumValue * fullyExploredBonus) - component.ConsumedPoints;
  69. }
  70. /// <summary>
  71. /// Adjusts how many points on the artifact have been consumed
  72. /// </summary>
  73. public void AdjustConsumedPoints(EntityUid uid, int amount, ArtifactComponent? component = null)
  74. {
  75. if (!Resolve(uid, ref component))
  76. return;
  77. component.ConsumedPoints += amount;
  78. }
  79. /// <summary>
  80. /// Sets whether or not the artifact is suppressed,
  81. /// preventing it from activating
  82. /// </summary>
  83. public void SetIsSuppressed(EntityUid uid, bool suppressed, ArtifactComponent? component = null)
  84. {
  85. if (!Resolve(uid, ref component))
  86. return;
  87. component.IsSuppressed = suppressed;
  88. }
  89. /// <summary>
  90. /// Gets the point value for an individual node
  91. /// </summary>
  92. private float GetNodePointValue(ArtifactNode node, ArtifactComponent component, bool getMaxPrice = false)
  93. {
  94. var valueDeduction = 1f;
  95. if (!getMaxPrice)
  96. {
  97. if (!node.Discovered)
  98. return 0;
  99. valueDeduction = !node.Triggered ? 0.25f : 1;
  100. }
  101. var triggerProto = _prototype.Index<ArtifactTriggerPrototype>(node.Trigger);
  102. var effectProto = _prototype.Index<ArtifactEffectPrototype>(node.Effect);
  103. var nodeDanger = (node.Depth + effectProto.TargetDepth + triggerProto.TargetDepth) / 3;
  104. return component.PointsPerNode * MathF.Pow(component.PointDangerMultiplier, nodeDanger) * valueDeduction;
  105. }
  106. /// <summary>
  107. /// Randomize a given artifact.
  108. /// </summary>
  109. [PublicAPI]
  110. public void RandomizeArtifact(EntityUid uid, ArtifactComponent component)
  111. {
  112. var nodeAmount = _random.Next(component.NodesMin, component.NodesMax);
  113. GenerateArtifactNodeTree(uid, component.NodeTree, nodeAmount);
  114. var firstNode = GetRootNode(component.NodeTree);
  115. EnterNode(uid, ref firstNode, component);
  116. }
  117. /// <summary>
  118. /// Tries to activate the artifact
  119. /// </summary>
  120. /// <param name="uid"></param>
  121. /// <param name="user"></param>
  122. /// <param name="component"></param>
  123. /// <param name="logMissing">Set this to false if you don't know if the entity is an artifact.</param>
  124. /// <returns></returns>
  125. public bool TryActivateArtifact(EntityUid uid, EntityUid? user = null, ArtifactComponent? component = null, bool logMissing = true)
  126. {
  127. if (!Resolve(uid, ref component, logMissing))
  128. return false;
  129. // check if artifact is under suppression field
  130. if (component.IsSuppressed)
  131. return false;
  132. // check if artifact isn't under cooldown
  133. var timeDif = _gameTiming.CurTime - component.LastActivationTime;
  134. if (timeDif < component.CooldownTime)
  135. return false;
  136. ForceActivateArtifact(uid, user, component);
  137. return true;
  138. }
  139. /// <summary>
  140. /// Forces an artifact to activate
  141. /// </summary>
  142. /// <param name="uid"></param>
  143. /// <param name="user"></param>
  144. /// <param name="component"></param>
  145. public void ForceActivateArtifact(EntityUid uid, EntityUid? user = null, ArtifactComponent? component = null)
  146. {
  147. if (!Resolve(uid, ref component))
  148. return;
  149. if (component.CurrentNodeId == null)
  150. return;
  151. _audio.PlayPvs(component.ActivationSound, uid);
  152. component.LastActivationTime = _gameTiming.CurTime;
  153. var ev = new ArtifactActivatedEvent
  154. {
  155. Activator = user
  156. };
  157. RaiseLocalEvent(uid, ev, true);
  158. var currentNode = GetNodeFromId(component.CurrentNodeId.Value, component);
  159. currentNode.Triggered = true;
  160. if (currentNode.Edges.Count == 0)
  161. return;
  162. var newNode = GetNewNode(uid, component);
  163. if (newNode == null)
  164. return;
  165. EnterNode(uid, ref newNode, component);
  166. }
  167. private ArtifactNode? GetNewNode(EntityUid uid, ArtifactComponent component)
  168. {
  169. if (component.CurrentNodeId == null)
  170. return null;
  171. var currentNode = GetNodeFromId(component.CurrentNodeId.Value, component);
  172. var allNodes = currentNode.Edges;
  173. Log.Debug($"our node: {currentNode.Id}");
  174. Log.Debug($"other nodes: {string.Join(", ", allNodes)}");
  175. if (TryComp<BiasedArtifactComponent>(uid, out var bias) &&
  176. TryComp<TraversalDistorterComponent>(bias.Provider, out var trav) &&
  177. this.IsPowered(bias.Provider, EntityManager))
  178. {
  179. switch (trav.BiasDirection)
  180. {
  181. case BiasDirection.Up:
  182. var upNodes = allNodes.Where(x => GetNodeFromId(x, component).Depth < currentNode.Depth).ToHashSet();
  183. if (upNodes.Count != 0)
  184. allNodes = upNodes;
  185. break;
  186. case BiasDirection.Down:
  187. var downNodes = allNodes.Where(x => GetNodeFromId(x, component).Depth > currentNode.Depth).ToHashSet();
  188. if (downNodes.Count != 0)
  189. allNodes = downNodes;
  190. break;
  191. }
  192. }
  193. var undiscoveredNodes = allNodes.Where(x => !GetNodeFromId(x, component).Discovered).ToList();
  194. Log.Debug($"Undiscovered nodes: {string.Join(", ", undiscoveredNodes)}");
  195. var newNode = _random.Pick(allNodes);
  196. if (undiscoveredNodes.Count != 0 && _random.Prob(0.75f))
  197. {
  198. newNode = _random.Pick(undiscoveredNodes);
  199. }
  200. Log.Debug($"Going to node {newNode}");
  201. return GetNodeFromId(newNode, component);
  202. }
  203. /// <summary>
  204. /// Try and get a data object from a node
  205. /// </summary>
  206. /// <param name="uid">The entity you're getting the data from</param>
  207. /// <param name="key">The data's key</param>
  208. /// <param name="data">The data you are trying to get.</param>
  209. /// <param name="component"></param>
  210. /// <typeparam name="T"></typeparam>
  211. /// <returns></returns>
  212. public bool TryGetNodeData<T>(EntityUid uid, string key, [NotNullWhen(true)] out T? data, ArtifactComponent? component = null)
  213. {
  214. data = default;
  215. if (!Resolve(uid, ref component))
  216. return false;
  217. if (component.CurrentNodeId == null)
  218. return false;
  219. var currentNode = GetNodeFromId(component.CurrentNodeId.Value, component);
  220. if (currentNode.NodeData.TryGetValue(key, out var dat) && dat is T value)
  221. {
  222. data = value;
  223. return true;
  224. }
  225. return false;
  226. }
  227. /// <summary>
  228. /// Sets the node data to a certain value
  229. /// </summary>
  230. /// <param name="uid">The artifact</param>
  231. /// <param name="key">The key being set</param>
  232. /// <param name="value">The value it's being set to</param>
  233. /// <param name="component"></param>
  234. public void SetNodeData(EntityUid uid, string key, object value, ArtifactComponent? component = null)
  235. {
  236. if (!Resolve(uid, ref component))
  237. return;
  238. if (component.CurrentNodeId == null)
  239. return;
  240. var currentNode = GetNodeFromId(component.CurrentNodeId.Value, component);
  241. currentNode.NodeData[key] = value;
  242. }
  243. /// <summary>
  244. /// Gets the base node (depth 0) of an artifact's node graph
  245. /// </summary>
  246. /// <param name="allNodes"></param>
  247. /// <returns></returns>
  248. public ArtifactNode GetRootNode(List<ArtifactNode> allNodes)
  249. {
  250. return allNodes.First(n => n.Depth == 0);
  251. }
  252. }