XenoArch [Science Overhaul] (#12204)
* multi-node xeno artifacts * refactor existing artifact effects * more tweaks to generation * more shit plus fix tests * more generation stuff plus threat levels * doink * now make it build * defer the artifact activation to not cause errors also pricing * some changes * all of the yaml + ui stuff for artifact analyzer * machine linking and starting to make the ui functional * artifact analyzer display * a shit ton of artifact analyzer stuff * more changes; making destroy work properly; progress bar tweaks * getting shit going! ALL RIGHT * small tweaks that didn't help much * Komm susser todd: the end of analysis * recipes and hints and ui, oh my! * add some in-game sources gotta prepare for day 1 launch * node data + ditch random seed in place of id * bunch of triggers * finish off the last few triggers * implement machine examine verb * knock, flicker, blink, throw * shatter, foam, shuffle, heat * fix all the shit i broke * *some* of these have to be good, no? 25 effects * callin' it there for effects * comments + reword some trigger hints * don't mind this little commit here * byref event * fix brokey node entry * fix low pressure trigger * mirror review plus fixing 0x40's bug also the throw artifact threw incorrectly * randomize the event message a teeny bit
This commit is contained in:
@@ -0,0 +1,198 @@
|
||||
using System.Linq;
|
||||
using Content.Server.Xenoarchaeology.XenoArtifacts.Events;
|
||||
using Content.Shared.Xenoarchaeology.XenoArtifacts;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Random;
|
||||
using Robust.Shared.Serialization.Manager;
|
||||
|
||||
namespace Content.Server.Xenoarchaeology.XenoArtifacts;
|
||||
|
||||
public sealed partial class ArtifactSystem
|
||||
{
|
||||
[Dependency] private readonly IPrototypeManager _prototype = default!;
|
||||
[Dependency] private readonly IComponentFactory _componentFactory = default!;
|
||||
[Dependency] private readonly ISerializationManager _serialization = default!;
|
||||
|
||||
private const int MaxEdgesPerNode = 3;
|
||||
|
||||
/// <summary>
|
||||
/// Generate an Artifact tree with fully developed nodes.
|
||||
/// </summary>
|
||||
/// <param name="tree">The tree being generated.</param>
|
||||
/// <param name="nodeAmount">The amount of nodes it has.</param>
|
||||
private void GenerateArtifactNodeTree(ref ArtifactTree tree, int nodeAmount)
|
||||
{
|
||||
if (nodeAmount < 1)
|
||||
{
|
||||
Logger.Error($"nodeAmount {nodeAmount} is less than 1. Aborting artifact tree generation.");
|
||||
return;
|
||||
}
|
||||
|
||||
var uninitializedNodes = new List<ArtifactNode> { new() };
|
||||
tree.StartNode = uninitializedNodes.First(); //the first node
|
||||
|
||||
while (uninitializedNodes.Any())
|
||||
{
|
||||
GenerateNode(ref uninitializedNodes, ref tree, nodeAmount);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Generate an individual node on the tree.
|
||||
/// </summary>
|
||||
private void GenerateNode(ref List<ArtifactNode> uninitializedNodes, ref ArtifactTree tree, int targetNodeAmount)
|
||||
{
|
||||
if (!uninitializedNodes.Any())
|
||||
return;
|
||||
|
||||
var node = uninitializedNodes.First();
|
||||
uninitializedNodes.Remove(node);
|
||||
|
||||
node.Id = _random.Next(0, 10000);
|
||||
|
||||
//Generate the connected nodes
|
||||
var maxEdges = Math.Max(1, targetNodeAmount - tree.AllNodes.Count - uninitializedNodes.Count - 1);
|
||||
maxEdges = Math.Min(maxEdges, MaxEdgesPerNode);
|
||||
var minEdges = Math.Clamp(targetNodeAmount - tree.AllNodes.Count - uninitializedNodes.Count - 1, 0, 1);
|
||||
|
||||
var edgeAmount = _random.Next(minEdges, maxEdges);
|
||||
|
||||
for (var i = 0; i < edgeAmount; i++)
|
||||
{
|
||||
var neighbor = new ArtifactNode
|
||||
{
|
||||
Depth = node.Depth + 1
|
||||
};
|
||||
node.Edges.Add(neighbor);
|
||||
neighbor.Edges.Add(node);
|
||||
|
||||
uninitializedNodes.Add(neighbor);
|
||||
}
|
||||
|
||||
node.Trigger = GetRandomTrigger(ref node);
|
||||
node.Effect = GetRandomEffect(ref node);
|
||||
|
||||
tree.AllNodes.Add(node);
|
||||
}
|
||||
|
||||
//yeah these two functions are near duplicates but i don't
|
||||
//want to implement an interface or abstract parent
|
||||
|
||||
private ArtifactTriggerPrototype GetRandomTrigger(ref ArtifactNode node)
|
||||
{
|
||||
var allTriggers = _prototype.EnumeratePrototypes<ArtifactTriggerPrototype>().ToList();
|
||||
var validDepth = allTriggers.Select(x => x.TargetDepth).Distinct().ToList();
|
||||
|
||||
var weights = GetDepthWeights(validDepth, node.Depth);
|
||||
var selectedRandomTargetDepth = GetRandomTargetDepth(weights);
|
||||
var targetTriggers = allTriggers.Where(x =>
|
||||
x.TargetDepth == selectedRandomTargetDepth).ToList();
|
||||
|
||||
return _random.Pick(targetTriggers);
|
||||
}
|
||||
|
||||
private ArtifactEffectPrototype GetRandomEffect(ref ArtifactNode node)
|
||||
{
|
||||
var allEffects = _prototype.EnumeratePrototypes<ArtifactEffectPrototype>().ToList();
|
||||
var validDepth = allEffects.Select(x => x.TargetDepth).Distinct().ToList();
|
||||
|
||||
var weights = GetDepthWeights(validDepth, node.Depth);
|
||||
var selectedRandomTargetDepth = GetRandomTargetDepth(weights);
|
||||
var targetEffects = allEffects.Where(x =>
|
||||
x.TargetDepth == selectedRandomTargetDepth).ToList();
|
||||
|
||||
return _random.Pick(targetEffects);
|
||||
}
|
||||
|
||||
/// <remarks>
|
||||
/// The goal is that the depth that is closest to targetDepth has the highest chance of appearing.
|
||||
/// The issue is that we also want some variance, so levels that are +/- 1 should also have a
|
||||
/// decent shot of appearing. This function should probably get some tweaking at some point.
|
||||
/// </remarks>
|
||||
private Dictionary<int, float> GetDepthWeights(IEnumerable<int> depths, int targetDepth)
|
||||
{
|
||||
var weights = new Dictionary<int, float>();
|
||||
foreach (var d in depths)
|
||||
{
|
||||
//TODO: is this equation sus? idk. -emo
|
||||
// 0.3 / (|current_iterated_depth - our_actual_depth| + 1)^2
|
||||
var w = 0.3f / MathF.Pow(Math.Abs(d - targetDepth) + 1, 2);
|
||||
weights.Add(d, w);
|
||||
}
|
||||
return weights;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Uses a weighted random system to get a random depth.
|
||||
/// </summary>
|
||||
private int GetRandomTargetDepth(Dictionary<int, float> weights)
|
||||
{
|
||||
var sum = weights.Values.Sum();
|
||||
var accumulated = 0f;
|
||||
|
||||
var rand = _random.NextFloat() * sum;
|
||||
|
||||
foreach (var (key, weight) in weights)
|
||||
{
|
||||
accumulated += weight;
|
||||
|
||||
if (accumulated >= rand)
|
||||
{
|
||||
return key;
|
||||
}
|
||||
}
|
||||
return _random.Pick(weights.Keys); //shouldn't happen
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Enter a node: attach the relevant components
|
||||
/// </summary>
|
||||
private void EnterNode(EntityUid uid, ref ArtifactNode node, ArtifactComponent? component = null)
|
||||
{
|
||||
if (!Resolve(uid, ref component))
|
||||
return;
|
||||
|
||||
if (component.CurrentNode != null)
|
||||
{
|
||||
ExitNode(uid, component);
|
||||
}
|
||||
|
||||
component.CurrentNode = node;
|
||||
node.Discovered = true;
|
||||
|
||||
var allComponents = node.Effect.Components.Concat(node.Effect.PermanentComponents).Concat(node.Trigger.Components);
|
||||
foreach (var (name, entry) in allComponents)
|
||||
{
|
||||
var reg = _componentFactory.GetRegistration(name);
|
||||
var comp = (Component) _componentFactory.GetComponent(reg);
|
||||
comp.Owner = uid;
|
||||
|
||||
var temp = (object) comp;
|
||||
_serialization.Copy(entry.Component, ref temp);
|
||||
EntityManager.AddComponent(uid, (Component) temp!, true);
|
||||
}
|
||||
|
||||
RaiseLocalEvent(uid, new ArtifactNodeEnteredEvent(component.CurrentNode.Id));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Exit a node: remove the relevant components.
|
||||
/// </summary>
|
||||
private void ExitNode(EntityUid uid, ArtifactComponent? component = null)
|
||||
{
|
||||
if (!Resolve(uid, ref component))
|
||||
return;
|
||||
|
||||
var node = component.CurrentNode;
|
||||
if (node == null)
|
||||
return;
|
||||
|
||||
foreach (var name in node.Effect.Components.Keys.Concat(node.Trigger.Components.Keys))
|
||||
{
|
||||
var comp = _componentFactory.GetRegistration(name);
|
||||
EntityManager.RemoveComponentDeferred(uid, comp.Type);
|
||||
}
|
||||
|
||||
component.CurrentNode = null;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user