396 lines
14 KiB
C#
396 lines
14 KiB
C#
using System.Linq;
|
|
using Content.Shared.EntityTable;
|
|
using Content.Shared.NameIdentifier;
|
|
using Content.Shared.Xenoarchaeology.Artifact.Components;
|
|
using Content.Shared.Xenoarchaeology.Artifact.Prototypes;
|
|
using Robust.Shared.Prototypes;
|
|
using Robust.Shared.Utility;
|
|
|
|
namespace Content.Shared.Xenoarchaeology.Artifact;
|
|
|
|
public abstract partial class SharedXenoArtifactSystem
|
|
{
|
|
[Dependency] private readonly EntityTableSystem _entityTable = default!;
|
|
|
|
private EntityQuery<XenoArtifactComponent> _xenoArtifactQuery;
|
|
private EntityQuery<XenoArtifactNodeComponent> _nodeQuery;
|
|
|
|
private void InitializeNode()
|
|
{
|
|
SubscribeLocalEvent<XenoArtifactNodeComponent, MapInitEvent>(OnNodeMapInit);
|
|
|
|
_xenoArtifactQuery = GetEntityQuery<XenoArtifactComponent>();
|
|
_nodeQuery = GetEntityQuery<XenoArtifactNodeComponent>();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Initializes artifact node on its creation (by setting durability).
|
|
/// </summary>
|
|
private void OnNodeMapInit(Entity<XenoArtifactNodeComponent> ent, ref MapInitEvent args)
|
|
{
|
|
XenoArtifactNodeComponent nodeComponent = ent;
|
|
nodeComponent.MaxDurability -= nodeComponent.MaxDurabilityCanDecreaseBy.Next(RobustRandom);
|
|
SetNodeDurability((ent, ent), nodeComponent.MaxDurability);
|
|
}
|
|
|
|
public void SetNodeUnlocked(Entity<XenoArtifactNodeComponent?> ent)
|
|
{
|
|
if (!Resolve(ent, ref ent.Comp))
|
|
return;
|
|
|
|
if (ent.Comp.Attached is not { } artifact)
|
|
return;
|
|
|
|
if (!TryComp<XenoArtifactComponent>(artifact, out var artifactComponent))
|
|
return;
|
|
|
|
SetNodeUnlocked((artifact, artifactComponent), (ent, ent.Comp));
|
|
}
|
|
|
|
public void SetNodeUnlocked(Entity<XenoArtifactComponent> artifact, Entity<XenoArtifactNodeComponent> node)
|
|
{
|
|
if (!node.Comp.Locked)
|
|
return;
|
|
|
|
node.Comp.Locked = false;
|
|
RebuildCachedActiveNodes((artifact, artifact));
|
|
Dirty(node);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Adds to the node's durability by the specified value. To reduce, provide negative value.
|
|
/// </summary>
|
|
public void AdjustNodeDurability(Entity<XenoArtifactNodeComponent?> ent, int durabilityDelta)
|
|
{
|
|
if (!Resolve(ent, ref ent.Comp))
|
|
return;
|
|
|
|
SetNodeDurability(ent, ent.Comp.Durability + durabilityDelta);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Sets a node's durability to the specified value. HIGHLY recommended to not be less than 0.
|
|
/// </summary>
|
|
public void SetNodeDurability(Entity<XenoArtifactNodeComponent?> ent, int durability)
|
|
{
|
|
if (!Resolve(ent, ref ent.Comp))
|
|
return;
|
|
|
|
ent.Comp.Durability = Math.Clamp(durability, 0, ent.Comp.MaxDurability);
|
|
UpdateNodeResearchValue((ent, ent.Comp));
|
|
Dirty(ent);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Creates artifact node entity, attaching trigger and marking depth level for future use.
|
|
/// </summary>
|
|
public Entity<XenoArtifactNodeComponent> CreateNode(Entity<XenoArtifactComponent> ent, ProtoId<XenoArchTriggerPrototype> trigger, int depth = 0)
|
|
{
|
|
var triggerProto = PrototypeManager.Index(trigger);
|
|
return CreateNode(ent, triggerProto, depth);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Creates artifact node entity, attaching trigger and marking depth level for future use.
|
|
/// </summary>
|
|
public Entity<XenoArtifactNodeComponent> CreateNode(Entity<XenoArtifactComponent> ent, XenoArchTriggerPrototype trigger, int depth = 0)
|
|
{
|
|
var entProtoId = _entityTable.GetSpawns(ent.Comp.EffectsTable)
|
|
.First();
|
|
|
|
AddNode((ent, ent), entProtoId, out var nodeEnt, dirty: false);
|
|
DebugTools.Assert(nodeEnt.HasValue, "Failed to create node on artifact.");
|
|
|
|
var nodeComponent = nodeEnt.Value.Comp;
|
|
nodeComponent.Depth = depth;
|
|
nodeComponent.TriggerTip = trigger.Tip;
|
|
EntityManager.AddComponents(nodeEnt.Value, trigger.Components);
|
|
|
|
Dirty(nodeEnt.Value);
|
|
return nodeEnt.Value;
|
|
}
|
|
|
|
/// <summary> Checks if all predecessor nodes are marked as 'unlocked'. </summary>
|
|
public bool HasUnlockedPredecessor(Entity<XenoArtifactComponent> ent, EntityUid node)
|
|
{
|
|
var predecessors = GetDirectPredecessorNodes((ent, ent), node);
|
|
if (predecessors.Count == 0)
|
|
{
|
|
return true;
|
|
}
|
|
|
|
foreach (var predecessor in predecessors)
|
|
{
|
|
if (predecessor.Comp.Locked)
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
/// <summary> Checks if node was marked as 'active'. Active nodes are invoked on artifact use (if durability is greater than zero). </summary>
|
|
public bool IsNodeActive(Entity<XenoArtifactComponent> ent, EntityUid node)
|
|
{
|
|
return ent.Comp.CachedActiveNodes.Contains(GetNetEntity(node));
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets list of 'active' nodes. Active nodes are invoked on artifact use (if durability is greater than zero).
|
|
/// </summary>
|
|
public List<Entity<XenoArtifactNodeComponent>> GetActiveNodes(Entity<XenoArtifactComponent> ent)
|
|
{
|
|
return ent.Comp.CachedActiveNodes
|
|
.Select(activeNode => _nodeQuery.Get(GetEntity(activeNode)))
|
|
.ToList();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets amount of research points that can be extracted from node.
|
|
/// We can only extract "what's left" - its base value, reduced by already consumed value.
|
|
/// Every drained durability brings more points to be extracted.
|
|
/// </summary>
|
|
public int GetResearchValue(Entity<XenoArtifactNodeComponent> ent)
|
|
{
|
|
if (ent.Comp.Locked)
|
|
return 0;
|
|
|
|
return Math.Max(0, ent.Comp.ResearchValue - ent.Comp.ConsumedResearchValue);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Sets amount of points already extracted from node.
|
|
/// </summary>
|
|
public void SetConsumedResearchValue(Entity<XenoArtifactNodeComponent> ent, int value)
|
|
{
|
|
ent.Comp.ConsumedResearchValue = value;
|
|
Dirty(ent);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Converts node entity uid to its display name (which is Identifier from <see cref="NameIdentifierComponent"/>.
|
|
/// </summary>
|
|
public string GetNodeId(EntityUid uid)
|
|
{
|
|
return (CompOrNull<NameIdentifierComponent>(uid)?.Identifier ?? 0).ToString("D3");
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets two-dimensional array in a form of nested lists, which holds artifact nodes, grouped by segments.
|
|
/// Segments are groups of interconnected nodes, there might be one or more segments in non-empty artifact.
|
|
/// </summary>
|
|
public List<List<Entity<XenoArtifactNodeComponent>>> GetSegments(Entity<XenoArtifactComponent> ent)
|
|
{
|
|
var output = new List<List<Entity<XenoArtifactNodeComponent>>>();
|
|
|
|
foreach (var segment in ent.Comp.CachedSegments)
|
|
{
|
|
var outSegment = new List<Entity<XenoArtifactNodeComponent>>();
|
|
foreach (var netNode in segment)
|
|
{
|
|
var node = GetEntity(netNode);
|
|
if (!_nodeQuery.TryComp(node, out var comp))
|
|
continue;
|
|
|
|
outSegment.Add((node, comp));
|
|
}
|
|
|
|
output.Add(outSegment);
|
|
}
|
|
|
|
return output;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets list of nodes, grouped by depth level. Depth level count starts from 0.
|
|
/// Only 0 depth nodes have no incoming edges - as only they are starting nodes.
|
|
/// </summary>
|
|
public Dictionary<int, List<Entity<XenoArtifactNodeComponent>>> GetDepthOrderedNodes(IEnumerable<Entity<XenoArtifactNodeComponent>> nodes)
|
|
{
|
|
var nodesByDepth = new Dictionary<int, List<Entity<XenoArtifactNodeComponent>>>();
|
|
|
|
foreach (var node in nodes)
|
|
{
|
|
if (!nodesByDepth.TryGetValue(node.Comp.Depth, out var depthList))
|
|
{
|
|
depthList = new List<Entity<XenoArtifactNodeComponent>>();
|
|
nodesByDepth.Add(node.Comp.Depth, depthList);
|
|
}
|
|
|
|
depthList.Add(node);
|
|
}
|
|
|
|
return nodesByDepth;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Rebuilds all the data, associated with nodes in an artifact, updating caches.
|
|
/// </summary>
|
|
public void RebuildXenoArtifactMetaData(Entity<XenoArtifactComponent?> artifact)
|
|
{
|
|
if (!Resolve(artifact, ref artifact.Comp))
|
|
return;
|
|
|
|
RebuildCachedActiveNodes(artifact);
|
|
RebuildCachedSegments(artifact);
|
|
foreach (var node in GetAllNodes((artifact, artifact.Comp)))
|
|
{
|
|
RebuildNodeMetaData(node);
|
|
}
|
|
|
|
CancelUnlockingOnGraphStructureChange((artifact, artifact.Comp));
|
|
}
|
|
|
|
public void RebuildNodeMetaData(Entity<XenoArtifactNodeComponent> node)
|
|
{
|
|
UpdateNodeResearchValue(node);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Clears all cached active nodes and rebuilds the list using the current node state.
|
|
/// Active nodes have the following property:
|
|
/// - Are unlocked themselves
|
|
/// - All successors are also unlocked
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// You could technically modify this to have a per-node method that only checks direct predecessors
|
|
/// and then does recursive updates for all successors, but I don't think the optimization is necessary right now.
|
|
/// </remarks>
|
|
public void RebuildCachedActiveNodes(Entity<XenoArtifactComponent?> ent)
|
|
{
|
|
if (!Resolve(ent, ref ent.Comp))
|
|
return;
|
|
|
|
ent.Comp.CachedActiveNodes.Clear();
|
|
var allNodes = GetAllNodes((ent, ent.Comp));
|
|
foreach (var node in allNodes)
|
|
{
|
|
// Locked nodes cannot be active.
|
|
if (node.Comp.Locked)
|
|
continue;
|
|
|
|
var successors = GetDirectSuccessorNodes(ent, node);
|
|
|
|
// If this node has no successors, then we don't need to bother with this extra logic.
|
|
if (successors.Count != 0)
|
|
{
|
|
// Checks for any of the direct successors being unlocked.
|
|
var successorIsUnlocked = false;
|
|
foreach (var sNode in successors)
|
|
{
|
|
if (sNode.Comp.Locked)
|
|
continue;
|
|
|
|
successorIsUnlocked = true;
|
|
break;
|
|
}
|
|
|
|
// Active nodes must be at the end of the path.
|
|
if (successorIsUnlocked)
|
|
continue;
|
|
}
|
|
|
|
var netEntity = GetNetEntity(node);
|
|
ent.Comp.CachedActiveNodes.Add(netEntity);
|
|
}
|
|
|
|
Dirty(ent);
|
|
}
|
|
|
|
public void RebuildCachedSegments(Entity<XenoArtifactComponent?> ent)
|
|
{
|
|
if (!Resolve(ent, ref ent.Comp))
|
|
return;
|
|
|
|
ent.Comp.CachedSegments.Clear();
|
|
|
|
var entities = GetAllNodes((ent, ent.Comp))
|
|
.ToList();
|
|
var segments = GetSegmentsFromNodes((ent, ent.Comp), entities);
|
|
var netEntities = segments.Select(
|
|
s => s.Select(n => GetNetEntity(n))
|
|
.ToList()
|
|
);
|
|
ent.Comp.CachedSegments.AddRange(netEntities);
|
|
|
|
Dirty(ent);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets two-dimensional array (as lists inside enumeration) that contains artifact nodes, grouped by segment.
|
|
/// </summary>
|
|
public IEnumerable<List<Entity<XenoArtifactNodeComponent>>> GetSegmentsFromNodes(Entity<XenoArtifactComponent> ent, List<Entity<XenoArtifactNodeComponent>> nodes)
|
|
{
|
|
var outSegments = new List<List<Entity<XenoArtifactNodeComponent>>>();
|
|
foreach (var node in nodes)
|
|
{
|
|
var segment = new List<Entity<XenoArtifactNodeComponent>>();
|
|
GetSegmentNodesRecursive(ent, node, segment, outSegments);
|
|
|
|
if (segment.Count == 0)
|
|
continue;
|
|
|
|
outSegments.Add(segment);
|
|
}
|
|
|
|
return outSegments;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Fills nodes into segments by recursively walking through collections of predecessors and successors.
|
|
/// </summary>
|
|
private void GetSegmentNodesRecursive(
|
|
Entity<XenoArtifactComponent> ent,
|
|
Entity<XenoArtifactNodeComponent> node,
|
|
List<Entity<XenoArtifactNodeComponent>> segment,
|
|
List<List<Entity<XenoArtifactNodeComponent>>> otherSegments
|
|
)
|
|
{
|
|
if (otherSegments.Any(s => s.Contains(node)))
|
|
return;
|
|
|
|
if (segment.Contains(node))
|
|
return;
|
|
|
|
segment.Add(node);
|
|
|
|
var predecessors = GetDirectPredecessorNodes((ent, ent), node);
|
|
foreach (var p in predecessors)
|
|
{
|
|
GetSegmentNodesRecursive(ent, p, segment, otherSegments);
|
|
}
|
|
|
|
var successors = GetDirectSuccessorNodes((ent, ent), node);
|
|
foreach (var s in successors)
|
|
{
|
|
GetSegmentNodesRecursive(ent, s, segment, otherSegments);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Sets node research point amount that can be extracted.
|
|
/// Used up durability increases amount to be extracted.
|
|
/// </summary>
|
|
public void UpdateNodeResearchValue(Entity<XenoArtifactNodeComponent> node)
|
|
{
|
|
XenoArtifactNodeComponent nodeComponent = node;
|
|
if (nodeComponent.Attached == null)
|
|
{
|
|
nodeComponent.ResearchValue = 0;
|
|
return;
|
|
}
|
|
|
|
var artifact = _xenoArtifactQuery.Get(nodeComponent.Attached.Value);
|
|
|
|
var nonactiveNodes = GetActiveNodes(artifact);
|
|
var durabilityEffect = MathF.Pow((float)nodeComponent.Durability / nodeComponent.MaxDurability, 2);
|
|
var durabilityMultiplier = nonactiveNodes.Contains(node)
|
|
? 1f - durabilityEffect
|
|
: 1f + durabilityEffect;
|
|
|
|
var predecessorNodes = GetPredecessorNodes((artifact, artifact), node);
|
|
nodeComponent.ResearchValue = (int)(Math.Pow(1.25, Math.Pow(predecessorNodes.Count, 1.5f)) * nodeComponent.BasePointValue * durabilityMultiplier);
|
|
}
|
|
}
|