ArtifactSystem.Nodes.cs 8.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244
  1. using System.Linq;
  2. using Content.Server.Xenoarchaeology.XenoArtifacts.Events;
  3. using Content.Shared.Whitelist;
  4. using Content.Shared.Xenoarchaeology.XenoArtifacts;
  5. using JetBrains.Annotations;
  6. using Robust.Shared.Random;
  7. namespace Content.Server.Xenoarchaeology.XenoArtifacts;
  8. public sealed partial class ArtifactSystem
  9. {
  10. [Dependency] private readonly EntityWhitelistSystem _whitelistSystem = default!;
  11. private const int MaxEdgesPerNode = 4;
  12. private readonly HashSet<int> _usedNodeIds = new();
  13. /// <summary>
  14. /// Generate an Artifact tree with fully developed nodes.
  15. /// </summary>
  16. /// <param name="artifact"></param>
  17. /// <param name="allNodes"></param>
  18. /// <param name="nodesToCreate">The amount of nodes it has.</param>
  19. private void GenerateArtifactNodeTree(EntityUid artifact, List<ArtifactNode> allNodes, int nodesToCreate)
  20. {
  21. if (nodesToCreate < 1)
  22. {
  23. Log.Error($"nodesToCreate {nodesToCreate} is less than 1. Aborting artifact tree generation.");
  24. return;
  25. }
  26. _usedNodeIds.Clear();
  27. var uninitializedNodes = new List<ArtifactNode> { new(){ Id = GetValidNodeId() } };
  28. var createdNodes = 1;
  29. while (uninitializedNodes.Count > 0)
  30. {
  31. var node = uninitializedNodes[0];
  32. uninitializedNodes.Remove(node);
  33. node.Trigger = GetRandomTrigger(artifact, ref node);
  34. node.Effect = GetRandomEffect(artifact, ref node);
  35. var maxChildren = _random.Next(1, MaxEdgesPerNode - 1);
  36. for (var i = 0; i < maxChildren; i++)
  37. {
  38. if (nodesToCreate <= createdNodes)
  39. {
  40. break;
  41. }
  42. var child = new ArtifactNode {Id = GetValidNodeId(), Depth = node.Depth + 1};
  43. node.Edges.Add(child.Id);
  44. child.Edges.Add(node.Id);
  45. uninitializedNodes.Add(child);
  46. createdNodes++;
  47. }
  48. allNodes.Add(node);
  49. }
  50. }
  51. private int GetValidNodeId()
  52. {
  53. var id = _random.Next(100, 1000);
  54. while (_usedNodeIds.Contains(id))
  55. {
  56. id = _random.Next(100, 1000);
  57. }
  58. _usedNodeIds.Add(id);
  59. return id;
  60. }
  61. //yeah these two functions are near duplicates but i don't
  62. //want to implement an interface or abstract parent
  63. private string GetRandomTrigger(EntityUid artifact, ref ArtifactNode node)
  64. {
  65. var allTriggers = _prototype.EnumeratePrototypes<ArtifactTriggerPrototype>()
  66. .Where(x => _whitelistSystem.IsWhitelistPassOrNull(x.Whitelist, artifact) &&
  67. _whitelistSystem.IsBlacklistFailOrNull(x.Blacklist, artifact)).ToList();
  68. var validDepth = allTriggers.Select(x => x.TargetDepth).Distinct().ToList();
  69. var weights = GetDepthWeights(validDepth, node.Depth);
  70. var selectedRandomTargetDepth = GetRandomTargetDepth(weights);
  71. var targetTriggers = allTriggers
  72. .Where(x => x.TargetDepth == selectedRandomTargetDepth).ToList();
  73. return _random.Pick(targetTriggers).ID;
  74. }
  75. private string GetRandomEffect(EntityUid artifact, ref ArtifactNode node)
  76. {
  77. var allEffects = _prototype.EnumeratePrototypes<ArtifactEffectPrototype>()
  78. .Where(x => _whitelistSystem.IsWhitelistPassOrNull(x.Whitelist, artifact) &&
  79. _whitelistSystem.IsBlacklistFailOrNull(x.Blacklist, artifact)).ToList();
  80. var validDepth = allEffects.Select(x => x.TargetDepth).Distinct().ToList();
  81. var weights = GetDepthWeights(validDepth, node.Depth);
  82. var selectedRandomTargetDepth = GetRandomTargetDepth(weights);
  83. var targetEffects = allEffects
  84. .Where(x => x.TargetDepth == selectedRandomTargetDepth).ToList();
  85. return _random.Pick(targetEffects).ID;
  86. }
  87. /// <remarks>
  88. /// The goal is that the depth that is closest to targetDepth has the highest chance of appearing.
  89. /// The issue is that we also want some variance, so levels that are +/- 1 should also have a
  90. /// decent shot of appearing. This function should probably get some tweaking at some point.
  91. /// </remarks>
  92. private Dictionary<int, float> GetDepthWeights(IEnumerable<int> depths, int targetDepth)
  93. {
  94. // this function is just a normal distribution with a
  95. // mean of target depth and standard deviation of 0.75
  96. var weights = new Dictionary<int, float>();
  97. foreach (var d in depths)
  98. {
  99. var w = 10f / (0.75f * MathF.Sqrt(2 * MathF.PI)) * MathF.Pow(MathF.E, -MathF.Pow((d - targetDepth) / 0.75f, 2));
  100. weights.Add(d, w);
  101. }
  102. return weights;
  103. }
  104. /// <summary>
  105. /// Uses a weighted random system to get a random depth.
  106. /// </summary>
  107. private int GetRandomTargetDepth(Dictionary<int, float> weights)
  108. {
  109. var sum = weights.Values.Sum();
  110. var accumulated = 0f;
  111. var rand = _random.NextFloat() * sum;
  112. foreach (var (key, weight) in weights)
  113. {
  114. accumulated += weight;
  115. if (accumulated >= rand)
  116. {
  117. return key;
  118. }
  119. }
  120. return _random.Pick(weights.Keys); //shouldn't happen
  121. }
  122. /// <summary>
  123. /// Enter a node: attach the relevant components
  124. /// </summary>
  125. private void EnterNode(EntityUid uid, ref ArtifactNode node, ArtifactComponent? component = null)
  126. {
  127. if (!Resolve(uid, ref component))
  128. return;
  129. if (component.CurrentNodeId != null)
  130. {
  131. ExitNode(uid, component);
  132. }
  133. component.CurrentNodeId = node.Id;
  134. var trigger = _prototype.Index<ArtifactTriggerPrototype>(node.Trigger);
  135. var effect = _prototype.Index<ArtifactEffectPrototype>(node.Effect);
  136. var allComponents = effect.Components.Concat(effect.PermanentComponents).Concat(trigger.Components);
  137. foreach (var (name, entry) in allComponents)
  138. {
  139. var reg = _componentFactory.GetRegistration(name);
  140. if (node.Discovered && EntityManager.HasComponent(uid, reg.Type))
  141. {
  142. // Don't re-add permanent components unless this is the first time you've entered this node
  143. if (effect.PermanentComponents.ContainsKey(name))
  144. continue;
  145. EntityManager.RemoveComponent(uid, reg.Type);
  146. }
  147. var comp = (Component)_componentFactory.GetComponent(reg);
  148. var temp = (object)comp;
  149. _serialization.CopyTo(entry.Component, ref temp);
  150. EntityManager.RemoveComponent(uid, temp!.GetType());
  151. EntityManager.AddComponent(uid, (Component)temp!);
  152. }
  153. node.Discovered = true;
  154. RaiseLocalEvent(uid, new ArtifactNodeEnteredEvent(component.CurrentNodeId.Value));
  155. }
  156. /// <summary>
  157. /// Exit a node: remove the relevant components.
  158. /// </summary>
  159. private void ExitNode(EntityUid uid, ArtifactComponent? component = null)
  160. {
  161. if (!Resolve(uid, ref component))
  162. return;
  163. if (component.CurrentNodeId == null)
  164. return;
  165. var currentNode = GetNodeFromId(component.CurrentNodeId.Value, component);
  166. var trigger = _prototype.Index<ArtifactTriggerPrototype>(currentNode.Trigger);
  167. var effect = _prototype.Index<ArtifactEffectPrototype>(currentNode.Effect);
  168. var entityPrototype = MetaData(uid).EntityPrototype;
  169. var toRemove = effect.Components.Keys.Concat(trigger.Components.Keys).ToList();
  170. foreach (var name in toRemove)
  171. {
  172. // if the entity prototype contained the component originally
  173. if (entityPrototype?.Components.TryGetComponent(name, out var entry) ?? false)
  174. {
  175. var comp = (Component)_componentFactory.GetComponent(name);
  176. var temp = (object)comp;
  177. _serialization.CopyTo(entry, ref temp);
  178. EntityManager.RemoveComponent(uid, temp!.GetType());
  179. EntityManager.AddComponent(uid, (Component)temp);
  180. continue;
  181. }
  182. EntityManager.RemoveComponentDeferred(uid, _componentFactory.GetRegistration(name).Type);
  183. }
  184. component.CurrentNodeId = null;
  185. }
  186. [PublicAPI]
  187. public ArtifactNode GetNodeFromId(int id, ArtifactComponent component)
  188. {
  189. return component.NodeTree.First(x => x.Id == id);
  190. }
  191. [PublicAPI]
  192. public ArtifactNode GetNodeFromId(int id, IEnumerable<ArtifactNode> nodes)
  193. {
  194. return nodes.First(x => x.Id == id);
  195. }
  196. }