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:
Nemanja
2022-11-06 18:05:44 -05:00
committed by GitHub
parent 0d4a605a94
commit 273e0968e4
107 changed files with 3321 additions and 358 deletions

View File

@@ -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;
}
}