using System.Diagnostics.CodeAnalysis; using System.Linq; using Content.Shared.Xenoarchaeology.Artifact.Components; using Robust.Shared.Audio; using Robust.Shared.Audio.Systems; using Robust.Shared.Collections; using Robust.Shared.Random; namespace Content.Shared.Xenoarchaeology.Artifact; public abstract partial class SharedXenoArtifactSystem { [Dependency] private readonly SharedAudioSystem _audio = default!; private EntityQuery _unlockingQuery; private void InitializeUnlock() { _unlockingQuery = GetEntityQuery(); SubscribeLocalEvent(OnUnlockingStarted); } /// Finish unlocking phase when the time is up. private void UpdateUnlock(float _) { var query = EntityQueryEnumerator(); while (query.MoveNext(out var uid, out var unlock, out var comp)) { if (_timing.CurTime < unlock.EndTime) continue; FinishUnlockingState((uid, unlock, comp)); } } /// /// Checks if node can be unlocked. /// Only those nodes, that have no predecessors, or have all /// predecessors unlocked can be unlocked themselves. /// Artifact being suppressed also prevents unlocking. /// public bool CanUnlockNode(Entity ent) { if (!Resolve(ent, ref ent.Comp)) return false; var artifact = ent.Comp.Attached; if (!TryComp(artifact, out var artiComp)) return false; if (artiComp.Suppressed) return false; if (!HasUnlockedPredecessor((artifact.Value, artiComp), ent) // unlocked final nodes should not listen for unlocking || (!ent.Comp.Locked && GetSuccessorNodes((artifact.Value, artiComp), (ent.Owner, ent.Comp)).Count == 0) ) return false; return true; } /// /// Finishes unlocking phase, removing related component, and sums up what nodes were triggered, /// that could be unlocked. Marks such nodes as unlocked, and pushes their node activation event. /// public void FinishUnlockingState(Entity ent) { string unlockAttemptResultMsg; XenoArtifactComponent artifactComponent = ent; XenoArtifactUnlockingComponent unlockingComponent = ent; SoundSpecifier? soundEffect; if (TryGetNodeFromUnlockState(ent, out var node)) { SetNodeUnlocked((ent, artifactComponent), node.Value); ActivateNode((ent, ent), (node.Value, node.Value), null, null, Transform(ent).Coordinates, false); unlockAttemptResultMsg = "artifact-unlock-state-end-success"; // as an experiment - unlocking node doesn't activate it, activation is left for player to decide. // var activated = ActivateNode((ent, artifactComponent), node.Value, null, null, Transform(ent).Coordinates, false); // if (activated) soundEffect = unlockingComponent.UnlockActivationSuccessfulSound; } else { unlockAttemptResultMsg = "artifact-unlock-state-end-failure"; soundEffect = unlockingComponent.UnlockActivationFailedSound; } if (_net.IsServer) { _popup.PopupEntity(Loc.GetString(unlockAttemptResultMsg), ent); _audio.PlayPvs(soundEffect, ent.Owner); } RemComp(ent, unlockingComponent); RaiseUnlockingFinished(ent, node); artifactComponent.NextUnlockTime = _timing.CurTime + artifactComponent.UnlockStateRefractory; } public void CancelUnlockingState(Entity ent) { RemComp(ent, ent.Comp1); RaiseUnlockingFinished(ent, null); } /// /// Gets first locked node that can be unlocked (it is locked and all predecessor are unlocked). /// public bool TryGetNodeFromUnlockState( Entity ent, [NotNullWhen(true)] out Entity? node ) { node = null; var potentialNodes = new ValueList>(); var artifactUnlockingComponent = ent.Comp1; foreach (var nodeIndex in GetAllNodeIndices((ent, ent))) { var artifactComponent = ent.Comp2; var curNode = GetNode((ent, artifactComponent), nodeIndex); if (!curNode.Comp.Locked || !CanUnlockNode((curNode, curNode))) continue; var requiredIndices = GetPredecessorNodes((ent, artifactComponent), nodeIndex); requiredIndices.Add(nodeIndex); if (!ent.Comp1.ArtifexiumApplied) { // Make sure the two sets are identical if (requiredIndices.Count != artifactUnlockingComponent.TriggeredNodeIndexes.Count || !artifactUnlockingComponent.TriggeredNodeIndexes.All(requiredIndices.Contains)) continue; node = curNode; return true; // exit early } // If we apply artifexium, check that the sets are identical EXCEPT for one extra node. // This node is a "wildcard" and we'll make a pool so we can pick one to actually unlock. if (!artifactUnlockingComponent.TriggeredNodeIndexes.All(requiredIndices.Contains) || requiredIndices.Count - 1 != artifactUnlockingComponent.TriggeredNodeIndexes.Count) continue; potentialNodes.Add(curNode); } if (potentialNodes.Count != 0) node = RobustRandom.Pick(potentialNodes); return node != null; } private void OnUnlockingStarted(Entity ent, ref MapInitEvent args) { var unlockingStartedEvent = new ArtifactUnlockingStartedEvent(); RaiseLocalEvent(ent.Owner, ref unlockingStartedEvent); } private void RaiseUnlockingFinished( Entity ent, Entity? node ) { var unlockingFinishedEvent = new ArtifactUnlockingFinishedEvent(node); RaiseLocalEvent(ent.Owner, ref unlockingFinishedEvent); } } /// /// Event for starting artifact unlocking stage. /// [ByRefEvent] public record struct ArtifactUnlockingStartedEvent; /// /// Event for finishing artifact unlocking stage. /// /// Node which were unlocked. Null if stage was finished without new unlocks. [ByRefEvent] public record struct ArtifactUnlockingFinishedEvent(EntityUid? UnlockedNode);